diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index d2d85aa..40fc44d 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react' +import { useState } from 'react' import { Navigate, useNavigate } from 'react-router-dom' import { Hotel, Eye, EyeOff, Sun, Moon, AlertCircle, @@ -14,165 +14,6 @@ const DEMO_EMAILS = [ 'admin@hotelsync.io', ] -// ── Tetris overlay ───────────────────────────────────────────────────────────── - -// All 7 tetrominoes with all rotations, each cell = [row, col] offset -const TETROMINOES: { cells: [number,number][][]; color: string }[] = [ - { color: '#06b6d4', cells: [[[0,0],[0,1],[0,2],[0,3]], [[0,0],[1,0],[2,0],[3,0]]] }, // I - { color: '#eab308', cells: [[[0,0],[0,1],[1,0],[1,1]]] }, // O - { color: '#a855f7', cells: [[[0,1],[1,0],[1,1],[1,2]], [[0,0],[1,0],[1,1],[2,0]], [[0,0],[0,1],[0,2],[1,1]], [[0,1],[1,0],[1,1],[2,1]]] }, // T - { color: '#22c55e', cells: [[[0,1],[0,2],[1,0],[1,1]], [[0,0],[1,0],[1,1],[2,1]]] }, // S - { color: '#ef4444', cells: [[[0,0],[0,1],[1,1],[1,2]], [[0,1],[1,0],[1,1],[2,0]]] }, // Z - { color: '#3b82f6', cells: [[[0,0],[1,0],[1,1],[1,2]], [[0,0],[0,1],[1,0],[2,0]], [[0,0],[0,1],[0,2],[1,2]], [[0,1],[1,1],[2,0],[2,1]]] }, // J - { color: '#f97316', cells: [[[0,2],[1,0],[1,1],[1,2]], [[0,0],[1,0],[2,0],[2,1]], [[0,0],[0,1],[0,2],[1,0]], [[0,0],[0,1],[1,1],[2,1]]] }, // L -] - -function TetrisOverlay() { - const canvasRef = useRef(null) - - useEffect(() => { - let rafId = 0 - - const delay = setTimeout(() => { - const canvas = canvasRef.current - if (!canvas) return - const parent = canvas.parentElement - if (!parent) return - - const W = parent.clientWidth - const H = parent.clientHeight - if (!W || !H) return - canvas.width = W - canvas.height = H - - const CELL = 32 - const COLS = Math.floor(W / CELL) - const ROWS = Math.ceil(H / CELL) - // offset to center the grid - const OX = Math.floor((W - COLS * CELL) / 2) - - // settled[row][col] = border color | null - const settled: (string | null)[][] = Array.from({ length: ROWS + 6 }, () => new Array(COLS).fill(null)) - - const ctx = canvas.getContext('2d')! - - // Draw one tetromino cell: white fill, colored border - const drawCell = (col: number, rowPx: number, color: string) => { - const x = OX + col * CELL + 1 - const y = rowPx + 1 - const s = CELL - 2 - ctx.beginPath() - ctx.roundRect(x, y, s, s, 3) - ctx.fillStyle = 'rgba(255,255,255,0.88)' - ctx.fill() - ctx.strokeStyle = color - ctx.lineWidth = 2.5 - ctx.stroke() - } - - interface Piece { - cells: [number,number][] - color: string - col: number // grid-col of leftmost cell - yPx: number // pixel Y of top row - vy: number - } - - const falling: Piece[] = [] - let spawnIn = 300 - let last = performance.now() - - const maxRow = (cells: [number,number][]) => Math.max(...cells.map(c => c[0])) - const maxCol = (cells: [number,number][]) => Math.max(...cells.map(c => c[1])) - - const collides = (cells: [number,number][], col: number, gridRow: number) => { - for (const [r, c] of cells) { - const gr = gridRow + r - const gc = col + c - if (gr >= ROWS || gc < 0 || gc >= COLS) return true - if (gr >= 0 && settled[gr][gc] !== null) return true - } - return false - } - - const settle = (p: Piece, gridRow: number) => { - for (const [r, c] of p.cells) { - const gr = gridRow + r - const gc = p.col + c - if (gr >= 0 && gr < ROWS && gc >= 0 && gc < COLS) - settled[gr][gc] = p.color - } - } - - const tick = (now: number) => { - const dt = Math.min(now - last, 50) - last = now - spawnIn -= dt - ctx.clearRect(0, 0, W, H) - - // Spawn new piece - if (spawnIn <= 0 && falling.length < 3) { - const tIdx = Math.floor(Math.random() * TETROMINOES.length) - const t = TETROMINOES[tIdx] - const rIdx = Math.floor(Math.random() * t.cells.length) - const cells = t.cells[rIdx] - const mxc = maxCol(cells) - const maxStartCol = COLS - mxc - 1 - if (maxStartCol >= 0) { - const col = Math.floor(Math.random() * (maxStartCol + 1)) - if (!collides(cells, col, 0)) { - falling.push({ - cells, - color: t.color, - col, - yPx: -(maxRow(cells) + 1) * CELL, - vy: 1.2 + Math.random() * 0.8, - }) - } - } - spawnIn = 700 + Math.random() * 500 - } - - // Draw settled cells - for (let r = 0; r < ROWS; r++) - for (let c = 0; c < COLS; c++) - if (settled[r][c]) drawCell(c, r * CELL, settled[r][c]!) - - // Update + draw falling pieces - for (let i = falling.length - 1; i >= 0; i--) { - const p = falling[i] - p.yPx += p.vy * dt / 16 - const gridRow = Math.floor(p.yPx / CELL) - - if (collides(p.cells, p.col, gridRow + 1)) { - const snapRow = Math.max(0, gridRow) - settle(p, snapRow) - falling.splice(i, 1) - } else { - for (const [r, c] of p.cells) - drawCell(p.col + c, p.yPx + r * CELL, p.color) - } - } - - rafId = requestAnimationFrame(tick) - } - - rafId = requestAnimationFrame(tick) - }, 5000) - - return () => { - clearTimeout(delay) - cancelAnimationFrame(rafId) - } - }, []) - - return ( - - ) -} type LegalType = 'ooo' | 'ip' | 'ao' | 'pao' | 'other' @@ -636,16 +477,15 @@ export function LoginPage() { return (
{/* Left panel — branding */} -
- -
+
+
HotelSync
-
+

Современная PMS система
для вашего отеля

@@ -684,7 +524,7 @@ export function LoginPage() { )}
-

© 2026 HotelSync · SaaS PMS Platform

+

© 2026 HotelSync · SaaS PMS Platform

{/* Right panel */}