<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>スフィア</title>
<style>
/* ======= 基本リセット ======= */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: #f7f9fc; color: #333; line-height: 1.5; }
header { background: #4a90e2; color: #fff; padding: 15px; text-align: center; }
header h1 { font-size: 28px; }
main { width: 90%; max-width: 800px; margin: 20px auto; }
/* ======= ユーザーアイコン ======= */
.user-icon { width: 40px; height: 40px; border-radius: 50%; margin-right: 10px; vertical-align: middle; }
/* ======= 認証セクション ======= */
.auth-section { background: #fff; padding: 15px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.auth-section h2 { margin-bottom: 10px; }
.auth-section input { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; }
.auth-section button { background: #4a90e2; color: #fff; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; }
.auth-section button:hover { background: #357ab8; }
.toggle-auth { margin-top: 10px; font-size: 14px; color: #4a90e2; cursor: pointer; text-decoration: underline; }
/* ======= 投稿フォーム ======= */
.post-form { background: #fff; padding: 15px; margin-bottom: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.post-form textarea { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; resize: vertical; font-size: 14px; }
.post-form button { background: #4a90e2; color: #fff; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; margin-top: 10px; }
.post-form button:hover { background: #357ab8; }
/* ======= 検索セクション ======= */
.search-section { background: #fff; padding: 10px; margin-bottom: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.search-section input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
/* ======= タイムライン ======= */
.timeline { display: flex; flex-direction: column; gap: 15px; }
.post-item { background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); position: relative; }
.post-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.post-username { font-weight: bold; }
.post-meta { font-size: 12px; color: #777; }
.post-content { font-size: 16px; margin-bottom: 10px; }
.post-actions { margin-top: 10px; }
.post-actions button { background: transparent; border: none; color: #4a90e2; cursor: pointer; margin-right: 10px; font-size: 14px; }
.post-actions button:hover { text-decoration: underline; }
.replies { margin-top: 15px; border-top: 1px solid #eee; padding-top: 10px; }
.reply-item { background: #f2f2f2; padding: 10px; border-radius: 6px; margin-bottom: 10px; }
.reply-header { font-size: 13px; font-weight: bold; margin-bottom: 5px; }
.reply-meta { font-size: 11px; color: #555; text-align: right; }
.edit-area { margin-top: 10px; }
.edit-area textarea { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
.edit-area button { background: #4a90e2; color: #fff; border: none; padding: 6px 10px; border-radius: 4px; cursor: pointer; margin-top: 5px; }
.edit-area button:hover { background: #357ab8; }
/* ======= ユーティリティ(ログ/RSS)セクション ======= */
.utility-section { margin-top: 20px; text-align: center; }
.logs-section { background: #fff; padding: 15px; margin-top: 10px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-size: 12px; color: #555; max-height: 200px; overflow-y: auto; }
.log-entry { border-bottom: 1px solid #eee; padding: 5px 0; }
footer { text-align: center; padding: 15px; font-size: 14px; color: #777; margin-top: 20px; }
</style>
</head>
<body>
<header>
<h1>スフィア</h1>
</header>
<main>
<!-- 認証セクション(ログイン/登録 or ユーザー情報表示) -->
<section id="authSection" class="auth-section"></section>
<!-- 投稿フォーム(ログイン中のみ表示) -->
<section id="postFormSection" class="post-form" style="display: none;">
<textarea id="postContent" placeholder="いまどうしてる?"></textarea>
<button id="postButton">投稿する</button>
</section>
<!-- 検索セクション -->
<section class="search-section">
<input type="text" id="searchInput" placeholder="投稿を検索">
</section>
<!-- タイムライン -->
<section id="timeline" class="timeline"></section>
<!-- ユーティリティ:ログ表示/RSSダウンロード -->
<section class="utility-section">
<button id="toggleLogsButton">ログを表示</button>
<button id="downloadRSSButton">RSSフィードをダウンロード</button>
</section>
<!-- ログ表示セクション(初期は非表示) -->
<section id="logsSection" class="logs-section" style="display: none;"></section>
</main>
<footer>
<p>© 2023 スフィア</p>
</footer>
<script>
(function(){
'use strict';
// === 定数&キー定義 ===
const POSTS_KEY = 'posts';
const USERS_KEY = 'users';
const CURRENT_USER_KEY = 'currentUser';
const LOGS_KEY = 'logs';
// === データ管理変数 ===
let posts = [];
let users = [];
let currentUser = null;
let logs = [];
// === ストレージ関連関数 ===
const saveToStorage = (key, data) => localStorage.setItem(key, JSON.stringify(data));
const loadFromStorage = (key) => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : null;
};
const loadData = () => {
posts = loadFromStorage(POSTS_KEY) || [];
users = loadFromStorage(USERS_KEY) || [];
currentUser = loadFromStorage(CURRENT_USER_KEY) || null;
logs = loadFromStorage(LOGS_KEY) || [];
};
const saveData = () => {
saveToStorage(POSTS_KEY, posts);
saveToStorage(USERS_KEY, users);
saveToStorage(CURRENT_USER_KEY, currentUser);
saveToStorage(LOGS_KEY, logs);
};
// === ログ処理 ===
const addLog = (message) => {
const entry = { timestamp: new Date(), message };
logs.push(entry);
saveToStorage(LOGS_KEY, logs);
};
const renderLogs = () => {
const logsSection = document.getElementById('logsSection');
logsSection.innerHTML = '';
logs.forEach(log => {
const div = document.createElement('div');
div.className = 'log-entry';
div.textContent = `[${formatDate(log.timestamp)}] ${log.message}`;
logsSection.appendChild(div);
});
};
// === 日付フォーマット ===
const formatDate = (date) => {
const d = new Date(date);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const h = String(d.getHours()).padStart(2, '0');
const min = String(d.getMinutes()).padStart(2, '0');
return `${y}-${m}-${day} ${h}:${min}`;
};
// === RSS生成 ===
const generateRSS = () => {
const now = new Date();
let rss = `<?xml version="1.0" encoding="UTF-8"?>\n`;
rss += `<rss version="2.0">\n<channel>\n`;
rss += `<title>Advanced Mini SNS RSS Feed</title>\n`;
rss += `<link>${location.origin + location.pathname}</link>\n`;
rss += `<description>RSS Feed of posts from Advanced Mini SNS</description>\n`;
rss += `<lastBuildDate>${now.toUTCString()}</lastBuildDate>\n`;
rss += `<language>ja</language>\n`;
posts.forEach(post => {
const postUrl = `${location.origin + location.pathname}?post=${post.id}`;
const title = `Post by ${post.username}: ${post.content.substring(0,20)}...`;
rss += `<item>\n`;
rss += `<title>${title}</title>\n`;
rss += `<link>${postUrl}</link>\n`;
rss += `<description><![CDATA[${post.content}]]></description>\n`;
rss += `<pubDate>${new Date(post.timestamp).toUTCString()}</pubDate>\n`;
rss += `<guid>${post.id}</guid>\n`;
rss += `</item>\n`;
});
rss += `</channel>\n</rss>`;
return rss;
};
const downloadRSS = () => {
const rssContent = generateRSS();
const blob = new Blob([rssContent], { type: 'application/rss+xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'feed.xml';
a.click();
URL.revokeObjectURL(url);
addLog('RSSフィードをダウンロード');
};
// === 認証処理 ===
const renderAuthSection = () => {
const authSection = document.getElementById('authSection');
authSection.innerHTML = '';
if (currentUser) {
// ログイン済み:アイコンとウェルカムメッセージ、ログアウトボタン
const container = document.createElement('div');
if (currentUser.icon) {
const img = document.createElement('img');
img.src = currentUser.icon;
img.className = 'user-icon';
container.appendChild(img);
}
const welcomeDiv = document.createElement('span');
welcomeDiv.textContent = `ようこそ、${currentUser.username} さん!`;
container.appendChild(welcomeDiv);
const logoutBtn = document.createElement('button');
logoutBtn.textContent = 'ログアウト';
logoutBtn.onclick = () => {
addLog(`ユーザー ${currentUser.username} ログアウト`);
currentUser = null;
saveData();
renderAuthSection();
togglePostForm();
renderTimeline();
};
authSection.appendChild(container);
authSection.appendChild(logoutBtn);
} else {
// 未ログイン:ログインフォームを表示
renderLoginForm(authSection);
}
};
const renderLoginForm = (container) => {
container.innerHTML = '<h2>ログイン</h2>';
const usernameInput = document.createElement('input');
usernameInput.type = 'text';
usernameInput.placeholder = 'ユーザー名';
const passwordInput = document.createElement('input');
passwordInput.type = 'password';
passwordInput.placeholder = 'パスワード';
const loginBtn = document.createElement('button');
loginBtn.textContent = 'ログイン';
loginBtn.onclick = () => {
const username = usernameInput.value.trim();
const password = passwordInput.value;
if (!username || !password) { alert('ユーザー名とパスワードを入力してください。'); return; }
const user = users.find(u => u.username === username && u.password === password);
if (user) {
currentUser = user;
saveData();
addLog(`ユーザー ${username} ログイン成功`);
renderAuthSection();
togglePostForm();
renderTimeline();
} else {
alert('ユーザー名またはパスワードが正しくありません。');
}
};
container.appendChild(usernameInput);
container.appendChild(passwordInput);
container.appendChild(loginBtn);
const toggleLink = document.createElement('div');
toggleLink.className = 'toggle-auth';
toggleLink.textContent = '新規登録はこちら';
toggleLink.onclick = () => renderRegisterForm(container);
container.appendChild(toggleLink);
};
const renderRegisterForm = (container) => {
container.innerHTML = '<h2>新規登録</h2>';
const usernameInput = document.createElement('input');
usernameInput.type = 'text';
usernameInput.placeholder = 'ユーザー名';
const passwordInput = document.createElement('input');
passwordInput.type = 'password';
passwordInput.placeholder = 'パスワード';
const confirmInput = document.createElement('input');
confirmInput.type = 'password';
confirmInput.placeholder = 'パスワード確認';
// アイコンアップロード用フィールド
const iconInput = document.createElement('input');
iconInput.type = 'file';
iconInput.accept = 'image/*';
iconInput.style.marginBottom = '10px';
const registerBtn = document.createElement('button');
registerBtn.textContent = '登録';
registerBtn.onclick = () => {
const username = usernameInput.value.trim();
const password = passwordInput.value;
const confirmPassword = confirmInput.value;
if (!username || !password || !confirmPassword) { alert('全ての項目を入力してください。'); return; }
if (password !== confirmPassword) { alert('パスワードが一致しません。'); return; }
if (users.some(u => u.username === username)) { alert('そのユーザー名は既に使用されています。'); return; }
// アイコンが選択されている場合、FileReader で Data URL に変換
if (iconInput.files && iconInput.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
const iconData = e.target.result;
const newUser = { username, password, icon: iconData };
users.push(newUser);
saveData();
addLog(`ユーザー ${username} 登録完了(アイコン設定済み)`);
alert('登録が完了しました。ログインしてください。');
renderLoginForm(container);
};
reader.readAsDataURL(iconInput.files[0]);
} else {
const newUser = { username, password, icon: '' };
users.push(newUser);
saveData();
addLog(`ユーザー ${username} 登録完了(アイコン未設定)`);
alert('登録が完了しました。ログインしてください。');
renderLoginForm(container);
}
};
container.appendChild(usernameInput);
container.appendChild(passwordInput);
container.appendChild(confirmInput);
container.appendChild(iconInput);
container.appendChild(registerBtn);
const toggleLink = document.createElement('div');
toggleLink.className = 'toggle-auth';
toggleLink.textContent = 'ログインはこちら';
toggleLink.onclick = () => renderLoginForm(container);
container.appendChild(toggleLink);
};
const togglePostForm = () => {
const postFormSection = document.getElementById('postFormSection');
postFormSection.style.display = currentUser ? 'block' : 'none';
};
// === タイムライン&投稿表示 ===
const renderTimeline = () => {
const timeline = document.getElementById('timeline');
timeline.innerHTML = '';
let filteredPosts = posts.slice();
const searchValue = document.getElementById('searchInput').value.trim().toLowerCase();
if (searchValue) {
filteredPosts = filteredPosts.filter(post =>
post.content.toLowerCase().includes(searchValue) ||
post.username.toLowerCase().includes(searchValue)
);
}
// 新しい投稿順にソート
filteredPosts.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
filteredPosts.forEach(post => {
timeline.appendChild(renderPost(post));
});
};
const renderPost = (post) => {
const postDiv = document.createElement('div');
postDiv.className = 'post-item';
postDiv.setAttribute('data-id', post.id);
// ヘッダー(投稿者名&日時)
const headerDiv = document.createElement('div');
headerDiv.className = 'post-header';
const usernameSpan = document.createElement('span');
usernameSpan.className = 'post-username';
usernameSpan.textContent = post.username;
const metaSpan = document.createElement('span');
metaSpan.className = 'post-meta';
metaSpan.textContent = formatDate(post.timestamp);
headerDiv.appendChild(usernameSpan);
headerDiv.appendChild(metaSpan);
postDiv.appendChild(headerDiv);
// 投稿内容(編集モードか通常表示か)
const contentDiv = document.createElement('div');
contentDiv.className = 'post-content';
if (post.editing) {
const editArea = document.createElement('div');
editArea.className = 'edit-area';
const editTextarea = document.createElement('textarea');
editTextarea.value = post.content;
editArea.appendChild(editTextarea);
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存';
saveBtn.onclick = () => {
const newContent = editTextarea.value.trim();
if (!newContent) { alert('投稿内容が空です。'); return; }
post.content = newContent;
post.editing = false;
saveData();
addLog(`投稿 ${post.id} 編集完了`);
renderTimeline();
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'キャンセル';
cancelBtn.style.marginLeft = '5px';
cancelBtn.onclick = () => { post.editing = false; renderTimeline(); };
editArea.appendChild(saveBtn);
editArea.appendChild(cancelBtn);
contentDiv.appendChild(editArea);
} else {
contentDiv.textContent = post.content;
}
postDiv.appendChild(contentDiv);
// アクションボタン(いいね、編集、削除、返信)
const actionsDiv = document.createElement('div');
actionsDiv.className = 'post-actions';
const likeBtn = document.createElement('button');
likeBtn.textContent = `いいね (${post.likes || 0})`;
likeBtn.onclick = () => {
post.likes = (post.likes || 0) + 1;
saveData();
addLog(`投稿 ${post.id} にいいね`);
renderTimeline();
};
actionsDiv.appendChild(likeBtn);
if (currentUser && currentUser.username === post.username) {
const editBtn = document.createElement('button');
editBtn.textContent = '編集';
editBtn.onclick = () => { post.editing = true; renderTimeline(); };
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '削除';
deleteBtn.onclick = () => {
if (confirm('本当に削除しますか?')) {
posts = posts.filter(p => p.id !== post.id);
saveData();
addLog(`投稿 ${post.id} 削除`);
renderTimeline();
}
};
actionsDiv.appendChild(editBtn);
actionsDiv.appendChild(deleteBtn);
}
if (currentUser) {
const replyBtn = document.createElement('button');
replyBtn.textContent = '返信';
replyBtn.onclick = () => toggleReplyForm(post.id);
actionsDiv.appendChild(replyBtn);
}
postDiv.appendChild(actionsDiv);
// 返信一覧表示
const repliesDiv = document.createElement('div');
repliesDiv.className = 'replies';
if (post.replies && post.replies.length > 0) {
post.replies.forEach(reply => {
repliesDiv.appendChild(renderReply(reply));
});
}
postDiv.appendChild(repliesDiv);
// 返信フォーム(初期は非表示)
const replyFormDiv = document.createElement('div');
replyFormDiv.className = 'reply-form';
replyFormDiv.style.display = 'none';
const replyTextarea = document.createElement('textarea');
replyTextarea.placeholder = '返信内容を入力';
const submitReplyBtn = document.createElement('button');
submitReplyBtn.textContent = '返信する';
submitReplyBtn.onclick = () => {
const replyContent = replyTextarea.value.trim();
if (!replyContent) { alert('返信内容が空です。'); return; }
addReply(post.id, replyContent);
replyTextarea.value = '';
toggleReplyForm(post.id, true);
};
replyFormDiv.appendChild(replyTextarea);
replyFormDiv.appendChild(submitReplyBtn);
postDiv.appendChild(replyFormDiv);
return postDiv;
};
const renderReply = (reply) => {
const replyDiv = document.createElement('div');
replyDiv.className = 'reply-item';
const replyHeader = document.createElement('div');
replyHeader.className = 'reply-header';
replyHeader.textContent = reply.username;
const replyContent = document.createElement('div');
replyContent.textContent = reply.content;
const replyMeta = document.createElement('div');
replyMeta.className = 'reply-meta';
replyMeta.textContent = formatDate(reply.timestamp);
replyDiv.appendChild(replyHeader);
replyDiv.appendChild(replyContent);
replyDiv.appendChild(replyMeta);
return replyDiv;
};
const toggleReplyForm = (postId, forceHide = false) => {
const postDiv = document.querySelector(`.post-item[data-id="${postId}"]`);
if (!postDiv) return;
const replyForm = postDiv.querySelector('.reply-form');
replyForm.style.display = (forceHide || replyForm.style.display === 'block') ? 'none' : 'block';
};
const addReply = (postId, replyContent) => {
const post = posts.find(p => p.id === postId);
if (!post) return;
if (!post.replies) { post.replies = []; }
post.replies.push({
id: Date.now(),
username: currentUser.username,
content: replyContent,
timestamp: new Date()
});
saveData();
addLog(`投稿 ${post.id} に返信追加`);
renderTimeline();
};
// === 投稿作成処理 ===
const initPostButton = () => {
document.getElementById('postButton').addEventListener('click', () => {
const postContentElem = document.getElementById('postContent');
const content = postContentElem.value.trim();
if (!content) { alert('投稿内容が空です。'); return; }
const newPost = {
id: Date.now(),
username: currentUser.username,
content: content,
timestamp: new Date(),
likes: 0,
replies: []
};
posts.push(newPost);
saveData();
addLog(`投稿 ${newPost.id} 追加`);
renderTimeline();
postContentElem.value = '';
});
};
// === ユーティリティ処理:ログ/RSS ===
const initUtilityButtons = () => {
document.getElementById('toggleLogsButton').addEventListener('click', () => {
const logsSection = document.getElementById('logsSection');
if (logsSection.style.display === 'none' || logsSection.style.display === '') {
renderLogs();
logsSection.style.display = 'block';
document.getElementById('toggleLogsButton').textContent = 'ログを非表示';
} else {
logsSection.style.display = 'none';
document.getElementById('toggleLogsButton').textContent = 'ログを表示';
}
});
document.getElementById('downloadRSSButton').addEventListener('click', downloadRSS);
};
// === 初期化処理 ===
const init = () => {
loadData();
renderAuthSection();
togglePostForm();
renderTimeline();
initPostButton();
initUtilityButtons();
document.getElementById('searchInput').addEventListener('input', renderTimeline);
};
document.addEventListener('DOMContentLoaded', init);
})();
</script>
</body>
</html>
カテゴリー: HTML
リアルタイムRSSトレンド.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>リアルタイムRSSトレンド</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; }
h1 { text-align: center; padding: 20px; background: #333; color: white; }
#rss-feed { max-width: 800px; margin: 20px auto; padding: 20px; background: white; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); }
.article { border-bottom: 1px solid #ccc; padding: 15px; display: flex; flex-direction: column; }
.article:last-child { border-bottom: none; }
.article a { text-decoration: none; color: #333; font-weight: bold; font-size: 16px; }
.article a:hover { color: #007bff; }
.article p { margin: 5px 0; color: #666; font-size: 14px; }
.loader { text-align: center; font-size: 18px; color: #666; }
.refresh-btn { display: block; width: 200px; margin: 20px auto; padding: 10px; background: #007bff; color: white; text-align: center; cursor: pointer; border-radius: 5px; }
.refresh-btn:hover { background: #0056b3; }
.error { text-align: center; color: red; font-weight: bold; }
</style>
</head>
<body>
<h1>リアルタイムRSSトレンド</h1>
<div id="rss-feed" class="loader">読み込み中...</div>
<div class="refresh-btn" onclick="loadRSS()">最新の情報を取得</div>
<div id="error-message" class="error"></div>
<script>
async function fetchRSS(url) {
try {
const response = await fetch(`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(url)}`);
if (!response.ok) throw new Error("RSS取得に失敗しました");
const data = await response.json();
return data.items || [];
} catch (error) {
console.error("RSSの取得に失敗しました", error);
return [];
}
}
async function loadRSS() {
const feedElement = document.getElementById("rss-feed");
const errorElement = document.getElementById("error-message");
feedElement.innerHTML = "読み込み中...";
errorElement.innerHTML = "";
const rssUrls = [
"https://news.google.com/rss?hl=ja&gl=JP&ceid=JP:ja",
"https://rss.itmedia.co.jp/rss/2.0/news_bursts.xml",
"https://www3.nhk.or.jp/rss/news/cat0.xml"
];
let allArticles = [];
for (const url of rssUrls) {
const articles = await fetchRSS(url);
allArticles = allArticles.concat(articles);
}
if (allArticles.length === 0) {
feedElement.innerHTML = "";
errorElement.innerHTML = "記事が取得できませんでした。";
return;
}
allArticles.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
feedElement.innerHTML = "";
allArticles.forEach(article => {
const articleElement = document.createElement("div");
articleElement.classList.add("article");
articleElement.innerHTML = `
<a href="${article.link}" target="_blank">${article.title}</a>
<p>公開日時: ${new Date(article.pubDate).toLocaleString()}</p>
`;
feedElement.appendChild(articleElement);
});
}
document.addEventListener("DOMContentLoaded", loadRSS);
setInterval(loadRSS, 300000); // 5分ごとに自動更新
</script>
</body>
</html>
VRRPG.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VRRPG - 拡張版 AR/VR RPG</title>
<!-- A-Frame ライブラリ -->
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<!-- Particle system コンポーネント(パーティクル演出用) -->
<script src="https://cdn.jsdelivr.net/gh/IdeaSpaceVR/aframe-particle-system-component@master/dist/aframe-particle-system-component.min.js"></script>
<style>
body { margin: 0; overflow: hidden; }
/* 各種オーバーレイ */
#mainMenuOverlay, #upgradeOverlay, #pauseOverlay, #gameOverOverlay {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.8);
color: #FFF;
display: flex;
justify-content: center;
align-items: center;
font-size: 48px;
z-index: 999;
display: none;
text-align: center;
flex-direction: column;
}
#mainMenuOverlay button, #upgradeOverlay button, #gameOverOverlay button {
font-size: 36px;
padding: 20px 40px;
margin-top: 20px;
}
</style>
</head>
<body>
<!-- メインメニュー -->
<div id="mainMenuOverlay" style="display: flex;">
<div>
<div>VRRPG - 拡張版 AR/VR RPG</div>
<button id="startButton">Start Game</button>
</div>
</div>
<!-- アップグレードストア -->
<div id="upgradeOverlay">
<div>
<div>Upgrade Store</div>
<div>Press 1: Increase Sword Damage (+10) (Cost: 50 Score)</div>
<div>Press 2: Increase Max Health (+20) (Cost: 50 Score)</div>
<button id="closeUpgrade">Close</button>
</div>
</div>
<!-- ポーズオーバーレイ -->
<div id="pauseOverlay">Paused</div>
<!-- ゲームオーバーオーバーレイ -->
<div id="gameOverOverlay">
<div>
<div>Game Over!</div>
<div id="finalScore">Final Score: 0</div>
<button id="restartButton">Restart</button>
</div>
</div>
<!-- AR/VRシーン:XRモードを AR に設定(Oculusパススルー利用) -->
<a-scene xr="mode: ar; referenceSpaceType: local-floor">
<!-- 背景音楽 -->
<a-entity id="bg-music" sound="src: url(bg-music.mp3); autoplay: true; loop: true; volume: 0.3"></a-entity>
<!-- 環境 -->
<a-sky color="#88ccee"></a-sky>
<a-plane position="0 0 0" rotation="-90 0 0" width="30" height="30" color="#77aa55"></a-plane>
<a-light type="directional" intensity="0.8" position="0 10 5"></a-light>
<!-- プレイヤー(カメラ、HUD、ポーズ対応) -->
<a-entity id="player" weapon-switcher position="0 1.6 5">
<a-camera wasd-controls look-controls>
<!-- 右手:カメラ内右下に固定表示(装備品) -->
<a-entity id="rightHand" position="0.5 -0.3 -1"></a-entity>
</a-camera>
<!-- HUD -->
<a-entity id="hud" position="0 -0.5 -1.5">
<a-text id="scoreText" value="Score: 0" position="-1 0.7 0" color="#FFF" width="4"></a-text>
<a-text id="healthText" value="Health: 100" position="-1 0.4 0" color="#FFF" width="4"></a-text>
<a-text id="waveText" value="Wave: 1" position="-1 0.1 0" color="#FFF" width="4"></a-text>
<a-text id="levelText" value="Lv: 1 Exp: 0" position="-1 -0.2 0" color="#FFF" width="4"></a-text>
<a-text id="weaponText" value="Weapon: None" position="-1 -0.5 0" color="#FFF" width="4"></a-text>
</a-entity>
</a-entity>
<!-- 落ちている剣(Sword) -->
<a-entity id="sword" position="0.3 1 -2" sword-swing pickup>
<!-- ブレード -->
<a-entity geometry="primitive: box; height: 1; width: 0.1; depth: 0.05"
material="color: silver; metalness: 0.8; roughness: 0.2"
position="0 0.5 0"></a-entity>
<!-- ガード -->
<a-entity geometry="primitive: box; height: 0.2; width: 0.3; depth: 0.05"
material="color: gold"
position="0 0.05 0"></a-entity>
<!-- ハンドル -->
<a-entity geometry="primitive: cylinder; radius: 0.05; height: 0.4"
material="color: brown"
position="0 -0.3 0" rotation="90 0 0"></a-entity>
<!-- 回転アニメーション(拾われるまで) -->
<a-animation attribute="rotation" dur="3000" fill="forwards" to="0 360 0" repeat="indefinite"></a-animation>
</a-entity>
<!-- 魔法の杖(Magic Wand) ※ 未使用 -->
<a-entity id="magicWand" geometry="primitive: cylinder; height: 0.8; radius: 0.05"
material="color: purple; emissive: #aa00ff"
position="0.3 1 -0.5" rotation="0 0 0" wand-fire visible="false"></a-entity>
<!-- 敵スポーン用エリア -->
<a-entity id="enemy-spawn"></a-entity>
<!-- サウンド設定 -->
<a-entity id="sword-sound" sound="src: url(sword-swing.mp3); on: none"></a-entity>
<a-entity id="pickup-sound" sound="src: url(pickup.mp3); on: none"></a-entity>
<a-entity id="wand-sound" sound="src: url(wand-fire.mp3); on: none"></a-entity>
<!-- インストラクション表示(初回のみ) -->
<a-entity id="instructions" position="0 2 -3">
<a-text value="Controls: Oculus Touch / Gamepad / WASD+Mouse クリックで剣を拾い、剣をクリックで振る Pキーでポーズ / Uキーでアップグレード"
align="center" color="#FFF" width="6"></a-text>
</a-entity>
<!-- ウェーブ管理 -->
<a-entity wave-manager></a-entity>
</a-scene>
<script>
/************ ゲームデータ管理 ************/
var gameData = {
score: 0,
playerHealth: 100,
wave: 1,
playerLevel: 1,
experience: 0,
currentWeapon: "None",
swordDamage: 50,
maxHealth: 100,
hasSword: false,
paused: false,
gameState: "menu" // "menu", "playing", "paused", "gameover"
};
/************ HUD 更新関数 ************/
function updateHUD() {
document.querySelector('#scoreText').setAttribute('value', 'Score: ' + gameData.score);
document.querySelector('#healthText').setAttribute('value', 'Health: ' + gameData.playerHealth);
document.querySelector('#waveText').setAttribute('value', 'Wave: ' + gameData.wave);
document.querySelector('#levelText').setAttribute('value', 'Lv: ' + gameData.playerLevel + ' Exp: ' + gameData.experience);
document.querySelector('#weaponText').setAttribute('value', 'Weapon: ' + gameData.currentWeapon);
}
/************ ゲームオーバーチェック ************/
function checkGameOver() {
if (gameData.playerHealth <= 0) {
gameData.gameState = "gameover";
document.getElementById('gameOverOverlay').style.display = "flex";
document.getElementById('finalScore').innerText = "Final Score: " + gameData.score;
}
}
/************ 経験値加算&レベルアップ ************/
function addExperience(exp) {
gameData.experience += exp;
if (gameData.experience >= 100) {
gameData.experience -= 100;
gameData.playerLevel++;
gameData.playerHealth = Math.min(gameData.maxHealth, gameData.playerHealth + 20);
openUpgradeStore();
}
updateHUD();
}
/************ 敵撃破時の演出 ************/
function killEnemy(enemy) {
if (!enemy) return;
let healthBar = enemy.querySelector('.health-bar');
if (healthBar) { healthBar.parentNode.removeChild(healthBar); }
let explosion = document.createElement('a-entity');
explosion.setAttribute('particle-system', 'preset: dust; particleCount: 100; color: #FFAA00, #FF0000;');
explosion.setAttribute('position', enemy.getAttribute('position'));
enemy.parentNode.appendChild(explosion);
setTimeout(function(){ if(explosion.parentNode) explosion.parentNode.removeChild(explosion); }, 1000);
enemy.setAttribute('animation', 'property: scale; to: 0 0 0; dur: 500; easing: easeInOutQuad');
setTimeout(function(){ if(enemy.parentNode) enemy.parentNode.removeChild(enemy); }, 500);
}
/************ カメラシェイク ************/
function cameraShake() {
let camera = document.querySelector('a-camera');
if (!camera) return;
let origPos = camera.getAttribute('position');
let shakePos = {
x: origPos.x + (Math.random()-0.5)*0.1,
y: origPos.y + (Math.random()-0.5)*0.1,
z: origPos.z
};
camera.setAttribute('position', shakePos);
setTimeout(function(){ camera.setAttribute('position', origPos); }, 100);
}
/************ アップグレードストア ************/
function openUpgradeStore() {
document.getElementById('upgradeOverlay').style.display = "flex";
gameData.paused = true;
}
function closeUpgradeStore() {
document.getElementById('upgradeOverlay').style.display = "none";
gameData.paused = false;
}
document.getElementById('closeUpgrade').addEventListener('click', closeUpgradeStore);
/************ メインメニュー&リスタート ************/
document.getElementById('startButton').addEventListener('click', function(){
document.getElementById('mainMenuOverlay').style.display = "none";
gameData.gameState = "playing";
});
document.getElementById('restartButton').addEventListener('click', function(){
window.location.reload();
});
/************ キー操作 ************/
document.addEventListener('keydown', function(e) {
if(e.key.toLowerCase() === 'p') {
gameData.paused = !gameData.paused;
document.getElementById('pauseOverlay').style.display = gameData.paused ? "flex" : "none";
}
if(e.key === 'u' && gameData.gameState === "playing" && !gameData.paused) {
openUpgradeStore();
}
if(document.getElementById('upgradeOverlay').style.display === "flex") {
if(e.key === '1') {
if(gameData.score >= 50) {
gameData.swordDamage += 10;
gameData.score -= 50;
updateHUD();
}
}
if(e.key === '2') {
if(gameData.score >= 50) {
gameData.maxHealth += 20;
gameData.score -= 50;
updateHUD();
}
}
}
});
/************ pickup コンポーネント ************/
AFRAME.registerComponent('pickup', {
init: function() {
let el = this.el;
el.addEventListener('click', function () {
if(gameData.paused || gameData.gameState !== "playing") return;
let player = document.querySelector('#player');
let playerPos = new THREE.Vector3();
player.object3D.getWorldPosition(playerPos);
let itemPos = new THREE.Vector3();
el.object3D.getWorldPosition(itemPos);
if(playerPos.distanceTo(itemPos) < 2) {
if(!gameData.hasSword) {
let pickupSound = document.querySelector('#pickup-sound');
if(pickupSound && pickupSound.components.sound) {
pickupSound.components.sound.playSound();
}
let rightHand = document.querySelector('#rightHand');
if(rightHand) {
rightHand.appendChild(el);
el.setAttribute('position', '0 0 0');
} else {
player.appendChild(el);
el.setAttribute('position', '0.3 0 -0.5');
}
gameData.currentWeapon = "Sword";
gameData.hasSword = true;
updateHUD();
console.log("Sword picked up!");
el.removeAttribute('animation');
} else {
console.log("Already holding a sword.");
}
}
});
}
});
/************ enemy-ai コンポーネント ************/
AFRAME.registerComponent('enemy-ai', {
schema: {
speed: {type: 'number', default: 0.02},
damage: {type: 'number', default: 5}
},
init: function() { this.attackCooldown = 0; },
tick: function(time, timeDelta) {
if(gameData.paused) return;
let player = document.querySelector('#player');
if(!player) return;
let enemy = this.el;
let enemyPos = enemy.object3D.position;
let playerPos = player.object3D.position;
let direction = new THREE.Vector3().subVectors(playerPos, enemyPos);
let distance = direction.length();
if(distance > 0.1) {
direction.normalize();
enemy.object3D.position.add(direction.multiplyScalar(this.data.speed * (timeDelta/16)));
}
if(distance < 1 && this.attackCooldown <= 0) {
gameData.playerHealth -= this.data.damage;
updateHUD();
cameraShake();
checkGameOver();
this.attackCooldown = 1000;
} else {
this.attackCooldown -= timeDelta;
}
}
});
/************ enemy-health コンポーネント ************/
AFRAME.registerComponent('enemy-health', {
schema: {
hp: {type: 'number', default: 100},
maxHp: {type: 'number', default: 100}
},
init: function(){
let bar = document.createElement('a-plane');
bar.setAttribute('class', 'health-bar');
bar.setAttribute('width', '1');
bar.setAttribute('height', '0.1');
bar.setAttribute('color', 'green');
bar.setAttribute('position', '0 0.8 0');
this.el.appendChild(bar);
},
updateHealthBar: function(){
let healthBar = this.el.querySelector('.health-bar');
if(healthBar) {
let hp = this.data.hp, max = this.data.maxHp;
let scaleX = Math.max(0, hp/max);
healthBar.setAttribute('scale', `${scaleX} 1 1`);
let color = (scaleX > 0.5) ? "green" : (scaleX > 0.2 ? "yellow" : "red");
healthBar.setAttribute('color', color);
}
}
});
/************ sword-swing コンポーネント ************/
AFRAME.registerComponent('sword-swing', {
init: function(){
let sword = this.el;
let self = this;
sword.addEventListener('triggerdown', function(){ self.swing(); });
sword.addEventListener('click', function(){ self.swing(); });
},
swing: function(){
if(gameData.paused) return;
this.el.emit('swing');
let soundEl = document.querySelector('#sword-sound');
if(soundEl && soundEl.components.sound){
soundEl.components.sound.playSound();
}
let swordPos = new THREE.Vector3();
this.el.object3D.getWorldPosition(swordPos);
let enemies = document.querySelectorAll('.enemy');
enemies.forEach(function(enemy){
let enemyPos = new THREE.Vector3();
enemy.object3D.getWorldPosition(enemyPos);
if(swordPos.distanceTo(enemyPos) < 1){
let eh = enemy.getAttribute('enemy-health');
eh.hp -= gameData.swordDamage;
enemy.setAttribute('enemy-health', 'hp', eh.hp);
enemy.components['enemy-health'].updateHealthBar();
if(eh.hp <= 0){
killEnemy(enemy);
gameData.score += 10;
addExperience(20);
} else {
enemy.setAttribute('material', 'color', '#ff4444');
setTimeout(function(){ enemy.setAttribute('material', 'color', '#66ff66'); }, 200);
}
updateHUD();
}
});
}
});
/************ wand-fire コンポーネント ************/
AFRAME.registerComponent('wand-fire', {
init: function(){
let wand = this.el;
let self = this;
wand.addEventListener('triggerdown', function(){ self.fire(); });
wand.addEventListener('click', function(){ self.fire(); });
},
fire: function(){
if(gameData.paused) return;
let wandSound = document.querySelector('#wand-sound');
if(wandSound && wandSound.components.sound){
wandSound.components.sound.playSound();
}
let projectile = document.createElement('a-sphere');
projectile.setAttribute('radius', '0.1');
projectile.setAttribute('color', 'orange');
let startPos = new THREE.Vector3();
this.el.object3D.getWorldPosition(startPos);
projectile.setAttribute('position', startPos);
projectile.setAttribute('projectile', '');
this.el.sceneEl.appendChild(projectile);
}
});
/************ projectile コンポーネント ************/
AFRAME.registerComponent('projectile', {
schema: { speed: {type: 'number', default: 0.1}, damage: {type: 'number', default: 30} },
init: function(){
this.direction = new THREE.Vector3();
this.el.object3D.getWorldDirection(this.direction);
},
tick: function(time, timeDelta){
if(gameData.paused) return;
let distance = this.data.speed * (timeDelta/16);
this.el.object3D.position.add(this.direction.clone().multiplyScalar(distance));
let projectilePos = new THREE.Vector3();
this.el.object3D.getWorldPosition(projectilePos);
let enemies = document.querySelectorAll('.enemy');
for(let i=0; i<enemies.length; i++){
let enemy = enemies[i];
let enemyPos = new THREE.Vector3();
enemy.object3D.getWorldPosition(enemyPos);
if(projectilePos.distanceTo(enemyPos) < 0.5){
let eh = enemy.getAttribute('enemy-health');
eh.hp -= this.data.damage;
enemy.setAttribute('enemy-health', 'hp', eh.hp);
enemy.components['enemy-health'].updateHealthBar();
if(eh.hp <= 0){
killEnemy(enemy);
gameData.score += 10;
addExperience(20);
} else {
enemy.setAttribute('material', 'color', '#ff4444');
setTimeout(function(){ enemy.setAttribute('material', 'color', '#66ff66'); }, 200);
}
updateHUD();
this.el.parentNode.removeChild(this.el);
return;
}
}
if(projectilePos.length() > 50){
this.el.parentNode.removeChild(this.el);
}
}
});
/************ weapon-switcher コンポーネント ************/
AFRAME.registerComponent('weapon-switcher', {
init: function(){
window.addEventListener('keydown', function(event){
if(event.key === '1'){
if(gameData.hasSword){
gameData.currentWeapon = "Sword";
document.querySelector('#rightHand').setAttribute('visible', 'true');
document.querySelector('#magicWand').setAttribute('visible', 'false');
}
updateHUD();
} else if(event.key === '2'){
gameData.currentWeapon = "Magic";
document.querySelector('#sword').setAttribute('visible', 'false');
document.querySelector('#magicWand').setAttribute('visible', 'true');
updateHUD();
}
});
}
});
/************ wave-manager コンポーネント ************/
// 敵が全滅したら次のウェーブを生成。ウェーブ番号が5の倍数の場合はボス出現。
AFRAME.registerComponent('wave-manager', {
tick: function(){
if(gameData.paused) return;
let spawnZone = document.querySelector('#enemy-spawn');
if(spawnZone.children.length === 0){
gameData.wave += 1;
updateHUD();
this.spawnWave();
}
},
spawnWave: function(){
let spawnZone = document.querySelector('#enemy-spawn');
if(gameData.wave % 5 === 0){
// ボスウェーブ
let boss = document.createElement('a-entity');
boss.classList.add('enemy');
boss.setAttribute('position', '0 1 -6');
boss.setAttribute('geometry', 'primitive: sphere; radius: 1');
boss.setAttribute('material', 'color: #aa0000; opacity: 0.9; transparent: true');
boss.setAttribute('animation__rotate', 'property: rotation; to: 0 360 0; dur: 6000; loop: true');
boss.setAttribute('enemy-ai', 'speed: 0.015; damage: 10');
boss.setAttribute('enemy-health', 'hp: 300; maxHp: 300');
spawnZone.appendChild(boss);
} else {
let enemyCount = 3 + gameData.wave - 1;
for(let i=0; i<enemyCount; i++){
let enemy = document.createElement('a-entity');
enemy.classList.add('enemy');
let angle = Math.random() * Math.PI * 2;
let radius = 5 + Math.random() * 5;
let x = Math.cos(angle) * radius;
let z = Math.sin(angle) * radius;
enemy.setAttribute('position', `${x} 1 ${z}`);
enemy.setAttribute('geometry', 'primitive: sphere; radius: 0.5');
enemy.setAttribute('material', 'color: #66ff66; opacity: 0.8; transparent: true');
enemy.setAttribute('animation__wobble', 'property: scale; to: 1.1 0.9 1.1; dur: 1000; dir: alternate; loop: true');
enemy.setAttribute('enemy-ai', 'speed: 0.02; damage: 5');
enemy.setAttribute('enemy-health', 'hp: 100; maxHp: 100');
spawnZone.appendChild(enemy);
}
}
}
});
/************ 初回 HUD 更新 & インストラクション削除 ************/
document.addEventListener('DOMContentLoaded', function(){
updateHUD();
setTimeout(function(){
let instructions = document.getElementById('instructions');
if(instructions){ instructions.parentNode.removeChild(instructions); }
}, 5000);
});
</script>
</body>
</html>
Automation Studio
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Automation Studio</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary-color: #00c7b7;
--secondary-color: #2d3436;
--background-color: #f8f9fa;
--card-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', system-ui;
}
body {
background: var(--background-color);
color: var(--secondary-color);
}
/* ヘッダー */
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
background: white;
box-shadow: var(--card-shadow);
}
.brand {
display: flex;
align-items: center;
gap: 1rem;
}
.brand-logo {
width: 40px;
height: 40px;
}
/* メインコンテンツ */
.main-container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
}
/* アプレットカード */
.applet-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.applet-card {
background: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow: var(--card-shadow);
transition: transform 0.2s;
position: relative;
}
.applet-card:hover {
transform: translateY(-5px);
}
.applet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--primary-color);
}
input:checked + .toggle-slider:before {
transform: translateX(24px);
}
.service-flow {
display: flex;
align-items: center;
gap: 1rem;
margin: 1.5rem 0;
}
.service-icon {
width: 48px;
height: 48px;
border-radius: 12px;
padding: 8px;
box-shadow: var(--card-shadow);
}
.divider-arrow {
flex-grow: 1;
border-top: 2px dashed #ddd;
position: relative;
}
.divider-arrow::after {
content: "➔";
position: absolute;
right: -10px;
top: -12px;
background: white;
padding: 0 4px;
color: var(--primary-color);
}
/* 作成モーダル */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 16px;
width: 90%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
padding: 2rem;
animation: modalSlideIn 0.3s ease-out;
}
@keyframes modalSlideIn {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.service-picker {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin: 2rem 0;
}
.service-option {
text-align: center;
padding: 1rem;
border: 2px solid #eee;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.service-option:hover {
border-color: var(--primary-color);
background: #f7f7f7;
}
.service-option img {
width: 64px;
height: 64px;
margin-bottom: 0.5rem;
}
/* レスポンシブデザイン */
@media (max-width: 768px) {
.applet-grid {
grid-template-columns: 1fr;
}
.service-picker {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<header class="dashboard-header">
<div class="brand">
<img src="https://img.icons8.com/color/96/automation.png" class="brand-logo" alt="Logo">
<h1>Automation Studio</h1>
</div>
<button class="create-button" onclick="openModal()">
<i class="fas fa-plus"></i> New Automation
</button>
</header>
<div class="main-container">
<div class="applet-grid">
<!-- アプレットが動的に追加されます -->
</div>
</div>
<!-- 作成モーダル -->
<div class="modal-overlay" id="modal">
<div class="modal-content">
<h2>Create New Automation</h2>
<div class="service-picker" id="triggerServices">
<!-- トリガーサービスが動的に追加されます -->
</div>
<div class="service-picker" id="actionServices">
<!-- アクションサービスが動的に追加されます -->
</div>
<div class="modal-actions">
<button class="btn-secondary" onclick="closeModal()">Cancel</button>
<button class="btn-primary" onclick="createApplet()">Create</button>
</div>
</div>
</div>
<script>
// ダミーデータ
const services = {
triggers: [
{ id: 'email', name: 'Email', icon: 'https://img.icons8.com/color/96/new-post.png' },
{ id: 'calendar', name: 'Calendar', icon: 'https://img.icons8.com/color/96/google-calendar.png' },
{ id: 'weather', name: 'Weather', icon: 'https://img.icons8.com/color/96/partly-cloudy-day.png' }
],
actions: [
{ id: 'slack', name: 'Slack', icon: 'https://img.icons8.com/color/96/slack.png' },
{ id: 'drive', name: 'Google Drive', icon: 'https://img.icons8.com/color/96/google-drive.png' },
{ id: 'notification', name: 'Notification', icon: 'https://img.icons8.com/color/96/appointment-reminders.png' }
]
};
// モーダル制御
function openModal() {
document.getElementById('modal').style.display = 'flex';
populateServices();
}
function closeModal() {
document.getElementById('modal').style.display = 'none';
}
// サービスの表示
function populateServices() {
const triggerContainer = document.getElementById('triggerServices');
const actionContainer = document.getElementById('actionServices');
triggerContainer.innerHTML = services.triggers.map(service => `
<div class="service-option" onclick="selectTrigger('${service.id}')">
<img src="${service.icon}" alt="${service.name}">
<div>${service.name}</div>
</div>
`).join('');
actionContainer.innerHTML = services.actions.map(service => `
<div class="service-option" onclick="selectAction('${service.id}')">
<img src="${service.icon}" alt="${service.name}">
<div>${service.name}</div>
</div>
`).join('');
}
// アプレット作成
let selectedTrigger = null;
let selectedAction = null;
function selectTrigger(serviceId) {
selectedTrigger = services.triggers.find(s => s.id === serviceId);
updateSelections();
}
function selectAction(serviceId) {
selectedAction = services.actions.find(s => s.id === serviceId);
updateSelections();
}
function updateSelections() {
document.querySelectorAll('.service-option').forEach(el => {
el.style.borderColor = '#eee';
});
if (selectedTrigger) {
const triggerEl = document.querySelector(`[onclick="selectTrigger('${selectedTrigger.id}')"]`);
triggerEl.style.borderColor = 'var(--primary-color)';
}
if (selectedAction) {
const actionEl = document.querySelector(`[onclick="selectAction('${selectedAction.id}')"]`);
actionEl.style.borderColor = 'var(--primary-color)';
}
}
function createApplet() {
if (!selectedTrigger || !selectedAction) return;
const appletGrid = document.querySelector('.applet-grid');
const newApplet = document.createElement('div');
newApplet.className = 'applet-card';
newApplet.innerHTML = `
<div class="applet-header">
<h3>${selectedTrigger.name} → ${selectedAction.name}</h3>
<label class="toggle-switch">
<input type="checkbox">
<span class="toggle-slider"></span>
</label>
</div>
<div class="service-flow">
<img src="${selectedTrigger.icon}" class="service-icon">
<div class="divider-arrow"></div>
<img src="${selectedAction.icon}" class="service-icon">
</div>
<p>Last triggered: Never</p>
`;
appletGrid.appendChild(newApplet);
closeModal();
selectedTrigger = null;
selectedAction = null;
}
// モーダル外をクリックで閉じる
window.onclick = function(event) {
if (event.target.classList.contains('modal-overlay')) {
closeModal();
}
}
</script>
</body>
</html>
ライブストリーミングサイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>疑似配信サイト - PC画面&システム音声</title>
<style>
/* ベースリセット */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: sans-serif;
background: #f5f5f5;
color: #333;
}
header, footer {
background: #333;
color: #fff;
padding: 15px;
text-align: center;
}
header h1 {
margin: 0;
font-size: 1.5rem;
letter-spacing: 0.05em;
}
footer p {
margin: 0;
font-size: 0.9rem;
}
main {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
background: #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.page-title {
text-align: center;
margin-bottom: 20px;
font-size: 1.4rem;
}
/* 2カラムレイアウト */
.container {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.video-section {
flex: 1 1 600px;
min-width: 300px;
}
.aside-section {
flex: 1 1 350px;
min-width: 300px;
background: #fafafa;
border-left: 1px solid #ccc;
padding: 10px;
}
/* 動画表示領域 */
.video-wrapper {
background: #000;
overflow: hidden;
}
.video-wrapper video {
display: block;
width: 100%;
height: auto;
background: #000;
}
.video-title {
margin: 10px 0;
font-weight: bold;
}
.video-controls {
text-align: center;
margin-top: 10px;
}
.video-controls button {
margin: 0 5px;
padding: 8px 16px;
font-size: 1rem;
cursor: pointer;
}
/* チャットUI */
.chat-section {
display: flex;
flex-direction: column;
height: 400px;
}
.chat-log {
flex: 1;
border: 1px solid #ccc;
padding: 10px;
overflow-y: auto;
margin-bottom: 10px;
background: #fff;
}
.chat-log p {
margin: 0 0 5px 0;
}
.chat-log p span.username {
font-weight: bold;
color: #3366cc;
margin-right: 5px;
}
.chat-input-area {
display: flex;
gap: 5px;
}
.chat-input-area input[type="text"] {
flex: 1;
padding: 8px;
font-size: 1rem;
}
.chat-input-area button {
padding: 8px 16px;
font-size: 1rem;
cursor: pointer;
}
/* スケジュール */
.schedule {
margin-top: 20px;
}
.schedule h2 {
font-size: 1.2rem;
margin-bottom: 10px;
}
.schedule table {
width: 100%;
border-collapse: collapse;
}
.schedule th, .schedule td {
border: 1px solid #ccc;
padding: 10px;
text-align: center;
}
.schedule th {
background: #f0f0f0;
}
.schedule tr:nth-child(even) {
background: #fafafa;
}
</style>
</head>
<body>
<!-- ヘッダー -->
<header>
<h1>My Advanced Live Streaming (Local Demo)</h1>
</header>
<!-- メインコンテンツ -->
<main>
<div class="page-title">PC画面&システム音声をローカル再生するデモ</div>
<div class="container">
<!-- ▼ 動画エリア ▼ -->
<section class="video-section">
<div class="video-title">疑似ライブ映像</div>
<div class="video-wrapper">
<video id="liveVideo" controls autoplay></video>
</div>
<!-- ボタン類 -->
<div style="text-align: center; margin-top: 10px;">
<button id="screenShareBtn">画面共有(システム音声)</button>
<button id="cameraShareBtn">カメラ&マイク</button>
<button id="stopBtn">停止</button>
</div>
<!-- 再生/一時停止/ミュート/全画面 -->
<div class="video-controls">
<button id="playBtn">再生</button>
<button id="pauseBtn">一時停止</button>
<button id="muteBtn">ミュート/解除</button>
<button id="fullscreenBtn">全画面</button>
</div>
<div class="schedule">
<h2>配信スケジュール(仮)</h2>
<table>
<thead>
<tr>
<th>日付</th>
<th>配信タイトル</th>
<th>開始時間</th>
<th>予定</th>
</tr>
</thead>
<tbody>
<tr>
<td>2/15</td>
<td>PC画面共有テスト</td>
<td>18:00</td>
<td>30分</td>
</tr>
<tr>
<td>2/18</td>
<td>音声ミキシング練習</td>
<td>20:00</td>
<td>1時間</td>
</tr>
<tr>
<td>2/20</td>
<td>週末雑談</td>
<td>21:30</td>
<td>2時間</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- ▼ チャットエリア ▼ -->
<aside class="aside-section">
<h2>チャット</h2>
<div class="chat-section">
<!-- チャットログ -->
<div id="chatLog" class="chat-log"></div>
<!-- 入力フォーム -->
<div class="chat-input-area">
<input type="text" id="usernameInput" placeholder="ユーザー名" />
<input type="text" id="messageInput" placeholder="メッセージを入力…" />
<button id="sendBtn">送信</button>
</div>
</div>
</aside>
</div><!-- /container -->
</main>
<!-- フッター -->
<footer>
<p>© 2025 My Advanced Live Streaming (Local Demo)</p>
</footer>
<script>
/*****************************************************************
* 1. PC画面&システム音声 or カメラ&マイクを取得
*****************************************************************/
const video = document.getElementById('liveVideo');
const screenShareBtn = document.getElementById('screenShareBtn');
const cameraShareBtn = document.getElementById('cameraShareBtn');
const stopBtn = document.getElementById('stopBtn');
let localStream = null;
// 画面共有 (PC画面 + システム音声)
async function startScreenShare() {
stopMedia(); // 既存ストリームを止めてから開始
try {
// getDisplayMediaで画面を共有
// audio:true -> Chromeの場合「システム音声を共有」オプションが表示される
localStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true // ただしブラウザやOSによっては無視される可能性あり
});
video.srcObject = localStream;
// システム音声をモニタリングする場合、ミュートOFFにする
video.muted = false;
console.log('画面共有開始 (システム音声含む可能性)');
} catch (err) {
alert('画面共有を開始できませんでした: ' + err.message);
}
}
// カメラ&マイク
async function startCameraShare() {
stopMedia();
try {
localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
video.srcObject = localStream;
// 自分のマイク音声がループしないようにしたい場合は video.muted = true;
video.muted = true;
console.log('カメラ&マイク開始');
} catch (err) {
alert('カメラ/マイクを取得できませんでした: ' + err.message);
}
}
// 停止
function stopMedia() {
if (!localStream) return;
localStream.getTracks().forEach(track => track.stop());
localStream = null;
video.srcObject = null;
console.log('メディア停止');
}
screenShareBtn.addEventListener('click', startScreenShare);
cameraShareBtn.addEventListener('click', startCameraShare);
stopBtn.addEventListener('click', stopMedia);
/*****************************************************************
* 2. 再生/一時停止/ミュート/全画面など動画コントロール
*****************************************************************/
const playBtn = document.getElementById('playBtn');
const pauseBtn = document.getElementById('pauseBtn');
const muteBtn = document.getElementById('muteBtn');
const fullscreenBtn = document.getElementById('fullscreenBtn');
playBtn.addEventListener('click', () => {
video.play();
});
pauseBtn.addEventListener('click', () => {
video.pause();
});
muteBtn.addEventListener('click', () => {
video.muted = !video.muted;
});
fullscreenBtn.addEventListener('click', () => {
if (!document.fullscreenElement) {
video.requestFullscreen().catch(err => {
console.warn('フルスクリーンモードにできません:', err);
});
} else {
document.exitFullscreen();
}
});
/*****************************************************************
* 3. 簡易チャット機能(ローカルのみ)
*****************************************************************/
const chatLog = document.getElementById('chatLog');
const usernameInput = document.getElementById('usernameInput');
const messageInput = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
let messages = [];
function renderChatLog() {
chatLog.innerHTML = '';
messages.forEach(msg => {
const p = document.createElement('p');
const userSpan = document.createElement('span');
userSpan.className = 'username';
userSpan.textContent = msg.username + ': ';
const textNode = document.createTextNode(msg.text);
p.appendChild(userSpan);
p.appendChild(textNode);
chatLog.appendChild(p);
});
chatLog.scrollTop = chatLog.scrollHeight;
}
function sendMessage() {
const username = usernameInput.value.trim();
const text = messageInput.value.trim();
if (!username || !text) return;
messages.push({ username, text });
renderChatLog();
messageInput.value = '';
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keydown', e => {
if (e.key === 'Enter') sendMessage();
});
// デモ用メッセージ
(function initDemoChat() {
messages.push(
{ username: 'Alice', text: 'こんにちは!' },
{ username: 'Bob', text: 'システム音声聞こえる?' },
{ username: 'Carol', text: 'タブの音は共有できてます〜' }
);
renderChatLog();
})();
</script>
</body>
</html>
ARTBOOK
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ARTBOOK - アートSNS</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: #f9fafb;
color: #333;
}
header {
background-color: #4a5568;
color: white;
padding: 1.5rem;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
header h1 {
margin: 0;
font-size: 2.5rem;
}
header p {
margin: 0.5rem 0;
font-size: 1.2rem;
}
header nav {
margin-top: 1rem;
}
header nav a {
color: white;
text-decoration: none;
margin: 0 1rem;
font-weight: bold;
font-size: 1.1rem;
padding: 0.5rem 1rem;
border-radius: 5px;
transition: background-color 0.3s;
}
header nav a:hover {
background-color: #2d3748;
}
.container {
max-width: 1200px;
margin: 2rem auto;
padding: 1rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.form-group input, .form-group textarea {
width: 100%;
padding: 0.8rem;
border: 1px solid #d2d6dc;
border-radius: 6px;
background-color: #f7fafc;
font-size: 1rem;
}
.form-group button {
background-color: #3182ce;
color: white;
border: none;
padding: 0.8rem 1.5rem;
font-size: 1rem;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s;
}
.form-group button:hover {
background-color: #2b6cb0;
}
.profile {
text-align: center;
}
.profile img {
width: 150px;
height: 150px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 1rem;
}
footer {
text-align: center;
padding: 1.5rem;
background-color: #4a5568;
color: white;
margin-top: 2rem;
}
footer p {
margin: 0;
font-size: 1rem;
}
</style>
</head>
<body>
<header>
<h1>ARTBOOK</h1>
<p>あなたのアートをシェアしよう!</p>
<nav>
<a href="#home" onclick="navigateTo('home')">ホーム</a>
<a href="#gallery" onclick="navigateTo('gallery')">作品一覧</a>
<a href="#post" onclick="navigateTo('post')">投稿する</a>
<a href="#profile" onclick="navigateTo('profile')">プロフィール</a>
</nav>
</header>
<div id="content" class="container">
<!-- コンテンツがここに動的に挿入されます -->
</div>
<footer>
<p>© 2025 ARTBOOK. All Rights Reserved.</p>
</footer>
<script>
let posts = JSON.parse(localStorage.getItem('posts')) || [];
let profile = JSON.parse(localStorage.getItem('profile')) || {
name: "未設定",
bio: "ここに自己紹介が表示されます",
image: "https://via.placeholder.com/150"
};
function navigateTo(section) {
const content = document.getElementById('content');
content.innerHTML = '';
if (section === 'home') {
content.innerHTML = `<h2>ホーム</h2><p>ようこそ、ARTBOOKへ!最新のアート作品をご覧ください。</p>`;
} else if (section === 'gallery') {
content.innerHTML = `<h2>作品一覧</h2><div id='gallery'>${renderPosts()}</div>`;
} else if (section === 'post') {
content.innerHTML = `
<h2>投稿する</h2>
<form id="postForm">
<div class="form-group">
<label for="title">タイトル</label>
<input type="text" id="title" name="title" required>
</div>
<div class="form-group">
<label for="description">説明</label>
<textarea id="description" name="description" rows="4" required></textarea>
</div>
<div class="form-group">
<label for="upload">画像アップロード</label>
<input type="file" id="upload" name="upload" accept="image/*" required>
</div>
<div class="form-group">
<button type="button" onclick="submitPost()">投稿する</button>
</div>
</form>`;
} else if (section === 'profile') {
content.innerHTML = `
<h2>プロフィール</h2>
<div class="profile">
<img src="${profile.image}" alt="プロフィール画像">
<h3>${profile.name}</h3>
<p>${profile.bio}</p>
<form id="profileForm">
<div class="form-group">
<label for="name">名前</label>
<input type="text" id="name" value="${profile.name}" required>
</div>
<div class="form-group">
<label for="bio">自己紹介</label>
<textarea id="bio" rows="4" required>${profile.bio}</textarea>
</div>
<div class="form-group">
<label for="profileImage">プロフィール画像</label>
<input type="file" id="profileImage" accept="image/*">
</div>
<div class="form-group">
<button type="button" onclick="updateProfile()">更新する</button>
</div>
</form>
</div>`;
}
}
function submitPost() {
const title = document.getElementById('title').value;
const description = document.getElementById('description').value;
const upload = document.getElementById('upload').files[0];
if (title && description && upload) {
const reader = new FileReader();
reader.onload = function(event) {
posts.push({ title, description, image: event.target.result });
localStorage.setItem('posts', JSON.stringify(posts));
alert('投稿が完了しました!');
navigateTo('gallery');
};
reader.readAsDataURL(upload);
} else {
alert('すべてのフィールドを入力してください。');
}
}
function updateProfile() {
const name = document.getElementById('name').value;
const bio = document.getElementById('bio').value;
const profileImage = document.getElementById('profileImage').files[0];
if (name && bio) {
if (profileImage) {
const reader = new FileReader();
reader.onload = function(event) {
profile.image = event.target.result;
saveProfile(name, bio);
};
reader.readAsDataURL(profileImage);
} else {
saveProfile(name, bio);
}
} else {
alert('すべてのフィールドを入力してください。');
}
}
function saveProfile(name, bio) {
profile.name = name;
profile.bio = bio;
localStorage.setItem('profile', JSON.stringify(profile));
alert('プロフィールが更新されました!');
navigateTo('profile');
}
function renderPosts() {
if (posts.length === 0) {
return '<p>まだ投稿がありません。</p>';
}
return posts.map(post => `
<div class="art-card">
<img src="${post.image}" alt="${post.title}">
<h2>${post.title}</h2>
<p>${post.description}</p>
</div>`).join('');
}
// 初期表示
navigateTo('home');
</script>
</body>
</html>
百科事典サイト
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Encyclopedia</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
header {
background-color: #2c3e50;
color: white;
padding: 20px;
text-align: center;
}
nav {
display: flex;
justify-content: center;
background-color: #34495e;
padding: 10px;
}
nav a {
color: white;
text-decoration: none;
margin: 0 15px;
font-weight: bold;
}
nav a:hover {
text-decoration: underline;
}
.search-bar {
display: flex;
justify-content: center;
margin: 20px 0;
}
.search-bar input {
width: 60%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 16px;
}
.search-bar button {
padding: 10px 20px;
background-color: #2c3e50;
color: white;
border: none;
border-radius: 5px;
margin-left: 5px;
cursor: pointer;
font-size: 16px;
}
.search-bar button:hover {
background-color: #34495e;
}
.categories, .articles, .about {
margin: 20px;
padding: 20px;
background-color: white;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.categories h2, .articles h2, .about h2 {
margin-bottom: 15px;
font-size: 24px;
}
.categories ul, .articles ul {
list-style: none;
padding: 0;
}
.categories li, .articles li {
margin-bottom: 10px;
}
.categories a, .articles a {
color: #2c3e50;
text-decoration: none;
font-size: 18px;
}
.categories a:hover, .articles a:hover {
text-decoration: underline;
}
.about p {
line-height: 1.6;
font-size: 18px;
color: #333;
}
.featured {
margin: 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.featured-item {
background-color: white;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.featured-item img {
width: 100%;
height: auto;
}
.featured-item h3 {
margin: 15px;
font-size: 20px;
color: #2c3e50;
}
.featured-item p {
margin: 15px;
line-height: 1.5;
color: #666;
}
.featured-item a {
display: block;
text-align: center;
padding: 10px;
margin: 15px;
background-color: #2c3e50;
color: white;
text-decoration: none;
border-radius: 5px;
}
.featured-item a:hover {
background-color: #34495e;
}
footer {
background-color: #2c3e50;
color: white;
text-align: center;
padding: 10px 0;
margin-top: 20px;
}
</style>
</head>
<body>
<header>
<h1>Welcome to the Encyclopedia</h1>
<p>Your go-to source for comprehensive knowledge</p>
</header>
<nav>
<a href="#">Home</a>
<a href="#categories">Categories</a>
<a href="#articles">Popular Articles</a>
<a href="#about">About</a>
</nav>
<div class="search-bar">
<input type="text" placeholder="Search articles...">
<button>Search</button>
</div>
<section id="categories" class="categories">
<h2>Categories</h2>
<ul>
<li><a href="#">Science</a></li>
<li><a href="#">Technology</a></li>
<li><a href="#">History</a></li>
<li><a href="#">Geography</a></li>
<li><a href="#">Culture</a></li>
</ul>
</section>
<section id="articles" class="articles">
<h2>Featured Articles</h2>
<ul>
<li><a href="#">The Wonders of Space Exploration</a></li>
<li><a href="#">The Rise and Fall of Ancient Civilizations</a></li>
<li><a href="#">Understanding Quantum Mechanics</a></li>
<li><a href="#">The Impact of AI on Modern Society</a></li>
<li><a href="#">World's Most Breathtaking Landscapes</a></li>
</ul>
</section>
<section class="featured">
<div class="featured-item">
<img src="https://via.placeholder.com/300" alt="Space Exploration">
<h3>The Wonders of Space Exploration</h3>
<p>Discover the mysteries of the universe and the advances in space technology that bring us closer to the stars.</p>
<a href="#">Read More</a>
</div>
<div class="featured-item">
<img src="https://via.placeholder.com/300" alt="Ancient Civilizations">
<h3>The Rise and Fall of Ancient Civilizations</h3>
<p>Explore the history of ancient empires and their lasting impact on the modern world.</p>
<a href="#">Read More</a>
</div>
<div class="featured-item">
<img src="https://via.placeholder.com/300" alt="Quantum Mechanics">
<h3>Understanding Quantum Mechanics</h3>
<p>Dive into the complex and fascinating world of quantum physics and its real-world applications.</p>
<a href="#">Read More</a>
</div>
</section>
<section id="about" class="about">
<h2>About This Encyclopedia</h2>
<p>This online encyclopedia is dedicated to providing reliable and well-researched information on a wide range of topics. From science and technology to history and culture, we aim to empower curious minds with knowledge.</p>
<p>Our content is curated by experts and enthusiasts from around the world, ensuring a comprehensive and engaging learning experience for all users.</p>
</section>
<footer>
<p>© 2025 Encyclopedia. All rights reserved.</p>
</footer>
</body>
</html>
MyCarousel
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Carousel</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<section class="carousel">
<div class="container">
<ul>
<li><img src="img/pic1.png"></li>
<li><img src="img/pic2.png"></li>
<li><img src="img/pic3.png"></li>
<li><img src="img/pic4.png"></li>
</ul>
<button id="prev">«</button>
<button id="next">»</button>
</div>
<nav>
</nav>
</section>
<script src="js/main.js"></script>
</body>
</html>
js/main.js
'use strict';
{
const next = document.getElementById('next');
const prev = document.getElementById('prev');
const ul = document.querySelector('ul');
const slides = ul.children;
const dots = [];
let currentIndex = 0;
function updateButtons() {
prev.classList.remove('hidden');
next.classList.remove('hidden');
if (currentIndex === 0) {
prev.classList.add('hidden');
}
if (currentIndex === slides.length - 1) {
next.classList.add('hidden');
}
}
function moveSlides() {
const slideWidth = slides[0].getBoundingClientRect().width;
ul.style.transform = `translateX(${-1 * slideWidth * currentIndex}px)`;
}
function setupDots() {
for (let i = 0; i < slides.length; i++) {
const button = document.createElement('button');
button.addEventListener('click', () => {
currentIndex = i;
updateDots();
updateButtons();
moveSlides();
});
dots.push(button);
document.querySelector('nav').appendChild(button);
}
dots[0].classList.add('current');
}
function updateDots() {
dots.forEach(dot => {
dot.classList.remove('current');
});
dots[currentIndex].classList.add('current');
}
updateButtons();
setupDots();
next.addEventListener('click', () => {
currentIndex++;
updateButtons();
updateDots();
moveSlides();
});
prev.addEventListener('click', () => {
currentIndex--;
updateButtons();
updateDots();
moveSlides();
});
window.addEventListener('resize', () => {
moveSlides();
});
}
css/style.css
.carousel {
width: 80%;
margin: 16px auto;
}
.container {
width: 100%;
height: 220px;
overflow: hidden;
position: relative;
}
ul {
list-style: none;
margin: 0;
padding: 0;
height: 100%;
display: flex;
transition: transform .3s;
}
li {
height: 100%;
min-width: 100%;
}
li img {
width: 100%;
height: 100%;
object-fit: cover;
}
#prev,
#next {
position: absolute;
top: 50%;
transform: translateY(-50%);
border: none;
background: rgba(0, 0, 0, .8);
color: #fff;
font-size: 24px;
padding: 0 8px 4px;
cursor: pointer;
}
#prev:hover,
#next:hover {
opacity: .8;
}
#prev {
left: 0;
}
#next {
right: 0;
}
.hidden {
display: none;
}
nav {
margin-top: 16px;
text-align: center;
}
nav button+button {
margin-left: 8px;
}
nav button {
border: none;
width: 16px;
height: 16px;
background: #ddd;
border-radius: 50%;
cursor: pointer;
}
nav .current {
background: #999;
}
アプリストア.html
<!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: 'Roboto', sans-serif;
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: #f9f9f9;
color: #333;
}
a {
text-decoration: none;
color: inherit;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
/* ヘッダー */
header {
background-color: #0066cc;
color: white;
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
header .container {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
header h1 a {
color: white;
font-size: 1.5rem;
font-weight: bold;
}
header nav ul {
display: flex;
gap: 1rem;
}
header nav a {
color: white;
font-size: 1rem;
font-weight: 500;
transition: color 0.3s;
}
header nav a:hover {
color: #ffcc00;
}
/* ヒーローセクション */
.hero {
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('hero-image.jpg') no-repeat center center/cover;
color: white;
text-align: center;
padding: 6rem 1rem;
}
.hero h2 {
font-size: 3rem;
margin-bottom: 1rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.hero p {
font-size: 1.5rem;
margin-bottom: 2rem;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
}
.hero .btn-primary {
background-color: #ffcc00;
color: #333;
padding: 1rem 2rem;
border-radius: 5px;
font-weight: bold;
font-size: 1.25rem;
transition: background-color 0.3s, transform 0.3s;
}
.hero .btn-primary:hover {
background-color: #e6b800;
transform: scale(1.05);
}
/* カテゴリセクション */
.categories {
background-color: #f1f1f1;
padding: 3rem 1rem;
text-align: center;
}
.categories h2 {
font-size: 2.5rem;
margin-bottom: 1.5rem;
}
.categories .category-list {
display: flex;
justify-content: center;
gap: 1.5rem;
flex-wrap: wrap;
}
.categories .category-list a {
background-color: #0066cc;
color: white;
padding: 0.75rem 1.5rem;
border-radius: 5px;
font-size: 1.25rem;
font-weight: bold;
transition: background-color 0.3s, transform 0.3s;
}
.categories .category-list a:hover {
background-color: #004d99;
transform: scale(1.1);
}
/* アプリ一覧 */
.app-list {
padding: 3rem 1rem;
background-color: white;
}
.app-list h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 2rem;
}
.apps-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.app-card {
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
transition: box-shadow 0.3s, transform 0.3s;
}
.app-card:hover {
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
transform: translateY(-5px);
}
.app-card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.app-card h3 {
font-size: 1.5rem;
margin: 1rem;
}
.app-card p {
margin: 0 1rem 1.5rem;
font-size: 1.125rem;
}
.app-card .btn-secondary {
display: block;
text-align: center;
margin: 0 1rem 1.5rem;
padding: 0.75rem;
background-color: #0066cc;
color: white;
font-size: 1rem;
border-radius: 5px;
transition: background-color 0.3s;
}
.app-card .btn-secondary:hover {
background-color: #004d99;
}
/* フッター */
footer {
background-color: #333;
color: white;
padding: 2rem 0;
text-align: center;
font-size: 1rem;
}
footer nav a {
color: #ffcc00;
margin: 0 0.5rem;
transition: color 0.3s;
}
footer nav a:hover {
color: white;
}
</style>
</head>
<body>
<header>
<div class="container">
<h1><a href="#home">アプリストア</a></h1>
<nav>
<ul>
<li><a href="#home">ホーム</a></li>
<li><a href="#categories">カテゴリ</a></li>
<li><a href="#apps">アプリ一覧</a></li>
<li><a href="#about">このサイトについて</a></li>
<li><a href="#contact">お問い合わせ</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
</nav>
</div>
</header>
<main>
<section id="home" class="hero">
<div class="hero-content">
<h2>お気に入りのアプリを見つけましょう!</h2>
<p>最新のアプリや便利なツールがここに揃っています。</p>
<a href="#apps" class="btn-primary">アプリを探す</a>
</div>
</section>
<section id="categories" class="categories">
<h2>カテゴリ</h2>
<ul class="category-list">
<li><a href="#games">🎮 ゲーム</a></li>
<li><a href="#productivity">📊 生産性向上</a></li>
<li><a href="#education">📚 教育</a></li>
<li><a href="#entertainment">🎥 エンターテイメント</a></li>
<li><a href="#health">💪 健康・フィットネス</a></li>
<li><a href="#finance">💰 ファイナンス</a></li>
</ul>
</section>
<section id="apps" class="app-list">
<h2>おすすめアプリ</h2>
<div class="apps-grid">
<div class="app-card">
<img src="app1.jpg" alt="アプリ名1">
<h3>アプリ名1</h3>
<p>説明文がここに入ります。最新機能と素晴らしいデザインを備えています。</p>
<a href="#" class="btn-secondary">詳細を見る</a>
</div>
<div class="app-card">
<img src="app2.jpg" alt="アプリ名2">
<h3>アプリ名2</h3>
<p>説明文がここに入ります。日常生活を豊かにするツールです。</p>
<a href="#" class="btn-secondary">詳細を見る</a>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<p>© 2025 アプリストア. 全著作権所有。</p>
<nav>
<a href="#privacy">プライバシーポリシー</a> |
<a href="#terms">利用規約</a> |
<a href="#sitemap">サイトマップ</a>
</nav>
</div>
</footer>
</body>
</html>
MyTabMenu
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Tab Menu</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div class="container">
<ul class="menu">
<li><a href="#" class="active" data-id="about">サイトの概要</a></li>
<li><a href="#" data-id="service">サービス内容</a></li>
<li><a href="#" data-id="contact">お問い合わせ</a></li>
</ul>
<section class="content active" id="about">
サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。
</section>
<section class="content" id="service">
サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。
</section>
<section class="content" id="contact">
お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。
</section>
</div>
<script src="js/main.js"></script>
</body>
</html>
css/styles.css
body {
font-size: 14px;
}
.container {
margin: 30px auto;
width: 500px;
}
.menu {
list-style: none;
padding: 0;
margin: 0;
display: flex;
}
.menu li a {
display: inline-block;
width: 100px;
text-align: center;
padding: 8px 0;
color: #333;
text-decoration: none;
border-radius: 4px 4px 0 0;
}
.menu li a.active {
background: #333;
color: #fff;
}
.menu li a:not(.active):hover {
opacity: 0.5;
transition: opacity 0.4s;
}
.content.active {
background: #333;
color: #fff;
min-height: 150px;
padding: 12px;
display: block;
}
.content {
display: none;
}
js/main.js
'use strict';
{
const menuItems = document.querySelectorAll('.menu li a');
const contents = document.querySelectorAll('.content');
menuItems.forEach(clickedItem => {
clickedItem.addEventListener('click', e => {
e.preventDefault();
menuItems.forEach(item => {
item.classList.remove('active');
});
clickedItem.classList.add('active');
contents.forEach(content => {
content.classList.remove('active');
});
document.getElementById(clickedItem.dataset.id).classList.add('active');
});
});
}
