Skip to content

Commit 76117ab

Browse files
dengzhaofunclaude
andauthored
feat(landing): mobile UX polish — icons, bento grid, navbar & overflow fixes (#173)
- Stack section: assign semantic icons per item (Globe/Cloud/Code2/KeyRound/Sparkles/History) instead of the same Wand2 for all six - Stack section: redesign layout as bento grid — featured hero card (col-span-2), four compact cards, full-width strip; mobile stays single-column with visual hierarchy - LanguageSwitcher: replace Select dropdown with compact pill toggle (EN | 中), eliminates mobile navbar crowding - TopNav: add shrink-0 + whitespace-nowrap to logo link and switcher to prevent text wrapping on narrow viewports - TopNav: hide ThemeToggle on mobile (hidden sm:contents) to free navbar space - MarketingShell: add overflow-x-hidden to root wrapper to prevent horizontal scroll - CodeShowcase / AICopilot / AnalyticsPreview / EventsAndWebhooks: wrap grid items in min-w-0 to fix CSS grid auto min-width causing sections to overflow viewport Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fe35cc0 commit 76117ab

3 files changed

Lines changed: 106 additions & 49 deletions

File tree

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
import { getLocale, setLocale } from '../paraglide/runtime.js'
22
import type { Locale } from '../paraglide/runtime.js'
3-
import {
4-
Select,
5-
SelectContent,
6-
SelectItem,
7-
SelectTrigger,
8-
SelectValue,
9-
} from '#/components/ui/select'
103

11-
const LOCALE_LABELS: Record<Locale, string> = {
12-
en: 'English',
13-
zh: '中文',
14-
}
4+
const LOCALES: { value: Locale; label: string }[] = [
5+
{ value: 'en', label: 'EN' },
6+
{ value: 'zh', label: '中' },
7+
]
158

169
export function LanguageSwitcher() {
10+
const current = getLocale()
1711
return (
18-
<Select value={getLocale()} onValueChange={(v) => setLocale(v as Locale)}>
19-
<SelectTrigger className="w-[100px]">
20-
<SelectValue />
21-
</SelectTrigger>
22-
<SelectContent>
23-
{Object.entries(LOCALE_LABELS).map(([locale, label]) => (
24-
<SelectItem key={locale} value={locale}>
25-
{label}
26-
</SelectItem>
27-
))}
28-
</SelectContent>
29-
</Select>
12+
<div className="flex shrink-0 items-center rounded-lg border border-border bg-background p-0.5 text-sm">
13+
{LOCALES.map(({ value, label }) => (
14+
<button
15+
key={value}
16+
onClick={() => setLocale(value)}
17+
className={[
18+
'rounded-md px-2.5 py-1 font-medium transition-colors whitespace-nowrap',
19+
current === value
20+
? 'bg-foreground text-background'
21+
: 'text-muted-foreground hover:text-foreground',
22+
].join(' ')}
23+
>
24+
{label}
25+
</button>
26+
))}
27+
</div>
3028
)
3129
}

apps/admin/src/components/landing/Landing.tsx

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
BookOpen,
1010
CalendarCheck,
1111
Check,
12+
Cloud,
13+
Code2,
1214
Coins,
1315
Contact,
1416
Database,
@@ -17,6 +19,7 @@ import {
1719
GalleryHorizontal,
1820
Gift,
1921
Globe,
22+
History,
2023
KeyRound,
2124
Layers,
2225
LayoutGrid,
@@ -42,7 +45,6 @@ import {
4245
Trophy,
4346
UserPlus,
4447
Users,
45-
Wand2,
4648
WandSparkles,
4749
Webhook,
4850
Zap,
@@ -747,7 +749,7 @@ function AICopilot() {
747749
}}
748750
/>
749751
<div className="relative mx-auto grid max-w-7xl gap-12 px-4 sm:px-6 lg:grid-cols-[1fr_1.1fr] lg:items-center">
750-
<div>
752+
<div className="min-w-0">
751753
<SectionEyebrow>{m.landing_copilot_eyebrow()}</SectionEyebrow>
752754
<SectionTitle>
753755
{m.landing_copilot_title_line1()}
@@ -800,7 +802,7 @@ function AICopilot() {
800802
</div>
801803
</div>
802804

803-
<CopilotMock />
805+
<div className="min-w-0"><CopilotMock /></div>
804806
</div>
805807
</section>
806808
)
@@ -959,7 +961,7 @@ function AnalyticsPreview() {
959961
}}
960962
/>
961963
<div className="relative mx-auto grid max-w-7xl gap-12 px-4 sm:px-6 lg:grid-cols-[1fr_1.1fr] lg:items-center">
962-
<div>
964+
<div className="min-w-0">
963965
<SectionEyebrow>{m.landing_analytics_eyebrow()}</SectionEyebrow>
964966
<SectionTitle>
965967
{m.landing_analytics_title_line1()}
@@ -988,7 +990,7 @@ function AnalyticsPreview() {
988990
</div>
989991
</div>
990992

991-
<AnalyticsMock />
993+
<div className="min-w-0"><AnalyticsMock /></div>
992994
</div>
993995
</section>
994996
)
@@ -1159,7 +1161,7 @@ function CodeShowcase() {
11591161
return (
11601162
<section id="developer" className="relative py-24">
11611163
<div className="mx-auto grid max-w-7xl gap-12 px-4 sm:px-6 lg:grid-cols-[1fr_1.1fr] lg:items-center">
1162-
<div>
1164+
<div className="min-w-0">
11631165
<SectionEyebrow>{m.landing_dev_eyebrow()}</SectionEyebrow>
11641166
<SectionTitle>{m.landing_dev_title()}</SectionTitle>
11651167
<p className="mt-5 max-w-xl text-muted-foreground">
@@ -1186,7 +1188,7 @@ function CodeShowcase() {
11861188
</ul>
11871189
</div>
11881190

1189-
<CodeWindow />
1191+
<div className="min-w-0"><CodeWindow /></div>
11901192
</div>
11911193
</section>
11921194
)
@@ -1325,8 +1327,8 @@ function EventsAndWebhooks() {
13251327
</div>
13261328

13271329
<div className="mt-16 grid gap-10 lg:grid-cols-[1.05fr_1fr] lg:items-center">
1328-
<EventFlowDiagram />
1329-
<WebhookCodeWindow />
1330+
<div className="min-w-0"><EventFlowDiagram /></div>
1331+
<div className="min-w-0"><WebhookCodeWindow /></div>
13301332
</div>
13311333
</div>
13321334
</section>
@@ -1556,30 +1558,43 @@ function WebhookCodeWindow() {
15561558
}
15571559

15581560
function Stack() {
1559-
const items = [
1561+
type StackVariant = "featured" | "compact" | "strip"
1562+
const items: { icon: LucideIcon; k: string; d: string; variant: StackVariant }[] = [
15601563
{
1564+
icon: Globe,
15611565
k: m.landing_stack_global_title(),
15621566
d: m.landing_stack_global_desc(),
1567+
variant: "featured",
15631568
},
15641569
{
1570+
icon: Cloud,
15651571
k: m.landing_stack_serverless_title(),
15661572
d: m.landing_stack_serverless_desc(),
1573+
variant: "compact",
15671574
},
15681575
{
1576+
icon: Code2,
15691577
k: m.landing_stack_sdk_title(),
15701578
d: m.landing_stack_sdk_desc(),
1579+
variant: "compact",
15711580
},
15721581
{
1582+
icon: KeyRound,
15731583
k: m.landing_stack_auth_title(),
15741584
d: m.landing_stack_auth_desc(),
1585+
variant: "compact",
15751586
},
15761587
{
1588+
icon: Sparkles,
15771589
k: m.landing_stack_ai_title(),
15781590
d: m.landing_stack_ai_desc(),
1591+
variant: "compact",
15791592
},
15801593
{
1594+
icon: History,
15811595
k: m.landing_stack_audit_title(),
15821596
d: m.landing_stack_audit_desc(),
1597+
variant: "strip",
15831598
},
15841599
]
15851600
return (
@@ -1591,19 +1606,63 @@ function Stack() {
15911606
{m.landing_stack_desc()}
15921607
</p>
15931608

1594-
<div className="mt-12 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
1595-
{items.map((it) => (
1596-
<div
1597-
key={it.k}
1598-
className="group flex items-start gap-4 rounded-2xl border border-border bg-card/60 p-5 transition-colors hover:border-foreground/30"
1599-
>
1600-
<Wand2 className="mt-0.5 size-5 shrink-0 text-[var(--ak-accent-2)]" strokeWidth={1.75} />
1601-
<div>
1602-
<div className="font-semibold">{it.k}</div>
1603-
<div className="text-sm text-muted-foreground">{it.d}</div>
1609+
{/* Bento: Globe hero (col-span-2) | 4 compact (col-span-1) | History strip (col-span-3) */}
1610+
<div className="mt-12 grid gap-3 lg:grid-cols-3">
1611+
{items.map((it) => {
1612+
const Icon = it.icon
1613+
1614+
if (it.variant === "featured") {
1615+
return (
1616+
<div
1617+
key={it.k}
1618+
className="group relative overflow-hidden rounded-2xl border border-border bg-card/60 p-7 transition-[border-color,box-shadow] hover:border-[var(--ak-accent-2)]/40 hover:shadow-[0_20px_60px_-20px_var(--ak-glow-2)] lg:col-span-2"
1619+
>
1620+
<div
1621+
className="absolute inset-x-0 top-0 h-px"
1622+
style={{ background: "linear-gradient(90deg, transparent, var(--ak-accent-2), transparent)" }}
1623+
aria-hidden
1624+
/>
1625+
<div className="mb-6 grid size-12 place-items-center rounded-xl bg-[var(--ak-accent-2)]/10 text-[var(--ak-accent-2)] ring-1 ring-[var(--ak-accent-2)]/25 transition-colors group-hover:bg-[var(--ak-accent-2)]/15">
1626+
<Icon className="size-6" strokeWidth={1.5} />
1627+
</div>
1628+
<div className="text-xl font-bold tracking-tight">{it.k}</div>
1629+
<div className="mt-2 max-w-sm text-sm leading-relaxed text-muted-foreground">{it.d}</div>
1630+
</div>
1631+
)
1632+
}
1633+
1634+
if (it.variant === "strip") {
1635+
return (
1636+
<div
1637+
key={it.k}
1638+
className="group flex items-center gap-5 rounded-2xl border border-border bg-card/60 px-6 py-5 transition-colors hover:border-foreground/30 lg:col-span-3"
1639+
>
1640+
<div className="grid size-10 shrink-0 place-items-center rounded-xl bg-foreground/5 text-[var(--ak-accent-2)] ring-1 ring-border transition-colors group-hover:bg-[var(--ak-accent-2)]/10">
1641+
<Icon className="size-5" strokeWidth={1.75} />
1642+
</div>
1643+
<div>
1644+
<div className="font-semibold">{it.k}</div>
1645+
<div className="text-sm text-muted-foreground">{it.d}</div>
1646+
</div>
1647+
</div>
1648+
)
1649+
}
1650+
1651+
return (
1652+
<div
1653+
key={it.k}
1654+
className="group flex items-start gap-4 rounded-2xl border border-border bg-card/60 p-5 transition-colors hover:border-foreground/30 lg:flex-col lg:gap-5 lg:p-6"
1655+
>
1656+
<div className="mt-0.5 grid size-9 shrink-0 place-items-center rounded-lg bg-foreground/5 text-[var(--ak-accent-2)] ring-1 ring-border transition-colors group-hover:bg-[var(--ak-accent-2)]/10 lg:mt-0">
1657+
<Icon className="size-4.5" strokeWidth={1.75} />
1658+
</div>
1659+
<div>
1660+
<div className="font-semibold">{it.k}</div>
1661+
<div className="mt-1 text-sm leading-relaxed text-muted-foreground">{it.d}</div>
1662+
</div>
16041663
</div>
1605-
</div>
1606-
))}
1664+
)
1665+
})}
16071666
</div>
16081667
</div>
16091668
</section>

apps/admin/src/components/landing/MarketingShell.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ function TopNav() {
5252
return (
5353
<header className="sticky top-0 z-40 border-b border-border/60 bg-background/70 backdrop-blur-xl">
5454
<div className="mx-auto flex h-14 w-full max-w-7xl items-center gap-6 px-4 sm:px-6">
55-
<Link to="/" className="flex items-center gap-2 font-black tracking-tight">
55+
<Link to="/" className="flex shrink-0 items-center gap-2 font-black tracking-tight">
5656
<span className="grid size-7 overflow-hidden place-items-center rounded-lg bg-white">
5757
<img src="/logo192.png" alt="ApolloKit" className="size-full object-contain" />
5858
</span>
59-
<span>ApolloKit</span>
59+
<span className="whitespace-nowrap">ApolloKit</span>
6060
</Link>
6161
<nav className="hidden items-center gap-6 text-sm text-muted-foreground md:flex">
6262
{getNavLinks().map((l) => (
@@ -67,7 +67,7 @@ function TopNav() {
6767
</nav>
6868
<div className="ml-auto flex items-center gap-2">
6969
<LanguageSwitcher />
70-
<ThemeToggle />
70+
<span className="hidden sm:contents"><ThemeToggle /></span>
7171
<Button
7272
render={
7373
<Link to="/auth/$authView" params={{ authView: "sign-in" }}>
@@ -203,7 +203,7 @@ export default function MarketingShell({
203203
children: React.ReactNode
204204
}) {
205205
return (
206-
<div className="ak-landing min-h-screen bg-background text-foreground">
206+
<div className="ak-landing min-h-screen overflow-x-hidden bg-background text-foreground">
207207
<TopNav />
208208
{children}
209209
<Footer />

0 commit comments

Comments
 (0)