← 실습 자료

계정별 자료 분리 개념 실험

방문자/A/B 역할을 바꾸는 화면 시뮬레이션입니다. 실제 인증·DB가 아닙니다.

HTML 복사하기

HTML 직접 보기
<!doctype html>
<html lang="ko"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>계정별 자료 분리 · 개념 실험</title><style>*{box-sizing:border-box}body{margin:0;background:#f5f3ee;color:#203d35;font:17px/1.6 system-ui,-apple-system,"Noto Sans KR",sans-serif}main{max-width:900px;margin:36px auto;padding:32px;background:#fff;border:1px solid #dcded5;border-radius:24px}h1{font-size:clamp(26px,5vw,40px);line-height:1.3;letter-spacing:-.03em;margin:8px 0 12px}h2{font-size:22px}p{margin:10px 0}.eyebrow{font-size:13px;font-weight:800;letter-spacing:.09em;color:#547060}.muted{color:#68786f;font-size:15px}.note{padding:14px 18px;background:#fff2c8;border-radius:12px;color:#4f481f}.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:16px 0}button,input,textarea,select{font:inherit;border-radius:10px;padding:12px 16px}button{background:#245445;color:white;border:0;cursor:pointer;font-weight:700}button.secondary{background:#edf2ed;color:#245445}button:disabled{opacity:.45;cursor:wait}input,textarea,select{border:1px solid #bacbc2;background:#fff;color:#203d35;max-width:100%}input,textarea{flex:1;min-width:180px}textarea{width:100%;min-height:150px;resize:vertical}label{font-weight:700}button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:3px solid #e3ae35;outline-offset:3px}.card{border:1px solid #dce5df;padding:18px;border-radius:16px;margin:12px 0}.big{font-size:clamp(36px,8vw,72px);font-weight:800;text-align:center;padding:40px 0;background:#edf3ed;border-radius:18px}.badge{display:inline-block;background:#ecf2ed;color:#245445;padding:4px 10px;border-radius:7px;font-size:14px}.todo{display:flex;align-items:center;gap:10px;padding:12px 0;border-bottom:1px solid #e7ece8}.todo span{flex:1;overflow-wrap:anywhere}.todo.done span{text-decoration:line-through;color:#809086}.error{color:#9c382a}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:16px/1.7 system-ui,"Noto Sans KR",sans-serif}.hidden{display:none}nav{display:flex;gap:8px;flex-wrap:wrap}@media(max-width:600px){main{margin:0;padding:22px 18px;border-radius:0;border:0;min-height:100vh}.todo{flex-wrap:wrap}button{min-height:48px}}</style></head><body><main><div class="eyebrow">05–06 · 자료 소유자의 개념</div><h1>내 자료만 보이는 이유</h1><p class="note">학습용 역할 전환 화면입니다. 실제 로그인·Supabase·RLS가 아닙니다. 실제 앱은 별도로 두 계정과 API 권한을 검사해야 해요.</p><label for="role">화면에서 가정할 역할</label><div class="row"><select id="role"><option value="guest">방문자 · 미로그인</option><option value="A">계정 A</option><option value="B">계정 B</option></select><span id="identity" class="badge"></span></div><label for="title">가상 할 일</label><div class="row"><input id="title" maxlength="200" placeholder="개념 실험용 내용"><button id="add">내 목록에 추가</button></div><p id="message" role="status" aria-live="polite"></p><div id="list"></div><section class="card"><h2>타인 행에 접근해 보기</h2><p>현재 역할이 소유하지 않은 행의 수정 요청을 흉내 냅니다.</p><button id="foreign" class="secondary">타인 행 수정 시도</button><p id="denied" role="status" aria-live="polite"></p></section><p class="muted">이 예시는 메모리에서 역할별 목록을 나눕니다. 새로고침하면 모든 실험이 초기화됩니다.</p></main><script>let rows=[{id:1,user:'A',title:'A의 수업 준비',done:false},{id:2,user:'B',title:'B의 독서 기록',done:false}],nextId=3;const $=id=>document.getElementById(id);const current=()=>$('role').value;function render(){const role=current();$('identity').textContent=role==='guest'?'읽기·쓰기 거절':'user_id = '+role;$('add').disabled=role==='guest';$('title').disabled=role==='guest';$('list').replaceChildren();const visible=rows.filter(r=>r.user===role);if(!visible.length){const p=document.createElement('p');p.textContent=role==='guest'?'로그인한 사용자만 자기 목록을 볼 수 있어요.':'내 목록이 비어 있어요.';$('list').append(p)}visible.forEach(row=>{const el=document.createElement('div');el.className='todo'+(row.done?' done':'');const text=document.createElement('span');text.textContent=row.title;const toggle=document.createElement('button');toggle.className='secondary';toggle.textContent=row.done?'완료 취소':'완료';toggle.addEventListener('click',()=>{if(row.user!==current())return;row.done=!row.done;render()});const remove=document.createElement('button');remove.className='secondary';remove.textContent='삭제';remove.addEventListener('click',()=>{if(row.user!==current())return;rows=rows.filter(r=>r.id!==row.id);render()});el.append(text,toggle,remove);$('list').append(el)})}$('role').addEventListener('change',()=>{$('title').value='';$('message').textContent='';$('denied').textContent='';render()});$('add').addEventListener('click',()=>{const title=$('title').value.trim();if(current()==='guest')return;if(!title||title.length>200){$('message').textContent='1~200자로 적어 주세요.';return}rows.push({id:nextId++,user:current(),title,done:false});$('title').value='';$('message').textContent='현재 역할의 목록에 추가했어요.';render()});$('foreign').addEventListener('click',()=>{$('denied').textContent='거절: 현재 역할과 행의 소유자가 다릅니다. (화면 시뮬레이션)'});render();</script></body></html>