⚙️ 공통 Shell 규격
모든 독립 퍼즐 HTML 파일이 따르는 레이아웃·컴포넌트 규격입니다.
공통 헤더
56px
css/style.css 의 #app-header (전체 공통)
툴바 (#topbar)
46px
퍼즐별 컨트롤 바 (통일)
사이드바 (#side)
220px
전체 통일 — 기존 148~202px → 220px
QR (화면)
64px
.ps-qr-box 안에 60px QR
QR (인쇄)
200px
dataURI → 20×20mm 출력
인쇄 레이아웃
1/2/4
페이지당 문제 수 선택 버튼
📐 사이드바 구성 순서 (통일)
  1. 퍼즐 유형 — .ti 항목 목록 (있는 퍼즐만)
  2. 난이도 — .dbtn 4단계: 쉬움/보통/어려움/전문가
  3. SEED 입력 — #side-seed (monospace, 8자)
  4. 같은 Seed 재생성 — .dbtn 스타일 버튼
  5. ── 구분선 ──
  6. 📄 인쇄 페이지 수 — PuzzleShell.PageQty (−/숫자/+/페이지)
  7. 🖨 인쇄 설정 — .ps-print-btn (초록 테두리)
  8. 🎲 새 문제 생성 — .ps-gen-btn (그라디언트)
  9. ── 선택 ──
  10. 문제 정보 통계 / 범례 / 설명
🖨️ 인쇄 모달 구성 (통일)
항목내용기본값
페이지당 문제 수1 / 2 / 4 버튼 (퍼즐에 따라 지원 범위 다름)자동추천
자동 추천난이도별 권장 수 표시 (RECOMMEND 객체)노란 박스
총 인쇄 분량N페이지 × 페이지당 M문제 = 총 K문제자동계산
출력 내용문제만 / 문제+정답 (라디오)문제만
힌트 포함체크박스 (지원 퍼즐만)미포함
인쇄 시작그라디언트 버튼 → PrintCommon.openPrintWindow()
📊 파일별 마이그레이션 현황
파일 타입 shell.css shell.js 사이드바 220px PS 인쇄모달 빈 첫화면 상태
app.html?game=weighing-puzzle Type B ✅ 220px ✅ wp-pm 완료
app.html?game=mirror-puzzle Type B ✅ 220px ✅ mp-pm 완료
app.html?game=baseball-puzzle Type B ✅ 220px ✅ bp-pm 완료
app.html?game=matchstick-puzzle Type B ✅ 220px ✅ ms-pm 완료
app.html?game=symmetry Type B ✅ 220px ✅ sy-pm 완료
app.html?game=land-divide Type B ✅ 220px ✅ lp-pm 완료
app.html?game=yajilin Type B ✅ 220px ✅ yj-pm 완료
puzzle-new.html Type C — (자체) ✅ 240px ✅ 자체모달 완료
logic-arrange.html Type C — (자체) ✅ 자체 ✅ 자체모달 완료
app.html (노노그램 등) Type A 별도 체계 별도 체계 해당없음 자체 모달 해당없음 별도유지
✅ 전체 통일 완료 (v2.0 · 2026-05)

모든 독립 퍼즐 파일이 탑바 / 사이드바 분리 규칙, 빈 첫화면(emptyStateHtml), PS 인쇄 모달, 사이드바 하단 고정(sidebarBottomHtml) 패턴을 완전히 적용했습니다.
• 탑바: 새 문제·재생성·정답·힌트·초기화 등 현재 문제 조작만
• 사이드바: 유형→난이도→SEED→기타→인쇄설정+생성 (하단 고정)
• 첫 화면: 캐시 무관하게 항상 빈 상태로 시작 (generatePuzzle() 제거)

🛠️ 향후 에디터 목록
학생 활동, 교사 설명, 작품 저장 목적으로 제작 예정인 인터랙티브 에디터/뷰어입니다.
계획
🔢 노노그램 활동 뷰어
인쇄된 노노그램을 앱에서 이어 풀 수 있는 인터랙티브 플레이어. 색칠하기, 틀린 칸 체크, 완성 저장.
노노그램 컬러 노노그램
계획
🔥 성냥개비 퍼즐 에디터
성냥개비를 드래그로 이동/제거하여 식을 완성하는 활동 에디터. 학생 풀이 저장 및 공유.
matchstick-puzzle
계획
🏙️ 스카이스크래퍼 에디터
숫자를 입력하여 빌딩 배치를 완성하는 스카이스크래퍼 퍼즐 인터랙티브 플레이어.
신규 퍼즐
계획
⚓ 전함 배치 에디터
전함을 배치하고 힌트를 검증하는 배틀십 인터랙티브 플레이어. puzzle-new.html 연동.
puzzle-new (battleship)
계획
📈 그래프 그리기
좌표 격자 위에 점을 찍고 선을 연결하여 수학 그래프 개념을 탐구하는 도구.
수학 탐구 교과연계
계획
📌 지오보드
고무줄(선분)로 핀 보드 위에 도형을 만들고 넓이·둘레를 탐구하는 활동 에디터.
도형·공간 3~4학년
계획
⭐ 별자리 잇기
별(점)을 순서대로 이어 별자리를 완성하는 활동. 방향·거리 개념 연습.
수학 탐구 1~2학년
🔗 퍼즐↔에디터 연결 매핑
퍼즐 인쇄지 QR 코드로 접속 → 에디터에서 이어 풀기 / 교사 설명 도구로 활용
퍼즐 (인쇄 생성기) 연결 에디터 QR URL 예시 우선순위 상태
⚖️ 무게 측정 (app.html?game=weighing-puzzle) 인터랙티브 저울 (계획) weighing-play.html?seed=… 중간 계획
🪞 거울 퍼즐 (app.html?game=mirror-puzzle) 레이저 시뮬레이터 (계획) mirror-play.html?seed=… 중간 계획
⚾ 숫자야구 (app.html?game=baseball-puzzle) 숫자야구 앱 통합 화면 app.html?game=baseball-puzzle&seed=… 낮음 계획
🔥 성냥개비 (app.html?game=matchstick-puzzle) 성냥개비 에디터 matchstick-play.html?seed=… 높음 계획
🔢 노노그램 (app.html) 노노그램 활동 뷰어 grading.html?seeds=… 높음 일부 있음
⚓ 전함 배치 (puzzle-new.html) 전함 배치 에디터 battleship-play.html?seed=… 높음 계획
🔆 아카리 (puzzle-new.html) 아카리 플레이어 (계획) akari-play.html?seed=… 중간 계획
🌐 야지린 (app.html?game=yajilin) 야지린 앱 통합 화면 app.html?game=yajilin&seed=… 완료 에디터 있음
📦 QR 데이터 포맷 (인쇄 푸터)
// 인쇄 QR에 담기는 URL 예시 puzzleSeed = "A3F1B2" // SEED 6자리 puzzleType = "battleship" // 퍼즐 종류 difficulty = "normal" // 난이도 // 결과 URL battleship-play.html?seed=A3F1B2&diff=normal // 학생 저장 완료 후 URL (Table API ID 포함) battleship-play.html?seed=A3F1B2&save=uuid-1234
👨‍🎓 학생 작품 저장 → 교사 수집 → 인쇄 → 재배포
📄
① 인쇄 학습지 배포
교사가 퍼즐 생성기에서 학습지 인쇄. 각 문제에 QR 코드 포함.
puzzle-new.html
📱
② 학생 QR 스캔
막히면 QR 스캔 → 에디터에서 같은 문제 앱으로 이어 풀기.
battleship-play.html?seed=…
💾
③ 작품 저장
학생이 풀이 완성 → "저장" 버튼 → Table API에 저장. 저장 코드 표시.
Table API POST
🗂️
④ 교사 수집
학생번호 수집 → 관리 페이지에서 반 전체 작품 일괄 조회.
teacher-collect.html
🖨️
⑤ 재인쇄·재배포
학생 작품 선별하여 인쇄 → 우수작 게시 / 다음 단계 학습지로 재배포.
print bulk
🗄️ Table API 데이터 모델 (학생 작품 저장)
// 테이블: student_works { id: UUID (자동), puzzle_type: "battleship", // 퍼즐 종류 seed: "A3F1B2", // 문제 SEED difficulty: "normal", // 난이도 answer_data:"JSON 직렬화 풀이", // 학생 풀이 상태 student_name:"홍길동", // 학생 이름(선택) class_code: "3-2", // 반 코드(선택) is_correct: true, // 정답 여부 created_at: timestamp }
💻 Shell 사용법 (신규 파일 작성 시)
1. 헤드에 링크 추가
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/puzzle-shell.css"> <script src="js/ui/print-common.js"></script> <script src="js/ui/puzzle-shell.js"></script>
2. 사이드바 하단 컴포넌트 삽입
// HTML: 사이드바 하단에 컨테이너 추가 <div id="ps-sidebar-bottom"></div> // JS: DOMContentLoaded 에서 document.getElementById('ps-sidebar-bottom').innerHTML = PuzzleShell.sidebarBottomHtml({ pageQtyId: 'ps-pages', modalId: 'mypuzzle-pm', genBtnLabel: '🎲 새 문제 생성', genBtnId: 'gen-btn', pageQtyHint: '서로 다른 문제 N페이지 인쇄' });
3. 인쇄 모달 삽입 + 콜백 (v2.0 패턴)
// HTML: body 끝에 모달 컨테이너 추가 (id는 파일별 접두어 사용) <div id="xx-pm-modal-container"></div> // JS: DOMContentLoaded 에서 document.getElementById('xx-pm-modal-container').innerHTML = PuzzleShell.PrintModal.html({ id: 'xx-pm', puzzleName: '내 퍼즐', recommend: { easy:4, normal:2, hard:1 }, hasAnswer: true, layouts: [1, 2, 4], pageQtyId: 'xx-pages' // ← 사이드바 PageQty와 연동 }); PuzzleShell.PrintModal.onPrint('xx-pm', function(o) { // o.layout: 1|2|4 o.output: 'problem'|'both' o.pageCount: N executePrint(o); // o 객체 그대로 전달 권장 }, 'xx-pages'); // openPrintModal 함수 (탑바 버튼 등에서 호출) function openPrintModal() { if (!puzzle) { alert('먼저 문제를 생성해주세요.'); return; } PuzzleShell.PrintModal.open('xx-pm', PuzzleShell.PageQty.get('xx-pages'), currentDiff ); } // executePrint: o 파라미터로 모달 값 수신 (print-qty2 참조 제거) async function executePrint(o) { PuzzleShell.PrintModal.close('xx-pm'); const pages = o?.pageCount || 1; const layout = o?.layout || 4; const inclAns = o?.output === 'both'; // ... 인쇄 HTML 생성 → PrintCommon.openPrintWindow() }
4. 빈 첫화면 (emptyStateHtml)
// HTML: main 안 최상단에 컨테이너 추가 <main id="main"> <div id="xx-empty-area"></div> <div id="grid-wrap" style="display:none"> ... </div> </main> // JS: DOMContentLoaded 에서 주입 document.getElementById('xx-empty-area').innerHTML = PuzzleShell.emptyStateHtml({ icon: '🧩', title: '퍼즐 생성기 이름', desc: '퍼즐 설명 한 줄', ctaLabel: '첫 문제 생성하기', ctaOnClick: 'newPuzzle()' }); // applyPuzzle/renderPuzzle 등 문제 생성 후 호출되는 함수 안에서 해제 function applyPuzzle(result) { document.getElementById('xx-empty-area').style.display = 'none'; document.getElementById('grid-wrap').style.display = ''; // ... 기존 코드 }
5. 화면용 QR 렌더링
// HTML: 카드 내 <div class="ps-qr-section"> <div class="ps-qr-box" id="qr-puzzle-A3F1B2"></div> <div class="ps-qr-info"> <b>앱에서 이어 풀기</b><br> <span class="ps-qr-label">QR 스캔 → 같은 문제 이어 풀기</span> </div> </div> // JS PuzzleShell.QR.renderToBox('qr-puzzle-A3F1B2', editorUrl);