Skip to content

Commit 10bcd22

Browse files
committed
feat: hero quick play cards and dev cleanup
1 parent 8a3d2c8 commit 10bcd22

9 files changed

Lines changed: 329 additions & 73 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
.quickPlayRoot {
2+
position: relative;
3+
z-index: 1;
4+
display: flex;
5+
flex-direction: column;
6+
gap: var(--space-lg);
7+
width: 100%;
8+
max-width: 560px;
9+
padding: 0 var(--space-md);
10+
}
11+
12+
.quickPlaySection {
13+
display: flex;
14+
flex-direction: column;
15+
gap: var(--space-sm);
16+
}
17+
18+
.sectionLabel {
19+
font-family: var(--font-display);
20+
font-size: var(--text-lg);
21+
font-weight: 700;
22+
text-transform: uppercase;
23+
letter-spacing: 0.08em;
24+
color: var(--accent);
25+
user-select: none;
26+
}
27+
28+
.playRow {
29+
display: grid;
30+
grid-template-columns: repeat(4, 1fr);
31+
gap: var(--space-xs);
32+
}
33+
34+
.puzzleRow {
35+
display: grid;
36+
grid-template-columns: repeat(3, 1fr);
37+
gap: var(--space-xs);
38+
}
39+
40+
.quickCard {
41+
display: flex;
42+
flex-direction: column;
43+
align-items: center;
44+
justify-content: center;
45+
gap: var(--space-xs);
46+
padding: var(--space-md) var(--space-sm);
47+
background: var(--surface-2);
48+
border: 1px solid var(--border-default);
49+
border-radius: var(--radius-md);
50+
color: var(--accent);
51+
cursor: pointer;
52+
animation: slideUp 0.4s ease both;
53+
animation-delay: var(--stagger, 0ms);
54+
transition:
55+
background var(--transition-fast),
56+
border-color var(--transition-fast),
57+
transform var(--transition-fast),
58+
box-shadow var(--transition-fast);
59+
}
60+
61+
.quickCard:hover {
62+
background: var(--surface-3);
63+
border-color: var(--accent-muted);
64+
transform: translateY(-2px);
65+
box-shadow: 0 4px 16px var(--accent-glow);
66+
}
67+
68+
.quickCard:active {
69+
transform: translateY(0);
70+
}
71+
72+
.cardIcon {
73+
width: 1.5rem;
74+
height: 1.5rem;
75+
color: var(--text-secondary);
76+
}
77+
78+
.cardIcon svg {
79+
width: 100%;
80+
height: 100%;
81+
}
82+
83+
.cardLabel {
84+
font-family: var(--font-display);
85+
font-size: var(--text-base);
86+
font-weight: 700;
87+
letter-spacing: 0.02em;
88+
color: var(--text-primary);
89+
line-height: 1;
90+
}
91+
92+
.cardSublabel {
93+
font-family: var(--font-display);
94+
font-size: 0.65rem;
95+
font-weight: 600;
96+
text-transform: uppercase;
97+
letter-spacing: 0.06em;
98+
color: var(--accent);
99+
line-height: 1;
100+
}
101+
102+
@media (max-width: 639px) {
103+
.quickPlayRoot {
104+
padding: 0 var(--space-sm);
105+
}
106+
107+
.playRow {
108+
grid-template-columns: repeat(2, 1fr);
109+
}
110+
111+
.quickCard {
112+
padding: var(--space-sm);
113+
}
114+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { useNavigate } from '@solidjs/router';
2+
import { For, type Component } from 'solid-js';
3+
import { type PuzzleCategory, type StartGameOptions } from '../../../types/game';
4+
import styles from './HomeQuickPlay.module.css';
5+
6+
const BulletIcon = () => (
7+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
8+
<path
9+
d="M15 8.5c0-2.5-1.5-5.5-3-6.5-1.5 1-3 4-3 6.5v5c0 .6.4 1 1 1h4c.6 0 1-.4 1-1v-5zM10 17h4v2h-4z"
10+
fill="currentColor"
11+
/>
12+
</svg>
13+
);
14+
15+
const BlitzIcon = () => (
16+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
17+
<path d="M13 2L3 14h8l-2 8 12-14h-8l2-6z" fill="currentColor" />
18+
</svg>
19+
);
20+
21+
const RapidIcon = () => (
22+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
23+
<path
24+
fill-rule="evenodd"
25+
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zm1-15h-2v6l5.25 3.15.75-1.23-4-2.42V7z"
26+
fill="currentColor"
27+
/>
28+
</svg>
29+
);
30+
31+
const ClassicalIcon = () => (
32+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
33+
<path
34+
d="M6 2v4l4 4-4 4v4h12v-4l-4-4 4-4V2H6zm2 1h8v2.5l-4 4-4-4V3zm0 18v-2.5l4-4 4 4V21H8z"
35+
fill="currentColor"
36+
/>
37+
</svg>
38+
);
39+
40+
const CrownIcon = () => (
41+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
42+
<path d="M3 18l2.5-8L9 14l3-9 3 9 3.5-4L21 18H3zm1 2h16v2H4z" fill="currentColor" />
43+
</svg>
44+
);
45+
46+
interface QuickPlayCard {
47+
icon: () => ReturnType<typeof BulletIcon>;
48+
label: string;
49+
sublabel: string;
50+
ariaLabel: string;
51+
minutes: number;
52+
increment: number;
53+
}
54+
55+
interface QuickPuzzleCard {
56+
label: string;
57+
category: PuzzleCategory;
58+
}
59+
60+
const PLAY_CARDS: QuickPlayCard[] = [
61+
{
62+
icon: BulletIcon,
63+
label: '1+0',
64+
sublabel: 'Bullet',
65+
ariaLabel: 'Quick play Bullet 1+0',
66+
minutes: 1,
67+
increment: 0,
68+
},
69+
{
70+
icon: BlitzIcon,
71+
label: '3+2',
72+
sublabel: 'Blitz',
73+
ariaLabel: 'Quick play Blitz 3+2',
74+
minutes: 3,
75+
increment: 2,
76+
},
77+
{
78+
icon: RapidIcon,
79+
label: '10+5',
80+
sublabel: 'Rapid',
81+
ariaLabel: 'Quick play Rapid 10+5',
82+
minutes: 10,
83+
increment: 5,
84+
},
85+
{
86+
icon: ClassicalIcon,
87+
label: '30+15',
88+
sublabel: 'Classical',
89+
ariaLabel: 'Quick play Classical 30+15',
90+
minutes: 30,
91+
increment: 15,
92+
},
93+
];
94+
95+
const PUZZLE_CARDS: QuickPuzzleCard[] = [
96+
{ label: 'Mate in 1', category: 'mate-in-1' },
97+
{ label: 'Mate in 2', category: 'mate-in-2' },
98+
{ label: 'Mate in 3', category: 'mate-in-3' },
99+
];
100+
101+
const HomeQuickPlay: Component = () => {
102+
const navigate = useNavigate();
103+
104+
const handleQuickPlay = (minutes: number, increment: number) => {
105+
const difficulty = 5;
106+
const side = Math.random() < 0.5 ? 'w' : 'b';
107+
const config: StartGameOptions = {
108+
side,
109+
mode: 'play',
110+
newTimeControl: minutes,
111+
newIncrement: increment,
112+
newDifficultyLevel: difficulty,
113+
};
114+
navigate('/play', { replace: true, state: { quickPlay: config } });
115+
};
116+
117+
const handlePuzzle = (category: PuzzleCategory) => {
118+
const config: StartGameOptions = {
119+
side: 'w',
120+
mode: 'puzzle',
121+
puzzleCategory: category,
122+
puzzleRated: false,
123+
};
124+
navigate('/puzzles', { replace: true, state: { quickStart: config } });
125+
};
126+
127+
return (
128+
<div class={styles.quickPlayRoot}>
129+
<div class={styles.quickPlaySection}>
130+
<span class={styles.sectionLabel}>Quick Play</span>
131+
<div class={styles.playRow}>
132+
<For each={PLAY_CARDS}>
133+
{(card, i) => (
134+
<button
135+
class={styles.quickCard}
136+
aria-label={card.ariaLabel}
137+
style={{ '--stagger': `${i() * 60}ms` }}
138+
onClick={() => handleQuickPlay(card.minutes, card.increment)}
139+
>
140+
<span class={styles.cardIcon}>
141+
<card.icon />
142+
</span>
143+
<span class={styles.cardLabel}>{card.label}</span>
144+
<span class={styles.cardSublabel}>{card.sublabel}</span>
145+
</button>
146+
)}
147+
</For>
148+
</div>
149+
</div>
150+
151+
<div class={styles.quickPlaySection}>
152+
<span class={styles.sectionLabel}>Puzzles</span>
153+
<div class={styles.puzzleRow}>
154+
<For each={PUZZLE_CARDS}>
155+
{(card, i) => (
156+
<button
157+
class={styles.quickCard}
158+
style={{ '--stagger': `${(i() + PLAY_CARDS.length) * 60}ms` }}
159+
onClick={() => handlePuzzle(card.category)}
160+
>
161+
<span class={styles.cardIcon}>
162+
<CrownIcon />
163+
</span>
164+
<span class={styles.cardLabel}>{card.label}</span>
165+
</button>
166+
)}
167+
</For>
168+
</div>
169+
</div>
170+
</div>
171+
);
172+
};
173+
174+
export default HomeQuickPlay;

apps/frontend/src/components/home/HomeSiteHero/HomeSiteHero.module.css

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
justify-content: center;
77
width: 100vw;
88
margin-left: calc(-50vw + 50%);
9-
height: 90vh;
9+
min-height: 85vh;
10+
padding: var(--space-2xl) var(--space-md);
1011
overflow: hidden;
1112
gap: var(--space-xl);
1213
}
@@ -65,37 +66,10 @@
6566
max-width: 400px;
6667
}
6768

