Medal & Trophy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Line Grid with Preview</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 24px;
font-family: Arial, sans-serif;
background: #f8f9fa;
color: #202124;
}
.sheet-title,
.preview-title {
margin-bottom: 12px;
font-size: 18px;
font-weight: 700;
}
.sheet-wrapper {
width: fit-content;
border: 1px solid #dadce0;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.sheet {
display: grid;
grid-template-columns: 60px repeat(5, 180px) 120px;
grid-auto-rows: 42px;
}
.corner,
.col-header,
.row-header,
.cell,
.action-cell {
border-right: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
}
.corner {
background: #f1f3f4;
}
.col-header,
.row-header {
background: #f8f9fa;
color: #5f6368;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 4px;
user-select: none;
}
.cell {
position: relative;
background: #fff;
}
.cell input {
width: 100%;
height: 100%;
border: none;
outline: none;
padding: 0 10px;
font-size: 14px;
background: transparent;
font-family: Arial, sans-serif;
}
.cell.active::after {
content: "";
position: absolute;
inset: -1px;
border: 2px solid #1a73e8;
pointer-events: none;
}
.action-cell {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
background: #fff;
padding: 0 6px;
}
.icon-btn {
width: 32px;
height: 32px;
border: 1px solid #dadce0;
background: #fff;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, border-color 0.2s;
}
.icon-btn:hover {
background: #f1f3f4;
border-color: #c6c6c6;
}
.icon-btn.delete:hover {
background: #fce8e6;
border-color: #ea4335;
}
.section-space {
height: 28px;
}
.preview-area {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.preview-card {
width: 260px;
min-height: 180px;
border: 2px solid #202124;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 18px;
text-align: center;
}
.preview-line {
width: 100%;
text-align: center;
margin: 3px 0;
font-size: 16px;
font-weight: 600;
line-height: 1.3;
word-break: break-word;
}
.empty-preview {
color: #5f6368;
font-size: 14px;
padding: 12px 0;
}
</style>
</head>
<body>
<div class="sheet-wrapper">
<div class="sheet" id="sheet">
<div class="corner"></div>
</div>
</div>
<div class="section-space"></div>
<div class="preview-title">Preview</div>
<div class="preview-area" id="previewArea">
<div class="empty-preview">No preview yet. Add text to any row above.</div>
</div>
<script>
const sheet = document.getElementById("sheet");
const previewArea = document.getElementById("previewArea");
const rows = 8;
const cols = 5;
const headers = ["LINE 1", "LINE 2", "LINE 3", "LINE 4", "LINE 5"];
let activeInput = null;
function getInput(row, col) {
return document.querySelector(`input[data-row="${row}"][data-col="${col}"]`);
}
function setActiveCell(input) {
document.querySelectorAll(".cell").forEach(cell => cell.classList.remove("active"));
input.parentElement.classList.add("active");
activeInput = input;
}
function clearRow(row) {
for (let col = 0; col < cols; col++) {
const input = getInput(row, col);
if (input) input.value = "";
}
renderPreview();
}
function deleteRow(row) {
for (let currentRow = row; currentRow < rows; currentRow++) {
for (let col = 0; col < cols; col++) {
const currentInput = getInput(currentRow, col);
const nextInput = getInput(currentRow + 1, col);
if (currentInput) {
currentInput.value = nextInput ? nextInput.value : "";
}
}
}
renderPreview();
}
function getRowValues(row) {
const values = [];
for (let col = 0; col < cols; col++) {
const input = getInput(row, col);
values.push(input ? input.value.trim() : "");
}
return values;
}
function rowHasContent(values) {
return values.some(value => value !== "");
}
function renderPreview() {
previewArea.innerHTML = "";
let hasAnyPreview = false;
for (let row = 1; row <= rows; row++) {
const values = getRowValues(row);
if (!rowHasContent(values)) {
continue;
}
hasAnyPreview = true;
const card = document.createElement("div");
card.className = "preview-card";
values.forEach(value => {
if (value !== "") {
const line = document.createElement("div");
line.className = "preview-line";
line.textContent = value;
card.appendChild(line);
}
});
previewArea.appendChild(card);
}
if (!hasAnyPreview) {
previewArea.innerHTML = '<div class="empty-preview">No preview yet. Add text to any row above.</div>';
}
}
// Header row
headers.forEach(text => {
const header = document.createElement("div");
header.className = "col-header";
header.textContent = text;
sheet.appendChild(header);
});
const actionHeader = document.createElement("div");
actionHeader.className = "col-header";
actionHeader.textContent = "ACTIONS";
sheet.appendChild(actionHeader);
// Rows
for (let r = 1; r <= rows; r++) {
const rowHeader = document.createElement("div");
rowHeader.className = "row-header";
rowHeader.textContent = r;
sheet.appendChild(rowHeader);
for (let c = 0; c < cols; c++) {
const cell = document.createElement("div");
cell.className = "cell";
const input = document.createElement("input");
input.type = "text";
input.dataset.row = r;
input.dataset.col = c;
cell.appendChild(input);
sheet.appendChild(cell);
input.addEventListener("focus", () => {
setActiveCell(input);
});
input.addEventListener("input", () => {
renderPreview();
});
input.addEventListener("keydown", (e) => {
const row = parseInt(input.dataset.row, 10);
const col = parseInt(input.dataset.col, 10);
let nextRow = row;
let nextCol = col;
if (e.key === "ArrowRight") nextCol++;
else if (e.key === "ArrowLeft") nextCol--;
else if (e.key === "ArrowDown") nextRow++;
else if (e.key === "ArrowUp") nextRow--;
else if (e.key === "Enter") nextRow++;
else if (e.key === "Tab") {
e.preventDefault();
nextCol++;
} else {
return;
}
if (nextRow >= 1 && nextRow <= rows && nextCol >= 0 && nextCol < cols) {
e.preventDefault();
const nextInput = getInput(nextRow, nextCol);
if (nextInput) nextInput.focus();
}
});
input.addEventListener("paste", (e) => {
e.preventDefault();
const pastedText = (e.clipboardData || window.clipboardData).getData("text");
const startRow = parseInt(input.dataset.row, 10);
const startCol = parseInt(input.dataset.col, 10);
const pastedRows = pastedText
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n")
.split("\n")
.filter(row => row.length > 0);
pastedRows.forEach((rowText, rowIndex) => {
const values = rowText.split("\t");
values.forEach((value, colIndex) => {
const targetRow = startRow + rowIndex;
const targetCol = startCol + colIndex;
if (targetRow <= rows && targetCol < cols) {
const targetInput = getInput(targetRow, targetCol);
if (targetInput) {
targetInput.value = value;
}
}
});
});
renderPreview();
});
}
const actionCell = document.createElement("div");
actionCell.className = "action-cell";
const clearBtn = document.createElement("button");
clearBtn.className = "icon-btn";
clearBtn.innerHTML = "🧹";
clearBtn.title = "Clear row";
clearBtn.addEventListener("click", () => clearRow(r));
const deleteBtn = document.createElement("button");
deleteBtn.className = "icon-btn delete";
deleteBtn.innerHTML = "🗑️";
deleteBtn.title = "Delete row";
deleteBtn.addEventListener("click", () => deleteRow(r));
actionCell.appendChild(clearBtn);
actionCell.appendChild(deleteBtn);
sheet.appendChild(actionCell);
}
renderPreview();
</script>
</body>
</html>
