Spaces:
Running
Running
Upload index.html
Browse files[feat] bigger pannel
- index.html +211 -99
index.html
CHANGED
|
@@ -196,23 +196,81 @@
|
|
| 196 |
padding:16px;
|
| 197 |
}
|
| 198 |
.piece-head{
|
| 199 |
-
display:flex; align-items:
|
| 200 |
-
|
|
|
|
| 201 |
}
|
| 202 |
.piece-label{
|
| 203 |
font-family:'Space Grotesk',sans-serif;
|
| 204 |
font-weight:600;
|
| 205 |
font-size:13px;
|
| 206 |
-
display:flex;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
}
|
| 208 |
-
.piece-dot{width:9px;height:9px;border-radius:50%;}
|
| 209 |
.piece-clear{
|
| 210 |
background:none; border:none; color:var(--muted-2);
|
| 211 |
font-size:11px; cursor:pointer; font-family:'Inter',sans-serif;
|
| 212 |
padding:2px 4px;
|
|
|
|
| 213 |
}
|
| 214 |
.piece-clear:hover{color:var(--ink);}
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
/* ---- mode tabs (preset vs draw) ---- */
|
| 217 |
.mode-tabs{
|
| 218 |
display:flex;
|
|
@@ -244,8 +302,12 @@
|
|
| 244 |
.mode-tab:focus-visible{outline:2px solid var(--clear); outline-offset:1px;}
|
| 245 |
|
| 246 |
/* ---- preset picker ---- */
|
|
|
|
|
|
|
|
|
|
| 247 |
.preset-panel{
|
| 248 |
-
|
|
|
|
| 249 |
overflow-y:auto;
|
| 250 |
padding-right:4px;
|
| 251 |
}
|
|
@@ -255,21 +317,23 @@
|
|
| 255 |
letter-spacing:0.03em;
|
| 256 |
text-transform:uppercase;
|
| 257 |
color:var(--muted-2);
|
| 258 |
-
margin:
|
| 259 |
}
|
| 260 |
.preset-group-label:first-child{margin-top:0;}
|
| 261 |
.preset-group-grid{
|
| 262 |
-
display:
|
| 263 |
-
|
| 264 |
-
|
|
|
|
| 265 |
}
|
| 266 |
.preset-thumb-btn{
|
| 267 |
-
|
|
|
|
| 268 |
background:var(--panel-2);
|
| 269 |
border:1.5px solid var(--grid-line);
|
| 270 |
border-radius:8px;
|
| 271 |
cursor:pointer;
|
| 272 |
-
display:flex;
|
| 273 |
align-items:center;
|
| 274 |
justify-content:center;
|
| 275 |
padding:6px;
|
|
@@ -283,12 +347,12 @@
|
|
| 283 |
box-shadow:0 0 0 1px var(--piece-color, var(--clear)) inset;
|
| 284 |
}
|
| 285 |
.thumb-grid{
|
| 286 |
-
width:100%;
|
| 287 |
-
height:100%;
|
| 288 |
display:grid;
|
| 289 |
-
gap:
|
| 290 |
}
|
| 291 |
.thumb-cell{
|
|
|
|
|
|
|
| 292 |
background:transparent;
|
| 293 |
border-radius:1.5px;
|
| 294 |
}
|
|
@@ -481,39 +545,32 @@
|
|
| 481 |
<aside class="pieces">
|
| 482 |
<div class="piece-card">
|
| 483 |
<div class="piece-head">
|
| 484 |
-
<div class="piece-label"><span class="
|
| 485 |
-
<button class="piece-clear"
|
| 486 |
-
</div>
|
| 487 |
-
<div class="mode-tabs" data-piece="a">
|
| 488 |
-
<button class="mode-tab active" data-mode="preset" type="button">프리셋에서 선택</button>
|
| 489 |
-
<button class="mode-tab" data-mode="draw" type="button">직접 그리기</button>
|
| 490 |
-
</div>
|
| 491 |
-
<div class="preset-panel" id="preset-a"></div>
|
| 492 |
-
<div class="piece-grid" id="piece-a" style="--piece-color:var(--piece-a); display:none;"></div>
|
| 493 |
-
</div>
|
| 494 |
-
<div class="piece-card">
|
| 495 |
-
<div class="piece-head">
|
| 496 |
-
<div class="piece-label"><span class="piece-dot" style="background:var(--piece-b)"></span>조각 B</div>
|
| 497 |
-
<button class="piece-clear" data-clear="b">지우기</button>
|
| 498 |
</div>
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
<button class="
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
<
|
| 509 |
-
<button class="
|
|
|
|
|
|
|
|
|
|
| 510 |
</div>
|
| 511 |
-
|
|
|
|
| 512 |
<button class="mode-tab active" data-mode="preset" type="button">프리셋에서 선택</button>
|
| 513 |
<button class="mode-tab" data-mode="draw" type="button">직접 그리기</button>
|
| 514 |
</div>
|
| 515 |
-
|
| 516 |
-
<div class="
|
|
|
|
| 517 |
</div>
|
| 518 |
|
| 519 |
<div class="solve-row">
|
|
@@ -602,6 +659,7 @@
|
|
| 602 |
};
|
| 603 |
let pieceMode = {a:'preset', b:'preset', c:'preset'}; // 'preset' | 'draw'
|
| 604 |
let selectedPreset = {a:null, b:null, c:null};
|
|
|
|
| 605 |
let lastSolution = null; // {steps:[{piece,r,c,offsets,boardBeforeClear,clearRows,clearCols,boardAfterClear}], totalLines, finalEmpty}
|
| 606 |
|
| 607 |
// ---- DOM refs ----
|
|
@@ -633,27 +691,26 @@
|
|
| 633 |
boardCells.push(row);
|
| 634 |
}
|
| 635 |
|
| 636 |
-
// ---- build piece
|
| 637 |
-
const pieceCellEls =
|
| 638 |
-
|
| 639 |
-
const el = document.getElementById('
|
| 640 |
for(let r=0;r<PIECE_GRID;r++){
|
| 641 |
const rowEls=[];
|
| 642 |
for(let c=0;c<PIECE_GRID;c++){
|
| 643 |
const b = document.createElement('button');
|
| 644 |
b.type='button';
|
| 645 |
b.className='pcell';
|
| 646 |
-
b.setAttribute('aria-label', `${pieceNameKo[k]} 조각 ${r+1},${c+1}`);
|
| 647 |
b.dataset.role='piece';
|
| 648 |
-
b.dataset.key=
|
| 649 |
b.dataset.r=r;
|
| 650 |
b.dataset.c=c;
|
| 651 |
el.appendChild(b);
|
| 652 |
rowEls.push(b);
|
| 653 |
}
|
| 654 |
-
pieceCellEls
|
| 655 |
}
|
| 656 |
-
}
|
| 657 |
|
| 658 |
// ---- shared cell get/set (used by both click and drag-paint) ----
|
| 659 |
function getCellValue(role, key, r, c){
|
|
@@ -668,7 +725,7 @@
|
|
| 668 |
} else {
|
| 669 |
selectedPreset[key] = null; // manual edit no longer matches any preset
|
| 670 |
renderPiece(key);
|
| 671 |
-
|
| 672 |
}
|
| 673 |
return true;
|
| 674 |
}
|
|
@@ -748,13 +805,15 @@
|
|
| 748 |
return {h:maxR+1, w:maxC+1};
|
| 749 |
}
|
| 750 |
|
|
|
|
|
|
|
| 751 |
function buildThumb(cells){
|
| 752 |
const {h,w} = shapeBBox(cells);
|
| 753 |
const filled = new Set(cells.map(([r,c])=>r+','+c));
|
| 754 |
const grid = document.createElement('div');
|
| 755 |
grid.className = 'thumb-grid';
|
| 756 |
-
grid.style.gridTemplateColumns = `repeat(${w},
|
| 757 |
-
grid.style.gridTemplateRows = `repeat(${h},
|
| 758 |
for(let r=0;r<h;r++){
|
| 759 |
for(let c=0;c<w;c++){
|
| 760 |
const cell = document.createElement('div');
|
|
@@ -765,24 +824,33 @@
|
|
| 765 |
return grid;
|
| 766 |
}
|
| 767 |
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
clearSolution();
|
| 773 |
-
|
| 774 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
}
|
| 776 |
|
| 777 |
-
function
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
btn.classList.toggle('selected', btn.dataset.presetId === selectedPreset[k]);
|
| 781 |
});
|
| 782 |
}
|
| 783 |
|
| 784 |
-
function buildPresetPanel(
|
| 785 |
-
const container = document.getElementById('
|
| 786 |
container.innerHTML = '';
|
| 787 |
const groups = {};
|
| 788 |
const groupOrder = [];
|
|
@@ -803,55 +871,88 @@
|
|
| 803 |
btn.type = 'button';
|
| 804 |
btn.className = 'preset-thumb-btn';
|
| 805 |
btn.dataset.presetId = shape.id;
|
| 806 |
-
btn.
|
| 807 |
-
btn.setAttribute('aria-label', `${pieceNameKo[k]} 조각: ${groupName} 모양 선택`);
|
| 808 |
btn.appendChild(buildThumb(shape.cells));
|
| 809 |
-
btn.addEventListener('click', ()=>
|
| 810 |
row.appendChild(btn);
|
| 811 |
});
|
| 812 |
container.appendChild(row);
|
| 813 |
});
|
| 814 |
}
|
|
|
|
| 815 |
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
const
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 831 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
});
|
| 833 |
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
|
|
|
|
|
|
|
|
|
| 842 |
});
|
| 843 |
});
|
| 844 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 845 |
document.getElementById('resetAllBtn').addEventListener('click', ()=>{
|
| 846 |
board = Array.from({length:N}, ()=>Array(N).fill(0));
|
| 847 |
pieceKeys.forEach(k=>{
|
| 848 |
pieceGrids[k] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0));
|
| 849 |
selectedPreset[k] = null;
|
|
|
|
| 850 |
});
|
| 851 |
clearSolution();
|
| 852 |
renderBoard();
|
| 853 |
-
pieceKeys.forEach(
|
| 854 |
-
|
| 855 |
statusLine.textContent='';
|
| 856 |
});
|
| 857 |
|
|
@@ -876,15 +977,24 @@
|
|
| 876 |
}
|
| 877 |
}
|
| 878 |
|
| 879 |
-
|
|
|
|
| 880 |
for(let r=0;r<PIECE_GRID;r++){
|
| 881 |
for(let c=0;c<PIECE_GRID;c++){
|
| 882 |
-
const el = pieceCellEls[
|
| 883 |
-
el.
|
|
|
|
|
|
|
| 884 |
}
|
| 885 |
}
|
| 886 |
}
|
| 887 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
function clearSolution(){
|
| 889 |
lastSolution = null;
|
| 890 |
resultsBlock.style.display='none';
|
|
@@ -893,7 +1003,8 @@
|
|
| 893 |
}
|
| 894 |
|
| 895 |
renderBoard();
|
| 896 |
-
pieceKeys.forEach(
|
|
|
|
| 897 |
|
| 898 |
// ---- shape trimming ----
|
| 899 |
function trimShape(grid){
|
|
@@ -1153,11 +1264,12 @@
|
|
| 1153 |
pieceKeys.forEach(k=>{
|
| 1154 |
pieceGrids[k] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0));
|
| 1155 |
selectedPreset[k] = null;
|
|
|
|
| 1156 |
});
|
| 1157 |
clearSolution();
|
| 1158 |
renderBoard();
|
| 1159 |
-
pieceKeys.forEach(
|
| 1160 |
-
|
| 1161 |
statusLine.textContent = '이어서 다음 조각 3개를 선택해주세요.';
|
| 1162 |
emptyNote.textContent = '아래에서 다음 세 조각을 고르고 해답 찾기를 누르면 여기 결과가 표시됩니다.';
|
| 1163 |
}
|
|
|
|
| 196 |
padding:16px;
|
| 197 |
}
|
| 198 |
.piece-head{
|
| 199 |
+
display:flex; align-items:flex-start; justify-content:space-between;
|
| 200 |
+
gap:10px;
|
| 201 |
+
margin-bottom:14px;
|
| 202 |
}
|
| 203 |
.piece-label{
|
| 204 |
font-family:'Space Grotesk',sans-serif;
|
| 205 |
font-weight:600;
|
| 206 |
font-size:13px;
|
| 207 |
+
display:flex; flex-direction:column; gap:3px;
|
| 208 |
+
}
|
| 209 |
+
.hint-inline{
|
| 210 |
+
font-family:'Inter',sans-serif;
|
| 211 |
+
font-weight:400;
|
| 212 |
+
font-size:11px;
|
| 213 |
+
color:var(--muted-2);
|
| 214 |
}
|
|
|
|
| 215 |
.piece-clear{
|
| 216 |
background:none; border:none; color:var(--muted-2);
|
| 217 |
font-size:11px; cursor:pointer; font-family:'Inter',sans-serif;
|
| 218 |
padding:2px 4px;
|
| 219 |
+
white-space:nowrap;
|
| 220 |
}
|
| 221 |
.piece-clear:hover{color:var(--ink);}
|
| 222 |
|
| 223 |
+
/* ---- slot row: pick which of A/B/C you're filling ---- */
|
| 224 |
+
.slot-row{
|
| 225 |
+
display:grid;
|
| 226 |
+
grid-template-columns:repeat(3, 1fr);
|
| 227 |
+
gap:8px;
|
| 228 |
+
margin-bottom:14px;
|
| 229 |
+
}
|
| 230 |
+
.slot-chip{
|
| 231 |
+
background:var(--panel-2);
|
| 232 |
+
border:1.5px solid var(--grid-line);
|
| 233 |
+
border-radius:10px;
|
| 234 |
+
padding:8px 6px;
|
| 235 |
+
cursor:pointer;
|
| 236 |
+
display:flex;
|
| 237 |
+
flex-direction:column;
|
| 238 |
+
align-items:center;
|
| 239 |
+
gap:6px;
|
| 240 |
+
transition:border-color .12s, box-shadow .12s, opacity .12s;
|
| 241 |
+
opacity:0.72;
|
| 242 |
+
}
|
| 243 |
+
.slot-chip:hover{border-color:var(--muted-2);}
|
| 244 |
+
.slot-chip:focus-visible{outline:2px solid var(--clear); outline-offset:2px;}
|
| 245 |
+
.slot-chip.active{
|
| 246 |
+
opacity:1;
|
| 247 |
+
border-color:var(--piece-color);
|
| 248 |
+
box-shadow:0 0 0 1px var(--piece-color) inset, 0 0 14px -4px var(--piece-color);
|
| 249 |
+
}
|
| 250 |
+
.slot-thumb-wrap{
|
| 251 |
+
width:100%;
|
| 252 |
+
height:34px;
|
| 253 |
+
display:flex;
|
| 254 |
+
align-items:center;
|
| 255 |
+
justify-content:center;
|
| 256 |
+
}
|
| 257 |
+
.slot-empty{
|
| 258 |
+
font-family:'JetBrains Mono',monospace;
|
| 259 |
+
font-size:11px;
|
| 260 |
+
color:var(--muted-2);
|
| 261 |
+
border:1.5px dashed var(--grid-line);
|
| 262 |
+
border-radius:6px;
|
| 263 |
+
width:26px; height:26px;
|
| 264 |
+
display:flex; align-items:center; justify-content:center;
|
| 265 |
+
}
|
| 266 |
+
.slot-chip.active .slot-empty{border-color:var(--piece-color); color:var(--piece-color);}
|
| 267 |
+
.slot-label{
|
| 268 |
+
font-family:'Space Grotesk',sans-serif;
|
| 269 |
+
font-weight:700;
|
| 270 |
+
font-size:12px;
|
| 271 |
+
color:var(--piece-color);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
/* ---- mode tabs (preset vs draw) ---- */
|
| 275 |
.mode-tabs{
|
| 276 |
display:flex;
|
|
|
|
| 302 |
.mode-tab:focus-visible{outline:2px solid var(--clear); outline-offset:1px;}
|
| 303 |
|
| 304 |
/* ---- preset picker ---- */
|
| 305 |
+
/* Cell size is FIXED (--cell-px) across every shape, so a thumbnail's overall
|
| 306 |
+
footprint scales with the piece's real size instead of every thumbnail
|
| 307 |
+
being squeezed into an identical box. */
|
| 308 |
.preset-panel{
|
| 309 |
+
--cell-px:9px;
|
| 310 |
+
max-height:340px;
|
| 311 |
overflow-y:auto;
|
| 312 |
padding-right:4px;
|
| 313 |
}
|
|
|
|
| 317 |
letter-spacing:0.03em;
|
| 318 |
text-transform:uppercase;
|
| 319 |
color:var(--muted-2);
|
| 320 |
+
margin:14px 0 7px;
|
| 321 |
}
|
| 322 |
.preset-group-label:first-child{margin-top:0;}
|
| 323 |
.preset-group-grid{
|
| 324 |
+
display:flex;
|
| 325 |
+
flex-wrap:wrap;
|
| 326 |
+
align-items:center;
|
| 327 |
+
gap:8px;
|
| 328 |
}
|
| 329 |
.preset-thumb-btn{
|
| 330 |
+
min-width:38px;
|
| 331 |
+
min-height:38px;
|
| 332 |
background:var(--panel-2);
|
| 333 |
border:1.5px solid var(--grid-line);
|
| 334 |
border-radius:8px;
|
| 335 |
cursor:pointer;
|
| 336 |
+
display:inline-flex;
|
| 337 |
align-items:center;
|
| 338 |
justify-content:center;
|
| 339 |
padding:6px;
|
|
|
|
| 347 |
box-shadow:0 0 0 1px var(--piece-color, var(--clear)) inset;
|
| 348 |
}
|
| 349 |
.thumb-grid{
|
|
|
|
|
|
|
| 350 |
display:grid;
|
| 351 |
+
gap:2px;
|
| 352 |
}
|
| 353 |
.thumb-cell{
|
| 354 |
+
width:var(--cell-px);
|
| 355 |
+
height:var(--cell-px);
|
| 356 |
background:transparent;
|
| 357 |
border-radius:1.5px;
|
| 358 |
}
|
|
|
|
| 545 |
<aside class="pieces">
|
| 546 |
<div class="piece-card">
|
| 547 |
<div class="piece-head">
|
| 548 |
+
<div class="piece-label">조각 선택 <span class="hint-inline">같은 판에서 3번 클릭 → A, B, C 순서로 채워��요</span></div>
|
| 549 |
+
<button class="piece-clear" id="clearActiveBtn">지금 조각 지우기</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
</div>
|
| 551 |
+
|
| 552 |
+
<div class="slot-row" id="slotRow">
|
| 553 |
+
<button class="slot-chip active" data-slot="a" style="--piece-color:var(--piece-a)" type="button">
|
| 554 |
+
<span class="slot-thumb-wrap" id="slotThumb-a"><span class="slot-empty">A</span></span>
|
| 555 |
+
<span class="slot-label">A</span>
|
| 556 |
+
</button>
|
| 557 |
+
<button class="slot-chip" data-slot="b" style="--piece-color:var(--piece-b)" type="button">
|
| 558 |
+
<span class="slot-thumb-wrap" id="slotThumb-b"><span class="slot-empty">B</span></span>
|
| 559 |
+
<span class="slot-label">B</span>
|
| 560 |
+
</button>
|
| 561 |
+
<button class="slot-chip" data-slot="c" style="--piece-color:var(--piece-c)" type="button">
|
| 562 |
+
<span class="slot-thumb-wrap" id="slotThumb-c"><span class="slot-empty">C</span></span>
|
| 563 |
+
<span class="slot-label">C</span>
|
| 564 |
+
</button>
|
| 565 |
</div>
|
| 566 |
+
|
| 567 |
+
<div class="mode-tabs" id="modeTabs">
|
| 568 |
<button class="mode-tab active" data-mode="preset" type="button">프리셋에서 선택</button>
|
| 569 |
<button class="mode-tab" data-mode="draw" type="button">직접 그리기</button>
|
| 570 |
</div>
|
| 571 |
+
|
| 572 |
+
<div class="preset-panel" id="presetPanel"></div>
|
| 573 |
+
<div class="piece-grid" id="pieceGridEditor" style="display:none;"></div>
|
| 574 |
</div>
|
| 575 |
|
| 576 |
<div class="solve-row">
|
|
|
|
| 659 |
};
|
| 660 |
let pieceMode = {a:'preset', b:'preset', c:'preset'}; // 'preset' | 'draw'
|
| 661 |
let selectedPreset = {a:null, b:null, c:null};
|
| 662 |
+
let activeSlot = 'a'; // which piece (a/b/c) the shared picker is currently filling
|
| 663 |
let lastSolution = null; // {steps:[{piece,r,c,offsets,boardBeforeClear,clearRows,clearCols,boardAfterClear}], totalLines, finalEmpty}
|
| 664 |
|
| 665 |
// ---- DOM refs ----
|
|
|
|
| 691 |
boardCells.push(row);
|
| 692 |
}
|
| 693 |
|
| 694 |
+
// ---- build the single shared 5x5 piece draw grid (used by whichever slot is active) ----
|
| 695 |
+
const pieceCellEls = [];
|
| 696 |
+
{
|
| 697 |
+
const el = document.getElementById('pieceGridEditor');
|
| 698 |
for(let r=0;r<PIECE_GRID;r++){
|
| 699 |
const rowEls=[];
|
| 700 |
for(let c=0;c<PIECE_GRID;c++){
|
| 701 |
const b = document.createElement('button');
|
| 702 |
b.type='button';
|
| 703 |
b.className='pcell';
|
|
|
|
| 704 |
b.dataset.role='piece';
|
| 705 |
+
b.dataset.key='a'; // updated live to whichever slot is active
|
| 706 |
b.dataset.r=r;
|
| 707 |
b.dataset.c=c;
|
| 708 |
el.appendChild(b);
|
| 709 |
rowEls.push(b);
|
| 710 |
}
|
| 711 |
+
pieceCellEls.push(rowEls);
|
| 712 |
}
|
| 713 |
+
}
|
| 714 |
|
| 715 |
// ---- shared cell get/set (used by both click and drag-paint) ----
|
| 716 |
function getCellValue(role, key, r, c){
|
|
|
|
| 725 |
} else {
|
| 726 |
selectedPreset[key] = null; // manual edit no longer matches any preset
|
| 727 |
renderPiece(key);
|
| 728 |
+
renderPresetSelection();
|
| 729 |
}
|
| 730 |
return true;
|
| 731 |
}
|
|
|
|
| 805 |
return {h:maxR+1, w:maxC+1};
|
| 806 |
}
|
| 807 |
|
| 808 |
+
// Cell size is fixed (CSS var --cell-px on .preset-panel), so a shape's thumbnail
|
| 809 |
+
// grows/shrinks with its real footprint instead of every shape sharing one box size.
|
| 810 |
function buildThumb(cells){
|
| 811 |
const {h,w} = shapeBBox(cells);
|
| 812 |
const filled = new Set(cells.map(([r,c])=>r+','+c));
|
| 813 |
const grid = document.createElement('div');
|
| 814 |
grid.className = 'thumb-grid';
|
| 815 |
+
grid.style.gridTemplateColumns = `repeat(${w}, var(--cell-px))`;
|
| 816 |
+
grid.style.gridTemplateRows = `repeat(${h}, var(--cell-px))`;
|
| 817 |
for(let r=0;r<h;r++){
|
| 818 |
for(let c=0;c<w;c++){
|
| 819 |
const cell = document.createElement('div');
|
|
|
|
| 824 |
return grid;
|
| 825 |
}
|
| 826 |
|
| 827 |
+
const SLOT_ORDER = ['a','b','c'];
|
| 828 |
+
|
| 829 |
+
// apply a chosen preset shape to whichever slot is currently active, then
|
| 830 |
+
// auto-advance to the next slot (a -> b -> c) so 3 clicks in this one panel
|
| 831 |
+
// fill all three pieces in order.
|
| 832 |
+
function applyPresetToActiveSlot(shape){
|
| 833 |
+
const key = activeSlot;
|
| 834 |
+
pieceGrids[key] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0));
|
| 835 |
+
shape.cells.forEach(([r,c])=>{ pieceGrids[key][r][c] = 1; });
|
| 836 |
+
selectedPreset[key] = shape.id;
|
| 837 |
clearSolution();
|
| 838 |
+
renderSlotChip(key);
|
| 839 |
+
renderPresetSelection();
|
| 840 |
+
const idx = SLOT_ORDER.indexOf(key);
|
| 841 |
+
if(idx < SLOT_ORDER.length - 1){
|
| 842 |
+
setActiveSlot(SLOT_ORDER[idx+1]);
|
| 843 |
+
}
|
| 844 |
}
|
| 845 |
|
| 846 |
+
function renderPresetSelection(){
|
| 847 |
+
document.querySelectorAll('#presetPanel .preset-thumb-btn').forEach(btn=>{
|
| 848 |
+
btn.classList.toggle('selected', btn.dataset.presetId === selectedPreset[activeSlot]);
|
|
|
|
| 849 |
});
|
| 850 |
}
|
| 851 |
|
| 852 |
+
function buildPresetPanel(){
|
| 853 |
+
const container = document.getElementById('presetPanel');
|
| 854 |
container.innerHTML = '';
|
| 855 |
const groups = {};
|
| 856 |
const groupOrder = [];
|
|
|
|
| 871 |
btn.type = 'button';
|
| 872 |
btn.className = 'preset-thumb-btn';
|
| 873 |
btn.dataset.presetId = shape.id;
|
| 874 |
+
btn.setAttribute('aria-label', `${groupName} 모양 선택`);
|
|
|
|
| 875 |
btn.appendChild(buildThumb(shape.cells));
|
| 876 |
+
btn.addEventListener('click', ()=> applyPresetToActiveSlot(shape));
|
| 877 |
row.appendChild(btn);
|
| 878 |
});
|
| 879 |
container.appendChild(row);
|
| 880 |
});
|
| 881 |
}
|
| 882 |
+
buildPresetPanel();
|
| 883 |
|
| 884 |
+
// ---- slot chip preview (small thumbnail of whatever's currently in that slot) ----
|
| 885 |
+
function renderSlotChip(key){
|
| 886 |
+
const wrap = document.getElementById('slotThumb-'+key);
|
| 887 |
+
wrap.innerHTML = '';
|
| 888 |
+
const trimmed = trimShape(pieceGrids[key]);
|
| 889 |
+
if(trimmed){
|
| 890 |
+
wrap.appendChild(buildThumb(trimmed.offsets));
|
| 891 |
+
} else {
|
| 892 |
+
const span = document.createElement('span');
|
| 893 |
+
span.className = 'slot-empty';
|
| 894 |
+
span.textContent = pieceNameKo[key];
|
| 895 |
+
wrap.appendChild(span);
|
| 896 |
+
}
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
// ---- switch which slot (A/B/C) the shared picker + draw grid apply to ----
|
| 900 |
+
function setActiveSlot(key){
|
| 901 |
+
activeSlot = key;
|
| 902 |
+
document.querySelectorAll('.slot-chip').forEach(chip=>{
|
| 903 |
+
chip.classList.toggle('active', chip.dataset.slot===key);
|
| 904 |
+
});
|
| 905 |
+
document.querySelectorAll('#modeTabs .mode-tab').forEach(tabBtn=>{
|
| 906 |
+
tabBtn.classList.toggle('active', tabBtn.dataset.mode===pieceMode[key]);
|
| 907 |
});
|
| 908 |
+
document.getElementById('presetPanel').style.display = pieceMode[key]==='preset' ? 'block' : 'none';
|
| 909 |
+
const editor = document.getElementById('pieceGridEditor');
|
| 910 |
+
editor.style.display = pieceMode[key]==='draw' ? 'grid' : 'none';
|
| 911 |
+
editor.style.setProperty('--piece-color', pieceColorVar[key]);
|
| 912 |
+
document.getElementById('presetPanel').style.setProperty('--piece-color', pieceColorVar[key]);
|
| 913 |
+
renderPresetSelection();
|
| 914 |
+
renderDrawGrid();
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
document.querySelectorAll('.slot-chip').forEach(chip=>{
|
| 918 |
+
chip.addEventListener('click', ()=> setActiveSlot(chip.dataset.slot));
|
| 919 |
});
|
| 920 |
|
| 921 |
+
// ---- mode tabs: 프리셋에서 선택 (default) vs 직접 그리기, applies to the active slot ----
|
| 922 |
+
document.querySelectorAll('#modeTabs .mode-tab').forEach(tabBtn=>{
|
| 923 |
+
tabBtn.addEventListener('click', ()=>{
|
| 924 |
+
const mode = tabBtn.dataset.mode;
|
| 925 |
+
if(pieceMode[activeSlot] === mode) return;
|
| 926 |
+
pieceMode[activeSlot] = mode;
|
| 927 |
+
document.querySelectorAll('#modeTabs .mode-tab').forEach(b=> b.classList.toggle('active', b===tabBtn));
|
| 928 |
+
document.getElementById('presetPanel').style.display = mode==='preset' ? 'block' : 'none';
|
| 929 |
+
const editor = document.getElementById('pieceGridEditor');
|
| 930 |
+
editor.style.display = mode==='draw' ? 'grid' : 'none';
|
| 931 |
+
if(mode==='draw') renderDrawGrid();
|
| 932 |
});
|
| 933 |
});
|
| 934 |
|
| 935 |
+
document.getElementById('clearActiveBtn').addEventListener('click', ()=>{
|
| 936 |
+
const key = activeSlot;
|
| 937 |
+
pieceGrids[key] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0));
|
| 938 |
+
selectedPreset[key] = null;
|
| 939 |
+
clearSolution();
|
| 940 |
+
renderSlotChip(key);
|
| 941 |
+
renderPresetSelection();
|
| 942 |
+
renderDrawGrid();
|
| 943 |
+
});
|
| 944 |
+
|
| 945 |
document.getElementById('resetAllBtn').addEventListener('click', ()=>{
|
| 946 |
board = Array.from({length:N}, ()=>Array(N).fill(0));
|
| 947 |
pieceKeys.forEach(k=>{
|
| 948 |
pieceGrids[k] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0));
|
| 949 |
selectedPreset[k] = null;
|
| 950 |
+
pieceMode[k] = 'preset';
|
| 951 |
});
|
| 952 |
clearSolution();
|
| 953 |
renderBoard();
|
| 954 |
+
pieceKeys.forEach(renderSlotChip);
|
| 955 |
+
setActiveSlot('a');
|
| 956 |
statusLine.textContent='';
|
| 957 |
});
|
| 958 |
|
|
|
|
| 977 |
}
|
| 978 |
}
|
| 979 |
|
| 980 |
+
// refresh the single shared draw grid to show the active slot's shape
|
| 981 |
+
function renderDrawGrid(){
|
| 982 |
for(let r=0;r<PIECE_GRID;r++){
|
| 983 |
for(let c=0;c<PIECE_GRID;c++){
|
| 984 |
+
const el = pieceCellEls[r][c];
|
| 985 |
+
el.dataset.key = activeSlot;
|
| 986 |
+
el.setAttribute('aria-label', `${pieceNameKo[activeSlot]} 조각 ${r+1},${c+1}`);
|
| 987 |
+
el.classList.toggle('on', !!pieceGrids[activeSlot][r][c]);
|
| 988 |
}
|
| 989 |
}
|
| 990 |
}
|
| 991 |
|
| 992 |
+
// called after a manual (draw-mode) edit changes pieceGrids[key]
|
| 993 |
+
function renderPiece(key){
|
| 994 |
+
renderDrawGrid();
|
| 995 |
+
renderSlotChip(key);
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
function clearSolution(){
|
| 999 |
lastSolution = null;
|
| 1000 |
resultsBlock.style.display='none';
|
|
|
|
| 1003 |
}
|
| 1004 |
|
| 1005 |
renderBoard();
|
| 1006 |
+
pieceKeys.forEach(renderSlotChip);
|
| 1007 |
+
setActiveSlot('a');
|
| 1008 |
|
| 1009 |
// ---- shape trimming ----
|
| 1010 |
function trimShape(grid){
|
|
|
|
| 1264 |
pieceKeys.forEach(k=>{
|
| 1265 |
pieceGrids[k] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0));
|
| 1266 |
selectedPreset[k] = null;
|
| 1267 |
+
pieceMode[k] = 'preset';
|
| 1268 |
});
|
| 1269 |
clearSolution();
|
| 1270 |
renderBoard();
|
| 1271 |
+
pieceKeys.forEach(renderSlotChip);
|
| 1272 |
+
setActiveSlot('a');
|
| 1273 |
statusLine.textContent = '이어서 다음 조각 3개를 선택해주세요.';
|
| 1274 |
emptyNote.textContent = '아래에서 다음 세 조각을 고르고 해답 찾기를 누르면 여기 결과가 표시됩니다.';
|
| 1275 |
}
|