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:
2026-04-29 14:53:08 +07:00
parent bfe540f537
commit 2fb35f229a
8 changed files with 237 additions and 198 deletions
+152 -69
View File
@@ -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
View File
@@ -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; }
}
+3 -3
View File
@@ -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
View File
@@ -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'
: ''}"
+1 -1
View File
@@ -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
View File
@@ -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>
+9 -10
View File
@@ -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>
+3 -6
View File
@@ -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>