<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Memory Recorder Pro</title>
<!-- Bootstrap CSS & Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<!-- Chart.js for statistics -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
:root{
--bg-main:#ffffff;
--bg-gradient:linear-gradient(135deg,#f8f9fa 0%,#e9ecef 100%);
--text-main:#212529;
--accent:#0d6efd;
}
:root.dark{
--bg-main:#1e1e1e;
--bg-gradient:linear-gradient(135deg,#2b2b2b 0%,#1e1e1e 100%);
--text-main:#f8f9fa;
}
body{
background:var(--bg-gradient);
color:var(--text-main);
min-height:100vh;
display:flex;
align-items:center;
justify-content:center;
font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
transition:background .3s ease,color .3s ease;
}
.memory-app{
width:100%;
max-width:920px;
background:var(--bg-main);
padding:2rem 2.5rem;
border-radius:1.5rem;
box-shadow:0 4px 20px rgba(0,0,0,.1);
transition:background .3s ease;
}
.memory-card{
border-left:4px solid var(--accent);
padding-left:1rem;
margin-bottom:1rem;
}
.tag-badge{
background:var(--accent);
color:#fff;
margin-right:.25rem;
cursor:pointer;
}
.btn-accent{
background:var(--accent);
border-color:var(--accent);
color:#fff;
}
</style>
</head>
<body>
<div class="memory-app">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="m-0">📝 Memory Recorder <small class="h6 fw-light">Pro</small></h1>
<div class="d-flex align-items-center gap-3">
<button id="statsBtn" class="btn btn-outline-secondary btn-sm"><i class="bi bi-bar-chart"></i></button>
<div class="form-check form-switch m-0">
<input class="form-check-input" type="checkbox" id="darkModeSwitch">
</div>
</div>
</div>
<!-- Search & Stats row -->
<div class="row g-3 align-items-end mb-4">
<div class="col-md-8">
<label for="searchInput" class="form-label">検索(テキスト / タグ)</label>
<input type="text" id="searchInput" class="form-control" placeholder="キーワードで検索...">
</div>
<div class="col-md-4 text-md-end">
<p id="stats" class="mb-0 small text-muted"></p>
</div>
</div>
<!-- Input Area -->
<div class="mb-3">
<label for="memoryText" class="form-label">新しい記憶</label>
<textarea class="form-control" id="memoryText" rows="3" placeholder="出来事・思い出など..."></textarea>
</div>
<div class="mb-4 row g-2 align-items-end">
<div class="col-md-8">
<label for="memoryTags" class="form-label">タグ(カンマ区切り)</label>
<input type="text" id="memoryTags" class="form-control" placeholder="例: 仕事, 家族, 趣味">
</div>
<div class="col-md-4 d-flex gap-2 mt-md-4">
<button id="saveBtn" class="btn btn-primary flex-grow-1"><i class="bi bi-save"></i> 保存</button>
<button id="voiceBtn" class="btn btn-outline-secondary" title="音声入力"><i class="bi bi-mic"></i></button>
</div>
</div>
<!-- File / Clear row -->
<div class="d-flex flex-wrap gap-2 mb-4">
<button id="exportBtn" class="btn btn-outline-secondary"><i class="bi bi-download"></i> エクスポート</button>
<button id="importBtn" class="btn btn-outline-secondary"><i class="bi bi-upload"></i> インポート</button>
<button id="clearAllBtn" class="btn btn-outline-danger ms-auto"><i class="bi bi-trash"></i> 全削除</button>
<input type="file" id="importFile" accept="application/json" class="d-none">
</div>
<hr>
<h2 class="h5 mb-3">📚 保存された記憶</h2>
<div id="memoryList"></div>
</div>
<!-- Statistics Modal -->
<div class="modal fade" id="statsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-graph-up"></i> 記憶統計</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<canvas id="statsChart" height="120"></canvas>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">記憶を編集</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="editText" class="form-label">内容</label>
<textarea id="editText" class="form-control" rows="4"></textarea>
</div>
<div class="mb-3">
<label for="editTags" class="form-label">タグ(カンマ区切り)</label>
<input id="editTags" class="form-control">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">キャンセル</button>
<button id="editSaveBtn" class="btn btn-primary">保存</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script>
// --------- Constants ---------
const STORAGE_KEY = "memories";
const THEME_KEY = "prefers-dark";
// --------- Helpers ---------
const $ = sel => document.querySelector(sel);
const modal = id => new bootstrap.Modal($(id));
const memories = {
list() { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); },
save(arr) { localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); },
add(obj){ const arr = this.list(); arr.push(obj); this.save(arr);} ,
remove(id){ this.save(this.list().filter(m=>m.id!==id));},
update(id,data){ const arr=this.list().map(m=>m.id===id?{...m,...data}:m); this.save(arr);} ,
};
const fmtDate = d => new Intl.DateTimeFormat("ja-JP",{dateStyle:"medium",timeStyle:"short"}).format(d);
// --------- Rendering ---------
function renderMemories(filter=""){
const listEl = $("#memoryList");
listEl.innerHTML="";
const all = memories.list();
const lower = filter.toLowerCase();
const visible = all.filter(m=>{
const tagMatch = m.tags.some(t=>t.toLowerCase().includes(lower));
const textMatch= m.text.toLowerCase().includes(lower);
return !lower || tagMatch || textMatch;
}).reverse();
// stats
$("#stats").textContent=`合計: ${all.length} 件`;
if(!visible.length){listEl.innerHTML='<p class="text-muted">該当する記憶がありません。</p>';return;}
visible.forEach(m=>{
const card=document.createElement("div");
card.className="memory-card card";
card.innerHTML=`<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<h5 class="card-title mb-1">${fmtDate(new Date(m.date))}</h5>
<div class="btn-group btn-group-sm">
<button class="btn btn-link text-primary" title="編集" onclick="openEditor('${m.id}')"><i class="bi bi-pencil"></i></button>
<button class="btn btn-link text-danger" title="削除" onclick="deleteMemory('${m.id}')"><i class="bi bi-trash"></i></button>
</div>
</div>
<p class="card-text" style="white-space:pre-wrap;">${m.text}</p>
<div class="mt-2">${m.tags.map(t=>`<span class=\"badge tag-badge\" onclick=\"filterTag('${t}')\">${t}</span>`).join(" ")}</div>
</div>`;
listEl.appendChild(card);
});
}
// --------- CRUD ---------
function saveMemory(){
const text=$("#memoryText").value.trim();
const tagRaw=$("#memoryTags").value.trim();
if(!text)return;
const tags=tagRaw?tagRaw.split(/\s*,\s*/).filter(Boolean):[];
memories.add({id:crypto.randomUUID(),text,tags,date:new Date().toISOString()});
$("#memoryText").value="";
$("#memoryTags").value="";
renderMemories($("#searchInput").value);
}
function deleteMemory(id){
if(!confirm("削除しますか?"))return;
memories.remove(id);
renderMemories($("#searchInput").value);
}
// --------- Edit ---------
let editingId=null;
function openEditor(id){
editingId=id;
const m=memories.list().find(x=>x.id===id);
$("#editText").value=m.text;
$("#editTags").value=m.tags.join(", ");
modal('#editModal').show();
}
$("#editSaveBtn").addEventListener("click",()=>{
const text=$("#editText").value.trim();
const tags=$("#editTags").value.trim().split(/\s*,\s*/).filter(Boolean);
memories.update(editingId,{text,tags});
modal('#editModal').hide();
renderMemories($("#searchInput").value);
});
// --------- Filter helper ---------
function filterTag(tag){
$("#searchInput").value=tag;
renderMemories(tag);
}
// --------- Export / Import ---------
function exportMemories(){
const blob=new Blob([JSON.stringify(memories.list(),null,2)],{type:"application/json"});
const url=URL.createObjectURL(blob);
const a=document.createElement("a");
a.href=url;a.download="memories.json";a.click();URL.revokeObjectURL(url);
}
function importMemories(file){
const reader=new FileReader();
reader.onload=e=>{try{const arr=JSON.parse(e.target.result);if(Array.isArray(arr)){memories.save([...memories.list(),...arr]);renderMemories($("#searchInput").value);}else throw 0;}catch{alert("無効なファイルです");}};
reader.readAsText(file);
}
// --------- Statistics ---------
let chartInstance=null;
function openStats(){
const data=memories.list();
const counts={};
data.forEach(m=>{
const key=m.date.slice(0,7); // YYYY-MM
counts[key]=(counts[key]||0)+1;
});
const labels=Object.keys(counts).sort();
const values=labels.map(l=>counts[l]);
const ctx=$("#statsChart");
if(chartInstance)chartInstance.destroy();
chartInstance=new Chart(ctx,{type:'bar',data:{labels,datasets:[{label:'記憶数',data:values}]},options:{plugins:{legend:{display:false}}}});
modal('#statsModal').show();
}
// --------- Dark Mode ---------
function applyTheme(dark){
document.documentElement.classList.toggle('dark',dark);
localStorage.setItem(THEME_KEY,dark?'1':'0');
$("#darkModeSwitch").checked=dark;
}
// --------- Voice Input (Experimental) ---------
let rec=null;
async function startVoice(){
if(!('webkitSpeechRecognition'in window||'SpeechRecognition'in window)){alert('音声認識非対応');return;}
const Speech=window.SpeechRecognition||window.webkitSpeechRecognition;
rec=new Speech();
rec.lang='ja-JP';
rec.continuous=false;
rec.interimResults=false;
rec.onresult=e=>{$('#memoryText').value=e.results[0][0].transcript;};
rec.start();
}
// --------- Init ---------
document.addEventListener('DOMContentLoaded',()=>{
// Theme
applyTheme(localStorage.getItem(THEME_KEY)==='1');
// Render memories
renderMemories();
// Listeners
$('#saveBtn').addEventListener('click',saveMemory);
$('#clearAllBtn').addEventListener('click',()=>{if(confirm('すべて削除しますか?')){localStorage.removeItem(STORAGE_KEY);renderMemories();}});
$('#exportBtn').addEventListener('click',exportMemories);
$('#importBtn').addEventListener('click',()=>$('#importFile').click());
$('#importFile').addEventListener('change',e=>{if(e.target.files[0])importMemories(e.target.files[0]);e.target.value='';});
$('#darkModeSwitch').addEventListener('change',e=>applyTheme(e.target.checked));
$('#searchInput').addEventListener('input',e=>renderMemories(e.target.value));
$('#memoryText').addEventListener('keydown',e=>{if(e.key==='Enter'&&(e.ctrlKey||e.metaKey))saveMemory();});
$('#statsBtn').addEventListener('click',openStats);
$('#voiceBtn').addEventListener('click',startVoice);
});
</script>
</body>
</html>
投稿者: chosuke
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>
Eternal.html 画像投稿サイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Eternal</title>
<style>
body {
font-family: sans-serif;
background: #f5f5f5;
margin: 0;
padding: 0;
}
header {
background: #e60023;
color: white;
padding: 1em;
text-align: center;
font-size: 1.8em;
}
.controls {
padding: 1em;
background: #fff;
text-align: center;
}
.controls input, .controls button {
margin: 0.5em;
padding: 0.5em;
font-size: 1em;
}
.grid {
column-count: 4;
column-gap: 1em;
padding: 1em;
}
.pin {
background: white;
display: inline-block;
margin-bottom: 1em;
width: 100%;
box-sizing: border-box;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
position: relative;
}
.pin img {
width: 100%;
cursor: pointer;
}
.description, .tags {
padding: 0.5em;
}
.favorite {
position: absolute;
top: 8px;
right: 8px;
font-size: 22px;
color: gray;
cursor: pointer;
user-select: none;
}
.favorite.active {
color: gold;
}
.likes {
font-size: 0.9em;
text-align: right;
padding: 0 0.5em 0.5em 0;
color: #888;
}
.tag {
display: inline-block;
background: #eee;
padding: 0.2em 0.5em;
border-radius: 5px;
margin: 0.2em;
cursor: pointer;
}
.tag:hover {
background: #ccc;
}
@media screen and (max-width: 1024px) {
.grid {
column-count: 3;
}
}
@media screen and (max-width: 768px) {
.grid {
column-count: 2;
}
}
@media screen and (max-width: 480px) {
.grid {
column-count: 1;
}
}
#modal {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
#modal img {
max-width: 90%;
max-height: 80vh;
border-radius: 10px;
}
</style>
</head>
<body>
<header>Pintorrest 完全版</header>
<div class="controls">
<input type="file" id="imageInput" accept="image/*">
<input type="text" id="descInput" placeholder="説明">
<input type="text" id="tagInput" placeholder="タグ(カンマ区切り)">
<button onclick="addPin()">投稿</button>
<br>
<input type="text" id="searchBox" placeholder="検索..." oninput="filterPins()">
</div>
<div class="grid" id="grid"></div>
<div id="modal" onclick="this.style.display='none'">
<img id="modalImg" src="">
</div>
<script>
let pins = JSON.parse(localStorage.getItem("pins") || "[]");
function renderPins() {
const grid = document.getElementById("grid");
grid.innerHTML = '';
pins.forEach((pin, index) => {
const pinElem = document.createElement("div");
pinElem.className = "pin";
pinElem.innerHTML = `
<span class="favorite ${pin.favorited ? 'active' : ''}" onclick="toggleFavorite(${index}, this)">★</span>
<img src="${pin.image}" onclick="showModal('${pin.image}')">
<div class="description">${pin.desc}</div>
<div class="tags">${pin.tags.map(tag => `<span class="tag" onclick="filterByTag('${tag}')">${tag}</span>`).join(' ')}</div>
<div class="likes">♥ ${pin.likes}</div>
`;
grid.appendChild(pinElem);
});
}
function addPin() {
const imageInput = document.getElementById("imageInput");
const desc = document.getElementById("descInput").value.trim();
const tags = document.getElementById("tagInput").value.split(',').map(t => t.trim()).filter(Boolean);
const file = imageInput.files[0];
if (!file || !desc) return alert("画像と説明を入力してください");
const reader = new FileReader();
reader.onload = function(e) {
pins.unshift({
image: e.target.result,
desc: desc,
tags: tags,
likes: 0,
favorited: false
});
savePins();
renderPins();
document.getElementById("descInput").value = '';
document.getElementById("tagInput").value = '';
document.getElementById("imageInput").value = '';
};
reader.readAsDataURL(file);
}
function showModal(src) {
document.getElementById("modalImg").src = src;
document.getElementById("modal").style.display = 'flex';
}
function toggleFavorite(index, elem) {
pins[index].favorited = !pins[index].favorited;
pins[index].likes += pins[index].favorited ? 1 : -1;
savePins();
renderPins();
}
function savePins() {
localStorage.setItem("pins", JSON.stringify(pins));
}
function filterPins() {
const keyword = document.getElementById("searchBox").value.toLowerCase();
document.querySelectorAll(".pin").forEach(pin => {
const text = pin.textContent.toLowerCase();
pin.style.display = text.includes(keyword) ? "inline-block" : "none";
});
}
function filterByTag(tag) {
document.getElementById("searchBox").value = tag;
filterPins();
}
renderPins();
</script>
</body>
</html>
『SDガンダム ジージェネレーション エターナル』レビュー
『SDガンダム ジージェネレーション エターナル』レビュー
ジャンル:シミュレーション / 開発・運営:バンダイナムコエンターテインメント
概要
『Gジェネエターナル』は、ガンダムシリーズのモビルスーツとキャラクターを集めて、自軍を編成し、様々なステージを攻略していくモバイル向け戦略シミュレーションゲームです。『SDガンダム Gジェネレーション』シリーズのスマートフォン向け最新作で、シリーズファンの期待を背負ってリリースされました。
■良かった点
◎歴代ガンダム作品を網羅したボリューム
『機動戦士ガンダム』から『鉄血のオルフェンズ』、さらには外伝系作品やゲームオリジナルまで、数多くの機体やキャラが登場。ファンなら思わずニヤリとする場面も多く、図鑑埋めの楽しさは健在。
◎戦略性の高いユニット編成
部隊の編成やスキルの組み合わせによって難関ステージも突破可能。自分だけのドリームチームを作るのは、やはりGジェネならではの醍醐味。
◎フルボイス&原作再現シナリオ
ストーリー面では原作の名シーンがしっかり再現されており、アニメを追体験するような気持ちで楽しめます。フルボイス演出も没入感を高めています。
■気になった点
△課金バランスとガチャの厳しさ
ガチャの排出率が厳しめで、好きな機体やキャラを手に入れるにはかなりの課金が必要な印象。「お気に入りの機体で戦いたい」というシリーズの魅力を活かしきれていない部分があります。
△UIが重く、動作が不安定なことも
端末によってはロードが長く、操作がもたつくシーンもあります。改善アップデートに期待。
△オート戦闘頼りになりがち
戦闘のテンポや演出の派手さはやや地味で、結局「オートで周回して素材集め」が主なプレイスタイルになりがち。戦略性を楽しめるモードがもっと欲しいところ。
■総評
Gジェネファンにはたまらない「ガンダムのお祭りゲーム」であり、機体収集や編成の楽しさは健在。ただし、課金面や周回の作業感、UIなどスマホゲームとしての快適さには改善の余地がある。
評価:★★★☆☆(3.5/5)
ファン向けにはおすすめ。ただしガチャ運と根気も必要。
任天堂Switch 2ブレスオブザワイルドのグラフィック
任天堂Switch 2でブレスオブザワイルドのグラフィックは大幅に向上し、より美しく滑らかな表現が期待できます。具体的には、解像度が2倍以上になり、フレームレートも30fpsから60fpsに向上すると予想されます。
詳細な改善点:
- 解像度:初代Switchの1080pから、Switch 2では約1440p(2.5K相当)まで向上。
- フレームレート:初代Switchの30fpsから、Switch 2では60fpsに向上。
- ローディング時間:初代Switchで25秒ほどかかっていたローディング時間が、Switch 2では15秒ほどに短縮されると予想されています。
その他:
- Switch 2は、最大120fpsのフレームレートをサポートしますが、4K出力時は最大60fpsに制限されると 任天堂のよくあるご質問 に記載されています。
- Switch 2ではHDR対応も期待できるため、より鮮やかな色彩表現も可能になると考えられます。
これらの向上により、ブレスオブザワイルドのグラフィックは、より美しく、より滑らかで、より快適なプレイ体験を提供すると期待されます。
RSSreader.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>超RSSニュースリーダー</title>
<style>
body {
font-family: "Segoe UI", sans-serif;
background: #f4f4f4;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
color: #222;
}
#search {
display: block;
margin: 10px auto;
padding: 10px;
width: 80%;
font-size: 16px;
border-radius: 8px;
border: 1px solid #ccc;
}
#feeds {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
}
.feed-card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
width: 320px;
padding: 12px;
box-sizing: border-box;
}
.feed-card img {
width: 100%;
height: auto;
border-radius: 8px;
margin-bottom: 10px;
}
.feed-card h2 {
font-size: 16px;
margin: 0 0 5px;
}
.feed-card p {
font-size: 14px;
color: #555;
}
.feed-card time {
display: block;
font-size: 12px;
color: #999;
margin-top: 5px;
}
</style>
</head>
<body>
<h1>超RSSニュースリーダー</h1>
<input type="text" id="search" placeholder="記事を検索...">
<div id="feeds"></div>
<script>
const rssUrls = [
"https://news.yahoo.co.jp/rss/topics/top-picks.xml",
"https://b.hatena.ne.jp/hotentry/it.rss",
"https://www.nhk.or.jp/rss/news/cat0.xml",
"http://tyosuke20xx.com/rss"
];
const proxy = "https://api.allorigins.win/get?url=";
const feedsContainer = document.getElementById("feeds");
const searchInput = document.getElementById("search");
let allCards = [];
function formatDate(pubDateStr) {
const date = new Date(pubDateStr);
return isNaN(date) ? "" : `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`;
}
function extractImageFromDescription(desc) {
const match = desc.match(/<img.*?src="(.*?)"/);
return match ? match[1] : null;
}
async function fetchAndDisplayFeeds() {
feedsContainer.innerHTML = "🔄 更新中…";
allCards = [];
for (const url of rssUrls) {
try {
const res = await fetch(proxy + encodeURIComponent(url));
const data = await res.json();
const parser = new DOMParser();
const xml = parser.parseFromString(data.contents, "text/xml");
const items = xml.querySelectorAll("item");
items.forEach((item, i) => {
if (i >= 5) return;
const title = item.querySelector("title")?.textContent || "";
const link = item.querySelector("link")?.textContent || "#";
const desc = item.querySelector("description")?.textContent || "";
const pubDate = item.querySelector("pubDate")?.textContent || "";
const dateFormatted = formatDate(pubDate);
const img = extractImageFromDescription(desc);
const card = document.createElement("div");
card.className = "feed-card";
card.innerHTML = `
${img ? `<img src="${img}" alt="thumbnail">` : ""}
<h2><a href="${link}" target="_blank">${title}</a></h2>
<p>${desc.replace(/<[^>]+>/g, '').slice(0, 100)}...</p>
<time>${dateFormatted}</time>
`;
allCards.push({ title, desc, element: card });
feedsContainer.appendChild(card);
});
} catch (e) {
console.error("RSS取得失敗:", url, e);
}
}
}
searchInput.addEventListener("input", () => {
const keyword = searchInput.value.toLowerCase();
feedsContainer.innerHTML = "";
allCards.forEach(({ title, desc, element }) => {
if (title.toLowerCase().includes(keyword) || desc.toLowerCase().includes(keyword)) {
feedsContainer.appendChild(element);
}
});
});
fetchAndDisplayFeeds();
setInterval(fetchAndDisplayFeeds, 60000); // 60秒ごと自動更新
</script>
</body>
</html>
美少女ゲーム業界の衰退について
🔻 主な衰退の原因
1. スマホゲーム・ソーシャルゲームの台頭
- 2010年代以降、スマートフォン向けのソシャゲ(例:Fate/Grand Order、ブルーアーカイブ)が市場を急速に拡大。
- 無料で始められ、課金で収益を得るモデルが主流となり、従来のパッケージ型PCエロゲは売上が激減。
2. ユーザー層の高齢化と新規参入の減少
- 2000年代のエロゲブーム時代にプレイしていた層がそのまま年齢を重ね、若年層の参入が少ない。
- 新規ファンを獲得するマーケティングやジャンル刷新が進まず、固定ファン頼みの構造に。
3. 表現規制と倫理的圧力の強化
- 政治的・社会的にアダルト表現に対する目が厳しくなり、過激な内容を出しづらくなった。
- SteamやDMMなどのプラットフォームでも規制強化や販売制限があり、制作の自由度が下がる。
4. 制作コストの増加と売上の減少
- フルボイス、アニメーション、豪華な原画・シナリオなど、品質向上で開発費が増大。
- しかし、パッケージ版は売れなくなり、回収が難しくなる → 開発会社の倒産が相次ぐ。
5. ユーザーの性癖やニーズの細分化
- 需要が「NTR」「ロリ」「男の娘」「異種姦」など多様化し、大衆向け作品では満足されにくくなった。
- その結果、ニッチ特化の低予算同人ゲーに市場を奪われつつある。
🔁 現在の潮流と生き残り戦略
✅ DL販売・同人の台頭
- DLsiteやFANZAなどを通じたダウンロード販売が主流に。
- 小規模な個人・同人サークルが高コスパな作品を出しやすくなった(例:「対魔忍」「ドーナドーナ」など)。
✅ ジャンルの変化
- 従来の「純愛・学園モノ」よりも、「異世界×エロ」「ゲーム性×エロ(RPG、SLGなど)」へのシフト。
- ゲーム性を伴うことでSteamなどの一般プラットフォームでも一部販売が可能に。
✅ 海外市場の開拓
- 英語・中国語翻訳での海外展開。
- ただし、欧米では性表現に厳しい文化もあり、ローカライズの壁が大きい。
📉 代表的な衰退の事例
- âge(アージュ):「マブラヴ」シリーズで有名だが、近年は新作が出せず苦戦。
- minori(ミノリ):ビジュアルと演出で一世を風靡したが、2020年に解散。
- Nitro+:エロゲから一般作品(アニメやFGO)に移行し、エロゲ制作を事実上撤退。
🔮 今後どうなるか?
- 同人・個人開発+DL販売が中心となり、大手ブランドは減少。
- VRエロゲやAIエロゲのような新技術との融合で、新しい波が起きる可能性はあり。
- 一方で、完全復活は難しいという見方が一般的。ニッチな趣味市場として生き残っていく形になるでしょう。
新しい技術のアイディア
🔮 1. 感情同期型ウェアラブルAI(EmoSync)
概要: 身に着けると、ユーザーの感情をリアルタイムでAIが解析し、対話や環境を調整してくれる。
例:
- ユーザーがストレスを感じている → 照明を暖色に、音楽をリラックス系に自動変更
- ネガティブな会話をしている → AIがそっと話題を変えるアドバイスを表示
🌐 2. バーチャル記憶共有ネットワーク(MemNet)
概要: VR/AR空間で記憶や体験を他人と部分的に共有できるネットワーク。
用途:
- 歴史の授業 → 偉人の記憶にアクセスしてその時代を体験
- PTSD治療 → セラピストと一緒に記憶を「再体験」して段階的に克服
🧠 3. ノンインベイシブ脳内入力UI(ThoughtTap)
概要: 頭に装着するだけで、思考をコマンドとして認識し、スマホやPCを操作できる技術。
特徴:
- キーボード・マウス不要
- 「メールを送信」と思うだけで送信動作に反映
- 一種の“考えるだけで操作するOS”
🏙️ 4. 空間圧縮型住宅システム(FoldSpace)
概要: 家具や空間を動的に再配置できる可変型居住空間。壁や床が折りたたまれたり、伸縮したりして用途が変わる。
活用例:
- 6畳の部屋が、昼は書斎・夜はベッドルームに自動切り替え
- 壁収納が展開して3Dプリンタや調理ロボになる
🐾 5. バーチャルペット触感再現装置(HaptiPet)
概要: 仮想ペットを実際に“撫でる”感触を与えてくれる触覚デバイス。
仕組み:
- 手に装着するグローブ型デバイス
- 仮想キャラクターの動きに合わせて反力・温度・振動を再現
- VRゲームや孤独対策にも応用可能
🎮 ゲーム企画書:『カスタムロボ RE:BOOST(仮)』
🎮 ゲーム企画書:『カスタムロボ RE:BOOST(仮)』
1. タイトル案
- 『カスタムロボ RE:BOOST』
- 『カスタムロボ R:Reboot』
- 『カスタムロボ∞(インフィニティ)』
2. コンセプト
「少年の夢が、再び現実になる。」
プレイヤーはカスタムロボのパイロットとして、機体を自由にカスタマイズし、リアルかつスピーディな戦闘を繰り広げる。かつてのロボットバトルの熱狂を、現代のビジュアルとシステムで完全再現し、シリーズ未体験の若年層と当時のファン両方に向けた“進化系リメイク”。
3. ターゲットユーザー
- 【主】20~40代:旧作ファン層(N64 / GC / DSユーザー)
- 【副】10~20代:現代のロボットアクションゲームファン(スプラトゥーン、ARMORED CORE、ガンダム系)
4. プラットフォーム
- Nintendo Switch 2(想定)
- PC(Steam)
- PS5(マルチプラットフォーム想定)
5. ゲームジャンル
- 対戦型ロボットカスタマイズバトルアクション
(3Dアクション+パーツ収集+オンライン対戦)
6. 主なゲーム要素
| 要素 | 内容 |
| ロボットのカスタマイズ | 頭・胴・脚・ガン・ボム・ポッドなど1000種類以上のパーツ。性能とビジュアルに影響。 |
| スピード感重視のバトル | 従来のダッシュ・ジャンプ・バーストに加え、新アクション「ブーストチェイン」導入。 |
| シングルモード | オリジナルストーリー+リメイク要素。「ラウンドダクロン」のようなドラマ展開。 |
| オンライン対戦 | ランクマッチ・ルームマッチ・トーナメント。観戦モードあり。 |
| ギルド/クラブ要素 | ユーザー同士でクランを組み、週替わりのイベントに参加。 |
| スキン/アバター | キャラやロボの見た目を変更できる。課金なしでも入手可能。 |
7. 世界観とストーリー(概要)
西暦2089年。AR空間で行われる仮想バトル「ホロロイド」が世界大会に昇格し、ロボット競技は再び脚光を浴びていた。
主人公は「伝説のロボ乗り」の息子として大会に挑む。背後には国家機密とされる人工知能兵器の陰謀が…。
8. ビジュアルイメージ
- キャラデザイン:アニメ調+SFテイスト(イラスト例:天神英貴・貞本義行風)
- ロボデザイン:トイ的なかわいさとメカのかっこよさを両立(例:LBX×AC)
9. 技術・開発情報
- エンジン:Unreal Engine 5
- ネットワーク:Photon Fusion / Epic Online Services
- カスタムAI:バディAIとの協力バトルモードあり
10. マネタイズ・展開
| 方式 | 内容 |
| 買い切り型 | 基本価格:6,980円(DLCパックあり) |
| DLC | ストーリー拡張・新パーツセット(例:コラボ機体・歴代作品機体) |
| コラボ展開 | 他ゲーム/玩具/アニメとコラボ(例:メダロット、LBX、ガンダム) |
| メディア展開 | アニメ化・コミカライズ・プラモデル販売(3Dプリント連携も) |
11. 差別化ポイント
- パーツごとの「フィジカル挙動」シミュレーションでリアル感UP
- AIロボ同士のオート戦も可能(観戦専用モード)
- 「仮想バトル空間」×「現実の世界」の行き来するハイブリッドストーリー
12. 開発スケジュール(案)
| フェーズ | 内容 | 期間 |
| 企画・プロトタイプ | プロトタイピング/ビジュアル検証 | 3ヶ月 |
| α開発 | 基本システム構築 | 6ヶ月 |
| β開発 | 全体完成・バグ修正 | 3ヶ月 |
| PR・リリース | 体験版配布/事前登録/製品版発売 | 2ヶ月 |
Github風サイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>GitHub風 - SampleRepo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap & FontAwesome -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<style>
body {
background: #f6f8fa;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.repo-header { background: #fff; padding: 20px; border: 1px solid #ddd; margin-bottom: 10px; }
.nav-tabs .nav-link.active { border-color: #ddd #ddd #fff; background: #fff; }
.file-list li { border-bottom: 1px solid #eee; padding: 8px 0; display: flex; align-items: center; }
.file-list i { margin-right: 10px; }
.readme-box, .issues-box, .commits-box { background: #fff; padding: 20px; border: 1px solid #ddd; margin-top: 10px; }
#editor { height: 400px; width: 100%; border: 1px solid #ccc; }
</style>
</head>
<body>
<!-- ヘッダー -->
<nav class="navbar navbar-dark bg-dark px-3">
<a class="navbar-brand" href="#"><i class="fab fa-github"></i> GitHub風サイト</a>
<span class="text-white"><i class="fas fa-user-circle"></i> yuhei</span>
</nav>
<div class="container mt-3">
<!-- リポジトリヘッダー -->
<div class="repo-header">
<h3><i class="fas fa-book"></i> yuhei / <strong>SampleRepo</strong></h3>
<p class="text-muted">最終更新: 2025年5月26日</p>
<button class="btn btn-sm btn-outline-secondary"><i class="fas fa-star"></i> Star</button>
<button class="btn btn-sm btn-outline-secondary"><i class="fas fa-code-branch"></i> Fork</button>
</div>
<!-- タブ -->
<ul class="nav nav-tabs" id="repoTabs">
<li class="nav-item"><a class="nav-link active" href="#" onclick="switchTab('code')"><i class="fas fa-code"></i> Code</a></li>
<li class="nav-item"><a class="nav-link" href="#" onclick="switchTab('issues')"><i class="fas fa-exclamation-circle"></i> Issues</a></li>
<li class="nav-item"><a class="nav-link" href="#" onclick="switchTab('commits')"><i class="fas fa-history"></i> Commits</a></li>
</ul>
<!-- Codeタブ -->
<div id="tab-code" class="tab-content">
<ul class="file-list list-unstyled bg-white p-3 border">
<li><i class="fas fa-folder"></i> src/</li>
<li><i class="fas fa-file-code"></i> index.js</li>
<li><i class="fas fa-file-alt"></i> README.md</li>
<li><i class="fas fa-file-alt"></i> LICENSE</li>
</ul>
<div class="readme-box">
<h4><i class="fas fa-book-open"></i> README.md</h4>
<hr>
<div id="readme-content"></div>
</div>
<div class="mt-4">
<h5><i class="fas fa-code"></i> コード編集 (Monaco Editor)</h5>
<div id="editor"></div>
<button id="saveCode" class="btn btn-success mt-2"><i class="fas fa-save"></i> 保存</button>
<button id="themeToggle" class="btn btn-secondary mt-2"><i class="fas fa-adjust"></i> テーマ切替</button>
</div>
</div>
<!-- Issuesタブ -->
<div id="tab-issues" class="tab-content" style="display:none;">
<div class="issues-box">
<h4><i class="fas fa-exclamation-circle"></i> Open Issues</h4>
<ul class="list-group">
<li class="list-group-item">
<strong>#1</strong> READMEの翻訳が必要です<br>
<small class="text-muted">posted by yuhei - 1日前</small>
</li>
<li class="list-group-item">
<strong>#2</strong> index.jsにテストコードを追加してください<br>
<small class="text-muted">posted by yuhei - 2日前</small>
</li>
</ul>
</div>
</div>
<!-- Commitsタブ -->
<div id="tab-commits" class="tab-content" style="display:none;">
<div class="commits-box">
<h4><i class="fas fa-history"></i> Commits</h4>
<ul class="list-group">
<li class="list-group-item">
<strong>Initial commit</strong> - 2025-05-25<br>
<small class="text-muted">by yuhei</small>
</li>
<li class="list-group-item">
<strong>README updated</strong> - 2025-05-26<br>
<small class="text-muted">by yuhei</small>
</li>
</ul>
</div>
</div>
</div>
<!-- ライブラリ -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js"></script>
<!-- タブ切替・Markdown表示・Monaco起動 -->
<script>
const markdown = `
# SampleRepo
ようこそ!これはGitHub風サイトのデモです。
## 特徴
- Monaco Editorでコード編集
- Markdown表示(README)
- ダークモード対応
- Issue・コミットのUI
## 技術
- HTML/CSS/JS
- Bootstrap5
- Monaco Editor
- Marked.js
`;
document.getElementById('readme-content').innerHTML = marked.parse(markdown);
function switchTab(tab) {
['code', 'issues', 'commits'].forEach(id => {
document.getElementById('tab-' + id).style.display = (id === tab) ? 'block' : 'none';
});
document.querySelectorAll('#repoTabs .nav-link').forEach(link => link.classList.remove('active'));
document.querySelector(`#repoTabs .nav-link[onclick*="${tab}"]`).classList.add('active');
}
// Monaco起動
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' }});
require(['vs/editor/editor.main'], function () {
window.editor = monaco.editor.create(document.getElementById('editor'), {
value: localStorage.getItem('savedCode') || `function hello() {\n console.log("Hello from Monaco Editor!");\n}`,
language: 'javascript',
theme: 'vs-light',
automaticLayout: true
});
document.getElementById('saveCode').onclick = function () {
const code = editor.getValue();
localStorage.setItem('savedCode', code);
alert('保存しました!');
};
document.getElementById('themeToggle').onclick = function () {
const theme = monaco.editor.getTheme() === 'vs-dark' ? 'vs-light' : 'vs-dark';
monaco.editor.setTheme(theme);
};
});
</script>
</body>
</html>