68-
.playNowButton {
69-
position: relative;
70-
z-index: 1;
71-
padding: var(--space-md) var(--space-2xl);
72-
font-family: var(--font-display);
73-
font-size: var(--text-lg);
74-
font-weight: 700;
75-
text-transform: uppercase;
76-
letter-spacing: 0.1em;
77-
color: var(--accent);
78-
background: transparent;
79-
border: 2px solid var(--accent);
80-
border-radius: var(--radius-md);
81-
cursor: pointer;
82-
transition:
83-
transform var(--transition-base),
84-
box-shadow var(--transition-base),
85-
background var(--transition-base),
86-
color var(--transition-base);
87-
box-shadow: 0 0 20px var(--accent-ghost);
88-
}
89-
90-
.playNowButton:hover {
91-
transform: translateY(-3px);
92-
background: var(--accent);
93-
color: var(--surface-0);
94-
box-shadow:
95-
0 0 40px var(--accent-muted),
96-
0 10px 30px rgba(0, 0, 0, 0.3);
97-
}
98-
99-
.playNowButton:active {
100-
transform: translateY(-1px);
69+
@media (max-width: 639px) {
70+
.hero {
71+
min-height: 90vh;
72+
padding: var(--space-xl) var(--space-sm);
73+
gap: var(--space-lg);
74+
}
10175
}

apps/frontend/src/components/home/HomeSiteHero/HomeSiteHero.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,13 @@
1-
import { useNavigate } from '@solidjs/router';
21
import { type Component } from 'solid-js';
3-
import { getRandomQuickPlayConfig } from '../../../services/game/chessGameService';
4-
import { type StartGameOptions } from '../../../types/game';
52
import FloatingPieces from '../../common/FloatingPieces/FloatingPieces';
3+
import HomeQuickPlay from '../HomeQuickPlay/HomeQuickPlay';
64
import styles from './HomeSiteHero.module.css';
75

86
const HomeSiteHero: Component = () => {
9-
const navigate = useNavigate();
10-
11-
const handlePlayNow = () => {
12-
const [quickPlayTime, quickPlayDifficulty, quickPlaySide] = getRandomQuickPlayConfig();
13-
const quickPlayConfig: StartGameOptions = {
14-
side: quickPlaySide,
15-
mode: 'play',
16-
newTimeControl: quickPlayTime,
17-
newDifficultyLevel: quickPlayDifficulty,
18-
};
19-
navigate('/play', { replace: true, state: { quickPlay: quickPlayConfig } });
20-
};
21-
227
return (
238
<section class={styles.hero}>
249
<FloatingPieces />
25-
<button class={styles.playNowButton} onClick={handlePlayNow}>
26-
Play Now
27-
</button>
10+
<HomeQuickPlay />
2811
</section>
2912
);
3013
};

0 commit comments

Comments
 (0)