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:
tiennm99 committed 2026-04-29 14:53:08 +07:00
1 parent bfe540f537
commit 2fb35f229a
8 files changed
+237 -198

No files matched your search

+152 -69
View File
@@ -2,122 +2,185 @@
## Visual Identity ## Visual Identity
Clean, flat, single-accent system — no gradients anywhere. Color carries
hierarchy via solid weights; emphasis via type weight + scale.
### Color Palette ### Color Palette
**Primary (Player)** **Primary (Player + UI accent)**
- Gradient: indigo-500 → purple-500 - Solid: rose-600 `#E11D48` (light) / rose-400 `#FB7185` (dark)
- Use for: Main headings, generate button, hover states - Use for: Title, "Tạo bảng mới", "Tuyệt vời!", "Xong", focus rings,
- Semantic: Calm, welcoming, player-friendly selected pickers, link in footer
- Semantic: warm, lucky-red Vietnamese-fair feel
**Secondary (Host)** **Secondary (Host)**
- Gradient: orange-500 → red-500 - Solid: amber-600 `#D97706` (light) / amber-400 `#FBBF24` (dark)
- Use for: Host headings, new game button, high-stakes actions - Use for: "Quản trò" subhead, "Ván mới", host chips, bingo "Kinh!" h2
- Semantic: Energetic, commanding, host authority - 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)** **Success (Completed Rows)**
- Background: emerald-100 (light) / emerald-900/40 (dark) - Background: emerald-100 (light) / emerald-900/60 (dark)
- Text: emerald-500 (light) / emerald-400 (dark) - Text: emerald-700 (light) / emerald-200 (dark)
- Use for: Crossed cells in completed rows - Slash: solid emerald-600 stroke
**Attention (Waiting Toast)** **Attention (Waiting Toast / "Chờ" ring)**
- Background: amber-500/90 - Background: amber-500/95 (toast); amber-500/45→0.85 inset ring (label)
- Text: white - Text: white
- Use for: "Chờ X" toast notifications - Use for: "Chờ X" toast and persistent section-band cue
**Neutral (Grid & Text)** **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) - Borders: slate-200 (light) / slate-700 (dark)
- Background: white (light) / slate-800 (dark) - Body text: slate-600 (light) / slate-300 (dark)
- Text: slate-600 (light) / slate-400 (dark) - Heading text: slate-800/foreground (light) / slate-100 (dark)
### Dark Mode ### 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 ## Typography
- **Font**: Geist Sans (Google Fonts), fallback to Arial/Helvetica. - **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. - **Grid numbers**: Roboto Condensed (self-hosted via
- **Headings**: Extrabold (font-extrabold) for main title, bold (font-bold) for secondary. @fontsource/roboto-condensed, font-display: swap) in `tan-tan-num`
- **Body**: Regular weight, slate-600 (light) / slate-400 (dark). stack, bold weight.
- **Emphasis**: `<strong>` for important copy (e.g., button labels in instructions). - **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 ### Sizing
- **Page title**: `text-4xl sm:text-5xl` (responsive) - **Page title**: `text-5xl sm:text-7xl`
- **Subheading**: `text-3xl sm:text-4xl` - **Subheading**: `text-lg sm:text-xl uppercase tracking-wider`
- **Body**: `text-base sm:text-lg` - **Body**: `text-base sm:text-lg`
- **Small**: `text-xs text-slate-400` - **Small**: `text-xs text-slate-500 dark:text-slate-400`
## Layout & Spacing ## Layout & Spacing
### Breakpoints ### Breakpoints
- **Mobile first**: Base styles for mobile, then `sm:` (640px+), `md:` (768px+), `lg:` (1024px+). - **Mobile first**: Base styles for mobile, then `sm:` (640px+),
- **Padding**: `px-3 py-8 sm:py-12` (horizontal, vertical responsive). `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). - **Gaps**: `gap-3`, `gap-6`, `gap-1.5` (consistent spacing scale).
### Grid Layout ### Grid Layout
- **Player/Host grid**: `grid-template-columns: repeat(9, 1fr)` (equal cells, no gap). - **Player/Host grid**: `grid-template-columns: repeat(9, 1fr)` (equal
- **Master board**: Same 9-column layout (9×10 = 90 cells). cells, no gap).
- **Cell sizing**: `aspect-square` for perfect squares. - **Master board**: Same 9-column layout (9×11 = 99 cells, of which 90
- **Borders**: `border-r border-b` (right & bottom edges only, for clean lines). 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 ### Containers
- **Max width**: `max-w-lg` (player), `max-w-2xl` (host) — centered with `mx-auto`. - **Max width**: `max-w-2xl` (page wrapper) — centered with `mx-auto`.
- **Flex wrap**: Use `flex flex-wrap gap-1.5` for number history chips. - **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 ## Settings & UI Toggles
### Mode Picker (SettingsButton) ### Mode Picker (SettingsButton)
Three-way segmented picker in settings modal with inline SVG glyphs: Three-way segmented picker in settings modal with inline SVG glyphs:
- **Player mode** (rectangle icon) — PlayerBoard only, default UX for players - **Player mode** (rectangle icon)
- **Master mode** (rounded ellipse icon) — MasterPanel only, host-only view (e.g., projector) - **Master mode** (megaphone icon)
- **Both mode** (double-box icon) — PlayerBoard + MasterPanel stacked inline, master auto-ticks player board on draw - **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) ### Color Picker (SettingsButton)
Grouped card with two sub-sections: 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 hex entry
- **Tuỳ chỉnh** (Custom): Native `<input type="color">` for custom 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 ## Component Patterns
### Button Styles ### 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 ```html
<!-- Primary Action (Indigo/Purple) --> <!-- Player primary -->
<button class="px-8 py-3 rounded-full font-semibold text-white <button class="px-8 py-3 rounded-full font-semibold text-white
bg-gradient-to-r from-indigo-500 to-purple-500 bg-rose-600 hover:bg-rose-700
hover:from-indigo-600 hover:to-purple-600 active:scale-95 transition-all shadow-md">
active:scale-95 transition-all shadow-lg shadow-indigo-500/25">
Tạo bảng mới Tạo bảng mới
</button> </button>
<!-- Host Action (Orange/Red) --> <!-- Host primary -->
<button class="px-8 py-4 rounded-full font-semibold text-white text-lg <button class="px-8 py-4 rounded-full font-semibold text-white text-lg
bg-gradient-to-r from-orange-500 to-red-500 bg-amber-600 hover:bg-amber-700
hover:from-orange-600 hover:to-red-600 active:scale-95 transition-all shadow-md">
active:scale-95 transition-all shadow-lg shadow-orange-500/25">
Ván mới Ván mới
</button> </button>
<!-- Secondary (Emerald, Draw Action) --> <!-- Draw / Go -->
<button class="px-10 py-4 rounded-full font-semibold text-white text-lg <button class="px-10 py-4 rounded-full font-semibold text-white text-lg
bg-gradient-to-r from-emerald-500 to-teal-500 bg-emerald-600 hover:bg-emerald-700
hover:from-emerald-600 hover:to-teal-600 active:scale-95 transition-all shadow-md">
active:scale-95 transition-all shadow-lg shadow-emerald-500/25">
Xổ số Xổ số
</button> </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 ### Card Styling
- Border: `border border-slate-200 dark:border-slate-700` - 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` - Shadow: `shadow-xl shadow-slate-200/50 dark:shadow-black/30`
- Overflow: `overflow-hidden` (clip content to rounded corners) - 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 ### Text Links
- Color: `text-indigo-500 dark:text-indigo-400` (player page) - Color: `text-rose-600 dark:text-rose-400`
- Color: `text-orange-500 dark:text-orange-400` (host page)
- Hover: `hover:underline` - Hover: `hover:underline`
## Animations ## Animations
@@ -133,46 +196,66 @@ Card has rounded border, shadow, padding. Compact layout fits mobile.
### Ephemeral ### Ephemeral
- **toast** (5s): "Chờ X" notification (scale 0.8→1.05→1, fade build) - **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 ### Interactive
- **active:scale-95**: Button press response - **active:scale-95**: Button press response
- **active:scale-90**: Cell tap response
- **transition-all**: Smooth color/shadow changes on hover - **transition-all**: Smooth color/shadow changes on hover
All animations respect `prefers-reduced-motion: reduce`.
## Emoji Usage ## Emoji Usage
Intentional emojis (matching traditional bingo celebration): Intentional emojis (matching traditional bingo + Vietnamese fair):
- 🎉 ✨ 🎊 🥳 ❤️ (bingo popup) - 🎉 ✨ 🎊 🥳 ❤️ (bingo popup)
- 🏮 (header subtitle)
- 🎫 🎤 🎶 (empty/celebration messages)
- 🥢 🎋 🏮 (tier-2 confetti, alongside 🎊 ✨ 🎉 🥳)
- ❤️ (footer) - ❤️ (footer)
Keep emojis rare; use for celebration only. Keep emojis decorative only. Functional UI uses SVG glyphs.
## Vietnamese Copy ## 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). - **Toast messages**: Short. "Chờ X" (waiting for X).
- **Popups**: Celebratory. "Kinh!" (victory cry), "Tuyệt vời!" (awesome!). - **Popups**: Celebratory. "Kinh!" (victory cry),
- **Links**: Clear CTA. "Trang quản trò →" (host page), "← Về trang người chơi" (back to player). "Tuyệt vời!" (awesome!).
- **Mode hints**: One-liner per option, italic, slate-500.
## Mobile Responsiveness ## Mobile Responsiveness
- Stack vertically on mobile (`flex flex-col`). - 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`). - Enlarge text for readability on mobile (`text-base sm:text-lg`).
- Buttons stay full-width or flex-wrap on mobile. - 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 ## Accessibility
- Alt text: Not critical (no images), but grid cells have semantic ARIA if needed. - Alt text: Not critical (no images). Grid cells expose
- Focus states: Implicit via Tailwind (`focus:ring-2`), add if extending components. `aria-label="Số {n}, đã đánh dấu"` and `aria-pressed`.
- Contrast: Tailwind slate/indigo/emerald combos meet WCAG AA. - Focus states: `focus:ring-2 focus:ring-rose-400` (interactive
- Dark mode: Respects `prefers-color-scheme`, not forced. controls).
- SVG glyphs: Simple, high-contrast shapes (no fill patterns); keyboard-navigable via fieldset + label. - 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 ## PWA Features
- **Install Prompt**: Native browser-driven (no custom banner). Manifest enables Android/iOS install. - **Install Prompt**: Native browser-driven (no custom banner).
- **Offline**: App shell precache enables instant load. Audio cached on first play. Manifest enables Android/iOS install.
- **Auto-Update**: SW detects new version; user sees reload toast (non-intrusive). - **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
View File
@@ -12,23 +12,15 @@
@variant dark (&:where(.dark, .dark *)); @variant dark (&:where(.dark, .dark *));
:root { :root {
--background: #f8fafc; /* Warm cream — keeps festive warmth without a radial gradient. */
--background: #fffbeb;
--foreground: #1e293b; --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 /* Default empty-cell color: Excel "Standard Color: Purple". Overridden
at runtime by the settings store via documentElement.style. */ at runtime by the settings store via documentElement.style. */
--empty-cell-bg: #7030A0; --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. */ dividers/label flanks, so they always read as one unified band. */
--section-accent: #1565c0; --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); --section-band-bg: rgb(232 240 250 / 0.78);
} }
@@ -37,35 +29,16 @@
theme switch reads instantly. */ theme switch reads instantly. */
--background: #050813; --background: #050813;
--foreground: #e2e8f0; --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-accent: #64b5f6;
--section-band-bg: rgb(13 71 161 / 0.4); --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 { html {
background-color: var(--background); background-color: var(--background);
color: var(--foreground); color: var(--foreground);
} }
body { body {
background-color: transparent; background-color: transparent;
background-image: var(--bg-glow);
background-repeat: no-repeat;
background-attachment: fixed;
} }
/* Equal-width grid cells */ /* Equal-width grid cells */
@@ -81,33 +54,20 @@ body {
gap: 0; gap: 0;
} }
/* Decorative cross-hatch borders that frame and segment the player /* Solid bars that frame and segment the player sheet. */
sheet, echoing the ✚✚✚ blue borders on a Tân Tân BAMBOORAFT card. */
.section-divider { .section-divider {
height: 8px; height: 4px;
background-image: repeating-linear-gradient( background-color: var(--section-accent);
90deg,
transparent 0 6px,
var(--section-accent) 6px 8px,
transparent 8px 14px
);
background-color: var(--section-band-bg);
} }
.section-divider-vertical { .section-divider-vertical {
width: 8px; width: 4px;
background-image: repeating-linear-gradient( background-color: var(--section-accent);
0deg,
transparent 0 6px,
var(--section-accent) 6px 8px,
transparent 8px 14px
);
background-color: var(--section-band-bg);
} }
.section-label { .section-label {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 12px;
font-size: 0.8125rem; font-size: 0.8125rem;
letter-spacing: 0.14em; letter-spacing: 0.14em;
text-transform: uppercase; text-transform: uppercase;
@@ -118,19 +78,15 @@ body {
font-style: italic; font-style: italic;
white-space: nowrap; white-space: nowrap;
} }
/* Cross-hatch ✚✚✚ flanks on both sides of the label text — flex:1 fills /* Solid editorial rules on both sides of the label — flex:1 fills
the remaining width so the text stays centered with comfy padding. */ the remaining width so the text stays centered. */
.section-label::before, .section-label::before,
.section-label::after { .section-label::after {
content: ""; content: "";
flex: 1; flex: 1;
height: 8px; height: 2px;
background-image: repeating-linear-gradient( background-color: var(--section-accent);
90deg, opacity: 0.7;
transparent 0 6px,
var(--section-accent) 6px 8px,
transparent 8px 14px
);
} }
/* Persistent visual cue when any of the section's 3 rows is in "Chờ" /* 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; } .animate-toast { animation: toast 5s ease-in-out forwards; }
@keyframes cross-draw { @keyframes cross-draw {
from { clip-path: inset(0 100% 0 0); } from { stroke-dashoffset: 200; }
to { clip-path: inset(0 0 0 0); } to { stroke-dashoffset: 0; }
} }
/* Falling-confetti burst for the 3rd-and-up bingo of a single card. */ /* Falling-confetti burst for the 3rd-and-up bingo of a single card. */
@@ -210,28 +166,24 @@ body {
will-change: transform, opacity; will-change: transform, opacity;
} }
.cell-crossed::after { /* Diagonal slash drawn as a true solid stroke via SVG. preserveAspectRatio
content: ""; 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; position: absolute;
inset: 6px; inset: 6px;
background-image: linear-gradient(
to bottom right,
transparent calc(50% - 1.5px),
#ef4444,
transparent calc(50% + 1.5px)
);
pointer-events: none; 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; animation: cross-draw 200ms ease-out;
} }
/* Winning-row slash uses emerald instead of red so a completed row .cross-slash-win .cross-slash-line {
reads as "win" rather than "marked off". */ stroke: #10b981;
.cell-crossed-win::after {
background-image: linear-gradient(
to bottom right,
transparent calc(50% - 1.5px),
#10b981,
transparent calc(50% + 1.5px)
);
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
@@ -242,9 +194,7 @@ body {
.animate-spin-slow-reverse, .animate-spin-slow-reverse,
.animate-toast, .animate-toast,
.confetti, .confetti,
.cell-crossed::after { .cross-slash-line {
animation: none !important; animation: none !important;
} }
/* Keep the crossed slash visible (no draw animation, but still drawn). */
.cell-crossed::after { clip-path: none; }
} }
+3 -3
View File
@@ -25,14 +25,14 @@
<span <span
class="inline-block px-3 py-1 rounded-full text-xs font-semibold class="inline-block px-3 py-1 rounded-full text-xs font-semibold
tracking-wider uppercase tracking-wider uppercase
bg-orange-100 dark:bg-orange-950/40 bg-amber-100 dark:bg-amber-950/40
text-orange-700 dark:text-orange-300" text-amber-700 dark:text-amber-300"
> >
Chế độ Quản trò Chế độ Quản trò
</span> </span>
<p class="mt-3 text-base text-slate-600 dark:text-slate-300 italic"> <p class="mt-3 text-base text-slate-600 dark:text-slate-300 italic">
Nhấn 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" "Ván mới"
</span> </span>
để bắt đầu xổ số để bắt đầu xổ số
+14 -16
View File
@@ -187,9 +187,8 @@
<button <button
onclick={handleNewGame} onclick={handleNewGame}
class="px-8 py-4 rounded-full font-semibold text-white text-lg class="px-8 py-4 rounded-full font-semibold text-white text-lg
bg-gradient-to-r from-orange-500 to-red-500 bg-amber-600 hover:bg-amber-700
hover:from-orange-600 hover:to-red-600 active:scale-95 transition-all shadow-md"
active:scale-95 transition-all shadow-lg shadow-orange-500/25"
> >
Ván mới Ván mới
</button> </button>
@@ -198,10 +197,10 @@
<button <button
onclick={toggleAuto} onclick={toggleAuto}
class="px-10 py-4 rounded-full font-semibold text-white text-lg class="px-10 py-4 rounded-full font-semibold text-white text-lg
bg-gradient-to-r {autoRunning {autoRunning
? 'from-red-500 to-rose-500 hover:from-red-600 hover:to-rose-600 shadow-red-500/25' ? 'bg-rose-600 hover:bg-rose-700'
: 'from-emerald-500 to-teal-500 hover:from-emerald-600 hover:to-teal-600 shadow-emerald-500/25'} : 'bg-emerald-600 hover:bg-emerald-700'}
active:scale-95 transition-all shadow-lg" active:scale-95 transition-all shadow-md"
> >
{autoRunning ? "Dừng" : "Bắt đầu"} {autoRunning ? "Dừng" : "Bắt đầu"}
</button> </button>
@@ -209,9 +208,8 @@
<button <button
onclick={handleDrawNext} onclick={handleDrawNext}
class="px-10 py-4 rounded-full font-semibold text-white text-lg class="px-10 py-4 rounded-full font-semibold text-white text-lg
bg-gradient-to-r from-emerald-500 to-teal-500 bg-emerald-600 hover:bg-emerald-700
hover:from-emerald-600 hover:to-teal-600 active:scale-95 transition-all shadow-md"
active:scale-95 transition-all shadow-lg shadow-emerald-500/25"
> >
Xổ số Xổ số
</button> </button>
@@ -247,13 +245,13 @@
flex items-center justify-center flex items-center justify-center
shadow-xl scroll-mt-4 shadow-xl scroll-mt-4
{lastIsLow {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'}" : 'border-emerald-500 dark:border-emerald-400 shadow-emerald-500/30'}"
> >
<span <span
class="text-6xl sm:text-8xl font-black tabular-nums class="text-6xl sm:text-8xl font-black tabular-nums
{lastIsLow {lastIsLow
? 'text-pink-500 dark:text-pink-400' ? 'text-sky-700 dark:text-sky-400'
: 'text-emerald-500 dark:text-emerald-400'}" : 'text-emerald-500 dark:text-emerald-400'}"
> >
{lastCalled} {lastCalled}
@@ -282,7 +280,7 @@
text-base sm:text-lg font-black tabular-nums rounded-full text-base sm:text-lg font-black tabular-nums rounded-full
border-2 bg-amber-50 dark:bg-amber-100 border-2 bg-amber-50 dark:bg-amber-100
{isLow {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'}" : 'border-emerald-500 dark:border-emerald-400 text-emerald-600 dark:text-emerald-500'}"
> >
{num} {num}
@@ -314,7 +312,7 @@
{isLast ? 'z-10' : ''}" {isLast ? 'z-10' : ''}"
> >
{#if hasNumber} {#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. --> gray-ringed and dim when uncalled. -->
<div <div
class="flex items-center justify-center class="flex items-center justify-center
@@ -324,8 +322,8 @@
{!isCalled {!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' ? '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 : isLow
? 'border-pink-500 dark:border-pink-400 bg-amber-50 dark:bg-amber-100 text-pink-600 dark:text-pink-500' ? 'border-sky-600 dark:border-sky-400 bg-amber-50 dark:bg-amber-100 text-sky-700 dark:text-sky-700'
: 'border-emerald-500 dark:border-emerald-400 bg-amber-50 dark:bg-amber-100 text-emerald-600 dark:text-emerald-500'} : 'border-emerald-600 dark:border-emerald-400 bg-amber-50 dark:bg-amber-100 text-emerald-700 dark:text-emerald-700'}
{isLast {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' ? 'ring-2 ring-red-500 dark:ring-red-400 ring-offset-1 ring-offset-white dark:ring-offset-slate-800 scale-110 shadow-md'
: ''}" : ''}"
+1 -1
View File
@@ -10,7 +10,7 @@
href="https://miti99.com" href="https://miti99.com"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="text-indigo-500 dark:text-indigo-400 hover:underline" class="text-rose-600 dark:text-rose-400 hover:underline"
> >
miti99 miti99
</a> </a>
+25 -13
View File
@@ -259,9 +259,8 @@
<button <button
onclick={handleGenerate} onclick={handleGenerate}
class="px-8 py-3 rounded-full font-semibold text-white class="px-8 py-3 rounded-full font-semibold text-white
bg-gradient-to-r from-indigo-500 to-purple-500 bg-rose-600 hover:bg-rose-700
hover:from-indigo-600 hover:to-purple-600 active:scale-95 transition-all shadow-md"
active:scale-95 transition-all shadow-lg shadow-indigo-500/25"
> >
Tạo bảng mới Tạo bảng mới
</button> </button>
@@ -329,14 +328,28 @@
text-xl sm:text-2xl md:text-3xl text-xl sm:text-2xl md:text-3xl
border border-slate-400/50 dark:border-slate-600/40 border border-slate-400/50 dark:border-slate-600/40
transition-all select-none cursor-pointer active:scale-90 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 {isCrossed
? rowComplete ? rowComplete
? 'cell-crossed cell-crossed-win bg-emerald-100 dark:bg-emerald-900/60 text-emerald-700 dark:text-emerald-200' ? '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-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-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} {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> </button>
{/if} {/if}
{/each} {/each}
@@ -394,7 +407,7 @@
</div> </div>
<p class="text-base text-slate-600 dark:text-slate-300 italic"> <p class="text-base text-slate-600 dark:text-slate-300 italic">
Nhấn 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 để bắt đầu chơi
</p> </p>
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1.5"> <p class="text-sm text-slate-500 dark:text-slate-400 mt-1.5">
@@ -452,13 +465,13 @@
<h2 <h2
id="congrats-title" 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! Kinh!
</h2> </h2>
<p class="mt-3 text-base text-slate-700 dark:text-slate-200"> <p class="mt-3 text-base text-slate-700 dark:text-slate-200">
Hàng 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} {congratsRow}
</span> </span>
đã đầy đủ! đã đầy đủ!
@@ -469,9 +482,8 @@
<button <button
onclick={() => (showCongrats = false)} onclick={() => (showCongrats = false)}
class="mt-6 px-8 py-2.5 rounded-full font-semibold text-white class="mt-6 px-8 py-2.5 rounded-full font-semibold text-white
bg-gradient-to-r from-pink-500 to-purple-500 bg-rose-600 hover:bg-rose-700
hover:from-pink-600 hover:to-purple-600 active:scale-95 transition-all shadow-md"
active:scale-95 transition-all shadow-lg"
> >
Tuyệt vời! 🥳 Tuyệt vời! 🥳
</button> </button>
+9 -10
View File
@@ -129,7 +129,7 @@
class="inline-flex items-center justify-center w-9 h-9 rounded-full class="inline-flex items-center justify-center w-9 h-9 rounded-full
text-slate-500 dark:text-slate-400 text-slate-500 dark:text-slate-400
hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors 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 <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -199,7 +199,7 @@
onclick={() => pickTheme(v)} onclick={() => pickTheme(v)}
class="px-3 py-2 rounded-lg border-2 text-sm font-medium transition-all class="px-3 py-2 rounded-lg border-2 text-sm font-medium transition-all
{selected {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'}" : 'border-slate-200 dark:border-slate-600 text-slate-600 dark:text-slate-300 hover:border-slate-300 dark:hover:border-slate-500'}"
> >
{label} {label}
@@ -225,7 +225,7 @@
class="flex items-center justify-between gap-3 px-3 py-2 rounded-lg 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 border-2 border-slate-200 dark:border-slate-600 cursor-pointer
hover:border-slate-300 dark:hover:border-slate-500 transition-colors 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 class="text-sm text-slate-700 dark:text-slate-200">{label}</span>
<span <span
@@ -260,7 +260,7 @@
onclick={() => pickMode(v)} 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 class="flex flex-col items-center gap-1 px-2 py-2 rounded-lg border-2 text-sm font-medium transition-all
{selected {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'}" : '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"} {#if v === "player"}
@@ -324,7 +324,7 @@
oninput={onSpeedInput} oninput={onSpeedInput}
aria-label="Tốc độ tự động xổ" aria-label="Tốc độ tự động xổ"
aria-valuetext="{settings.autoCallSpeed} giây mỗi số" 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> </label>
{/if} {/if}
@@ -381,7 +381,7 @@
onclick={() => pickVoice(v.id)} onclick={() => pickVoice(v.id)}
class="px-3 py-2 rounded-lg border-2 text-sm transition-all class="px-3 py-2 rounded-lg border-2 text-sm transition-all
{selected {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'}" : '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} {v.label}
@@ -436,7 +436,7 @@
style:background-color={hex} style:background-color={hex}
class="aspect-square rounded-lg border-2 transition-all class="aspect-square rounded-lg border-2 transition-all
{selected {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'}" : 'border-slate-200 dark:border-slate-600 hover:scale-105'}"
></button> ></button>
{/each} {/each}
@@ -465,9 +465,8 @@
type="button" type="button"
onclick={close} onclick={close}
class="px-6 py-2 rounded-full text-sm font-semibold text-white class="px-6 py-2 rounded-full text-sm font-semibold text-white
bg-gradient-to-r from-indigo-500 to-purple-500 bg-rose-600 hover:bg-rose-700
hover:from-indigo-600 hover:to-purple-600 active:scale-95 transition-all shadow-md"
active:scale-95 transition-all shadow-lg"
> >
Xong Xong
</button> </button>
+3 -6
View File
@@ -12,14 +12,11 @@
> >
<div class="w-full max-w-2xl"> <div class="w-full max-w-2xl">
<header class="relative text-center mb-6"> <header class="relative text-center mb-6">
<!-- z-10: H1 has a CSS `drop-shadow` filter which creates a stacking <div class="absolute right-0 top-0">
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">
<SettingsButton /> <SettingsButton />
</div> </div>
<h1 <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ô Lô tô
</h1> </h1>
@@ -44,7 +41,7 @@
transition:slide={{ duration: 250 }} transition:slide={{ duration: 250 }}
> >
<h2 <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ò Quản trò
</h2> </h2>