Remove Tetris animation from login page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef, useEffect } from 'react'
|
import { useState } from 'react'
|
||||||
import { Navigate, useNavigate } from 'react-router-dom'
|
import { Navigate, useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
Hotel, Eye, EyeOff, Sun, Moon, AlertCircle,
|
Hotel, Eye, EyeOff, Sun, Moon, AlertCircle,
|
||||||
@@ -14,165 +14,6 @@ const DEMO_EMAILS = [
|
|||||||
'admin@hotelsync.io',
|
'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<HTMLCanvasElement>(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 (
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
className="absolute inset-0 pointer-events-none z-10"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LegalType = 'ooo' | 'ip' | 'ao' | 'pao' | 'other'
|
type LegalType = 'ooo' | 'ip' | 'ao' | 'pao' | 'other'
|
||||||
|
|
||||||
@@ -636,16 +477,15 @@ export function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-brand-50 via-white to-slate-100 dark:from-slate-900 dark:via-slate-900 dark:to-brand-950 flex">
|
<div className="min-h-screen bg-gradient-to-br from-brand-50 via-white to-slate-100 dark:from-slate-900 dark:via-slate-900 dark:to-brand-950 flex">
|
||||||
{/* Left panel — branding */}
|
{/* Left panel — branding */}
|
||||||
<div className="hidden lg:flex flex-col justify-between w-1/2 bg-brand-600 dark:bg-brand-800 p-12 relative overflow-hidden">
|
<div className="hidden lg:flex flex-col justify-between w-1/2 bg-brand-600 dark:bg-brand-800 p-12">
|
||||||
<TetrisOverlay />
|
<div className="flex items-center gap-3">
|
||||||
<div className="relative z-20 flex items-center gap-3">
|
|
||||||
<div className="w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center">
|
||||||
<Hotel size={22} className="text-white" />
|
<Hotel size={22} className="text-white" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-2xl font-bold text-white">HotelSync</span>
|
<span className="text-2xl font-bold text-white">HotelSync</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-20">
|
<div>
|
||||||
<h1 className="text-4xl font-bold text-white leading-tight mb-4">
|
<h1 className="text-4xl font-bold text-white leading-tight mb-4">
|
||||||
Современная PMS система<br />для вашего отеля
|
Современная PMS система<br />для вашего отеля
|
||||||
</h1>
|
</h1>
|
||||||
@@ -684,7 +524,7 @@ export function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="relative z-20 text-brand-200 text-sm">© 2026 HotelSync · SaaS PMS Platform</p>
|
<p className="text-brand-200 text-sm">© 2026 HotelSync · SaaS PMS Platform</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right panel */}
|
{/* Right panel */}
|
||||||
|
|||||||
Reference in New Issue
Block a user