Z

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Z – 次世代ソーシャルネットワーク</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/aframe/1.5.0/aframe.min.js"></script>
<style>
:root{
  --primary:#1DA1F2;--background:#fff;--text:#000;--border:#E1E8ED;--card:#F7F9F9;--danger:#E0245E;
}
[data-theme="dark"]{--background:#15202B;--text:#fff;--border:#38444D;--card:#192734}
*{box-sizing:border-box;margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
body{background:var(--background);color:var(--text);min-height:100vh;transition:.3s}
.hidden{display:none}
.wrapper{max-width:640px;margin-inline:auto;padding:20px}
.timeline{margin-top:2rem}
.timeline-item{background:var(--card);border-radius:12px;padding:1rem;margin-bottom:1rem;box-shadow:0 2px 6px rgba(0,0,0,.05)}
.timeline-item h3{margin:0 0 .5rem;font-size:1.1rem}
.timeline-item p{margin:0;white-space:pre-wrap;line-height:1.4}
.timeline-item small{display:block;margin-top:.5rem;font-size:.75rem;color:var(--border)}
.auth-box{background:var(--card);border-radius:12px;padding:1.5rem;margin-bottom:2rem}
.auth-box input{padding:.75rem;border:1px solid var(--border);border-radius:8px;width:100%;margin-bottom:.5rem}
.auth-box button{background:var(--primary);color:white;border:none;border-radius:8px;padding:.75rem;margin-top:.5rem;width:100%;cursor:pointer;font-weight:bold}
.profile-edit{background:var(--card);padding:1rem;border-radius:12px;margin-bottom:2rem}
.profile-edit h3{margin-bottom:.75rem}
.profile-edit input{width:100%;margin:.5rem 0;padding:.5rem;border:1px solid var(--border);border-radius:8px}
.follow-btn{background:#ccc;padding:.3rem .8rem;border-radius:8px;border:none;cursor:pointer;font-size:.85rem;margin-top:.5rem}
img.upload-preview{max-width:100px;border-radius:8px;margin-top:.5rem}
</style>
</head>
<body>
<div class="wrapper">
  <div id="authBox" class="auth-box">
    <h2>ログイン / 登録</h2>
    <input type="email" id="email" placeholder="メールアドレス">
    <input type="tel" id="phone" placeholder="電話番号">
    <input type="password" id="password" placeholder="パスワード">
    <input type="text" id="username" placeholder="ユーザー名">
    <button onclick="loginOrRegister()">ログイン / 登録</button>
  </div>
  <div id="mainBox" class="hidden">
    <h1 style="font-size:1.5rem;margin-bottom:1rem">Zタイムライン</h1>
    <div style="margin-bottom:1rem">ようこそ、<span id="userEmail"></span> さん!</div>
    <div class="profile-edit">
      <h3>プロフィール編集</h3>
      <input type="text" id="editName" placeholder="表示名を編集">
      <input type="text" id="editBio" placeholder="自己紹介を編集">
      <button onclick="saveProfile()">プロフィール保存</button>
    </div>
    <form id="timelineForm" style="display:flex;flex-direction:column;gap:.75rem;margin-bottom:2rem">
      <input id="timelineTitle" type="text" placeholder="タイトル" required>
      <textarea id="timelineContent" placeholder="投稿内容" required style="min-height:100px"></textarea>
      <input type="file" id="imageUpload" accept="image/*">
      <img id="preview" class="upload-preview hidden">
      <button type="submit">タイムラインに投稿</button>
    </form>
    <section id="timelineList" class="timeline"></section>
    <button onclick="logout()">ログアウト</button>
  </div>
</div>
<div id="vrScene" class="hidden" style="position:fixed;inset:0;z-index:9999"></div>
<button id="vrBtn" style="position:fixed;bottom:20px;right:20px;width:56px;height:56px;border-radius:50%;background:var(--primary);color:#fff;border:none;font-size:1.3rem;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,.25)" onclick="enterVR()"><i class="fa-brands fa-vr-cardboard"></i></button>
<script>
let timeline = JSON.parse(localStorage.getItem('z_timeline')||'[]');
let feeds = JSON.parse(localStorage.getItem('z_feeds')||'[]');
let currentUser = JSON.parse(localStorage.getItem('z_user')||'null');
const authBox = document.getElementById('authBox');
const mainBox = document.getElementById('mainBox');
const timelineForm = document.getElementById('timelineForm');
const timelineList = document.getElementById('timelineList');
const userEmailSpan = document.getElementById('userEmail');
const previewImg = document.getElementById('preview');
const imageUpload = document.getElementById('imageUpload');
function loginOrRegister(){
  const email = document.getElementById('email').value.trim();
  const phone = document.getElementById('phone').value.trim();
  const pass = document.getElementById('password').value;
  const name = document.getElementById('username').value.trim();
  if(!email || !pass || !name){ alert('メール、パスワード、ユーザー名を入力してください'); return; }
  currentUser = {email, phone, name, bio:"", followers:[], following:[]};
  localStorage.setItem('z_user', JSON.stringify(currentUser));
  authBox.classList.add('hidden');
  mainBox.classList.remove('hidden');
  userEmailSpan.textContent = email;
  renderTimeline();
}
function logout(){ localStorage.removeItem('z_user'); location.reload(); }
function saveProfile(){
  const name = document.getElementById('editName').value;
  const bio = document.getElementById('editBio').value;
  if(name) currentUser.name = name;
  if(bio) currentUser.bio = bio;
  localStorage.setItem('z_user', JSON.stringify(currentUser));
  alert('プロフィールを保存しました');
}
function renderTimeline(){
  if(!timeline.length){ timelineList.innerHTML = '<p style="color:var(--border)">投稿がまだありません</p>'; return; }
  timelineList.innerHTML = timeline.map((t, index)=>{
    return `<div class="timeline-item">
      <h3>${t.title}</h3>
      <p>${t.content}</p>
      ${t.image ? `<img src="${t.image}" style="max-width:100%;margin-top:.5rem;border-radius:8px">` : ''}
      <small>${new Date(t.created).toLocaleString()}</small>
      <button onclick="followUser('${t.email}')" class="follow-btn">フォロー</button>
      <button onclick="deletePost(${index})" style="margin-top:.5rem;padding:.3rem .6rem;border:none;background:#eee;border-radius:6px;font-size:.8rem;cursor:pointer">削除</button>
    </div>`;
  }).join('');
}
function followUser(email){
  if(!currentUser.following.includes(email)){
    currentUser.following.push(email);
    localStorage.setItem('z_user', JSON.stringify(currentUser));
    alert(`${email} をフォローしました`);
  }
}
function deletePost(index){
  if(confirm('この投稿を削除しますか?')){
    timeline.splice(index,1);
    localStorage.setItem('z_timeline', JSON.stringify(timeline));
    renderTimeline();
  }
}
timelineForm.addEventListener('submit',e=>{
  e.preventDefault();
  const title = document.getElementById('timelineTitle').value.trim();
  const content = document.getElementById('timelineContent').value.trim();
  const file = imageUpload.files[0];
  if(!title || !content) return;
  const newPost = {title, content, image:null, created:new Date().toISOString(), email: currentUser.email};
  if(file){
    const reader = new FileReader();
    reader.onload = ()=>{
      newPost.image = reader.result;
      timeline.unshift(newPost);
      localStorage.setItem('z_timeline', JSON.stringify(timeline));
      renderTimeline();
    };
    reader.readAsDataURL(file);
  } else {
    timeline.unshift(newPost);
    localStorage.setItem('z_timeline', JSON.stringify(timeline));
    renderTimeline();
  }
  timelineForm.reset();
  previewImg.classList.add('hidden');
});
imageUpload.addEventListener('change',()=>{
  const file = imageUpload.files[0];
  if(file){
    const reader = new FileReader();
    reader.onload = ()=>{
      previewImg.src = reader.result;
      previewImg.classList.remove('hidden');
    };
    reader.readAsDataURL(file);
  }
});
function botAutoPost(){
  const phrases = ['こんにちは!', '今日も頑張ろう!', 'Zへようこそ!'];
  const msg = phrases[Math.floor(Math.random()*phrases.length)];
  timeline.unshift({title:'BOT投稿', content:msg, image:null, created:new Date().toISOString(), email:'bot@z.jp'});
  localStorage.setItem('z_timeline', JSON.stringify(timeline));
  renderTimeline();
}
setInterval(botAutoPost, 60000);
function fetchFeed(url){
  fetch(`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(url)}`)
    .then(res=>res.json())
    .then(data=>{
      if(!data.items) return;
      data.items.slice(0,3).forEach(item=>{
        timeline.unshift({title:item.title, content:item.link, image:null, created:new Date().toISOString(), email:data.feed.title});
      });
      localStorage.setItem('z_timeline', JSON.stringify(timeline));
      renderTimeline();
    }).catch(e=>console.error('feed error',e));
}
feeds.forEach(fetchFeed);
function enterVR(){
  document.getElementById('vrScene').innerHTML = `
    <a-scene embedded>
      <a-sky color="#ECECEC"></a-sky>
      ${timeline.slice(0,10).map((p,i)=>`<a-entity text="value:${p.title}: ${p.content.replace(/\n/g,' ')};wrapCount:30" position="0 ${3-i*1.5} -3"></a-entity>`).join('')}
      <a-camera position="0 1.6 0"></a-camera>
    </a-scene>`;
  document.getElementById('vrScene').classList.remove('hidden');
  document.getElementById('vrBtn').classList.add('hidden');
}
document.addEventListener('keydown',e=>{
  if(e.key==='Escape' && !document.getElementById('vrScene').classList.contains('hidden')){
    document.getElementById('vrScene').classList.add('hidden');
    document.getElementById('vrBtn').classList.remove('hidden');
    document.getElementById('vrScene').innerHTML='';
  }
});
if(currentUser){
  authBox.classList.add('hidden');
  mainBox.classList.remove('hidden');
  userEmailSpan.textContent = currentUser.email;
  renderTimeline();
}
</script>
</body>
</html>

投稿者: chosuke

趣味はゲームやアニメや漫画などです

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です