This commit is contained in:
2025-10-20 22:27:15 +05:00
parent 88643415aa
commit a9af432e27
22 changed files with 378 additions and 3122 deletions

View File

@@ -24,6 +24,10 @@
0,
2
],
[
0,
-1
],
[
0,
-1

View File

@@ -8,75 +8,80 @@
</head>
<body>
<div class="container">
<h1>🏁 Редактор карт</h1>
<p class="subtitle">Гонки на бумаге / Paper Racing</p>
<div class="nav-links">
<a href="editor.html">🏁 Редактор карт</a>
<a href="player.html">🎬 Визуализатор решений</a>
</div>
<div class="controls">
<div class="control-group">
<h3>📐 Размеры карты</h3>
<div class="size-inputs">
<div class="input-wrapper">
<label for="width">Ширина:</label>
<input type="number" id="width" min="5" max="100" value="15">
</div>
<div class="input-wrapper">
<label for="height">Высота:</label>
<input type="number" id="height" min="5" max="100" value="15">
</div>
</div>
<div class="buttons">
<button class="btn-primary" onclick="resizeMap()">Применить</button>
<button class="btn-danger" onclick="clearMap()">Очистить</button>
</div>
<div class="header">
<div class="header-title">
<h1>🏁 Гонки на бумаге</h1>
<p class="subtitle">Редактор карт</p>
</div>
<div class="control-group">
<h3>🎨 Тип ячейки</h3>
<div class="palette">
<div class="cell-type road active" data-type="0" onclick="selectCellType(0)">
<span>Дорога</span>
<span class="code">(0)</span>
</div>
<div class="cell-type stone" data-type="1" onclick="selectCellType(1)">
<span>Камень</span>
<span class="code">(1)</span>
</div>
<div class="cell-type snow" data-type="2" onclick="selectCellType(2)">
<span>Снег</span>
<span class="code">(2)</span>
</div>
<div class="cell-type ice" data-type="3" onclick="selectCellType(3)">
<span>Лёд</span>
<span class="code">(3)</span>
</div>
<div class="cell-type checkpoint" data-type="4" onclick="selectCellType(4)">
<span>Чекпоинт</span>
<span class="code">(4)</span>
</div>
<div class="cell-type start" data-type="5" onclick="selectCellType(5)">
<span>Старт</span>
<span class="code">(5)</span>
</div>
</div>
</div>
<div class="control-group">
<h3>💾 Импорт / Экспорт</h3>
<div class="buttons">
<button class="btn-success" onclick="exportMap()">📥 Экспорт JSON</button>
<button class="btn-warning" onclick="document.getElementById('fileInput').click()">📤 Импорт JSON</button>
</div>
<input type="file" id="fileInput" accept=".json" onchange="importMap(event)">
<div class="nav-links">
<a href="editor.html" class="active">🏁 Редактор карт</a>
<a href="player.html">🎬 Визуализатор решений</a>
</div>
</div>
<div class="canvas-wrapper">
<canvas id="mapCanvas"></canvas>
<div class="main-content">
<div class="sidebar">
<div class="control-group">
<h3>📐 Размеры карты</h3>
<div class="size-inputs">
<div class="input-wrapper">
<label for="width">Ширина:</label>
<input type="number" id="width" min="5" max="1000" value="15">
</div>
<div class="input-wrapper">
<label for="height">Высота:</label>
<input type="number" id="height" min="5" max="1000" value="15">
</div>
</div>
<div class="buttons">
<button class="btn-primary" onclick="resizeMap()">Применить</button>
</div>
</div>
<div class="control-group">
<h3>💾 Импорт / Экспорт</h3>
<div class="buttons">
<button class="btn-success" onclick="exportMap()">📥 Экспорт JSON</button>
<button class="btn-success" onclick="document.getElementById('fileInput').click()">📤 Импорт JSON</button>
<button class="btn-danger" onclick="clearMap()">Очистить</button>
</div>
<input type="file" id="fileInput" accept=".json" onchange="importMap(event)">
</div>
<div class="control-group">
<h3>🎨 Тип ячейки</h3>
<div class="palette">
<div class="cell-type road active" data-type="0" onclick="selectCellType(0)">
<span>Дорога</span>
<span class="code">(0)</span>
</div>
<div class="cell-type stone" data-type="1" onclick="selectCellType(1)">
<span>Камень</span>
<span class="code">(1)</span>
</div>
<div class="cell-type snow" data-type="2" onclick="selectCellType(2)">
<span>Снег</span>
<span class="code">(2)</span>
</div>
<div class="cell-type ice" data-type="3" onclick="selectCellType(3)">
<span>Лёд</span>
<span class="code">(3)</span>
</div>
<div class="cell-type checkpoint" data-type="4" onclick="selectCellType(4)">
<span>Чекпоинт</span>
<span class="code">(4)</span>
</div>
<div class="cell-type start" data-type="5" onclick="selectCellType(5)">
<span>Старт</span>
<span class="code">(5)</span>
</div>
</div>
</div>
</div>
<div class="canvas-wrapper">
<canvas id="mapCanvas"></canvas>
</div>
</div>
<div class="info">
@@ -86,36 +91,6 @@
• Экспортируйте карту в JSON для использования в игре<br>
• Для визуализации решений используйте <a href="player.html" style="color: #0d47a1; font-weight: bold;">Визуализатор решений</a>
</div>
<div class="control-group">
<h3>📖 Легенда цветов</h3>
<div class="legend">
<div class="legend-item">
<div class="legend-color road"></div>
<span>Дорога (0)</span>
</div>
<div class="legend-item">
<div class="legend-color stone"></div>
<span>Камень (1)</span>
</div>
<div class="legend-item">
<div class="legend-color snow"></div>
<span>Снег (2)</span>
</div>
<div class="legend-item">
<div class="legend-color ice"></div>
<span>Лёд (3)</span>
</div>
<div class="legend-item">
<div class="legend-color checkpoint"></div>
<span>Чекпоинт (4)</span>
</div>
<div class="legend-item">
<div class="legend-color start"></div>
<span>Старт (5)</span>
</div>
</div>
</div>
</div>
<script src="common.js"></script>

View File

@@ -8,64 +8,105 @@
</head>
<body>
<div class="container">
<h1>🎬 Визуализатор решений</h1>
<p class="subtitle">Гонки на бумаге / Paper Racing</p>
<div class="nav-links">
<a href="editor.html">🏁 Редактор карт</a>
<a href="player.html">🎬 Визуализатор решений</a>
</div>
<div class="controls">
<div class="control-group">
<h3>📂 Загрузка файлов</h3>
<div class="buttons">
<button class="btn-primary" id="loadMapBtn" onclick="document.getElementById('mapInput').click()">📂 Загрузить карту</button>
<button class="btn-success" id="loadSolutionBtn" onclick="document.getElementById('solutionInput').click()">🎬 Загрузить решение</button>
</div>
<input type="file" id="mapInput" accept=".json" onchange="loadMap(event)">
<input type="file" id="solutionInput" accept=".json" onchange="loadSolution(event)">
<div class="header">
<div class="header-title">
<h1>🎬 Гонки на бумаге</h1>
<p class="subtitle">Визуализатор решений</p>
</div>
<div class="nav-links">
<a href="editor.html">🏁 Редактор карт</a>
<a href="player.html" class="active">🎬 Визуализатор решений</a>
</div>
</div>
<div class="canvas-wrapper">
<canvas id="mapCanvas"></canvas>
</div>
<div class="main-content">
<div class="sidebar">
<div class="control-group">
<h3>📂 Загрузка файлов</h3>
<div class="buttons">
<button class="btn-primary" id="loadMapBtn" onclick="document.getElementById('mapInput').click()">📂 Загрузить карту</button>
<button class="btn-success" id="loadSolutionBtn" onclick="document.getElementById('solutionInput').click()">🎬 Загрузить решение</button>
</div>
<input type="file" id="mapInput" accept=".json" onchange="loadMap(event)">
<input type="file" id="solutionInput" accept=".json" onchange="loadSolution(event)">
</div>
<div id="playbackControls" class="visualization-panel hidden">
<h3>🎮 Управление воспроизведением</h3>
<div class="playback-controls">
<button class="playback-btn" onclick="playVisualization()" id="playBtn">▶ Play</button>
<button class="playback-btn" onclick="pauseVisualization()" id="pauseBtn" disabled>Pause</button>
<button class="playback-btn" onclick="resetVisualization()">Reset</button>
<button class="playback-btn" onclick="stepBackward()">Back</button>
<button class="playback-btn" onclick="stepForward()">⏩ Forward</button>
<div class="speed-control">
<label for="speedSlider">Скорость:</label>
<input type="range" id="speedSlider" min="1" max="10" value="5" onchange="updateSpeed()">
<span id="speedValue">5x</span>
<div id="playbackControls" class="visualization-panel">
<h3>🎮 Управление воспроизведением</h3>
<div class="playback-controls">
<button class="playback-btn" onclick="playVisualization()" id="playBtn" disabled>▶ Воспроизвести</button>
<button class="playback-btn" onclick="pauseVisualization()" id="pauseBtn" disabled>Пауза</button>
<button class="playback-btn" onclick="resetVisualization()" id="resetBtn" disabled>Сброс</button>
<button class="playback-btn" onclick="stepBackward()" id="backBtn" disabled>Назад</button>
<button class="playback-btn" onclick="stepForward()" id="forwardBtn" disabled>⏩ Вперёд</button>
<div class="speed-control">
<div class="speed-control-header">
<label for="speedSlider">Скорость:</label>
<span id="speedValue">5x</span>
</div>
<input type="range" id="speedSlider" min="1" max="10" value="5" onchange="updateSpeed()" disabled>
</div>
</div>
</div>
<div id="stepInfo" class="step-info">
<div class="info-item">
<span class="info-label">Шаг</span>
<span class="info-value" id="stepNumber">0 / 0</span>
</div>
<div class="info-item">
<span class="info-label">Позиция (x, y)</span>
<span class="info-value" id="positionValue">(0, 0)</span>
</div>
<div class="info-item">
<span class="info-label">Скорость (vx, vy)</span>
<span class="info-value" id="velocityValue">(0, 0)</span>
</div>
<div class="info-item">
<span class="info-label">Ускорение (ax, ay)</span>
<span class="info-value" id="accelerationValue">(0, 0)</span>
</div>
<div class="info-item">
<span class="info-label">Очки</span>
<span class="info-value" id="scoreValue">0</span>
</div>
</div>
<div class="control-group">
<h3>📖 Легенда цветов</h3>
<div class="legend">
<div class="legend-item">
<div class="legend-color road"></div>
<span>Дорога (0)</span>
</div>
<div class="legend-item">
<div class="legend-color stone"></div>
<span>Камень (1)</span>
</div>
<div class="legend-item">
<div class="legend-color snow"></div>
<span>Снег (2)</span>
</div>
<div class="legend-item">
<div class="legend-color ice"></div>
<span>Лёд (3)</span>
</div>
<div class="legend-item">
<div class="legend-color checkpoint"></div>
<span>Чекпоинт (4)</span>
</div>
<div class="legend-item">
<div class="legend-color start"></div>
<span>Старт (5)</span>
</div>
</div>
</div>
</div>
</div>
<div id="stepInfo" class="step-info hidden">
<div class="info-item">
<span class="info-label">Шаг</span>
<span class="info-value" id="stepNumber">0 / 0</span>
</div>
<div class="info-item">
<span class="info-label">Позиция (x, y)</span>
<span class="info-value" id="positionValue">(0, 0)</span>
</div>
<div class="info-item">
<span class="info-label">Скорость (vx, vy)</span>
<span class="info-value" id="velocityValue">(0, 0)</span>
</div>
<div class="info-item">
<span class="info-label">Ускорение (ax, ay)</span>
<span class="info-value" id="accelerationValue">(0, 0)</span>
<div class="canvas-wrapper">
<canvas id="mapCanvas"></canvas>
</div>
</div>
@@ -78,36 +119,6 @@
• 🔴 Красный круг - текущая позиция<br>
• ➡️ Красная стрелка - направление и скорость движения
</div>
<div class="control-group">
<h3>📖 Легенда цветов</h3>
<div class="legend">
<div class="legend-item">
<div class="legend-color road"></div>
<span>Дорога (0)</span>
</div>
<div class="legend-item">
<div class="legend-color stone"></div>
<span>Камень (1)</span>
</div>
<div class="legend-item">
<div class="legend-color snow"></div>
<span>Снег (2)</span>
</div>
<div class="legend-item">
<div class="legend-color ice"></div>
<span>Лёд (3)</span>
</div>
<div class="legend-item">
<div class="legend-color checkpoint"></div>
<span>Чекпоинт (4)</span>
</div>
<div class="legend-item">
<div class="legend-color start"></div>
<span>Старт (5)</span>
</div>
</div>
</div>
</div>
<script src="common.js"></script>

View File

@@ -26,6 +26,8 @@ function init() {
initMap();
resizeCanvas();
drawMap();
updateControlsState();
updateStepInfo();
}
// Инициализация пустой карты
@@ -148,9 +150,11 @@ function simulateTrajectory(accelerations, start) {
let y = start.y;
let vx = 0;
let vy = 0;
let passedCheckpoints = new Set();
let totalSteps = accelerations.length;
// Начальная позиция
traj.push({ x, y, vx, vy, ax: 0, ay: 0 });
traj.push({ x, y, vx, vy, ax: 0, ay: 0, passedCheckpoints: 0, score: 0 });
// Применяем каждое ускорение
for (let i = 0; i < accelerations.length; i++) {
@@ -164,7 +168,21 @@ function simulateTrajectory(accelerations, start) {
x += vx;
y += vy;
traj.push({ x, y, vx, vy, ax, ay });
// Проверяем, попали ли на чекпоинт
const checkpointKey = `${Math.floor(x)},${Math.floor(y)}`;
if (map[Math.floor(y)] && map[Math.floor(y)][Math.floor(x)] === 4) {
passedCheckpoints.add(checkpointKey);
}
// Вычисляем очки по формуле: 100 * количество пройденных чекпоинтов ^ 1.3 / количество шагов
const checkpointCount = passedCheckpoints.size;
const score = totalSteps > 0 ? Math.round(100 * Math.pow(checkpointCount, 1.3) / totalSteps) : 0;
traj.push({
x, y, vx, vy, ax, ay,
passedCheckpoints: checkpointCount,
score: score
});
}
return traj;
@@ -237,11 +255,8 @@ function loadSolution(event) {
trajectory = simulateTrajectory(solution, startPosition);
currentStep = 0;
// Показываем панель визуализации
document.getElementById('playbackControls').classList.remove('hidden');
document.getElementById('stepInfo').classList.remove('hidden');
// Обновляем информацию
// Обновляем состояние кнопок и информацию
updateControlsState();
updateStepInfo();
drawMap();
@@ -261,9 +276,28 @@ function loadSolution(event) {
event.target.value = '';
}
// Обновление состояния элементов управления
function updateControlsState() {
const hasTrajectory = trajectory && trajectory.length > 0;
document.getElementById('playBtn').disabled = !hasTrajectory;
document.getElementById('pauseBtn').disabled = !hasTrajectory || !isPlaying;
document.getElementById('resetBtn').disabled = !hasTrajectory;
document.getElementById('backBtn').disabled = !hasTrajectory;
document.getElementById('forwardBtn').disabled = !hasTrajectory;
document.getElementById('speedSlider').disabled = !hasTrajectory;
}
// Обновление информации о текущем шаге
function updateStepInfo() {
if (!trajectory || trajectory.length === 0) return;
if (!trajectory || trajectory.length === 0) {
document.getElementById('stepNumber').textContent = '0 / 0';
document.getElementById('positionValue').textContent = '(0, 0)';
document.getElementById('velocityValue').textContent = '(0, 0)';
document.getElementById('accelerationValue').textContent = '(0, 0)';
document.getElementById('scoreValue').textContent = '0';
return;
}
const current = trajectory[currentStep];
@@ -271,6 +305,7 @@ function updateStepInfo() {
document.getElementById('positionValue').textContent = `(${current.x}, ${current.y})`;
document.getElementById('velocityValue').textContent = `(${current.vx}, ${current.vy})`;
document.getElementById('accelerationValue').textContent = `(${current.ax}, ${current.ay})`;
document.getElementById('scoreValue').textContent = current.score || 0;
}
// Воспроизведение визуализации
@@ -278,8 +313,7 @@ function playVisualization() {
if (!trajectory || trajectory.length === 0) return;
isPlaying = true;
document.getElementById('playBtn').disabled = true;
document.getElementById('pauseBtn').disabled = false;
updateControlsState();
playbackInterval = setInterval(() => {
if (currentStep < trajectory.length - 1) {
@@ -295,8 +329,7 @@ function playVisualization() {
// Пауза воспроизведения
function pauseVisualization() {
isPlaying = false;
document.getElementById('playBtn').disabled = false;
document.getElementById('pauseBtn').disabled = true;
updateControlsState();
if (playbackInterval) {
clearInterval(playbackInterval);
@@ -354,9 +387,8 @@ function clearVisualization() {
currentStep = 0;
startPosition = null;
document.getElementById('playbackControls').classList.add('hidden');
document.getElementById('stepInfo').classList.add('hidden');
updateControlsState();
updateStepInfo();
drawMap();
}

View File

@@ -18,77 +18,100 @@ body {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 30px;
padding: 20px;
width: 100%;
display: flex;
flex-direction: column;
min-height: calc(100vh - 40px);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding: 10px 15px;
background: #f8f9fa;
border-radius: 10px;
flex-wrap: wrap;
gap: 15px;
}
.header-title {
flex: 1;
min-width: 200px;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 10px;
font-size: 2em;
margin-bottom: 2px;
font-size: 1.4em;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 20px;
font-size: 0.9em;
font-size: 0.8em;
margin: 0;
}
.nav-links {
text-align: center;
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
display: flex;
gap: 10px;
align-items: center;
}
.nav-links a {
margin: 0 15px;
padding: 10px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 8px 16px;
background: #e9ecef;
color: #495057;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
transition: all 0.3s;
display: inline-block;
font-size: 0.9em;
white-space: nowrap;
}
.nav-links a:hover {
background: #dee2e6;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
.controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
.nav-links a.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.4);
}
.nav-links a.active:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.5);
}
.control-group {
background: #f8f9fa;
padding: 20px;
padding: 15px;
border-radius: 12px;
border: 2px solid #e9ecef;
}
.sidebar .control-group {
padding: 12px;
}
.control-group h3 {
margin-bottom: 15px;
margin-bottom: 12px;
color: #495057;
font-size: 1.1em;
font-size: 1em;
border-bottom: 2px solid #dee2e6;
padding-bottom: 8px;
padding-bottom: 6px;
}
.size-inputs {
display: flex;
gap: 15px;
gap: 10px;
margin-bottom: 15px;
}
@@ -119,38 +142,39 @@ h1 {
}
.palette {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.cell-type {
padding: 15px;
border: 3px solid #dee2e6;
border-radius: 10px;
padding: 10px;
border: 2px solid #dee2e6;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
text-align: center;
text-align: left;
font-weight: 600;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
}
.cell-type:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
transform: translateX(-3px);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
.cell-type.active {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
transform: scale(1.05);
border-width: 3px;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
.cell-type .code {
font-size: 0.8em;
font-size: 0.75em;
color: #6c757d;
display: block;
margin-top: 5px;
}
.road { background: #f8f9fa; color: #495057; }
@@ -166,6 +190,14 @@ h1 {
flex-wrap: wrap;
}
.sidebar .buttons {
flex-direction: column;
}
.sidebar .buttons button {
width: 100%;
}
button {
flex: 1;
padding: 12px 24px;
@@ -213,22 +245,55 @@ button:disabled {
color: white;
}
.main-content {
display: flex;
gap: 20px;
margin-top: 15px;
flex: 1;
min-height: 600px;
}
.canvas-wrapper {
margin-top: 30px;
display: flex;
justify-content: center;
align-items: center;
align-items: flex-start;
background: #f8f9fa;
padding: 20px;
border-radius: 12px;
overflow: hidden;
flex: 1;
min-height: 1000px;
position: relative;
}
.sidebar {
width: 280px;
display: flex;
flex-direction: column;
gap: 15px;
flex-shrink: 0;
overflow-y: auto;
max-height: 100%;
padding-right: 5px;
}
.sidebar::-webkit-scrollbar {
width: 8px;
}
.sidebar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.sidebar::-webkit-scrollbar-thumb {
background: #667eea;
border-radius: 4px;
}
.sidebar::-webkit-scrollbar-thumb:hover {
background: #555;
}
canvas {
border: 3px solid #dee2e6;
border: 2px solid #dee2e6;
border-radius: 8px;
cursor: crosshair;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
@@ -237,12 +302,13 @@ canvas {
}
.info {
margin-top: 20px;
padding: 15px;
margin-top: 15px;
padding: 12px;
background: #e7f3ff;
border-left: 4px solid #2196f3;
border-radius: 8px;
color: #0d47a1;
font-size: 0.9em;
}
.info strong {
@@ -261,42 +327,49 @@ canvas {
margin-top: 15px;
}
.sidebar .legend {
grid-template-columns: 1fr;
gap: 4px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
gap: 8px;
padding: 6px;
background: white;
border-radius: 6px;
font-size: 0.85em;
}
.legend-color {
width: 30px;
height: 30px;
width: 24px;
height: 24px;
border-radius: 4px;
border: 2px solid #dee2e6;
flex-shrink: 0;
}
/* Стили для визуализатора */
.playback-controls {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
flex-direction: column;
gap: 8px;
margin-top: 15px;
}
.playback-btn {
padding: 10px 20px;
padding: 10px 15px;
border: none;
border-radius: 8px;
font-size: 16px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
background: #667eea;
color: white;
min-width: auto;
width: 100%;
}
.playback-btn:hover {
@@ -312,20 +385,44 @@ canvas {
.speed-control {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px;
background: white;
border-radius: 6px;
}
.speed-control-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.speed-control input[type="range"] {
width: 150px;
width: 100%;
}
.speed-control input[type="range"]:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.speed-control label {
font-size: 0.9em;
font-weight: 600;
}
.speed-control #speedValue {
font-size: 0.9em;
font-weight: 600;
color: #667eea;
}
.step-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
padding: 15px;
grid-template-columns: 1fr 1fr;
gap: 10px;
padding: 12px;
background: white;
border-radius: 8px;
}
@@ -342,7 +439,7 @@ canvas {
}
.info-value {
font-size: 1.2em;
font-size: 1.1em;
font-weight: 600;
color: #333;
}
@@ -352,15 +449,15 @@ canvas {
}
.visualization-panel {
margin-top: 20px;
padding: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 12px;
border: 2px solid #dee2e6;
}
.visualization-panel h3 {
margin-bottom: 15px;
margin-bottom: 10px;
color: #495057;
font-size: 1em;
}