diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html
index fcb3ecb..27a3648 100644
--- a/src/main/resources/static/index.html
+++ b/src/main/resources/static/index.html
@@ -77,12 +77,14 @@
Ваш SQL запрос:
-
+
-
+
diff --git a/src/main/resources/static/script.js b/src/main/resources/static/script.js
index 349e3f1..607ca3f 100644
--- a/src/main/resources/static/script.js
+++ b/src/main/resources/static/script.js
@@ -443,6 +443,7 @@ function showResult(message, isSuccess, resultData = {}) {
const nextBtn = document.getElementById('next-task-btn');
if (nextBtn) {
nextBtn.addEventListener('click', () => {
+ resetVisualization();
showTask(currentTaskIndex + 1);
container.style.display = 'none';
});
@@ -527,6 +528,20 @@ function setupQueryInput() {
const cursor = document.createElement('span');
cursor.className = 'cursor';
queryInput.parentNode.insertBefore(cursor, queryInput.nextSibling);
+
+ const submitBtn = document.getElementById('query-submit');
+ submitBtn.addEventListener('click', async () => {
+ const query = queryInput.value.trim();
+ if (!query) return;
+ // Космическая визуализация
+ const visContainer = document.getElementById('visualizationContainer');
+ visContainer.classList.add('active');
+ visualizeQuery(query);
+ // Ждем завершения анимации (1.3 сек), только потом проверяем запрос и показываем результат
+ await new Promise(res => setTimeout(res, 1300));
+ await checkQuery();
+ visContainer.classList.remove('active');
+ });
}
function setupEventListeners() {
@@ -548,8 +563,6 @@ function setupEventListeners() {
}
});
- document.getElementById('query-submit').addEventListener('click', checkQuery);
-
document.getElementById('query-input').addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
checkQuery();
@@ -740,6 +753,129 @@ function createJoinEffect() {
}
}
+// Космическая визуализация запроса
+function visualizeQuery(sqlQuery) {
+ const container = document.getElementById('visualizationContainer');
+ container.innerHTML = '';
+
+ // Смайлик-пользователь 👨💻 + подпись
+ const userBlock = document.createElement('div');
+ userBlock.className = 'cosmo-user';
+ userBlock.innerHTML = '👨💻Вы
';
+ container.appendChild(userBlock);
+
+ // Найти блок примера данных (БД)
+ const dbSidebar = document.querySelector('.data-example-sidebar');
+ if (!dbSidebar) return;
+
+ // Получить координаты блока БД относительно visualizationContainer
+ const visRect = container.getBoundingClientRect();
+ const dbRect = dbSidebar.getBoundingClientRect();
+ const offsetLeft = dbRect.left - visRect.left;
+ const offsetTop = dbRect.top - visRect.top;
+ const dbWidth = dbRect.width;
+ const dbHeight = dbRect.height;
+
+ // Динамически создать подпись <сервер> над примером данных (появляется только на время анимации)
+ const serverLabel = document.createElement('div');
+ serverLabel.className = 'server-label cosmo-server-label-anim';
+ serverLabel.textContent = 'сервер>';
+ serverLabel.style.left = (offsetLeft + dbWidth/2) + 'px';
+ serverLabel.style.top = (offsetTop - 38) + 'px';
+ serverLabel.style.opacity = '0';
+ serverLabel.style.position = 'absolute';
+ serverLabel.style.transform = 'translateX(-50%)';
+ container.appendChild(serverLabel);
+
+ // Неоновое облако вокруг блока БД
+ const dbGlow = document.createElement('div');
+ dbGlow.className = 'cosmo-db-glow';
+ dbGlow.style.left = offsetLeft + 'px';
+ dbGlow.style.top = offsetTop + 'px';
+ dbGlow.style.width = dbWidth + 'px';
+ dbGlow.style.height = dbHeight + 'px';
+ dbGlow.style.opacity = '0';
+ container.appendChild(dbGlow);
+
+ // Тонкая кривая стрелка (SVG), не заходит на пример данных
+ const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ arrow.setAttribute('class', 'cosmo-arrow-curve');
+ arrow.setAttribute('width', Math.max(180, offsetLeft + dbWidth/2));
+ arrow.setAttribute('height', Math.max(100, offsetTop - 10));
+ arrow.style.position = 'absolute';
+ arrow.style.left = '0px';
+ arrow.style.top = '0px';
+ // Старт: смайлик, конец — чуть выше блока сервера
+ const startX = 55, startY = 65;
+ const endX = offsetLeft + dbWidth/2, endY = offsetTop - 18;
+ const c1x = startX + 60, c1y = startY - 40;
+ const c2x = endX - 60, c2y = endY + 40;
+ arrow.innerHTML = `
+
+
+
+
+
+
+
+
+
+ `;
+ container.appendChild(arrow);
+
+ // Анимация стрелки, кружочка и появления <сервер>
+ setTimeout(() => {
+ const path = arrow.querySelector('.cosmo-arrow-path');
+ if (path) path.classList.add('drawn');
+ animateDotAlongPath(arrow, path, arrow.querySelector('#arrowDot'), arrow.querySelector('#arrowHead'), 1800);
+ setTimeout(() => {
+ dbGlow.classList.add('active');
+ dbGlow.style.opacity = '1';
+ serverLabel.style.opacity = '1';
+ }, 900);
+ }, 200);
+}
+
+// Анимация кружочка по SVG-пути (без изменений)
+function animateDotAlongPath(svg, path, dot, arrowHead, duration) {
+ if (!path || !dot || !arrowHead) return;
+ const len = path.getTotalLength();
+ let start = null;
+ function step(ts) {
+ if (!start) start = ts;
+ let progress = Math.min((ts - start) / duration, 1);
+ const point = path.getPointAtLength(len * progress);
+ dot.setAttribute('cx', point.x);
+ dot.setAttribute('cy', point.y);
+ // Поворот стрелки
+ if (arrowHead) {
+ const angle = progress < 0.99
+ ? Math.atan2(
+ path.getPointAtLength(len * progress + 1).y - point.y,
+ path.getPointAtLength(len * progress + 1).x - point.x
+ ) * 180 / Math.PI
+ : Math.atan2(
+ point.y - path.getPointAtLength(len * progress - 1).y,
+ point.x - path.getPointAtLength(len * progress - 1).x
+ ) * 180 / Math.PI;
+ arrowHead.setAttribute('transform', `translate(${point.x},${point.y - 5}) rotate(${angle})`);
+ }
+ if (progress < 1) {
+ requestAnimationFrame(step);
+ } else {
+ dot.setAttribute('cx', point.x);
+ dot.setAttribute('cy', point.y);
+ }
+ }
+ requestAnimationFrame(step);
+}
+
+// Сброс космической визуализации
+function resetVisualization() {
+ const container = document.getElementById('visualizationContainer');
+ if (container) container.innerHTML = '';
+}
+
// Инициализация приложения
document.addEventListener('DOMContentLoaded', () => {
initializeApp();
diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css
index 50b1a20..94a5063 100644
--- a/src/main/resources/static/styles.css
+++ b/src/main/resources/static/styles.css
@@ -1008,4 +1008,82 @@ h1 {
text-align: center;
animation: pulse 2s infinite;
}
+}
+
+/* --- КОСМИЧЕСКАЯ ВИЗУАЛИЗАЦИЯ V3 --- */
+#visualizationContainer {
+ position: relative;
+ min-height: 180px;
+ margin: 20px 0 10px 0;
+ z-index: 5;
+ pointer-events: none;
+}
+.cosmo-user {
+ position: absolute;
+ left: 10px;
+ top: 40px;
+ z-index: 10;
+ font-size: 2.8em;
+ filter: drop-shadow(0 0 10px #fffbe6);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+.user-label {
+ font-size: 1em;
+ color: #bdbfff;
+ text-shadow: 0 0 8px #1a1a2e;
+ margin-top: 0.2em;
+ font-weight: bold;
+ letter-spacing: 0.5px;
+}
+.server-label {
+ color: #00f0ff;
+ font-size: 1.12em;
+ font-family: 'Roboto Mono', monospace;
+ text-shadow: 0 0 8px #00f0ff, 0 0 12px #a259ff;
+ letter-spacing: 1px;
+ z-index: 20;
+ pointer-events: none;
+ font-weight: bold;
+ white-space: nowrap;
+ opacity: 1;
+ transition: opacity 0.4s;
+}
+.cosmo-server-label-anim {
+ opacity: 0;
+ transition: opacity 0.4s;
+}
+.cosmo-server-label-anim[style*='opacity: 1'] {
+ opacity: 1;
+}
+.cosmo-arrow-curve {
+ z-index: 11;
+ pointer-events: none;
+}
+.cosmo-arrow-path {
+ stroke-dasharray: 800;
+ stroke-dashoffset: 800;
+ animation: drawCurve 1.8s cubic-bezier(.7,.1,.3,1.4) forwards;
+ filter: drop-shadow(0 0 6px #00f0ff) drop-shadow(0 0 12px #a259ff);
+ stroke-width: 2.1;
+}
+@keyframes drawCurve {
+ to { stroke-dashoffset: 0; }
+}
+.cosmo-db-glow {
+ position: absolute;
+ border-radius: 32px;
+ box-shadow: 0 0 32px 12px #a259ff88, 0 0 16px 6px #00f0ff77;
+ border: 2.5px solid #00f0ffcc;
+ pointer-events: none;
+ transition: box-shadow 0.6s, border-color 0.4s;
+ z-index: 12;
+ opacity: 0.5;
+ background: rgba(30,16,60,0.08);
+}
+.cosmo-db-glow.active {
+ box-shadow: 0 0 60px 24px #a259ffcc, 0 0 32px 12px #00f0ffcc;
+ border-color: #a259ff;
+ opacity: 1;
}
\ No newline at end of file