mirror of
https://github.com/tiennm99/loto.git
synced 2026-07-31 04:24:11 +00:00
refactor(ui): drop gradients for clean solid-color palette
Single-accent system: rose-600 (player + UI), amber-600 (host), emerald-600 (draw), sky-600 (master low-half). Replaces every text gradient, button gradient, radial bg-glow, hatch-stripe divider, and the diagonal cell slash (now an SVG <line> with non-scaling stroke). Updates design-guidelines.md to match.
This commit is contained in:
+152
-69
@@ -2,122 +2,185 @@
|
||||
|
||||
## Visual Identity
|
||||
|
||||
Clean, flat, single-accent system — no gradients anywhere. Color carries
|
||||
hierarchy via solid weights; emphasis via type weight + scale.
|
||||
|
||||
### Color Palette
|
||||
|
||||
**Primary (Player)**
|
||||
- Gradient: indigo-500 → purple-500
|
||||
- Use for: Main headings, generate button, hover states
|
||||
- Semantic: Calm, welcoming, player-friendly
|
||||
**Primary (Player + UI accent)**
|
||||
- Solid: rose-600 `#E11D48` (light) / rose-400 `#FB7185` (dark)
|
||||
- Use for: Title, "Tạo bảng mới", "Tuyệt vời!", "Xong", focus rings,
|
||||
selected pickers, link in footer
|
||||
- Semantic: warm, lucky-red Vietnamese-fair feel
|
||||
|
||||
**Secondary (Host)**
|
||||
- Gradient: orange-500 → red-500
|
||||
- Use for: Host headings, new game button, high-stakes actions
|
||||
- Semantic: Energetic, commanding, host authority
|
||||
- Solid: amber-600 `#D97706` (light) / amber-400 `#FBBF24` (dark)
|
||||
- Use for: "Quản trò" subhead, "Ván mới", host chips, bingo "Kinh!" h2
|
||||
- Semantic: festive, energetic
|
||||
|
||||
**Draw / Go**
|
||||
- Solid: emerald-600 `#059669` (light) / emerald-400 `#34D399` (dark)
|
||||
- Use for: "Xổ số", "Bắt đầu", master grid token ring (≥50)
|
||||
|
||||
**Stop / Active-running**
|
||||
- Solid: rose-600 (same as primary). Distinguished by context (only
|
||||
appears as the running auto-call toggle).
|
||||
|
||||
**Master low-half marker (≤49)**
|
||||
- Solid: sky-600 `#0284C7` (light) / sky-400 `#38BDF8` (dark)
|
||||
- Paired with emerald-600 (≥50) for an elegant cool/warm binary
|
||||
- Replaces the previous pink — kept clearly distinct from the rose
|
||||
primary so the eye doesn't read "low-number" and "primary action" as
|
||||
the same brand color.
|
||||
|
||||
**Success (Completed Rows)**
|
||||
- Background: emerald-100 (light) / emerald-900/40 (dark)
|
||||
- Text: emerald-500 (light) / emerald-400 (dark)
|
||||
- Use for: Crossed cells in completed rows
|
||||
- Background: emerald-100 (light) / emerald-900/60 (dark)
|
||||
- Text: emerald-700 (light) / emerald-200 (dark)
|
||||
- Slash: solid emerald-600 stroke
|
||||
|
||||
**Attention (Waiting Toast)**
|
||||
- Background: amber-500/90
|
||||
**Attention (Waiting Toast / "Chờ" ring)**
|
||||
- Background: amber-500/95 (toast); amber-500/45→0.85 inset ring (label)
|
||||
- Text: white
|
||||
- Use for: "Chờ X" toast notifications
|
||||
- Use for: "Chờ X" toast and persistent section-band cue
|
||||
|
||||
**Neutral (Grid & Text)**
|
||||
- Page background: warm cream `#FFFBEB` (light) / near-black navy
|
||||
`#050813` (dark) — flat, no radial glow
|
||||
- Borders: slate-200 (light) / slate-700 (dark)
|
||||
- Background: white (light) / slate-800 (dark)
|
||||
- Text: slate-600 (light) / slate-400 (dark)
|
||||
- Body text: slate-600 (light) / slate-300 (dark)
|
||||
- Heading text: slate-800/foreground (light) / slate-100 (dark)
|
||||
|
||||
### Dark Mode
|
||||
All colors have corresponding `dark:` variants. Tailwind's `prefers-color-scheme: dark` media query is enabled (set in `app.css`).
|
||||
All colors have corresponding `dark:` variants. Tailwind's
|
||||
`prefers-color-scheme: dark` media query is enabled (set in `app.css`).
|
||||
Light/dark mode contrast pairs verified at WCAG AA minimum.
|
||||
|
||||
## Typography
|
||||
|
||||
- **Font**: Geist Sans (Google Fonts), fallback to Arial/Helvetica.
|
||||
- **Grid numbers**: Roboto Condensed (self-hosted via @fontsource/roboto-condensed, font-display: swap) in `tan-tan-num` stack, bold weight.
|
||||
- **Headings**: Extrabold (font-extrabold) for main title, bold (font-bold) for secondary.
|
||||
- **Body**: Regular weight, slate-600 (light) / slate-400 (dark).
|
||||
- **Emphasis**: `<strong>` for important copy (e.g., button labels in instructions).
|
||||
- **Grid numbers**: Roboto Condensed (self-hosted via
|
||||
@fontsource/roboto-condensed, font-display: swap) in `tan-tan-num`
|
||||
stack, bold weight.
|
||||
- **Headings**: Italic + `font-black` for the page title, `font-bold`
|
||||
for secondary headings (e.g. "Quản trò").
|
||||
- **Body**: Regular weight, slate-600 (light) / slate-300 (dark).
|
||||
- **Emphasis**: `<strong>` for important copy (e.g., button labels).
|
||||
|
||||
### Sizing
|
||||
- **Page title**: `text-4xl sm:text-5xl` (responsive)
|
||||
- **Subheading**: `text-3xl sm:text-4xl`
|
||||
- **Page title**: `text-5xl sm:text-7xl`
|
||||
- **Subheading**: `text-lg sm:text-xl uppercase tracking-wider`
|
||||
- **Body**: `text-base sm:text-lg`
|
||||
- **Small**: `text-xs text-slate-400`
|
||||
- **Small**: `text-xs text-slate-500 dark:text-slate-400`
|
||||
|
||||
## Layout & Spacing
|
||||
|
||||
### Breakpoints
|
||||
- **Mobile first**: Base styles for mobile, then `sm:` (640px+), `md:` (768px+), `lg:` (1024px+).
|
||||
- **Padding**: `px-3 py-8 sm:py-12` (horizontal, vertical responsive).
|
||||
- **Mobile first**: Base styles for mobile, then `sm:` (640px+),
|
||||
`md:` (768px+), `lg:` (1024px+).
|
||||
- **Padding**: `px-2 py-4 sm:px-3 sm:py-12` (page).
|
||||
- **Gaps**: `gap-3`, `gap-6`, `gap-1.5` (consistent spacing scale).
|
||||
|
||||
### Grid Layout
|
||||
- **Player/Host grid**: `grid-template-columns: repeat(9, 1fr)` (equal cells, no gap).
|
||||
- **Master board**: Same 9-column layout (9×10 = 90 cells).
|
||||
- **Cell sizing**: `aspect-square` for perfect squares.
|
||||
- **Borders**: `border-r border-b` (right & bottom edges only, for clean lines).
|
||||
- **Player/Host grid**: `grid-template-columns: repeat(9, 1fr)` (equal
|
||||
cells, no gap).
|
||||
- **Master board**: Same 9-column layout (9×11 = 99 cells, of which 90
|
||||
hold numbers).
|
||||
- **Cell sizing**: `aspect-[3/4] sm:aspect-[3/5]` (player) /
|
||||
`aspect-square` (master).
|
||||
- **Borders**: `border-r border-b` (right & bottom edges only).
|
||||
|
||||
### Containers
|
||||
- **Max width**: `max-w-lg` (player), `max-w-2xl` (host) — centered with `mx-auto`.
|
||||
- **Flex wrap**: Use `flex flex-wrap gap-1.5` for number history chips.
|
||||
- **Max width**: `max-w-2xl` (page wrapper) — centered with `mx-auto`.
|
||||
- **Flex wrap**: `flex flex-wrap gap-1.5` for number history chips.
|
||||
|
||||
### Section Frame (Tân Tân lô tô identity)
|
||||
- **Outer frame**: 4px solid bars (top/bottom/left/right) in
|
||||
`--section-accent` (`#1565c0` light, `#64b5f6` dark).
|
||||
- **Section label**: italic uppercase text on a pale section band, with
|
||||
thin 2px solid horizontal rules on each side via `::before`/`::after`.
|
||||
- **Waiting cue**: `box-shadow: inset 0 0 0 2px amber-500/0.6` plus a
|
||||
2.4s pulse animation on the label band.
|
||||
|
||||
## Settings & UI Toggles
|
||||
|
||||
### Mode Picker (SettingsButton)
|
||||
Three-way segmented picker in settings modal with inline SVG glyphs:
|
||||
- **Player mode** (rectangle icon) — PlayerBoard only, default UX for players
|
||||
- **Master mode** (rounded ellipse icon) — MasterPanel only, host-only view (e.g., projector)
|
||||
- **Both mode** (double-box icon) — PlayerBoard + MasterPanel stacked inline, master auto-ticks player board on draw
|
||||
- **Player mode** (rectangle icon)
|
||||
- **Master mode** (megaphone icon)
|
||||
- **Both mode** (mini grid + mini megaphone side-by-side)
|
||||
|
||||
SVG icons hand-drawn from primitives (no icon library). Selection persisted to localStorage.
|
||||
SVG icons hand-drawn from primitives (no icon library). Selected state
|
||||
uses solid rose-600 border + rose-50 fill. Selection persisted to
|
||||
localStorage.
|
||||
|
||||
### Color Picker (SettingsButton)
|
||||
Grouped card with two sub-sections:
|
||||
- **Mẫu** (Presets): 10 Excel color swatches in a flex row (purple, blue, cyan, green, yellow, orange, red, pink, gray, black)
|
||||
- **Tuỳ chỉnh** (Custom): Native `<input type="color">` for custom hex entry
|
||||
- **Tuỳ chỉnh** (Custom): native `<input type="color">` for hex entry
|
||||
- **Mẫu sẵn** (Presets): 10 Excel "Standard Colors" swatches (purple
|
||||
default)
|
||||
|
||||
Card has rounded border, shadow, padding. Compact layout fits mobile.
|
||||
Card has rounded border, padding. Compact layout fits mobile.
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### Button Styles
|
||||
All buttons are solid-color, `rounded-full`, with a soft shadow
|
||||
(`shadow-md`) and `active:scale-95` press feedback. Hover transitions
|
||||
to a darker step (`-700`).
|
||||
|
||||
```html
|
||||
<!-- Primary Action (Indigo/Purple) -->
|
||||
<!-- Player primary -->
|
||||
<button class="px-8 py-3 rounded-full font-semibold text-white
|
||||
bg-gradient-to-r from-indigo-500 to-purple-500
|
||||
hover:from-indigo-600 hover:to-purple-600
|
||||
active:scale-95 transition-all shadow-lg shadow-indigo-500/25">
|
||||
bg-rose-600 hover:bg-rose-700
|
||||
active:scale-95 transition-all shadow-md">
|
||||
Tạo bảng mới
|
||||
</button>
|
||||
|
||||
<!-- Host Action (Orange/Red) -->
|
||||
<!-- Host primary -->
|
||||
<button class="px-8 py-4 rounded-full font-semibold text-white text-lg
|
||||
bg-gradient-to-r from-orange-500 to-red-500
|
||||
hover:from-orange-600 hover:to-red-600
|
||||
active:scale-95 transition-all shadow-lg shadow-orange-500/25">
|
||||
bg-amber-600 hover:bg-amber-700
|
||||
active:scale-95 transition-all shadow-md">
|
||||
Ván mới
|
||||
</button>
|
||||
|
||||
<!-- Secondary (Emerald, Draw Action) -->
|
||||
<!-- Draw / Go -->
|
||||
<button class="px-10 py-4 rounded-full font-semibold text-white text-lg
|
||||
bg-gradient-to-r from-emerald-500 to-teal-500
|
||||
hover:from-emerald-600 hover:to-teal-600
|
||||
active:scale-95 transition-all shadow-lg shadow-emerald-500/25">
|
||||
bg-emerald-600 hover:bg-emerald-700
|
||||
active:scale-95 transition-all shadow-md">
|
||||
Xổ số
|
||||
</button>
|
||||
|
||||
<!-- Secondary outline -->
|
||||
<button class="px-6 py-3 rounded-full font-semibold
|
||||
text-slate-700 dark:text-slate-200
|
||||
bg-white dark:bg-slate-800
|
||||
border-2 border-slate-300 dark:border-slate-600
|
||||
hover:bg-slate-50 dark:hover:bg-slate-700
|
||||
active:scale-95 transition-all">
|
||||
Xoá đánh dấu
|
||||
</button>
|
||||
```
|
||||
|
||||
### Card Styling
|
||||
- Border: `border border-slate-200 dark:border-slate-700`
|
||||
- Rounded: `rounded-2xl`
|
||||
- Rounded: `rounded-md` (player sheet) / `rounded-2xl` (master grid,
|
||||
modal)
|
||||
- Shadow: `shadow-xl shadow-slate-200/50 dark:shadow-black/30`
|
||||
- Overflow: `overflow-hidden` (clip content to rounded corners)
|
||||
|
||||
### Cell Cross Slash
|
||||
Drawn as an SVG `<line>` overlay (no gradients). `viewBox="0 0 100 100"`
|
||||
with `preserveAspectRatio="none"` stretches the line corner-to-corner
|
||||
on any cell aspect ratio; `vector-effect="non-scaling-stroke"` keeps
|
||||
the visual width constant.
|
||||
|
||||
- Standard slash: `stroke: #ef4444` (red-500)
|
||||
- Winning-row slash (in `.cross-slash-win`): `stroke: #10b981` (emerald-500)
|
||||
- Draw animation: `stroke-dashoffset` 200→0 over 200ms (skipped under
|
||||
`prefers-reduced-motion`)
|
||||
|
||||
### Text Links
|
||||
- Color: `text-indigo-500 dark:text-indigo-400` (player page)
|
||||
- Color: `text-orange-500 dark:text-orange-400` (host page)
|
||||
- Color: `text-rose-600 dark:text-rose-400`
|
||||
- Hover: `hover:underline`
|
||||
|
||||
## Animations
|
||||
@@ -133,46 +196,66 @@ Card has rounded border, shadow, padding. Compact layout fits mobile.
|
||||
|
||||
### Ephemeral
|
||||
- **toast** (5s): "Chờ X" notification (scale 0.8→1.05→1, fade build)
|
||||
- **cross-draw** (200ms): SVG slash stroke-dashoffset reveal
|
||||
- **section-pulse** (2.4s): Amber inset-ring pulse on waiting section
|
||||
- **confetti-fall** (1.6s): Tier-2 bingo emoji confetti
|
||||
|
||||
### Interactive
|
||||
- **active:scale-95**: Button press response
|
||||
- **active:scale-90**: Cell tap response
|
||||
- **transition-all**: Smooth color/shadow changes on hover
|
||||
|
||||
All animations respect `prefers-reduced-motion: reduce`.
|
||||
|
||||
## Emoji Usage
|
||||
|
||||
Intentional emojis (matching traditional bingo celebration):
|
||||
Intentional emojis (matching traditional bingo + Vietnamese fair):
|
||||
- 🎉 ✨ 🎊 🥳 ❤️ (bingo popup)
|
||||
- 🏮 (header subtitle)
|
||||
- 🎫 🎤 🎶 (empty/celebration messages)
|
||||
- 🥢 🎋 🏮 (tier-2 confetti, alongside 🎊 ✨ 🎉 🥳)
|
||||
- ❤️ (footer)
|
||||
|
||||
Keep emojis rare; use for celebration only.
|
||||
Keep emojis decorative only. Functional UI uses SVG glyphs.
|
||||
|
||||
## Vietnamese Copy
|
||||
|
||||
- **Player instructions**: Simple, directive tone. "Nhấn" (press), "để" (to).
|
||||
- **Player instructions**: Simple, directive tone. "Nhấn" (press),
|
||||
"để" (to).
|
||||
- **Toast messages**: Short. "Chờ X" (waiting for X).
|
||||
- **Popups**: Celebratory. "Kinh!" (victory cry), "Tuyệt vời!" (awesome!).
|
||||
- **Links**: Clear CTA. "Trang quản trò →" (host page), "← Về trang người chơi" (back to player).
|
||||
- **Popups**: Celebratory. "Kinh!" (victory cry),
|
||||
"Tuyệt vời!" (awesome!).
|
||||
- **Mode hints**: One-liner per option, italic, slate-500.
|
||||
|
||||
## Mobile Responsiveness
|
||||
|
||||
- Stack vertically on mobile (`flex flex-col`).
|
||||
- Reduce padding on small screens (`py-8` → `sm:py-12`).
|
||||
- Reduce padding on small screens (`py-4` → `sm:py-12`).
|
||||
- Enlarge text for readability on mobile (`text-base sm:text-lg`).
|
||||
- Buttons stay full-width or flex-wrap on mobile.
|
||||
- Images/grids use `aspect-square` to maintain proportion.
|
||||
- Cells use `aspect-[3/4] sm:aspect-[3/5]` (taller than wide) so
|
||||
numbers read large on phones.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Alt text: Not critical (no images), but grid cells have semantic ARIA if needed.
|
||||
- Focus states: Implicit via Tailwind (`focus:ring-2`), add if extending components.
|
||||
- Contrast: Tailwind slate/indigo/emerald combos meet WCAG AA.
|
||||
- Dark mode: Respects `prefers-color-scheme`, not forced.
|
||||
- SVG glyphs: Simple, high-contrast shapes (no fill patterns); keyboard-navigable via fieldset + label.
|
||||
- Alt text: Not critical (no images). Grid cells expose
|
||||
`aria-label="Số {n}, đã đánh dấu"` and `aria-pressed`.
|
||||
- Focus states: `focus:ring-2 focus:ring-rose-400` (interactive
|
||||
controls).
|
||||
- Contrast: Tailwind slate/rose/amber/emerald combos meet WCAG AA;
|
||||
text-on-button pairs at 4.5:1 minimum.
|
||||
- Dark mode: respects `prefers-color-scheme`, not forced.
|
||||
- SVG glyphs: Simple, high-contrast shapes; keyboard-navigable via
|
||||
fieldset + label.
|
||||
- Reduced motion: all animations disabled; SVG slash stays drawn.
|
||||
|
||||
## PWA Features
|
||||
|
||||
- **Install Prompt**: Native browser-driven (no custom banner). Manifest enables Android/iOS install.
|
||||
- **Offline**: App shell precache enables instant load. Audio cached on first play.
|
||||
- **Auto-Update**: SW detects new version; user sees reload toast (non-intrusive).
|
||||
- **Install Prompt**: Native browser-driven (no custom banner).
|
||||
Manifest enables Android/iOS install.
|
||||
- **Offline**: App shell precache enables instant load. Audio cached
|
||||
on first play.
|
||||
- **Auto-Update**: SW detects new version; user sees reload toast
|
||||
(non-intrusive).
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
Last reviewed: 2026-04-29
|
||||
|
||||
+30
-80
@@ -12,23 +12,15 @@
|
||||
@variant dark (&:where(.dark, .dark *));
|
||||
|
||||
:root {
|
||||
--background: #f8fafc;
|
||||
/* Warm cream — keeps festive warmth without a radial gradient. */
|
||||
--background: #fffbeb;
|
||||
--foreground: #1e293b;
|
||||
/* Soft warm-amber glow at the top, fading out — echoes the festive
|
||||
rose/amber gradient in the header and gives the page some warmth. */
|
||||
--bg-glow: radial-gradient(
|
||||
ellipse 70% 40% at 50% -10%,
|
||||
rgba(251, 191, 36, 0.18) 0%,
|
||||
transparent 75%
|
||||
);
|
||||
/* Default empty-cell color: Excel "Standard Color: Purple". Overridden
|
||||
at runtime by the settings store via documentElement.style. */
|
||||
--empty-cell-bg: #7030A0;
|
||||
/* Section accent — used by both the label text and the cross-hatch in
|
||||
/* Section accent — used by both the label text and the solid bars in
|
||||
dividers/label flanks, so they always read as one unified band. */
|
||||
--section-accent: #1565c0;
|
||||
/* Pale blue band with a hint of warmth so it doesn't fight the
|
||||
amber top-glow at the topmost section label. */
|
||||
--section-band-bg: rgb(232 240 250 / 0.78);
|
||||
}
|
||||
|
||||
@@ -37,35 +29,16 @@
|
||||
theme switch reads instantly. */
|
||||
--background: #050813;
|
||||
--foreground: #e2e8f0;
|
||||
/* Indigo top-glow + faint rose lower-glow — adds depth and matches the
|
||||
header gradient against the deep-navy base. */
|
||||
--bg-glow:
|
||||
radial-gradient(
|
||||
ellipse 70% 40% at 50% -10%,
|
||||
rgba(99, 102, 241, 0.22) 0%,
|
||||
transparent 75%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 60% 50% at 50% 110%,
|
||||
rgba(244, 63, 94, 0.10) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
--section-accent: #64b5f6;
|
||||
--section-band-bg: rgb(13 71 161 / 0.4);
|
||||
}
|
||||
|
||||
/* Apply to html so the base color always paints the full viewport, even
|
||||
when body content is shorter than the screen. Body adds the glow on
|
||||
top — `transparent` falls back to html's color. */
|
||||
html {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
body {
|
||||
background-color: transparent;
|
||||
background-image: var(--bg-glow);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* Equal-width grid cells */
|
||||
@@ -81,33 +54,20 @@ body {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* Decorative cross-hatch borders that frame and segment the player
|
||||
sheet, echoing the ✚✚✚ blue borders on a Tân Tân BAMBOORAFT card. */
|
||||
/* Solid bars that frame and segment the player sheet. */
|
||||
.section-divider {
|
||||
height: 8px;
|
||||
background-image: repeating-linear-gradient(
|
||||
90deg,
|
||||
transparent 0 6px,
|
||||
var(--section-accent) 6px 8px,
|
||||
transparent 8px 14px
|
||||
);
|
||||
background-color: var(--section-band-bg);
|
||||
height: 4px;
|
||||
background-color: var(--section-accent);
|
||||
}
|
||||
.section-divider-vertical {
|
||||
width: 8px;
|
||||
background-image: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent 0 6px,
|
||||
var(--section-accent) 6px 8px,
|
||||
transparent 8px 14px
|
||||
);
|
||||
background-color: var(--section-band-bg);
|
||||
width: 4px;
|
||||
background-color: var(--section-accent);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
font-size: 0.8125rem;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
@@ -118,19 +78,15 @@ body {
|
||||
font-style: italic;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* Cross-hatch ✚✚✚ flanks on both sides of the label text — flex:1 fills
|
||||
the remaining width so the text stays centered with comfy padding. */
|
||||
/* Solid editorial rules on both sides of the label — flex:1 fills
|
||||
the remaining width so the text stays centered. */
|
||||
.section-label::before,
|
||||
.section-label::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background-image: repeating-linear-gradient(
|
||||
90deg,
|
||||
transparent 0 6px,
|
||||
var(--section-accent) 6px 8px,
|
||||
transparent 8px 14px
|
||||
);
|
||||
height: 2px;
|
||||
background-color: var(--section-accent);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Persistent visual cue when any of the section's 3 rows is in "Chờ"
|
||||
@@ -190,8 +146,8 @@ body {
|
||||
.animate-toast { animation: toast 5s ease-in-out forwards; }
|
||||
|
||||
@keyframes cross-draw {
|
||||
from { clip-path: inset(0 100% 0 0); }
|
||||
to { clip-path: inset(0 0 0 0); }
|
||||
from { stroke-dashoffset: 200; }
|
||||
to { stroke-dashoffset: 0; }
|
||||
}
|
||||
|
||||
/* Falling-confetti burst for the 3rd-and-up bingo of a single card. */
|
||||
@@ -210,28 +166,24 @@ body {
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
.cell-crossed::after {
|
||||
content: "";
|
||||
/* Diagonal slash drawn as a true solid stroke via SVG. preserveAspectRatio
|
||||
on the <svg> stretches the line to corner-to-corner regardless of cell
|
||||
aspect ratio; vector-effect keeps the visual stroke width constant. */
|
||||
.cross-slash {
|
||||
position: absolute;
|
||||
inset: 6px;
|
||||
background-image: linear-gradient(
|
||||
to bottom right,
|
||||
transparent calc(50% - 1.5px),
|
||||
#ef4444,
|
||||
transparent calc(50% + 1.5px)
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
.cross-slash-line {
|
||||
stroke: #ef4444;
|
||||
stroke-width: 3;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 200;
|
||||
stroke-dashoffset: 0;
|
||||
animation: cross-draw 200ms ease-out;
|
||||
}
|
||||
/* Winning-row slash uses emerald instead of red so a completed row
|
||||
reads as "win" rather than "marked off". */
|
||||
.cell-crossed-win::after {
|
||||
background-image: linear-gradient(
|
||||
to bottom right,
|
||||
transparent calc(50% - 1.5px),
|
||||
#10b981,
|
||||
transparent calc(50% + 1.5px)
|
||||
);
|
||||
.cross-slash-win .cross-slash-line {
|
||||
stroke: #10b981;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@@ -242,9 +194,7 @@ body {
|
||||
.animate-spin-slow-reverse,
|
||||
.animate-toast,
|
||||
.confetti,
|
||||
.cell-crossed::after {
|
||||
.cross-slash-line {
|
||||
animation: none !important;
|
||||
}
|
||||
/* Keep the crossed slash visible (no draw animation, but still drawn). */
|
||||
.cell-crossed::after { clip-path: none; }
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
<span
|
||||
class="inline-block px-3 py-1 rounded-full text-xs font-semibold
|
||||
tracking-wider uppercase
|
||||
bg-orange-100 dark:bg-orange-950/40
|
||||
text-orange-700 dark:text-orange-300"
|
||||
bg-amber-100 dark:bg-amber-950/40
|
||||
text-amber-700 dark:text-amber-300"
|
||||
>
|
||||
Chế độ Quản trò
|
||||
</span>
|
||||
<p class="mt-3 text-base text-slate-600 dark:text-slate-300 italic">
|
||||
Nhấn
|
||||
<span class="font-semibold text-orange-500 dark:text-orange-400 not-italic">
|
||||
<span class="font-semibold text-amber-700 dark:text-amber-400 not-italic">
|
||||
"Ván mới"
|
||||
</span>
|
||||
để bắt đầu xổ số
|
||||
|
||||
+14
-16
@@ -187,9 +187,8 @@
|
||||
<button
|
||||
onclick={handleNewGame}
|
||||
class="px-8 py-4 rounded-full font-semibold text-white text-lg
|
||||
bg-gradient-to-r from-orange-500 to-red-500
|
||||
hover:from-orange-600 hover:to-red-600
|
||||
active:scale-95 transition-all shadow-lg shadow-orange-500/25"
|
||||
bg-amber-600 hover:bg-amber-700
|
||||
active:scale-95 transition-all shadow-md"
|
||||
>
|
||||
Ván mới
|
||||
</button>
|
||||
@@ -198,10 +197,10 @@
|
||||
<button
|
||||
onclick={toggleAuto}
|
||||
class="px-10 py-4 rounded-full font-semibold text-white text-lg
|
||||
bg-gradient-to-r {autoRunning
|
||||
? 'from-red-500 to-rose-500 hover:from-red-600 hover:to-rose-600 shadow-red-500/25'
|
||||
: 'from-emerald-500 to-teal-500 hover:from-emerald-600 hover:to-teal-600 shadow-emerald-500/25'}
|
||||
active:scale-95 transition-all shadow-lg"
|
||||
{autoRunning
|
||||
? 'bg-rose-600 hover:bg-rose-700'
|
||||
: 'bg-emerald-600 hover:bg-emerald-700'}
|
||||
active:scale-95 transition-all shadow-md"
|
||||
>
|
||||
{autoRunning ? "Dừng" : "Bắt đầu"}
|
||||
</button>
|
||||
@@ -209,9 +208,8 @@
|
||||
<button
|
||||
onclick={handleDrawNext}
|
||||
class="px-10 py-4 rounded-full font-semibold text-white text-lg
|
||||
bg-gradient-to-r from-emerald-500 to-teal-500
|
||||
hover:from-emerald-600 hover:to-teal-600
|
||||
active:scale-95 transition-all shadow-lg shadow-emerald-500/25"
|
||||
bg-emerald-600 hover:bg-emerald-700
|
||||
active:scale-95 transition-all shadow-md"
|
||||
>
|
||||
Xổ số
|
||||
</button>
|
||||
@@ -247,13 +245,13 @@
|
||||
flex items-center justify-center
|
||||
shadow-xl scroll-mt-4
|
||||
{lastIsLow
|
||||
? 'border-pink-500 dark:border-pink-400 shadow-pink-500/30'
|
||||
? 'border-sky-600 dark:border-sky-400 shadow-sky-500/30'
|
||||
: 'border-emerald-500 dark:border-emerald-400 shadow-emerald-500/30'}"
|
||||
>
|
||||
<span
|
||||
class="text-6xl sm:text-8xl font-black tabular-nums
|
||||
{lastIsLow
|
||||
? 'text-pink-500 dark:text-pink-400'
|
||||
? 'text-sky-700 dark:text-sky-400'
|
||||
: 'text-emerald-500 dark:text-emerald-400'}"
|
||||
>
|
||||
{lastCalled}
|
||||
@@ -282,7 +280,7 @@
|
||||
text-base sm:text-lg font-black tabular-nums rounded-full
|
||||
border-2 bg-amber-50 dark:bg-amber-100
|
||||
{isLow
|
||||
? 'border-pink-500 dark:border-pink-400 text-pink-600 dark:text-pink-500'
|
||||
? 'border-sky-600 dark:border-sky-400 text-sky-700 dark:text-sky-400'
|
||||
: 'border-emerald-500 dark:border-emerald-400 text-emerald-600 dark:text-emerald-500'}"
|
||||
>
|
||||
{num}
|
||||
@@ -314,7 +312,7 @@
|
||||
{isLast ? 'z-10' : ''}"
|
||||
>
|
||||
{#if hasNumber}
|
||||
<!-- Token: cream inner when called (pink ≤49 / green ≥50 ring),
|
||||
<!-- Token: cream inner when called (sky ≤49 / emerald ≥50 ring),
|
||||
gray-ringed and dim when uncalled. -->
|
||||
<div
|
||||
class="flex items-center justify-center
|
||||
@@ -324,8 +322,8 @@
|
||||
{!isCalled
|
||||
? 'border-slate-300 dark:border-slate-600 bg-slate-50/40 dark:bg-slate-700/30 text-slate-400 dark:text-slate-500 opacity-70'
|
||||
: isLow
|
||||
? 'border-pink-500 dark:border-pink-400 bg-amber-50 dark:bg-amber-100 text-pink-600 dark:text-pink-500'
|
||||
: 'border-emerald-500 dark:border-emerald-400 bg-amber-50 dark:bg-amber-100 text-emerald-600 dark:text-emerald-500'}
|
||||
? 'border-sky-600 dark:border-sky-400 bg-amber-50 dark:bg-amber-100 text-sky-700 dark:text-sky-700'
|
||||
: 'border-emerald-600 dark:border-emerald-400 bg-amber-50 dark:bg-amber-100 text-emerald-700 dark:text-emerald-700'}
|
||||
{isLast
|
||||
? 'ring-2 ring-red-500 dark:ring-red-400 ring-offset-1 ring-offset-white dark:ring-offset-slate-800 scale-110 shadow-md'
|
||||
: ''}"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
href="https://miti99.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-indigo-500 dark:text-indigo-400 hover:underline"
|
||||
class="text-rose-600 dark:text-rose-400 hover:underline"
|
||||
>
|
||||
miti99
|
||||
</a>
|
||||
|
||||
+25
-13
@@ -259,9 +259,8 @@
|
||||
<button
|
||||
onclick={handleGenerate}
|
||||
class="px-8 py-3 rounded-full font-semibold text-white
|
||||
bg-gradient-to-r from-indigo-500 to-purple-500
|
||||
hover:from-indigo-600 hover:to-purple-600
|
||||
active:scale-95 transition-all shadow-lg shadow-indigo-500/25"
|
||||
bg-rose-600 hover:bg-rose-700
|
||||
active:scale-95 transition-all shadow-md"
|
||||
>
|
||||
Tạo bảng mới
|
||||
</button>
|
||||
@@ -329,14 +328,28 @@
|
||||
text-xl sm:text-2xl md:text-3xl
|
||||
border border-slate-400/50 dark:border-slate-600/40
|
||||
transition-all select-none cursor-pointer active:scale-90
|
||||
focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400
|
||||
focus:outline-none focus:ring-2 focus:ring-inset focus:ring-rose-400
|
||||
{isCrossed
|
||||
? rowComplete
|
||||
? 'cell-crossed cell-crossed-win bg-emerald-100 dark:bg-emerald-900/60 text-emerald-700 dark:text-emerald-200'
|
||||
: 'cell-crossed bg-red-50 dark:bg-red-950/40 text-red-700 dark:text-red-300'
|
||||
: 'bg-white dark:bg-slate-800 text-black dark:text-slate-100 hover:bg-indigo-50 dark:hover:bg-indigo-950/30 hover:text-indigo-600 dark:hover:text-indigo-400'}"
|
||||
? 'bg-emerald-100 dark:bg-emerald-900/60 text-emerald-700 dark:text-emerald-200'
|
||||
: 'bg-red-50 dark:bg-red-950/40 text-red-700 dark:text-red-300'
|
||||
: 'bg-white dark:bg-slate-800 text-black dark:text-slate-100 hover:bg-rose-50 dark:hover:bg-rose-950/30 hover:text-rose-600 dark:hover:text-rose-400'}"
|
||||
>
|
||||
{num}
|
||||
{#if isCrossed}
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="none"
|
||||
class="cross-slash {rowComplete ? 'cross-slash-win' : ''}"
|
||||
>
|
||||
<line
|
||||
class="cross-slash-line"
|
||||
x1="0" y1="0" x2="100" y2="100"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -394,7 +407,7 @@
|
||||
</div>
|
||||
<p class="text-base text-slate-600 dark:text-slate-300 italic">
|
||||
Nhấn
|
||||
<span class="font-semibold text-rose-500 dark:text-rose-400 not-italic">"Tạo bảng mới"</span>
|
||||
<span class="font-semibold text-rose-600 dark:text-rose-400 not-italic">"Tạo bảng mới"</span>
|
||||
để bắt đầu chơi
|
||||
</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1.5">
|
||||
@@ -452,13 +465,13 @@
|
||||
|
||||
<h2
|
||||
id="congrats-title"
|
||||
class="mt-6 text-4xl font-black bg-gradient-to-r from-amber-500 via-pink-500 to-purple-500 bg-clip-text text-transparent"
|
||||
class="mt-6 text-4xl font-black text-amber-600 dark:text-amber-400"
|
||||
>
|
||||
Kinh!
|
||||
</h2>
|
||||
<p class="mt-3 text-base text-slate-700 dark:text-slate-200">
|
||||
Hàng
|
||||
<span class="block text-5xl sm:text-6xl font-black text-pink-500 dark:text-pink-400 my-1 tabular-nums">
|
||||
<span class="block text-5xl sm:text-6xl font-black text-rose-600 dark:text-rose-400 my-1 tabular-nums">
|
||||
{congratsRow}
|
||||
</span>
|
||||
đã đầy đủ!
|
||||
@@ -469,9 +482,8 @@
|
||||
<button
|
||||
onclick={() => (showCongrats = false)}
|
||||
class="mt-6 px-8 py-2.5 rounded-full font-semibold text-white
|
||||
bg-gradient-to-r from-pink-500 to-purple-500
|
||||
hover:from-pink-600 hover:to-purple-600
|
||||
active:scale-95 transition-all shadow-lg"
|
||||
bg-rose-600 hover:bg-rose-700
|
||||
active:scale-95 transition-all shadow-md"
|
||||
>
|
||||
Tuyệt vời! 🥳
|
||||
</button>
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
class="inline-flex items-center justify-center w-9 h-9 rounded-full
|
||||
text-slate-500 dark:text-slate-400
|
||||
hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-400"
|
||||
focus:outline-none focus:ring-2 focus:ring-rose-400"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -199,7 +199,7 @@
|
||||
onclick={() => pickTheme(v)}
|
||||
class="px-3 py-2 rounded-lg border-2 text-sm font-medium transition-all
|
||||
{selected
|
||||
? 'border-indigo-500 dark:border-indigo-400 bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300'
|
||||
? 'border-rose-600 dark:border-rose-400 bg-rose-50 dark:bg-rose-950/40 text-rose-700 dark:text-rose-300'
|
||||
: 'border-slate-200 dark:border-slate-600 text-slate-600 dark:text-slate-300 hover:border-slate-300 dark:hover:border-slate-500'}"
|
||||
>
|
||||
{label}
|
||||
@@ -225,7 +225,7 @@
|
||||
class="flex items-center justify-between gap-3 px-3 py-2 rounded-lg
|
||||
border-2 border-slate-200 dark:border-slate-600 cursor-pointer
|
||||
hover:border-slate-300 dark:hover:border-slate-500 transition-colors
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-400"
|
||||
focus:outline-none focus:ring-2 focus:ring-rose-400"
|
||||
>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-200">{label}</span>
|
||||
<span
|
||||
@@ -260,7 +260,7 @@
|
||||
onclick={() => pickMode(v)}
|
||||
class="flex flex-col items-center gap-1 px-2 py-2 rounded-lg border-2 text-sm font-medium transition-all
|
||||
{selected
|
||||
? 'border-indigo-500 dark:border-indigo-400 bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300'
|
||||
? 'border-rose-600 dark:border-rose-400 bg-rose-50 dark:bg-rose-950/40 text-rose-700 dark:text-rose-300'
|
||||
: 'border-slate-200 dark:border-slate-600 text-slate-600 dark:text-slate-300 hover:border-slate-300 dark:hover:border-slate-500'}"
|
||||
>
|
||||
{#if v === "player"}
|
||||
@@ -324,7 +324,7 @@
|
||||
oninput={onSpeedInput}
|
||||
aria-label="Tốc độ tự động xổ"
|
||||
aria-valuetext="{settings.autoCallSpeed} giây mỗi số"
|
||||
class="w-full mt-1 accent-indigo-500"
|
||||
class="w-full mt-1 accent-rose-600"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
@@ -381,7 +381,7 @@
|
||||
onclick={() => pickVoice(v.id)}
|
||||
class="px-3 py-2 rounded-lg border-2 text-sm transition-all
|
||||
{selected
|
||||
? 'border-indigo-500 dark:border-indigo-400 bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300'
|
||||
? 'border-rose-600 dark:border-rose-400 bg-rose-50 dark:bg-rose-950/40 text-rose-700 dark:text-rose-300'
|
||||
: 'border-slate-200 dark:border-slate-600 text-slate-600 dark:text-slate-300 hover:border-slate-300 dark:hover:border-slate-500'}"
|
||||
>
|
||||
{v.label}
|
||||
@@ -436,7 +436,7 @@
|
||||
style:background-color={hex}
|
||||
class="aspect-square rounded-lg border-2 transition-all
|
||||
{selected
|
||||
? 'border-indigo-500 dark:border-indigo-400 ring-2 ring-indigo-300 dark:ring-indigo-600 scale-110'
|
||||
? 'border-rose-600 dark:border-rose-400 ring-2 ring-rose-300 dark:ring-rose-600 scale-110'
|
||||
: 'border-slate-200 dark:border-slate-600 hover:scale-105'}"
|
||||
></button>
|
||||
{/each}
|
||||
@@ -465,9 +465,8 @@
|
||||
type="button"
|
||||
onclick={close}
|
||||
class="px-6 py-2 rounded-full text-sm font-semibold text-white
|
||||
bg-gradient-to-r from-indigo-500 to-purple-500
|
||||
hover:from-indigo-600 hover:to-purple-600
|
||||
active:scale-95 transition-all shadow-lg"
|
||||
bg-rose-600 hover:bg-rose-700
|
||||
active:scale-95 transition-all shadow-md"
|
||||
>
|
||||
Xong
|
||||
</button>
|
||||
|
||||
@@ -12,14 +12,11 @@
|
||||
>
|
||||
<div class="w-full max-w-2xl">
|
||||
<header class="relative text-center mb-6">
|
||||
<!-- z-10: H1 has a CSS `drop-shadow` filter which creates a stacking
|
||||
context. Without z-index here, that filtered H1 (later in tree
|
||||
order) paints over the gear button and swallows clicks. -->
|
||||
<div class="absolute right-0 top-0 z-10">
|
||||
<div class="absolute right-0 top-0">
|
||||
<SettingsButton />
|
||||
</div>
|
||||
<h1
|
||||
class="text-5xl sm:text-7xl font-black italic tracking-tight bg-gradient-to-r from-rose-500 via-amber-500 to-rose-500 bg-clip-text text-transparent drop-shadow-[0_2px_0_rgba(0,0,0,0.15)]"
|
||||
class="text-5xl sm:text-7xl font-black italic tracking-tight text-rose-600 dark:text-rose-400"
|
||||
>
|
||||
Lô tô
|
||||
</h1>
|
||||
@@ -44,7 +41,7 @@
|
||||
transition:slide={{ duration: 250 }}
|
||||
>
|
||||
<h2
|
||||
class="text-center text-lg sm:text-xl font-bold tracking-wider uppercase mb-4 text-orange-500 dark:text-orange-400"
|
||||
class="text-center text-lg sm:text-xl font-bold tracking-wider uppercase mb-4 text-amber-700 dark:text-amber-400"
|
||||
>
|
||||
Quản trò
|
||||
</h2>
|
||||
|
||||
Reference in New Issue
Block a user