Skip to content

Commit 0e3b35d

Browse files
committed
feat: add Astro landing page home UI
1 parent 8302ee2 commit 0e3b35d

15 files changed

Lines changed: 637 additions & 7 deletions

astro.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import starlightLinksValidator from "starlight-links-validator";
88
// https://astro.build/config
99
export default defineConfig({
1010
outDir: "./build",
11+
publicDir: "./static",
1112
site: "https://fsd.how",
1213
redirects: {
13-
"/": "/docs/get-started/overview",
1414
"/ru": "/ru/docs/get-started/overview",
1515
"/uz": "/uz/docs/get-started/overview",
1616
"/kr": "/kr/docs/get-started/overview",
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<section class="HeroBanner">
2+
<div class="HeroBanner__inner">
3+
<article class="HeroBanner__content">
4+
<h1 class="HeroBanner__title">
5+
A clearer structure for frontend codebases
6+
</h1>
7+
<p class="HeroBanner__summary">
8+
Rules and conventions for organizing front-end code<br />
9+
to keep projects clear and stable as requirements change.
10+
</p>
11+
<a class="HeroBanner__button" href="/docs/get-started/overview/">
12+
Get Started
13+
<svg
14+
width="16"
15+
height="16"
16+
viewBox="0 0 16 16"
17+
fill="none"
18+
aria-hidden="true"
19+
xmlns="http://www.w3.org/2000/svg"
20+
>
21+
<path
22+
d="M3.33337 8H12.6667"
23+
stroke="currentColor"
24+
stroke-width="1.5"
25+
stroke-linecap="round"
26+
stroke-linejoin="round"></path>
27+
<path
28+
d="M8.66663 4L12.6666 8L8.66663 12"
29+
stroke="currentColor"
30+
stroke-width="1.5"
31+
stroke-linecap="round"
32+
stroke-linejoin="round"></path>
33+
</svg>
34+
</a>
35+
</article>
36+
</div>
37+
</section>
38+
39+
<style>
40+
.HeroBanner {
41+
position: relative;
42+
background-image: url("/img/landing/fsd-gradient.svg");
43+
background-position: center bottom;
44+
background-size: cover;
45+
background-color: var(--sl-color-black);
46+
border-radius: 16px;
47+
margin-top: 2rem;
48+
margin-bottom: 3rem;
49+
padding: 3rem 16px;
50+
overflow: hidden;
51+
isolation: isolate;
52+
}
53+
54+
@media (min-width: 960px) {
55+
.HeroBanner {
56+
padding-top: 60px;
57+
padding-bottom: 72px;
58+
}
59+
}
60+
61+
.HeroBanner__inner {
62+
max-width: 960px;
63+
box-sizing: border-box;
64+
margin: 0 auto;
65+
padding: 0 16px;
66+
position: relative;
67+
z-index: 1;
68+
}
69+
70+
.HeroBanner__content {
71+
position: relative;
72+
}
73+
74+
.HeroBanner__badge {
75+
display: inline-flex;
76+
align-items: center;
77+
gap: 6px;
78+
padding: 4px 10px;
79+
border-radius: 9999px;
80+
background: rgba(49, 147, 255, 0.1);
81+
border: 1px solid rgba(49, 147, 255, 0.2);
82+
color: var(--sl-color-accent);
83+
font-size: 0.75rem;
84+
font-weight: 600;
85+
margin-bottom: 0.5rem;
86+
backdrop-filter: blur(4px);
87+
}
88+
89+
.HeroBanner__badge-dot {
90+
width: 5px;
91+
height: 5px;
92+
border-radius: 50%;
93+
background-color: var(--sl-color-accent);
94+
box-shadow: 0 0 0 0 rgba(49, 147, 255, 0.7);
95+
animation: pulse-dot 2s infinite;
96+
}
97+
98+
@keyframes pulse-dot {
99+
0% {
100+
transform: scale(0.95);
101+
box-shadow: 0 0 0 0 rgba(49, 147, 255, 0.7);
102+
}
103+
104+
70% {
105+
transform: scale(1);
106+
box-shadow: 0 0 0 6px rgba(49, 147, 255, 0);
107+
}
108+
109+
100% {
110+
transform: scale(0.95);
111+
box-shadow: 0 0 0 0 rgba(49, 147, 255, 0);
112+
}
113+
}
114+
115+
.HeroBanner__title {
116+
font-size: 2rem;
117+
font-weight: 500;
118+
line-height: 1.3;
119+
color: var(--sl-color-white);
120+
margin-block: 0.75rem;
121+
}
122+
123+
@media (min-width: 960px) {
124+
.HeroBanner__title {
125+
font-size: 2.25rem;
126+
margin-block: 0.5rem;
127+
}
128+
}
129+
130+
.HeroBanner__summary {
131+
font-size: 1rem;
132+
line-height: 1.5;
133+
color: var(--sl-color-gray-2);
134+
margin-bottom: 1rem;
135+
max-width: 70ch;
136+
}
137+
138+
.HeroBanner__button {
139+
display: inline-flex;
140+
align-items: center;
141+
gap: 0.5rem;
142+
background-color: var(--sl-color-accent);
143+
color: #fff;
144+
padding: 0.625rem 1.25rem;
145+
border-radius: 6px;
146+
font-size: 0.9375rem;
147+
font-weight: 600;
148+
text-decoration: none;
149+
transition:
150+
background-color 150ms,
151+
transform 150ms;
152+
}
153+
</style>

src/pages/_home/ui/index.css

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
:root,
2+
:root[data-theme="dark"] {
3+
--hp-surface: #1e293b;
4+
--hp-border: #334155;
5+
--hp-hover-bg: #334155;
6+
--hp-card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
7+
--hp-card-hover-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4);
8+
--hp-badge-bg: #1e293b;
9+
--hp-badge-text: #e2e8f0;
10+
--hp-badge-border: rgba(255, 255, 255, 0.15);
11+
--hp-badge-svg-border: rgba(255, 255, 255, 0.2);
12+
}
13+
14+
:root[data-theme="light"] {
15+
--hp-surface: #ffffff;
16+
--hp-border: #e2dff1;
17+
--hp-hover-bg: #f8f8f8;
18+
--hp-card-shadow:
19+
0px 4px 4px 0px rgba(0, 0, 0, 0.01),
20+
0px 2px 2px 0px rgba(0, 0, 0, 0.04), 0px 0px 0px 1px rgba(0, 0, 0, 0.09);
21+
--hp-card-hover-shadow:
22+
0px 6px 7px -2px rgba(0, 0, 0, 0.08),
23+
0px 2px 2px 0px rgba(0, 0, 0, 0.04), 0px 0px 0px 1px rgba(0, 0, 0, 0.09);
24+
--hp-badge-bg: #ffffff;
25+
--hp-badge-text: #28243f;
26+
--hp-badge-border: rgba(0, 0, 0, 0.12);
27+
--hp-badge-svg-border: rgba(0, 0, 0, 0.2);
28+
}
29+
30+
[data-has-hero] .hero {
31+
display: none;
32+
}
33+
34+
[data-has-hero] .content-panel {
35+
padding: 0 !important;
36+
}
37+
38+
[data-has-hero] .sl-container {
39+
max-width: 100% !important;
40+
margin: 0 !important;
41+
padding: 0 !important;
42+
}
43+
44+
[data-has-hero] .sl-container > * + * {
45+
margin-top: 0 !important;
46+
}
47+
48+
[data-has-hero] .content-panel + .content-panel {
49+
border-top: none !important;
50+
}
51+
52+
[data-has-hero]
53+
.sl-markdown-content
54+
:not(a, strong, em, del, span, input, code, br)
55+
+ :not(a, strong, em, del, span, input, code, br, :where(.not-content *)) {
56+
margin-top: 0;
57+
}
58+
59+
.HomePage__container {
60+
max-width: 1440px;
61+
box-sizing: border-box;
62+
margin-left: auto;
63+
margin-right: auto;
64+
padding-left: 16px;
65+
padding-right: 16px;
66+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
---
2+
const guides = [
3+
{
4+
id: "layers",
5+
title: "Layers",
6+
summary: "Understand how layers structure your app",
7+
href: "/docs/reference/layers",
8+
},
9+
{
10+
id: "from_custom_architecture",
11+
title: "From a custom architecture",
12+
summary: "Migrate your existing project to FSD incrementally",
13+
href: "/docs/guides/migration/from-custom",
14+
},
15+
{
16+
id: "public_api",
17+
title: "Public API",
18+
summary: "Define clear boundaries between slices",
19+
href: "/docs/reference/public-api",
20+
},
21+
{
22+
id: "cross_imports",
23+
title: "Cross-imports",
24+
summary: "Avoid the most common architectural violation",
25+
href: "/docs/guides/issues/cross-imports",
26+
},
27+
{
28+
id: "excessive_entities",
29+
title: "Excessive Entities",
30+
summary: "Avoid over-splitting your domain into too many entities",
31+
href: "/docs/guides/issues/excessive-entities",
32+
},
33+
{
34+
id: "faq",
35+
title: "FAQ",
36+
summary: "Find answers about adopting FSD in practice",
37+
href: "/docs/get-started/faq",
38+
},
39+
];
40+
41+
const iconFiles: Record<string, string> = {
42+
layers: "/img/landing/icons/icon_layers.svg",
43+
from_custom_architecture: "/img/landing/icons/icon_from_custom_architecture.svg",
44+
public_api: "/img/landing/icons/icon_public_api.svg",
45+
cross_imports: "/img/landing/icons/icon_cross_imports.svg",
46+
excessive_entities: "/img/landing/icons/icon_excessive_entities.svg",
47+
faq: "/img/landing/icons/icon_faq.svg",
48+
};
49+
---
50+
51+
<section class="PopularGuides">
52+
<div class="PopularGuides__inner">
53+
<h2 class="PopularGuides__heading">Popular guides</h2>
54+
<div class="PopularGuides__grid">
55+
{
56+
guides.map((guide) => (
57+
<a class="PopularGuide" href={guide.href}>
58+
<img
59+
alt={`Icon: ${guide.title}`}
60+
class="PopularGuide__img"
61+
src={iconFiles[guide.id]}
62+
/>
63+
<div>
64+
<h3 class="PopularGuide__title">{guide.title}</h3>
65+
<p class="PopularGuide__summary">{guide.summary}</p>
66+
</div>
67+
</a>
68+
))
69+
}
70+
</div>
71+
</div>
72+
</section>
73+
74+
<style>
75+
.PopularGuides {
76+
margin-bottom: 4rem;
77+
margin-top: 4rem;
78+
}
79+
80+
.PopularGuides__inner {
81+
max-width: 960px;
82+
box-sizing: border-box;
83+
margin: 0 auto;
84+
padding: 0 16px;
85+
}
86+
87+
.PopularGuides__heading {
88+
font-size: 1.25rem;
89+
font-weight: 500;
90+
line-height: 1.3;
91+
color: var(--sl-color-white);
92+
margin-top: 1.75em;
93+
margin-bottom: 0.4em;
94+
}
95+
96+
.PopularGuides__grid {
97+
display: grid;
98+
margin-left: -16px;
99+
margin-right: -16px;
100+
margin-top: 0.5rem;
101+
}
102+
103+
@media (min-width: 768px) {
104+
.PopularGuides__grid {
105+
grid-template-columns: repeat(2, 1fr);
106+
}
107+
}
108+
109+
@media (min-width: 960px) {
110+
.PopularGuides__grid {
111+
grid-template-columns: repeat(3, 1fr);
112+
}
113+
}
114+
115+
.PopularGuide {
116+
border-radius: 6px;
117+
display: flex;
118+
align-items: flex-start;
119+
gap: 16px;
120+
padding: 16px;
121+
text-decoration: none;
122+
transition:
123+
background-color 150ms,
124+
box-shadow 150ms;
125+
}
126+
127+
.PopularGuide:hover {
128+
background-color: var(--hp-hover-bg);
129+
box-shadow: var(--hp-card-hover-shadow);
130+
text-decoration: none;
131+
}
132+
133+
.PopularGuide__img {
134+
height: 48px;
135+
width: 48px;
136+
flex-shrink: 0;
137+
}
138+
139+
.PopularGuide__title {
140+
font-size: 1rem;
141+
font-weight: 500;
142+
color: var(--sl-color-white);
143+
margin: 0 0 0.24rem 0;
144+
padding: 0;
145+
line-height: 1.3;
146+
}
147+
148+
.PopularGuide__summary {
149+
color: var(--sl-color-gray-3);
150+
font-size: 0.875rem;
151+
line-height: 1.5;
152+
margin: 0;
153+
padding: 0;
154+
}
155+
</style>

0 commit comments

Comments
 (0)