Files
2026-02-05 14:40:45 +01:00

625 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
font-family: Arial, sans-serif;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
background-color: #0d1117;
color: #c9d1d9;
}
h1 {
text-align: center;
color: #c9d1d9;
}
.size-controls {
display: flex;
justify-content: center;
gap: 20px;
margin: 20px 0;
padding: 15px;
background-color: #161b22;
border-radius: 8px;
border: 1px solid #30363d;
}
.size-input-group {
display: flex;
flex-direction: column;
align-items: center;
}
.size-input-group label {
font-weight: bold;
margin-bottom: 5px;
color: #c9d1d9;
}
.size-input-group input[type="number"] {
width: 80px;
padding: 8px;
font-size: 16px;
border: 1px solid #30363d;
border-radius: 6px;
text-align: center;
background-color: #0d1117;
color: #c9d1d9;
}
.size-input-group input[type="number"]:focus {
outline: none;
border-color: #58a6ff;
}
.color-controls {
display: flex;
justify-content: center;
gap: 20px;
margin: 20px 0;
padding: 15px;
background-color: #161b22;
border-radius: 8px;
border: 1px solid #30363d;
align-items: center;
}
.color-picker-group {
display: flex;
gap: 10px;
align-items: center;
}
.color-picker-group label {
font-weight: bold;
color: #c9d1d9;
}
.color-picker-wrapper {
display: flex;
align-items: center;
gap: 10px;
padding: 5px 10px;
background-color: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
}
input[type="color"] {
width: 50px;
height: 35px;
border: none;
border-radius: 4px;
cursor: pointer;
background: transparent;
}
.color-value {
font-family: 'Courier New', monospace;
font-size: 14px;
color: #c9d1d9;
min-width: 70px;
}
.canvas-container {
margin: 20px 0;
padding: 10px;
background-color: #161b22;
border-radius: 8px;
border: 1px solid #30363d;
display: inline-block;
}
#gridCanvas {
cursor: crosshair;
display: block;
background-color: #000000;
}
.controls {
margin: 20px 0;
text-align: center;
}
button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: transparent;
color: #c9d1d9;
border: 1px solid #c9d1d9;
border-radius: 6px;
margin: 0 5px;
transition: all 0.2s;
}
button:hover {
background-color: rgba(201, 209, 217, 0.1);
border-color: #ffffff;
color: #ffffff;
}
.export-section {
margin: 20px 0;
padding: 20px;
background-color: #161b22;
border-radius: 8px;
border: 1px solid #30363d;
text-align: center;
}
.export-section h3 {
margin-top: 0;
color: #c9d1d9;
}
.export-options {
display: flex;
justify-content: center;
gap: 15px;
margin: 15px 0;
flex-wrap: wrap;
}
.radio-group {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 15px;
background-color: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}
.radio-group:hover {
border-color: #58a6ff;
background-color: rgba(88, 166, 255, 0.1);
}
.radio-group input[type="radio"] {
cursor: pointer;
width: 18px;
height: 18px;
pointer-events: none;
}
.radio-group label {
cursor: pointer;
color: #c9d1d9;
margin: 0;
pointer-events: none;
}
.radio-group.selected {
border-color: #58a6ff;
background-color: rgba(88, 166, 255, 0.15);
}
.format-description {
font-size: 13px;
color: #8b949e;
margin-top: 10px;
}
.generate-button {
margin-top: 15px;
position: relative;
}
.copied-message {
color: #3fb950;
margin-left: 10px;
font-weight: bold;
display: inline-block;
opacity: 0;
transition: opacity 0.3s;
}
.copied-message.show {
opacity: 1;
}
.text-controls {
margin: 20px 0;
padding: 20px;
background-color: #161b22;
border-radius: 8px;
border: 1px solid #30363d;
text-align: center;
}
.text-controls h3 {
margin-top: 0;
color: #c9d1d9;
}
.text-input-group {
display: flex;
justify-content: center;
gap: 15px;
margin: 15px 0;
}
.text-input-group input[type="text"] {
width: 300px;
padding: 8px;
font-size: 16px;
border: 1px solid #30363d;
border-radius: 6px;
background-color: #0d1117;
color: #c9d1d9;
}
.text-buttons {
margin-top: 15px;
}
</style>
</head>
<body>
<h1>Grid Color Generator</h1>
<div class="size-controls">
<div class="size-input-group">
<label for="rowsInput">Rows:</label>
<input type="number" id="rowsInput" min="1" max="50" value="16">
</div>
<div class="size-input-group">
<label for="colsInput">Columns:</label>
<input type="number" id="colsInput" min="1" max="50" value="43">
</div>
</div>
<div class="color-controls">
<div class="color-picker-group">
<label for="colorPicker">Draw Color:</label>
<div class="color-picker-wrapper">
<input type="color" id="colorPicker" value="#ffffff">
<span class="color-value" id="colorValue">#ffffff</span>
</div>
</div>
</div>
<div class="canvas-container">
<canvas id="gridCanvas"></canvas>
</div>
<div class="controls">
<button onclick="clearGrid()">Clear All</button>
<button onclick="fillAll()">Fill All</button>
</div>
<div class="text-controls">
<h3>Send Text</h3>
<div class="text-input-group">
<input type="text" id="textInput" placeholder="Enter text...">
<input type="color" id="textColorPicker" value="#ffffff">
</div>
<div class="text-buttons">
<button onclick="sendText('top')">Send to Top</button>
<button onclick="sendText('bottom')">Send to Bottom</button>
</div>
</div>
<div class="export-section">
<h3>Export Format</h3>
<div class="export-options">
<div class="radio-group selected" onclick="selectFormat('boolean')">
<input type="radio" id="formatBoolean" name="exportFormat" value="boolean" checked>
<label for="formatBoolean">Boolean (true/false)</label>
</div>
<div class="radio-group" onclick="selectFormat('color')">
<input type="radio" id="formatColor" name="exportFormat" value="color">
<label for="formatColor">Color (hex codes)</label>
</div>
</div>
<div class="format-description">
<strong>Boolean:</strong> Exports filled cells as true, empty as false<br>
<strong>Color:</strong> Exports actual hex color values, empty cells as null
</div>
<div class="generate-button">
<button onclick="generateAndCopy()">Generate & Copy to Clipboard</button>
<span id="copiedMessage" class="copied-message">✓ Copied!</span>
</div>
</div>
<script>
const canvas = document.getElementById('gridCanvas');
const ctx = canvas.getContext('2d');
let currentRows = 16;
let currentCols = 43;
let cellSize = 25;
let gridState = []; // Stores color values or null
let isDrawing = false;
let drawMode = null; // 'fill' or 'erase'
let currentColor = '#ffffff';
// Colors
const colors = {
background: '#000000',
border: '#30363d',
borderHover: '#58a6ff'
};
// Select format radio button
function selectFormat(format) {
// Update radio buttons
const booleanRadio = document.getElementById('formatBoolean');
const colorRadio = document.getElementById('formatColor');
if (format === 'boolean') {
booleanRadio.checked = true;
colorRadio.checked = false;
} else {
booleanRadio.checked = false;
colorRadio.checked = true;
}
// Update visual selection
const radioGroups = document.querySelectorAll('.radio-group');
radioGroups.forEach(group => group.classList.remove('selected'));
event.currentTarget.classList.add('selected');
}
// Initialize grid state
function initializeGrid() {
gridState = [];
for (let i = 0; i < currentRows; i++) {
gridState[i] = [];
for (let j = 0; j < currentCols; j++) {
gridState[i][j] = null;
}
}
// Resize canvas
canvas.width = currentCols * cellSize;
canvas.height = currentRows * cellSize;
drawGrid();
}
// Draw the grid
function drawGrid() {
// Clear canvas with black background
ctx.fillStyle = colors.background;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw cells
for (let i = 0; i < currentRows; i++) {
for (let j = 0; j < currentCols; j++) {
drawCell(i, j, gridState[i][j]);
}
}
}
// Draw individual cell
function drawCell(row, col, colorValue) {
const x = col * cellSize;
const y = row * cellSize;
// Draw cell background (black)
ctx.fillStyle = colors.background;
ctx.fillRect(x, y, cellSize, cellSize);
// Draw cell border
ctx.strokeStyle = colors.border;
ctx.lineWidth = 1;
ctx.strokeRect(x + 0.5, y + 0.5, cellSize - 1, cellSize - 1);
// Draw filled state with color
if (colorValue !== null) {
ctx.fillStyle = colorValue;
const padding = 3;
ctx.fillRect(
x + padding,
y + padding,
cellSize - padding * 2,
cellSize - padding * 2
);
}
}
// Get cell coordinates from mouse position
function getCellFromMouse(e) {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const col = Math.floor(mouseX / cellSize);
const row = Math.floor(mouseY / cellSize);
if (row >= 0 && row < currentRows && col >= 0 && col < currentCols) {
return { row, col };
}
return null;
}
// Mouse down event
canvas.addEventListener('mousedown', function(e) {
e.preventDefault();
isDrawing = true;
const cell = getCellFromMouse(e);
if (cell) {
if (e.button === 0) { // Left click
drawMode = 'fill';
gridState[cell.row][cell.col] = currentColor;
} else if (e.button === 2) { // Right click
drawMode = 'erase';
gridState[cell.row][cell.col] = null;
}
drawCell(cell.row, cell.col, gridState[cell.row][cell.col]);
}
});
// Mouse move event
canvas.addEventListener('mousemove', function(e) {
if (!isDrawing) return;
const cell = getCellFromMouse(e);
if (cell) {
if (drawMode === 'fill') {
gridState[cell.row][cell.col] = currentColor;
} else if (drawMode === 'erase') {
gridState[cell.row][cell.col] = null;
}
drawCell(cell.row, cell.col, gridState[cell.row][cell.col]);
}
});
// Mouse up event
canvas.addEventListener('mouseup', function(e) {
isDrawing = false;
drawMode = null;
});
// Mouse leave event
canvas.addEventListener('mouseleave', function(e) {
isDrawing = false;
drawMode = null;
});
// Prevent context menu
canvas.addEventListener('contextmenu', function(e) {
e.preventDefault();
return false;
});
// Color picker change
document.getElementById('colorPicker').addEventListener('input', function(e) {
currentColor = e.target.value;
document.getElementById('colorValue').textContent = currentColor;
});
// Update grid size
function updateGridSize() {
const rowsInput = document.getElementById('rowsInput');
const colsInput = document.getElementById('colsInput');
currentRows = parseInt(rowsInput.value) || 1;
currentCols = parseInt(colsInput.value) || 1;
if (currentRows < 1) currentRows = 1;
if (currentCols < 1) currentCols = 1;
initializeGrid();
}
// Generate JSON and copy to clipboard
function generateAndCopy() {
const formatRadios = document.getElementsByName('exportFormat');
let selectedFormat = 'boolean';
for (const radio of formatRadios) {
if (radio.checked) {
selectedFormat = radio.value;
break;
}
}
let outputData = [];
if (selectedFormat === 'boolean') {
// Convert to boolean: filled = true, empty = false
for (let i = 0; i < currentRows; i++) {
outputData[i] = [];
for (let j = 0; j < currentCols; j++) {
outputData[i][j] = gridState[i][j] !== null;
}
}
} else {
// Keep color values: hex codes or null
outputData = JSON.parse(JSON.stringify(gridState)); // Deep copy
}
// Generate JSON string
const jsonText = JSON.stringify(outputData, null, 2);
// Copy to clipboard
navigator.clipboard.writeText(jsonText).then(() => {
const message = document.getElementById('copiedMessage');
message.classList.add('show');
setTimeout(() => {
message.classList.remove('show');
}, 2000);
}).catch(err => {
alert('Failed to copy to clipboard');
console.error('Copy failed:', err);
});
}
function sendText(position) {
const text = document.getElementById('textInput').value;
const color = document.getElementById('textColorPicker').value;
if (!text) {
alert('Please enter some text.');
return;
}
const formData = new FormData();
formData.append('text', text);
formData.append('color', color);
formData.append('position', position);
fetch('/text', {
method: 'POST',
body: new URLSearchParams(formData)
})
.then(response => {
if (!response.ok) {
alert('Error sending text.');
}
})
.catch(error => {
console.error('Error:', error);
alert('Error sending text.');
});
}
// Clear all cells
function clearGrid() {
for (let i = 0; i < currentRows; i++) {
for (let j = 0; j < currentCols; j++) {
gridState[i][j] = null;
}
}
drawGrid();
}
// Fill all cells
function fillAll() {
for (let i = 0; i < currentRows; i++) {
for (let j = 0; j < currentCols; j++) {
gridState[i][j] = currentColor;
}
}
drawGrid();
}
// Add event listeners to inputs
document.addEventListener('DOMContentLoaded', function() {
const rowsInput = document.getElementById('rowsInput');
const colsInput = document.getElementById('colsInput');
rowsInput.addEventListener('input', updateGridSize);
colsInput.addEventListener('input', updateGridSize);
// Initialize grid on page load
initializeGrid();
});
</script>
</body>
</html>