import React,{useEffect,useState}from "react"; import "./OpeningScreen.css"; const OpeningScreen = ({onFinish,ready}) =>{const [fadeOut,setFadeOut] = useState(false);const [canExit,setCanExit] = useState(false);useEffect(() => {// CSSのプログレスバーアニメーション(2.3s)が完了するのを待つ // これにより、ユーザーは「読み込みが完了した」という視覚的納得感を得られます const timer = setTimeout(() => {setCanExit(true);},2500); // 2.3秒のアニメーション + .2秒の余韻 return () => clearTimeout(timer);},[]);useEffect(() => {// 「データの準備(ready)」と「演出の完了(canExit)」の両方が揃ったらフェードアウト if (ready && canExit) {setFadeOut(true); // CSSの transition: opacity .8s に合わせて、完了通知を送る const finishTimer = setTimeout(onFinish,800); return () => clearTimeout(finishTimer);}},[ready,canExit,onFinish]);return (<div className={`opening-container ${fadeOut ? "exit" : ""}`}> <div className="opening-content"> {} <div className="logo-wrapper"> <h1 className="main-logo">Soft-B-Manager</h1> <div className="logo-underline"></div> </div> {} <div className="loading-area"> <p className="loading-text">SYSTEM INITIALIZING...</p> <div className="progress-bar"> <div className="progress-fill"></div> </div> </div> </div> </div>)}; export default OpeningScreen; .login-container{height:100vh;display:flex;justify-content:center;align-items:center;background:linear-gradient(135deg,#0f172a,#1e293b);position:relative;overflow:hidden;-webkit-user-select:none;user-select:none}.login-card{background:#1e293bb3;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);padding:30px;border-radius:24px;border:1px solid rgba(255,255,255,.1);width:90%;max-width:400px;z-index:10;box-shadow:0 25px 50px -12px #00000080}.login-header{text-align:center;margin-bottom:25px}.login-header h2{color:#fff;font-size:1.6rem;margin:0}.login-header p{color:#a0aec0;font-size:.9rem;margin-top:5px}.pro-badge{background:#63b3ed;color:#1a202c;font-size:.7rem;padding:2px 6px;border-radius:4px;margin-left:5px;vertical-align:middle}.team-selector-label{display:block;color:#63b3ed;font-size:.8rem;font-weight:700;margin-bottom:10px;text-align:center;letter-spacing:.1rem}.carousel-viewport{position:relative;width:100%;height:160px;margin-bottom:10px;display:flex;justify-content:center;align-items:center;perspective:1200px;cursor:grab;overflow:visible;z-index:5}.carousel-viewport:active{cursor:grabbing}.carousel-stage{position:absolute;width:100px;height:100px;transform-style:preserve-3d;transition:transform .6s cubic-bezier(.23,1,.32,1)}.team-card{position:absolute;left:0;top:0;width:100px;height:100px;display:flex;justify-content:center;align-items:center;backface-visibility:visible;transform-style:preserve-3d;transition:opacity .3s ease;opacity:.4}.team-card.active{opacity:1}.team-icon-centered{width:90px!important;height:90px!important;background:#ffffff1a;border-radius:50%;display:flex!important;align-items:center;justify-content:center;border:4px solid transparent;overflow:hidden;transition:all .3s ease;position:relative;transform:translateZ(1px)}.team-card.active .team-icon-centered{border-color:#63b3ed;box-shadow:0 0 25px #63b3ed80;background:#63b3ed1a}.centered-logo{width:100%!important;height:100%!important;object-fit:cover!important;display:block!important;opacity:1!important;pointer-events:none}.logo-placeholder{font-size:2.5rem;color:#cbd5e0;font-weight:700;transform:translateZ(2px)}.selected-team-name{color:#fff;text-align:center;font-size:1rem;font-weight:700;height:1.2rem;margin-bottom:20px;text-shadow:0 0 10px rgba(99,179,237,.8)}.carousel-btn{position:absolute;top:50%;transform:translateY(-50%);background:none;border:none;color:#63b3ed;font-size:3rem;cursor:pointer;z-index:200;opacity:.5;transition:opacity .3s;padding:0 10px}.carousel-btn:hover{opacity:1}.carousel-btn.prev{left:-10px}.carousel-btn.next{right:-10px}.input-row{display:flex;gap:15px;margin-bottom:25px}.input-group{flex:1;display:flex;flex-direction:column}.input-group label{color:#94a3b8;font-size:.75rem;margin-bottom:8px;margin-left:4px}.input-group input{width:100%;box-sizing:border-box;padding:14px;background:#0f172a99;border:1px solid rgba(255,255,255,.1);border-radius:14px;color:#fff;font-size:1.1rem;text-align:center;outline:none}.input-group input:focus{border-color:#63b3ed;background:#0f172acc}.checkbox-group{margin-bottom:25px}.checkbox-container{display:flex;align-items:center;color:#a0aec0;font-size:.85rem;cursor:pointer}.checkbox-container input{margin-right:10px;width:16px;height:16px;accent-color:#63b3ed}.login-button{width:100%;padding:18px;background:linear-gradient(135deg,#3182ce,#5a67d8);border:none;border-radius:16px;color:#fff;font-weight:700;font-size:1.1rem;cursor:pointer;transition:transform .2s}.login-button:active{transform:scale(.98)}.error-text{color:#fc8181;font-size:.8rem;text-align:center;margin-bottom:15px}.loading-screen{height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#0f172a;color:#fff}.catchball-animation{display:flex;align-items:center;gap:60px;margin-bottom:30px}.glove{font-size:4rem}.ball{font-size:2rem;animation:toss-ball .8s infinite alternate ease-in-out}@keyframes toss-ball{0%{transform:translate(-50px)}to{transform:translate(50px)}}.loading-text{font-size:1rem;color:#63b3ed;font-weight:700}.top-header{display:flex;justify-content:space-between;align-items:center;padding:8px 15px;background-color:#1a202c;border-bottom:1px solid rgba(255,255,255,.1);color:#fff;position:sticky;top:0;z-index:100;height:85px}.header-left{width:60px}.system-logo{width:50px;height:50px;border-radius:10px;object-fit:contain;background:#ffffff0d;border:1px solid rgba(255,255,255,.1)}.team-identity-block{display:flex;flex-direction:column;align-items:center;gap:2px;min-height:50px;justify-content:center}.header-team-logo{height:35px;width:auto;object-fit:contain;margin-bottom:4px}.header-center{flex-grow:1;display:flex;justify-content:center}.team-identity-block{display:flex;flex-direction:column;align-items:center;gap:2px}.header-team-logo{height:35px;width:auto;object-fit:contain}.team-text-info{display:flex;flex-direction:column;align-items:center}.team-name-display{color:#63b3ed;font-size:.95rem;font-weight:700;margin:0}.team-id-display{color:#718096;font-size:.65rem}.header-right{width:70px;display:flex;justify-content:flex-end}.user-profile-block{display:flex;flex-direction:column;align-items:center;gap:4px}.avatar-wrapper{position:relative;width:45px;height:45px}.user-avatar{width:100%;height:100%;border-radius:50%;border:2px solid #63b3ed;object-fit:cover}.number-badge-tr{position:absolute;top:-6px;right:-6px;background:#63b3ed;color:#1a202c;font-size:.65rem;font-weight:700;padding:1px 5px;border-radius:10px;border:2px solid #1a202c;white-space:nowrap}.user-name-label{color:#a0aec0;font-size:.7rem;font-weight:500;max-width:65px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.app-footer{position:fixed;bottom:0;left:0;width:100%;height:65px;background-color:#fff;border-top:1px solid #e0e0e0;display:flex;justify-content:center;align-items:center;z-index:1000;box-shadow:0 -2px 10px #0000000d}.footer-nav{display:flex;justify-content:space-around;align-items:center;width:100%;max-width:500px;height:100%;padding:0 10px}.nav-item{display:flex;flex-direction:column;align-items:center;justify-content:center;background:none;border:none;color:#666;font-size:.7rem;cursor:pointer;transition:all .2s;padding:5px;min-width:55px;outline:none}.nav-item.active{color:#007bff;font-weight:700;transform:translateY(-2px)}.nav-emoji{font-size:1.5rem;margin-bottom:2px;line-height:1.2}.nav-item:hover,.nav-item:active{color:#007bff}.nav-item.logout{color:#d9534f}.nav-item-container{position:relative;display:flex;flex-direction:column;align-items:center}.member-popup-menu{position:absolute;bottom:75px;left:50%;transform:translate(-50%);background:#fff;border:1px solid #ddd;border-radius:12px;box-shadow:0 5px 25px #0003;display:flex;flex-direction:column;min-width:180px;overflow:hidden;animation:fadeIn .2s ease-out;z-index:1100}@keyframes fadeIn{0%{opacity:0;transform:translate(-50%) translateY(10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}.popup-item{display:flex;align-items:center;gap:12px;padding:15px 20px;background:#fff;border:none;border-bottom:1px solid #eee;text-align:left;font-size:.95rem;color:#333;cursor:pointer;width:100%;transition:background-color .2s}.popup-emoji{font-size:1.2rem}.popup-item:last-child{border-bottom:none}.popup-item:hover{background-color:#f1f7ff}.popup-item:active{background-color:#e2efff}.admin-only{color:#f0ad4e;font-weight:700}import React,{useState,useEffect}from "react"; import{gasApi}from "../api/gasApi"; import "./MemberListScreen.css"; const MemberListScreen = ({currentUser,allTeams,onBack}) =>{const [selectedTeamId,setSelectedTeamId] = useState(currentUser.teamId);const [members,setMembers] = useState([]);const [loading,setLoading] = useState(false);// 自チームを先頭にソートして、使いやすさを向上 const sortedTeams = [...allTeams].sort((a,b) => {if (a.id === currentUser.teamId) return -1; if (b.id === currentUser.teamId) return 1; return 0;});useEffect(() => {const fetchMembers = async () => {setLoading(true); try {const result = await gasApi.getMemberList(selectedTeamId); if (result.success) {setMembers(result.data);}} catch (err) {console.error("メンバー一覧の取得に失敗しました:",err);} finally {setLoading(false);}}; fetchMembers();},[selectedTeamId]);return (<div className="ml-container"> {} <header className="ml-header"> <button className="ml-close-btn" onClick={onBack}>✕</button> <h2 className="ml-title">メンバー名簿</h2> </header> {} <nav className="ml-team-nav"> {sortedTeams.map(team => (<div key={team.id} className={`ml-team-item ${selectedTeamId === team.id ? "active" : ""}`} onClick={() => setSelectedTeamId(team.id)} > <div className="ml-team-icon-wrap"> {} {team.teamLogo ? (<img src={team.teamLogo} alt={team.name} className="ml-team-icon" onError={(e) => {e.target.style.display = "none";}} />) : (<div className="ml-team-no-icon">{team.name.substring(0,2)}</div>)} </div> <span className="ml-team-name">{team.name}</span> </div>))} </nav> {} <main className="ml-main"> {loading ? (<div className="ml-status">読み込み中...</div>) : (<div className="ml-grid"> {members.length > 0 ? (members.map(m => (<div key={m.number} className="ml-card"> <div className="ml-card-left"> <div className="ml-avatar-wrap"> <img src={m.profileImg || "/def.jpg"} alt={m.name} className="ml-avatar" onError={(e) => {e.target.src = "/def.jpg";}} /> {} <span className="ml-number-badge">#{m.number}</span> </div> </div> <div className="ml-card-right"> <div className="ml-name">{m.name}</div> <div className="ml-role">{m.role || "選手"}</div> </div> </div>))) : (<div className="ml-status">登録メンバーがいません</div>)} </div>)} </main> </div>)}; export default MemberListScreen; :root{--main-blue: #60a5fa;--bg-dark: #0f172a;--bg-input: #1e293b}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";background-color:#1a202c;color:#fff;-webkit-font-smoothing:antialiased}.app-viewport{min-height:100vh;display:flex;flex-direction:column}.app-header{position:fixed;top:0;width:100%;height:65px;background-color:#0f172a;display:flex;justify-content:space-between;align-items:center;padding:0 15px;box-sizing:border-box;border-bottom:1px solid #334155;z-index:100}.system-logo{width:50px;height:50px;border-radius:8px}.header-right-profile{display:flex;align-items:center;gap:12px}.user-details{text-align:right}.team-row{font-size:.75rem;color:#60a5fa;font-weight:700}.user-row{font-size:.9rem;margin-top:2px}.user-meta{font-size:.7rem;color:#94a3b8;margin-left:6px}.header-avatar{width:40px;height:40px;border-radius:50%;border:2px solid #60a5fa;object-fit:cover}.header h1{font-size:1.5rem;color:#60a5fa;margin-bottom:2rem;text-align:center;padding-top:2rem}.login-card,.profile-card{background-color:#0f172a;padding:2rem;border-radius:12px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;width:90%;max-width:400px;margin:2rem auto}.form{display:flex;flex-direction:column;gap:1rem}.input-group{display:flex;flex-direction:column;gap:.5rem;text-align:left}.input-group label{color:#94a3b8;font-size:.9rem}input{padding:1rem;border-radius:8px;border:1px solid #334155;background-color:#1e293b;color:#fff;font-size:1.1rem;outline:none}input:focus{border-color:#60a5fa}button{padding:1rem;border-radius:8px;border:none;background-color:#3b82f6;color:#fff;font-size:1.1rem;font-weight:700;cursor:pointer;transition:background-color .2s}button:hover{background-color:#2563eb}button:disabled{background-color:#6b7280;cursor:not-allowed}.error-message{color:#ef4444;margin-top:1rem;font-size:.9rem;text-align:center}.profile-header{display:flex;align-items:center;gap:1.5rem;text-align:left}.profile-image{width:80px;height:80px;border-radius:50%;object-fit:cover;border:3px solid #60a5fa}.profile-info h3{margin:0;font-size:1.5rem;color:#fff}.profile-info p{margin:0;color:#94a3b8;font-size:.9rem}.role-badge{display:inline-block;background-color:#334155;color:#60a5fa;padding:.25rem .5rem;border-radius:4px;font-size:.8rem;margin-top:.5rem!important}.logout-button{margin-top:1.5rem;width:100%;background-color:#334155}.logout-button:hover{background-color:#475569}.bottom-nav{position:fixed;bottom:0;width:100%;height:70px;background-color:#0f172a;display:flex;justify-content:space-around;align-items:center;border-top:1px solid #334155;padding-bottom:env(safe-area-inset-bottom)}.nav-item{font-size:.75rem;color:#94a3b8;cursor:pointer}.logout-nav{color:#ef4444;font-weight:700}.dashboard-content{padding:85px 20px 100px;max-width:500px;margin:0 auto}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:dark light;color:#ffffffde;background-color:#1e293b;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;display:block;min-height:100vh;overflow-x:hidden}#root{width:100%;min-height:100vh;margin:0;padding:0;text-align:center}
