<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0"><title>ROKUCHU BBS</title><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script><style>:root{--primary:#6c63ff;--secondary:#ff6c8f;--dark:#1a1a2e;--light:#f5f5f7;--success:#00d68f;--warning:#ffbe0b;--danger:#ff3860;--gray:#8c8c8c}*{margin:0;padding:0;box-sizing:border-box;font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif}body{background-color:var(--dark);color:var(--light);min-height:100vh;display:flex;flex-direction:column;background-image:radial-gradient(circle at 10%20%,rgba(108,99,255,0.1)0%,transparent 20%),radial-gradient(circle at 80%30%,rgba(255,108,143,0.15)0%,transparent 30%);overflow-x:hidden}header{padding:1.5rem;background-color:rgba(0,0,0,0.3);backdrop-filter:blur(10px);border-bottom:1px solid rgba(255,255,255,0.1);position:sticky;top:0;z-index:100}.header-container{max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;align-items:center}.logo{font-size:1.8rem;font-weight:700;background:linear-gradient(90deg,var(--primary),var(--secondary));-webkit-background-clip:text;background-clip:text;color:transparent;letter-spacing:1px}.nav-buttons{display:flex;gap:1rem}main{flex:1;max-width:1200px;margin:0 auto;width:100%;padding:2rem 1rem}.container{background-color:rgba(26,26,46,0.8);backdrop-filter:blur(10px);border-radius:12px;border:1px solid rgba(255,255,255,0.1);padding:2rem;width:100%;max-width:500px;margin:2rem auto;box-shadow:0 10px 25px rgba(0,0,0,0.2);animation:fadeIn 0.5s ease}.page-title{text-align:center;margin-bottom:2rem;font-size:2rem;font-weight:700;background:linear-gradient(90deg,var(--primary),var(--secondary));-webkit-background-clip:text;background-clip:text;color:transparent}.form-group{margin-bottom:1.5rem}label{display:block;margin-bottom:0.5rem;font-weight:500;color:var(--light)}input,textarea{width:100%;padding:0.75rem 1rem;background-color:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.1);border-radius:8px;color:var(--light);font-size:1rem;transition:all 0.3s ease}input:focus,textarea:focus{border-color:var(--primary);box-shadow:0 0 0 2px rgba(108,99,255,0.2);outline:none}.btn{padding:0.75rem 1.5rem;border:none;border-radius:8px;font-size:1rem;font-weight:600;cursor:pointer;transition:all 0.3s ease}.btn-block{display:block;width:100%}.btn-primary{background:linear-gradient(45deg,var(--primary),#837dff);color:white}.btn-primary:hover{background:linear-gradient(45deg,#5951ff,#726aff);transform:translateY(-2px);box-shadow:0 6px 15px rgba(108,99,255,0.3)}.btn-secondary{background:linear-gradient(45deg,var(--secondary),#ff86a4);color:white}.btn-secondary:hover{background:linear-gradient(45deg,#ff5b81,#ff7599);transform:translateY(-2px);box-shadow:0 6px 15px rgba(255,108,143,0.3)}.btn-outline{background:transparent;border:1px solid var(--primary);color:var(--primary)}.btn-outline:hover{background-color:rgba(108,99,255,0.1)}.text-center{text-align:center}.mt-1{margin-top:0.5rem}.mt-2{margin-top:1rem}.mt-3{margin-top:1.5rem}.mt-4{margin-top:2rem}.text-danger{color:var(--danger)}.text-success{color:var(--success)}@keyframes fadeIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.post{background-color:rgba(255,255,255,0.05);border-radius:12px;padding:1.5rem;margin-bottom:1.5rem;border:1px solid rgba(255,255,255,0.08);transition:transform 0.3s ease,box-shadow 0.3s ease}.post:hover{transform:translateY(-5px);box-shadow:0 10px 20px rgba(0,0,0,0.2)}.post-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:0.75rem;border-bottom:1px solid rgba(255,255,255,0.08)}.post-author{font-weight:600;color:var(--primary)}.post-date{font-size:0.85rem;color:var(--gray)}.post-content{line-height:1.6}.post-actions{display:flex;justify-content:flex-end;gap:1rem;margin-top:1rem;padding-top:0.75rem;border-top:1px solid rgba(255,255,255,0.08)}.post-actions button{background:transparent;border:none;color:var(--gray);cursor:pointer;display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;transition:color 0.3s ease}.post-actions button:hover{color:var(--light)}.new-post-form textarea{min-height:120px;resize:vertical}.form-footer{display:flex;justify-content:flex-end;margin-top:1rem}.notification{position:fixed;top:20px;right:20px;padding:1rem 1.5rem;border-radius:8px;color:white;font-weight:500;opacity:0;transform:translateY(-20px);transition:opacity 0.3s ease,transform 0.3s ease;z-index:1000}.notification.show{opacity:1;transform:translateY(0)}.notification.success{background-color:var(--success)}.notification.error{background-color:var(--danger)}.loading-spinner{display:inline-block;width:20px;height:20px;border:3px solid rgba(255,255,255,0.3);border-radius:50%;border-top-color:white;animation:spin 1s ease-in-out infinite;margin-right:0.5rem}@keyframes spin{to{transform:rotate(360deg)}}.btn.btn-text{display:inline-block}.btn.loading.btn-text{visibility:hidden;width:0}.btn.loading::after{content:"";display:inline-block;width:1rem;height:1rem;border:2px solid rgba(255,255,255,0.3);border-radius:50%;border-top-color:white;animation:spin 1s ease-in-out infinite}@media(max-width:768px){.header-container{flex-direction:column;gap:1rem}.container{padding:1.5rem}}</style></head><body><header><div class="header-container"><div class="logo">ROKUCHU BBS</div><div class="nav-buttons"><button id="loginBtn"class="btn btn-outline"onclick="loadLoginPage()">ログイン</button><button id="signupBtn"class="btn btn-primary"onclick="loadSignupPage()">新規登録</button><button id="logoutBtn"class="btn btn-outline"style="display: none;"onclick="handleLogout()">ログアウト</button></div></div></header><main id="mainContent"><!--Home page content will be loaded here--><div class="container"id="welcomeContainer"><h1 class="page-title">ROKUCHU BBSへようこそ</h1><p class="text-center">最新の議論に参加するには、ログインまたは新規登録してください。</p><div class="text-center mt-4"><button id="homeLoginBtn"class="btn btn-primary"onclick="loadLoginPage()">ログイン</button><button id="homeSignupBtn"class="btn btn-secondary mt-2"onclick="loadSignupPage()">新規登録</button></div></div></main><!--Login template--><template id="loginTemplate"><div class="container"><h1 class="page-title">ログイン</h1><form id="loginForm"><div class="form-group"><label for="loginUsername">ユーザー名</label><input type="text"id="loginUsername"required></div><div class="form-group"><label for="loginPassword">パスワード</label><input type="password"id="loginPassword"required></div><button type="submit"class="btn btn-primary btn-block"><span class="btn-text">ログイン</span></button><p class="text-center mt-3">アカウントをお持ちでないですか？<a href="#"id="goToSignup"style="color: var(--primary);">新規登録</a></p><p id="loginError"class="text-danger mt-2 text-center"style="display: none;"></p></form></div></template><!--Signup template--><template id="signupTemplate"><div class="container"><h1 class="page-title">新規登録</h1><form id="signupForm"><div class="form-group"><label for="signupUsername">ユーザー名</label><input type="text"id="signupUsername"required></div><div class="form-group"><label for="signupPassword">パスワード</label><input type="password"id="signupPassword"required></div><div class="form-group"><label for="signupAdminCode">管理者コード</label><input type="password"id="signupAdminCode"required><small style="display: block; margin-top: 0.5rem; color: var(--gray);">*管理者から提供されたコードを入力してください</small></div><button type="submit"class="btn btn-primary btn-block"><span class="btn-text">登録</span></button><p class="text-center mt-3">既にアカウントをお持ちですか？<a href="#"id="goToLogin"style="color: var(--primary);">ログイン</a></p><p id="signupError"class="text-danger mt-2 text-center"style="display: none;"></p></form></div></template><!--Forum template--><template id="forumTemplate"><div class="container"style="max-width: 800px;"><h1 class="page-title">Discussions</h1><div class="new-post-form"><div class="form-group"><label for="postTitle">タイトル</label><input type="text"id="postTitle"placeholder="タイトルを入力してください"></div><div class="form-group"><label for="postContent">内容</label><textarea id="postContent"placeholder="内容を入力してください"></textarea></div><div class="form-footer"><button id="submitPost"class="btn btn-primary">投稿する</button></div></div><h2 style="margin: 2rem 0 1rem; font-size: 1.5rem; color: var(--light);">最近の投稿</h2><div id="postsList"><!--Posts will be loaded here--><div class="text-center"id="loadingPosts"><div class="loading-spinner"></div><p>投稿を読み込み中...</p></div></div></div></template><!--Notification element--><div id="notification"class="notification"></div><script>function checkSupabaseLoaded(){if(typeof supabase==='undefined'){console.error('Supabase client is not loaded');showErrorNotification('Supabaseクライアントの読み込みに失敗しました。ページを更新してください。');return false}return true}function showErrorNotification(message){console.error('ERROR:',message);const notificationElement=document.getElementById('notification');if(notificationElement){notificationElement.textContent=message;notificationElement.className='notification error show';setTimeout(()=>{notificationElement.classList.remove('show')},5000)}else{alert('エラー: '+message)}}const SUPABASE_URL='https://dathtmkmpogocrjkpyut.supabase.co';const SUPABASE_KEY='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRhdGh0bWttcG9nb2NyamtweXV0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDY4NDY1MDksImV4cCI6MjA2MjQyMjUwOX0.zPKxal9lDIYllNuHjOC2yL0D9jRveXSQTKRVOOzsQdg';let supabase;function initSupabase(){try{if(typeof window.supabase==='undefined'){console.error('Supabase library not loaded');showErrorNotification('Supabaseライブラリの読み込みに失敗しました。ページを更新してください。');return false}supabase=window.supabase.createClient(SUPABASE_URL,SUPABASE_KEY);console.log('Supabase client initialized successfully');checkUser();return true}catch(error){console.error('Failed to initialize Supabase client:',error);showErrorNotification('Supabaseクライアントの初期化に失敗しました: '+error.message);return false}}window.addEventListener('DOMContentLoaded',function(){console.log('DOM loaded, initializing app...');if(typeof window.supabase==='undefined'){console.error('Supabase library not loaded on DOMContentLoaded');setTimeout(()=>{if(initSupabase()){console.log('Supabase initialized with delay');checkUser()}else{showErrorNotification('Supabaseの読み込みに失敗しました。ページを更新してください。')}},1000)}else{if(initSupabase()){console.log('Supabase initialized immediately');checkUser()}}const loginBtn=document.getElementById('loginBtn');const signupBtn=document.getElementById('signupBtn');const logoutBtn=document.getElementById('logoutBtn');if(loginBtn)loginBtn.addEventListener('click',loadLoginPage);if(signupBtn)signupBtn.addEventListener('click',loadSignupPage);if(logoutBtn)logoutBtn.addEventListener('click',handleLogout);console.log('Event listeners attached')});const mainContent=document.getElementById('mainContent');const loginBtn=document.getElementById('loginBtn');const signupBtn=document.getElementById('signupBtn');const logoutBtn=document.getElementById('logoutBtn');const homeLoginBtn=document.getElementById('homeLoginBtn');const homeSignupBtn=document.getElementById('homeSignupBtn');const notification=document.getElementById('notification');const loginTemplate=document.getElementById('loginTemplate');const signupTemplate=document.getElementById('signupTemplate');const forumTemplate=document.getElementById('forumTemplate');let currentUser=null;function showNotification(message,type='success'){notification.textContent=message;notification.className=`notification ${type} show`;setTimeout(()=>{notification.classList.remove('show')},3000)}function setButtonLoading(button,isLoading){if(isLoading){button.classList.add('loading');button.disabled=true}else{button.classList.remove('loading');button.disabled=false}}async function checkUser(){try{const{data,error}=await supabase.auth.getSession();if(error)throw error;if(data.session){currentUser=data.session.user;const{data:userData,error:userError}=await supabase.from('users').select('username').eq('id',currentUser.id).single();if(!userError&&userData){currentUser.username=userData.username}updateUIForLoggedInUser();loadForum()}else{updateUIForLoggedOutUser();loadWelcomePage()}}catch(error){console.error('Error checking auth status:',error);updateUIForLoggedOutUser();loadWelcomePage()}}function updateUIForLoggedInUser(){loginBtn.style.display='none';signupBtn.style.display='none';logoutBtn.style.display='block'}function updateUIForLoggedOutUser(){loginBtn.style.display='block';signupBtn.style.display='block';logoutBtn.style.display='none';currentUser=null}function loadWelcomePage(){mainContent.innerHTML='';const welcomeContainer=document.getElementById('welcomeContainer');if(welcomeContainer){mainContent.appendChild(welcomeContainer.cloneNode(true));const homeLoginBtn=document.getElementById('homeLoginBtn');const homeSignupBtn=document.getElementById('homeSignupBtn');if(homeLoginBtn)homeLoginBtn.addEventListener('click',loadLoginPage);if(homeSignupBtn)homeSignupBtn.addEventListener('click',loadSignupPage);console.log('Welcome page event listeners attached')}else{mainContent.innerHTML=`
                    <div class="container" id="welcomeContainer">
                        <h1 class="page-title">ROKUCHU BBSへようこそ</h1>
                        <p class="text-center">最新の議論に参加するには、ログインまたは新規登録してください。</p>
                        <div class="text-center mt-4">
                            <button id="homeLoginBtn" class="btn btn-primary">ログイン</button>
                            <button id="homeSignupBtn" class="btn btn-secondary mt-2">新規登録</button>
                        </div>
                    </div>
                `;document.getElementById('homeLoginBtn').addEventListener('click',loadLoginPage);document.getElementById('homeSignupBtn').addEventListener('click',loadSignupPage)}}function loadLoginPage(){mainContent.innerHTML='';mainContent.appendChild(loginTemplate.content.cloneNode(true));document.getElementById('loginForm').addEventListener('submit',handleLogin);document.getElementById('goToSignup').addEventListener('click',(e)=>{e.preventDefault();loadSignupPage()})}function loadSignupPage(){mainContent.innerHTML='';mainContent.appendChild(signupTemplate.content.cloneNode(true));document.getElementById('signupForm').addEventListener('submit',handleSignup);document.getElementById('goToLogin').addEventListener('click',(e)=>{e.preventDefault();loadLoginPage()})}function loadForum(){mainContent.innerHTML='';mainContent.appendChild(forumTemplate.content.cloneNode(true));document.getElementById('submitPost').addEventListener('click',handleCreatePost);loadPosts();setupRealtimeSubscription()}async function handleLogin(e){e.preventDefault();const username=document.getElementById('loginUsername').value;const password=document.getElementById('loginPassword').value;const loginError=document.getElementById('loginError');const submitButton=e.target.querySelector('button[type="submit"]');loginError.style.display='none';setButtonLoading(submitButton,true);try{const{data:users,error:userError}=await supabase.from('users').select('email').eq('username',username);if(userError){console.error('User lookup error:',userError);throw userError;}if(!users||users.length===0){loginError.textContent='ユーザー名またはパスワードが正しくありません';loginError.style.display='block';setButtonLoading(submitButton,false);return}const email=users[0].email;console.log('Found user email:',email);const{data,error}=await supabase.auth.signInWithPassword({email:email,password:password});if(error){console.error('Auth error:',error);loginError.textContent='ユーザー名またはパスワードが正しくありません';loginError.style.display='block'}else{currentUser=data.user;const{data:userData,error:userDataError}=await supabase.from('users').select('username').eq('id',currentUser.id).single();if(!userDataError&&userData){currentUser.username=userData.username}updateUIForLoggedInUser();loadForum();showNotification('ログインに成功しました！')}}catch(error){console.error('Login error:',error);loginError.textContent='エラーが発生しました。もう一度お試しください。';loginError.style.display='block'}finally{setButtonLoading(submitButton,false)}}async function handleSignup(e){e.preventDefault();if(!supabase){const initialized=initSupabase();if(!initialized){showNotification('Supabaseクライアントの初期化に失敗しました','error');return}}const username=document.getElementById('signupUsername').value;const password=document.getElementById('signupPassword').value;const adminCode=document.getElementById('signupAdminCode').value;const signupError=document.getElementById('signupError');const submitButton=e.target.querySelector('button[type="submit"]');signupError.style.display='none';setButtonLoading(submitButton,true);if(adminCode!=='1721'){signupError.textContent='管理者コードが正しくありません';signupError.style.display='block';setButtonLoading(submitButton,false);return}try{const{data:existingUsers,error:checkError}=await supabase.from('users').select('id').eq('username',username);if(checkError){throw checkError;}if(existingUsers&&existingUsers.length>0){signupError.textContent='このユーザー名は既に使用されています';signupError.style.display='block';setButtonLoading(submitButton,false);return}const email=`${username.toLowerCase().replace(/[^a-z0-9]/g, '')}@103.f5.si`;const{data,error}=await supabase.auth.signUp({email,password,options:{data:{username:username}}});if(error){throw error;}console.log('User created:',data.user);const{error:insertError}=await supabase.from('users').insert([{id:data.user.id,username:username,email:email}]);if(insertError){console.error('User insert error:',insertError);if(!insertError.message.includes('duplicate')){throw insertError;}}showNotification('アカウントが作成されました！');currentUser=data.user;currentUser.username=username;updateUIForLoggedInUser();loadForum()}catch(error){console.error('Signup error:',error);signupError.textContent='アカウント作成中にエラーが発生しました: '+(error.message||'不明なエラー');signupError.style.display='block'}finally{setButtonLoading(submitButton,false)}}async function handleLogout(){try{if(window.currentSubscription){supabase.removeChannel(window.currentSubscription);window.currentSubscription=null}await supabase.auth.signOut();updateUIForLoggedOutUser();loadWelcomePage();showNotification('ログアウトしました')}catch(error){console.error('Logout error:',error);showNotification('ログアウト中にエラーが発生しました','error')}}async function handleCreatePost(){const titleInput=document.getElementById('postTitle');const contentInput=document.getElementById('postContent');const submitButton=document.getElementById('submitPost');const title=titleInput.value.trim();const content=contentInput.value.trim();if(!title||!content){showNotification('タイトルと内容を入力してください','error');return}setButtonLoading(submitButton,true);try{const{data:userData,error:userError}=await supabase.from('users').select('username').eq('id',currentUser.id).single();if(userError)throw userError;const{error}=await supabase.from('posts').insert([{title:title,content:content,user_id:currentUser.id,author:userData.username,created_at:new Date()}]);if(error)throw error;titleInput.value='';contentInput.value='';loadPosts();showNotification('投稿が作成されました！')}catch(error){console.error('Create post error:',error);showNotification('投稿の作成中にエラーが発生しました','error')}finally{setButtonLoading(submitButton,false)}}async function loadPosts(){const postsContainer=document.getElementById('postsList');const loadingElement=document.getElementById('loadingPosts');try{const{data:posts,error}=await supabase.from('posts').select('*').order('created_at',{ascending:false});if(error)throw error;if(loadingElement){loadingElement.remove()}if(posts&&posts.length>0){posts.forEach(post=>{const postElement=createPostElement(post);postsContainer.appendChild(postElement)})}else{postsContainer.innerHTML='<p class="text-center">まだ投稿がありません。最初の投稿を作成しましょう！</p>'}}catch(error){console.error('Load posts error:',error);if(loadingElement){loadingElement.remove()}postsContainer.innerHTML='<p class="text-center text-danger">投稿の読み込み中にエラーが発生しました</p>'}}function createPostElement(post){const postElement=document.createElement('div');postElement.className='post';postElement.setAttribute('data-post-id',post.id);const postDate=new Date(post.created_at);const formattedDate=postDate.toLocaleDateString('ja-JP',{year:'numeric',month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'});postElement.innerHTML=`
        <div class="post-header">
            <div>
                <h3>${post.title}</h3>
                <span class="post-author">${post.author}</span>
            </div>
            <span class="post-date">${formattedDate}</span>
        </div>
        <div class="post-content">
            ${post.content}
        </div>
        <div class="post-actions">
            <button class="like-button">
                <span>👍</span>
                <span>${post.likes || 0}</span>
            </button>
            <button class="comment-button">
                <span>💬</span>
                <span>${post.comments || 0}</span>
            </button>
        </div>
    `;const likeButton=postElement.querySelector('.like-button');likeButton.addEventListener('click',async()=>{if(currentUser){try{const newLikes=(post.likes||0)+1;const{error}=await supabase.from('posts').update({likes:newLikes}).eq('id',post.id);if(error)throw error;post.likes=newLikes;likeButton.querySelector('span:last-child').textContent=newLikes}catch(error){console.error('Like post error:',error);showNotification('いいねの更新中にエラーが発生しました','error')}}else{showNotification('いいねするにはログインしてください','error')}});return postElement}window.addEventListener('load',()=>{const loginBtn=document.getElementById('loginBtn');const signupBtn=document.getElementById('signupBtn');const logoutBtn=document.getElementById('logoutBtn');const homeLoginBtn=document.getElementById('homeLoginBtn');const homeSignupBtn=document.getElementById('homeSignupBtn');if(loginBtn)loginBtn.addEventListener('click',loadLoginPage);if(signupBtn)signupBtn.addEventListener('click',loadSignupPage);if(logoutBtn)logoutBtn.addEventListener('click',handleLogout);if(homeLoginBtn)homeLoginBtn.addEventListener('click',loadLoginPage);if(homeSignupBtn)homeSignupBtn.addEventListener('click',loadSignupPage);console.log('Event listeners attached')});async function setupDatabase(){await supabase.rpc('create_table_if_not_exists',{table_name:'users',columns:`
                    id uuid primary key,
                    username text unique not null,
                    email text unique not null,
                    created_at timestamp with time zone default now()
                `});await supabase.rpc('create_table_if_not_exists',{table_name:'posts',columns:`
                    id uuid primary key default uuid_generate_v4(),
                    title text not null,
                    content text not null,
                    user_id uuid references users(id),
                    author text not null,
                    likes integer default 0,
                    comments integer default 0,
                    created_at timestamp with time zone default now()
                `})}function setupRealtimeSubscription(){try{if(window.currentSubscription){supabase.removeChannel(window.currentSubscription)}window.currentSubscription=supabase.channel('public:posts').on('postgres_changes',{event:'INSERT',schema:'public',table:'posts'},handleNewPost).subscribe(status=>{console.log('Subscription status:',status);if(status==='SUBSCRIBED'){showNotification('リアルタイム更新を開始しました','success')}})}catch(error){console.error('Failed to setup realtime subscription:',error);showNotification('リアルタイム更新の設定に失敗しました','error')}}function handleNewPost(payload){console.log('New post received:',payload);const newPost=payload.new;if(currentUser&&newPost.user_id===currentUser.id){return}const existingPost=document.querySelector(`[data-post-id="${newPost.id}"]`);if(existingPost){return}const postElement=createPostElement(newPost);postElement.classList.add('new-post-animation');const postsContainer=document.getElementById('postsList');const firstPost=postsContainer.querySelector('.post');if(firstPost){postsContainer.insertBefore(postElement,firstPost)}else{postsContainer.innerHTML='';postsContainer.appendChild(postElement)}showNotification('新しい投稿が追加されました！','success');setTimeout(()=>{postElement.classList.remove('new-post-animation')},2000)}</script></body></html>