nsbp-cli 0.2.27 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/nsbp.js +94 -74
- package/package.json +1 -1
- package/templates/basic/README.md +43 -0
- package/templates/basic/docs/DEVELOPMENT_GUIDE.md +290 -0
- package/templates/basic/docs/ESLINT_AND_PRETTIER.md +184 -0
- package/templates/basic/docs/HUSKY_9_UPGRADE.md +76 -0
- package/templates/basic/docs/HUSKY_ESLINT_SETUP.md +293 -0
- package/templates/basic/docs/SETUP_GIT_HOOKS.md +106 -0
- package/templates/basic/gitignore +3 -0
- package/templates/basic/package.json +27 -3
- package/templates/basic/scripts/setup-husky.js +24 -0
- package/templates/basic/src/Routers.tsx +4 -5
- package/templates/basic/src/client/index.tsx +5 -1
- package/templates/basic/src/component/Header.tsx +10 -10
- package/templates/basic/src/component/Layout.tsx +9 -3
- package/templates/basic/src/component/Theme.tsx +5 -1
- package/templates/basic/src/containers/Home.tsx +141 -76
- package/templates/basic/src/containers/Photo.tsx +30 -18
- package/templates/basic/src/externals/window.d.ts +3 -1
- package/templates/basic/src/reducers/photo.ts +7 -2
- package/templates/basic/src/server/index.ts +35 -26
- package/templates/basic/src/server/photo.ts +14 -7
- package/templates/basic/src/server/utils.tsx +9 -7
- package/templates/basic/src/services/home.ts +1 -1
- package/templates/basic/src/services/photo.ts +28 -30
- package/templates/basic/src/store/constants.ts +1 -1
- package/templates/basic/src/store/index.ts +2 -1
- package/templates/basic/src/styled/component/header.ts +5 -1
- package/templates/basic/src/styled/home.ts +100 -24
- package/templates/basic/src/styled/photo.ts +2 -2
- package/templates/basic/src/utils/config.ts +1 -1
- package/templates/basic/src/utils/fetch.ts +4 -8
- package/templates/basic/src/utils/index.ts +1 -1
|
@@ -11,7 +11,7 @@ import { useCurrentFlag } from '@utils/clientConfig'
|
|
|
11
11
|
import _ from 'lodash'
|
|
12
12
|
import { loadData } from '@services/photo'
|
|
13
13
|
|
|
14
|
-
const springSettings = { type:
|
|
14
|
+
const springSettings = { type: 'spring' as const, stiffness: 170, damping: 26 }
|
|
15
15
|
const NEXT = 'show-next'
|
|
16
16
|
|
|
17
17
|
const Photo = ({ query, data, menu, getPhotoMenu }: any) => {
|
|
@@ -24,7 +24,9 @@ const Photo = ({ query, data, menu, getPhotoMenu }: any) => {
|
|
|
24
24
|
|
|
25
25
|
const [currWidth, currHeight] = currPhotoData
|
|
26
26
|
|
|
27
|
-
const widths = photos.map(
|
|
27
|
+
const widths = photos.map(
|
|
28
|
+
([origW, origH]: any) => (currHeight / origH) * origW
|
|
29
|
+
)
|
|
28
30
|
|
|
29
31
|
// 同步 currPhoto 和 currPhotoData
|
|
30
32
|
useEffect(() => {
|
|
@@ -35,18 +37,22 @@ const Photo = ({ query, data, menu, getPhotoMenu }: any) => {
|
|
|
35
37
|
|
|
36
38
|
const leftStartCoords = widths
|
|
37
39
|
.slice(0, currPhoto)
|
|
38
|
-
.reduce((sum:any, width:any) => sum - width, 0)
|
|
40
|
+
.reduce((sum: any, width: any) => sum - width, 0)
|
|
39
41
|
|
|
40
42
|
// Calculate position for each photo
|
|
41
|
-
const photoPositions = photos.reduce(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
const photoPositions = photos.reduce(
|
|
44
|
+
(acc: any, [_origW, _origH]: any, i: any, _arr: any) => {
|
|
45
|
+
const prevLeft =
|
|
46
|
+
i === 0 ? leftStartCoords : acc[i - 1].left + acc[i - 1].width
|
|
47
|
+
acc.push({
|
|
48
|
+
left: prevLeft,
|
|
49
|
+
height: currHeight,
|
|
50
|
+
width: widths[i] || 0
|
|
51
|
+
})
|
|
52
|
+
return acc
|
|
53
|
+
},
|
|
54
|
+
[]
|
|
55
|
+
)
|
|
50
56
|
|
|
51
57
|
// console.log('photoPositions', photoPositions)
|
|
52
58
|
|
|
@@ -93,13 +99,13 @@ const Photo = ({ query, data, menu, getPhotoMenu }: any) => {
|
|
|
93
99
|
<Layout query={query}>
|
|
94
100
|
<Container>
|
|
95
101
|
<Row>
|
|
96
|
-
|
|
97
|
-
_.map(menu, (item:any, index:number) => {
|
|
102
|
+
{_.map(menu, (item: any, index: number) => {
|
|
98
103
|
return (
|
|
99
|
-
<Link key={`menu${index}`} to={`/photo?dic=${item.name}`}>
|
|
104
|
+
<Link key={`menu${index}`} to={`/photo?dic=${item.name}`}>
|
|
105
|
+
{item.name}
|
|
106
|
+
</Link>
|
|
100
107
|
)
|
|
101
|
-
})
|
|
102
|
-
}
|
|
108
|
+
})}
|
|
103
109
|
</Row>
|
|
104
110
|
<Row>Scroll Me</Row>
|
|
105
111
|
<Row>
|
|
@@ -123,7 +129,13 @@ const Photo = ({ query, data, menu, getPhotoMenu }: any) => {
|
|
|
123
129
|
<motion.img
|
|
124
130
|
key={i}
|
|
125
131
|
className="demo4-photo"
|
|
126
|
-
src={
|
|
132
|
+
src={
|
|
133
|
+
photos[i][2]
|
|
134
|
+
? useCurrentFlag
|
|
135
|
+
? `/images/${photos[i][2]}`
|
|
136
|
+
: photos[i][2]
|
|
137
|
+
: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
|
138
|
+
}
|
|
127
139
|
initial={false}
|
|
128
140
|
animate={{
|
|
129
141
|
left: pos.left,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
interface ServerState {
|
|
2
2
|
photo?: {
|
|
3
3
|
data?: [number, number, string][]
|
|
4
|
-
menu?:
|
|
4
|
+
menu?:
|
|
5
|
+
| Record<string, any>
|
|
6
|
+
| Array<{ name: string; cover?: string; count?: number }>
|
|
5
7
|
}
|
|
6
8
|
query?: Record<string, any>
|
|
7
9
|
}
|
|
@@ -2,10 +2,15 @@ import { GET_PHOTO_MENU, GET_PHOTO_WIDTH_HEIGHT } from '@store/constants'
|
|
|
2
2
|
|
|
3
3
|
interface PhotoState {
|
|
4
4
|
data: [number, number, string][]
|
|
5
|
-
menu:
|
|
5
|
+
menu:
|
|
6
|
+
| Record<string, any>
|
|
7
|
+
| Array<{ name: string; cover?: string; count?: number }>
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
export const photoReducer = (
|
|
10
|
+
export const photoReducer = (
|
|
11
|
+
state: PhotoState = { data: [[0, 0, '']], menu: {} },
|
|
12
|
+
action: any
|
|
13
|
+
) => {
|
|
9
14
|
const { type, data, menu } = action
|
|
10
15
|
|
|
11
16
|
switch (type) {
|
|
@@ -8,23 +8,25 @@ import { useCurrentFlag, outPhotoDicPath } from '@utils/config'
|
|
|
8
8
|
const app = express()
|
|
9
9
|
|
|
10
10
|
// 1. Security headers (helmet)
|
|
11
|
-
app.use(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
app.use(
|
|
12
|
+
helmet({
|
|
13
|
+
contentSecurityPolicy: {
|
|
14
|
+
directives: {
|
|
15
|
+
defaultSrc: ["'self'"],
|
|
16
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
|
|
17
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
18
|
+
imgSrc: ["'self'", 'data:', 'https:'],
|
|
19
|
+
connectSrc: ["'self'", 'https:'],
|
|
20
|
+
fontSrc: ["'self'", 'data:'],
|
|
21
|
+
objectSrc: ["'none'"],
|
|
22
|
+
mediaSrc: ["'self'"],
|
|
23
|
+
frameSrc: ["'none'"]
|
|
24
|
+
}
|
|
23
25
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
crossOriginEmbedderPolicy: false, // Allow inline scripts for SSR
|
|
27
|
+
crossOriginOpenerPolicy: false // Allow window.open for development
|
|
28
|
+
})
|
|
29
|
+
)
|
|
28
30
|
|
|
29
31
|
// 2. Hide X-Powered-By header
|
|
30
32
|
app.disable('x-powered-by')
|
|
@@ -36,23 +38,30 @@ if (process.env.ENABLE_RATE_LIMIT === '1') {
|
|
|
36
38
|
max: 100, // Limit each IP to 100 requests per windowMs
|
|
37
39
|
message: 'Too many requests from this IP, please try again later.',
|
|
38
40
|
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
|
39
|
-
legacyHeaders: false
|
|
41
|
+
legacyHeaders: false // Disable the `X-RateLimit-*` headers
|
|
40
42
|
})
|
|
41
43
|
app.use('/api', limiter)
|
|
42
44
|
console.log('🛡️ Rate limiting enabled for /api routes')
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
// 4. Static file serving (disable dotfiles access)
|
|
46
|
-
app.use(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
app.use(
|
|
49
|
+
express.static('public', {
|
|
50
|
+
dotfiles: 'ignore',
|
|
51
|
+
setHeaders: (res, filePath) => {
|
|
52
|
+
// Cache static assets for 1 year
|
|
53
|
+
if (
|
|
54
|
+
filePath.match(
|
|
55
|
+
/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$/
|
|
56
|
+
)
|
|
57
|
+
) {
|
|
58
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
!useCurrentFlag &&
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
!useCurrentFlag &&
|
|
64
|
+
app.use(express.static(outPhotoDicPath, { dotfiles: 'ignore' }))
|
|
56
65
|
|
|
57
66
|
// 5. Body parsing with size limits
|
|
58
67
|
app.use(express.json({ limit: '10mb' }))
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import path from "path"
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
4
3
|
import probe from 'probe-image-size'
|
|
5
4
|
|
|
6
5
|
// 获取项目根目录(无论从哪个目录运行服务器)
|
|
@@ -29,7 +28,9 @@ const getPhotosDicPath = () => {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
// 获取目录下的子目录(分类),并为每个分类找封面图和图片数量
|
|
32
|
-
const getFileMenu = (
|
|
31
|
+
const getFileMenu = (
|
|
32
|
+
dir: string
|
|
33
|
+
): { name: string; cover?: string; count?: number }[] => {
|
|
33
34
|
const arr = fs.readdirSync(dir)
|
|
34
35
|
const result: { name: string; cover?: string; count?: number }[] = []
|
|
35
36
|
|
|
@@ -40,7 +41,9 @@ const getFileMenu = (dir: string): { name: string; cover?: string; count?: numbe
|
|
|
40
41
|
// 分类名
|
|
41
42
|
const name = item
|
|
42
43
|
// 计算图片数量
|
|
43
|
-
const files = fs
|
|
44
|
+
const files = fs
|
|
45
|
+
.readdirSync(fullPath)
|
|
46
|
+
.filter((f) => /\.(jpg|jpeg|png|webp)$/i.test(f))
|
|
44
47
|
const count = files.length
|
|
45
48
|
|
|
46
49
|
// 在该目录下找 cover.jpg
|
|
@@ -110,7 +113,9 @@ export const getPhotoWH = (req: any, res: any) => {
|
|
|
110
113
|
res.json({ data: whArr })
|
|
111
114
|
} catch (err: any) {
|
|
112
115
|
console.error('getPhotoWH error:', err)
|
|
113
|
-
res
|
|
116
|
+
res
|
|
117
|
+
.status(500)
|
|
118
|
+
.json({ error: 'Internal Server Error', details: err.message })
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
|
|
@@ -124,6 +129,8 @@ export const getPhotoMenu = (_req: any, res: any) => {
|
|
|
124
129
|
res.json({ data: fileMenu })
|
|
125
130
|
} catch (err: any) {
|
|
126
131
|
console.error('getPhotoMenu error:', err)
|
|
127
|
-
res
|
|
132
|
+
res
|
|
133
|
+
.status(500)
|
|
134
|
+
.json({ error: 'Internal Server Error', details: err.message })
|
|
128
135
|
}
|
|
129
136
|
}
|
|
@@ -15,7 +15,7 @@ import { ChunkExtractor } from '@loadable/server'
|
|
|
15
15
|
|
|
16
16
|
export const render = (req: any, res: any) => {
|
|
17
17
|
const store = getStore()
|
|
18
|
-
const { path:reqPath, query } = req
|
|
18
|
+
const { path: reqPath, query } = req
|
|
19
19
|
const matchRoutes: any = []
|
|
20
20
|
const promises = []
|
|
21
21
|
|
|
@@ -71,10 +71,7 @@ export const render = (req: any, res: any) => {
|
|
|
71
71
|
|
|
72
72
|
const helmet: any = Helmet.renderStatic()
|
|
73
73
|
|
|
74
|
-
const webStats = path.resolve(
|
|
75
|
-
__dirname,
|
|
76
|
-
'../public/loadable-stats.json',
|
|
77
|
-
)
|
|
74
|
+
const webStats = path.resolve(__dirname, '../public/loadable-stats.json')
|
|
78
75
|
|
|
79
76
|
try {
|
|
80
77
|
let webEntryPoints = ['client', 'vendor']
|
|
@@ -89,13 +86,18 @@ export const render = (req: any, res: any) => {
|
|
|
89
86
|
publicPath: '/'
|
|
90
87
|
})
|
|
91
88
|
|
|
92
|
-
const jsx = webExtractor.collectChunks(
|
|
89
|
+
const jsx = webExtractor.collectChunks(
|
|
90
|
+
sheet.collectStyles(
|
|
93
91
|
<Theme>
|
|
94
92
|
<Provider store={store}>
|
|
95
93
|
<StaticRouter location={reqPath}>
|
|
96
94
|
<Routes>
|
|
97
95
|
{routers.map((router) => (
|
|
98
|
-
<Route
|
|
96
|
+
<Route
|
|
97
|
+
key={router.key}
|
|
98
|
+
path={router.path}
|
|
99
|
+
element={router.element}
|
|
100
|
+
/>
|
|
99
101
|
))}
|
|
100
102
|
</Routes>
|
|
101
103
|
</StaticRouter>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { doGet } from '@utils/fetch'
|
|
2
2
|
import { GET_PHOTO_MENU, GET_PHOTO_WIDTH_HEIGHT } from '@store/constants'
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
const getPhotoWH = (dispatch: any, callback: any, dic = '') => {
|
|
6
5
|
let action = 'getPhotoWH'
|
|
7
6
|
if (dic) {
|
|
@@ -9,47 +8,46 @@ const getPhotoWH = (dispatch: any, callback: any, dic = '') => {
|
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
doGet(action)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
})
|
|
19
|
-
callback && callback()
|
|
20
|
-
})
|
|
21
|
-
.catch((_e:any) => {
|
|
22
|
-
callback && callback()
|
|
11
|
+
.then((res: any) => {
|
|
12
|
+
// console.log('getPhotoWH_res', res)
|
|
13
|
+
// axios 响应格式是 { data: { data: [...] }, status: ... },需要取 res.data.data
|
|
14
|
+
dispatch({
|
|
15
|
+
type: GET_PHOTO_WIDTH_HEIGHT,
|
|
16
|
+
data: res?.data?.data || []
|
|
23
17
|
})
|
|
18
|
+
callback && callback()
|
|
19
|
+
})
|
|
20
|
+
.catch((_e: any) => {
|
|
21
|
+
callback && callback()
|
|
22
|
+
})
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
const getPhotoMenu = (dispatch:any, callback:any) => {
|
|
25
|
+
const getPhotoMenu = (dispatch: any, callback: any) => {
|
|
27
26
|
doGet('getPhotoMenu')
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
callback && callback(data)
|
|
38
|
-
})
|
|
39
|
-
.catch((_e:any) => {
|
|
40
|
-
callback && callback()
|
|
27
|
+
.then((res: any) => {
|
|
28
|
+
// console.log('getPhotoMenu_res', res)
|
|
29
|
+
// axios 响应格式是 { data: { data: [...] }, status: ... },需要取 res.data.data
|
|
30
|
+
const { data } = res?.data || {}
|
|
31
|
+
dispatch({
|
|
32
|
+
type: GET_PHOTO_MENU,
|
|
33
|
+
menu: data
|
|
41
34
|
})
|
|
35
|
+
|
|
36
|
+
callback && callback(data)
|
|
37
|
+
})
|
|
38
|
+
.catch((_e: any) => {
|
|
39
|
+
callback && callback()
|
|
40
|
+
})
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
const getData = (callback:any, dic:any) => {
|
|
43
|
+
const getData = (callback: any, dic: any) => {
|
|
45
44
|
return (dispatch: any) => {
|
|
46
|
-
|
|
47
45
|
if (dic) {
|
|
48
46
|
getPhotoMenu(dispatch, () => {
|
|
49
47
|
getPhotoWH(dispatch, callback, dic)
|
|
50
48
|
})
|
|
51
49
|
} else {
|
|
52
|
-
getPhotoMenu(dispatch, (data:any) => {
|
|
50
|
+
getPhotoMenu(dispatch, (data: any) => {
|
|
53
51
|
if (data && data.length > 0) {
|
|
54
52
|
// data[0] 是对象 {name, cover},需要取 name
|
|
55
53
|
getPhotoWH(dispatch, callback, data[0].name)
|
|
@@ -59,6 +57,6 @@ const getData = (callback:any, dic:any) => {
|
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
export const loadData = (resolve: any = null, dic='') => {
|
|
60
|
+
export const loadData = (resolve: any = null, dic = '') => {
|
|
63
61
|
return getData(resolve, dic)
|
|
64
62
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export const GITHUB_ZEITNEXT_GET = 'GITHUB_ZEITNEXT_GET'
|
|
2
2
|
export const REQUEST_QUERY = 'REQUEST_QUERY'
|
|
3
3
|
export const GET_PHOTO_MENU = 'GET_PHOTO_MENU'
|
|
4
|
-
export const GET_PHOTO_WIDTH_HEIGHT= 'GET_PHOTO_WIDTH_HEIGHT'
|
|
4
|
+
export const GET_PHOTO_WIDTH_HEIGHT = 'GET_PHOTO_WIDTH_HEIGHT'
|
|
@@ -5,7 +5,8 @@ const combineReducer = combineReducers({ ...reducers })
|
|
|
5
5
|
|
|
6
6
|
const getStore = (stateParam = {}) => {
|
|
7
7
|
return configureStore({
|
|
8
|
-
reducer: (state: any, action: any) =>
|
|
8
|
+
reducer: (state: any, action: any) =>
|
|
9
|
+
combineReducer(state || stateParam, action),
|
|
9
10
|
preloadedState: stateParam || {},
|
|
10
11
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware()
|
|
11
12
|
})
|
|
@@ -6,7 +6,11 @@ export const Container = styled.header`
|
|
|
6
6
|
align-items: center;
|
|
7
7
|
justify-content: space-between;
|
|
8
8
|
padding: 0.75rem 2rem;
|
|
9
|
-
background: linear-gradient(
|
|
9
|
+
background: linear-gradient(
|
|
10
|
+
135deg,
|
|
11
|
+
rgba(255, 255, 255, 0.95),
|
|
12
|
+
rgba(255, 255, 255, 0.85)
|
|
13
|
+
);
|
|
10
14
|
backdrop-filter: blur(20px);
|
|
11
15
|
-webkit-backdrop-filter: blur(20px);
|
|
12
16
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
@@ -12,12 +12,16 @@ export const GlobalStyle = styled.div`
|
|
|
12
12
|
padding: 0;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
html,
|
|
15
|
+
html,
|
|
16
|
+
body,
|
|
17
|
+
#root {
|
|
16
18
|
height: 100%;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
body {
|
|
20
|
-
font-family:
|
|
22
|
+
font-family:
|
|
23
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
|
24
|
+
Arial, sans-serif;
|
|
21
25
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
22
26
|
color: #2d3748;
|
|
23
27
|
-webkit-font-smoothing: antialiased;
|
|
@@ -57,7 +61,9 @@ export const GlobalStyle = styled.div`
|
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
@keyframes spin {
|
|
60
|
-
to {
|
|
64
|
+
to {
|
|
65
|
+
transform: rotate(360deg);
|
|
66
|
+
}
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
/* 淡入动画 */
|
|
@@ -129,9 +135,21 @@ export const HeroSection = styled.section`
|
|
|
129
135
|
right: 0;
|
|
130
136
|
bottom: 0;
|
|
131
137
|
background-image:
|
|
132
|
-
radial-gradient(
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
radial-gradient(
|
|
139
|
+
circle at 20% 80%,
|
|
140
|
+
rgba(255, 255, 255, 0.1) 0%,
|
|
141
|
+
transparent 50%
|
|
142
|
+
),
|
|
143
|
+
radial-gradient(
|
|
144
|
+
circle at 80% 20%,
|
|
145
|
+
rgba(255, 255, 255, 0.1) 0%,
|
|
146
|
+
transparent 50%
|
|
147
|
+
),
|
|
148
|
+
radial-gradient(
|
|
149
|
+
circle at 40% 40%,
|
|
150
|
+
rgba(255, 255, 255, 0.05) 0%,
|
|
151
|
+
transparent 50%
|
|
152
|
+
);
|
|
135
153
|
z-index: -1;
|
|
136
154
|
}
|
|
137
155
|
|
|
@@ -141,7 +159,11 @@ export const HeroSection = styled.section`
|
|
|
141
159
|
width: 300px;
|
|
142
160
|
height: 300px;
|
|
143
161
|
border-radius: 50%;
|
|
144
|
-
background: radial-gradient(
|
|
162
|
+
background: radial-gradient(
|
|
163
|
+
circle,
|
|
164
|
+
rgba(255, 255, 255, 0.1) 0%,
|
|
165
|
+
transparent 70%
|
|
166
|
+
);
|
|
145
167
|
filter: blur(40px);
|
|
146
168
|
animation: float 6s ease-in-out infinite;
|
|
147
169
|
}
|
|
@@ -159,8 +181,13 @@ export const HeroSection = styled.section`
|
|
|
159
181
|
}
|
|
160
182
|
|
|
161
183
|
@keyframes float {
|
|
162
|
-
0%,
|
|
163
|
-
|
|
184
|
+
0%,
|
|
185
|
+
100% {
|
|
186
|
+
transform: translateY(0px) scale(1);
|
|
187
|
+
}
|
|
188
|
+
50% {
|
|
189
|
+
transform: translateY(-20px) scale(1.1);
|
|
190
|
+
}
|
|
164
191
|
}
|
|
165
192
|
|
|
166
193
|
@media (max-width: 768px) {
|
|
@@ -219,7 +246,11 @@ export const HeroSubtitle = styled.p`
|
|
|
219
246
|
|
|
220
247
|
export const HeroBadge = styled.span`
|
|
221
248
|
display: inline-block;
|
|
222
|
-
background: linear-gradient(
|
|
249
|
+
background: linear-gradient(
|
|
250
|
+
135deg,
|
|
251
|
+
rgba(255, 255, 255, 0.2),
|
|
252
|
+
rgba(255, 255, 255, 0.1)
|
|
253
|
+
);
|
|
223
254
|
backdrop-filter: blur(15px);
|
|
224
255
|
padding: 0.75rem 1.5rem;
|
|
225
256
|
border-radius: 999px;
|
|
@@ -239,13 +270,22 @@ export const HeroBadge = styled.span`
|
|
|
239
270
|
left: -100%;
|
|
240
271
|
width: 100%;
|
|
241
272
|
height: 100%;
|
|
242
|
-
background: linear-gradient(
|
|
273
|
+
background: linear-gradient(
|
|
274
|
+
90deg,
|
|
275
|
+
transparent,
|
|
276
|
+
rgba(255, 255, 255, 0.2),
|
|
277
|
+
transparent
|
|
278
|
+
);
|
|
243
279
|
animation: shine 3s infinite;
|
|
244
280
|
}
|
|
245
281
|
|
|
246
282
|
@keyframes shine {
|
|
247
|
-
0% {
|
|
248
|
-
|
|
283
|
+
0% {
|
|
284
|
+
left: -100%;
|
|
285
|
+
}
|
|
286
|
+
100% {
|
|
287
|
+
left: 100%;
|
|
288
|
+
}
|
|
249
289
|
}
|
|
250
290
|
`
|
|
251
291
|
|
|
@@ -271,7 +311,11 @@ export const HeroStats = styled.div`
|
|
|
271
311
|
`
|
|
272
312
|
|
|
273
313
|
export const StatCard = styled.div`
|
|
274
|
-
background: linear-gradient(
|
|
314
|
+
background: linear-gradient(
|
|
315
|
+
135deg,
|
|
316
|
+
rgba(255, 255, 255, 0.2),
|
|
317
|
+
rgba(255, 255, 255, 0.1)
|
|
318
|
+
);
|
|
275
319
|
backdrop-filter: blur(20px);
|
|
276
320
|
padding: 1.75rem;
|
|
277
321
|
border-radius: 16px;
|
|
@@ -292,12 +336,21 @@ export const StatCard = styled.div`
|
|
|
292
336
|
left: 0;
|
|
293
337
|
right: 0;
|
|
294
338
|
height: 1px;
|
|
295
|
-
background: linear-gradient(
|
|
339
|
+
background: linear-gradient(
|
|
340
|
+
90deg,
|
|
341
|
+
transparent,
|
|
342
|
+
rgba(255, 255, 255, 0.4),
|
|
343
|
+
transparent
|
|
344
|
+
);
|
|
296
345
|
}
|
|
297
346
|
|
|
298
347
|
&:hover {
|
|
299
348
|
transform: translateY(-8px) scale(1.02);
|
|
300
|
-
background: linear-gradient(
|
|
349
|
+
background: linear-gradient(
|
|
350
|
+
135deg,
|
|
351
|
+
rgba(255, 255, 255, 0.25),
|
|
352
|
+
rgba(255, 255, 255, 0.15)
|
|
353
|
+
);
|
|
301
354
|
box-shadow:
|
|
302
355
|
0 12px 40px rgba(0, 0, 0, 0.15),
|
|
303
356
|
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
|
@@ -337,7 +390,11 @@ export const StatLabel = styled.div`
|
|
|
337
390
|
|
|
338
391
|
export const TechSection = styled.section`
|
|
339
392
|
padding: 6rem 0;
|
|
340
|
-
background: linear-gradient(
|
|
393
|
+
background: linear-gradient(
|
|
394
|
+
135deg,
|
|
395
|
+
rgba(255, 255, 255, 0.1) 0%,
|
|
396
|
+
rgba(255, 255, 255, 0.05) 100%
|
|
397
|
+
);
|
|
341
398
|
display: flex;
|
|
342
399
|
flex-direction: column;
|
|
343
400
|
align-items: center;
|
|
@@ -353,8 +410,16 @@ export const TechSection = styled.section`
|
|
|
353
410
|
right: 0;
|
|
354
411
|
bottom: 0;
|
|
355
412
|
background-image:
|
|
356
|
-
radial-gradient(
|
|
357
|
-
|
|
413
|
+
radial-gradient(
|
|
414
|
+
circle at 10% 20%,
|
|
415
|
+
rgba(102, 126, 234, 0.05) 0%,
|
|
416
|
+
transparent 50%
|
|
417
|
+
),
|
|
418
|
+
radial-gradient(
|
|
419
|
+
circle at 90% 80%,
|
|
420
|
+
rgba(118, 75, 162, 0.05) 0%,
|
|
421
|
+
transparent 50%
|
|
422
|
+
);
|
|
358
423
|
pointer-events: none;
|
|
359
424
|
}
|
|
360
425
|
`
|
|
@@ -461,7 +526,8 @@ export const CodeExample = styled.pre`
|
|
|
461
526
|
padding: 1rem;
|
|
462
527
|
border-radius: 8px;
|
|
463
528
|
overflow-x: auto;
|
|
464
|
-
font-family:
|
|
529
|
+
font-family:
|
|
530
|
+
'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
465
531
|
font-size: 0.85rem;
|
|
466
532
|
line-height: 1.6;
|
|
467
533
|
margin-bottom: 1rem;
|
|
@@ -757,7 +823,8 @@ export const QuickStartCode = styled.pre`
|
|
|
757
823
|
padding: 1rem;
|
|
758
824
|
border-radius: 8px;
|
|
759
825
|
overflow-x: auto;
|
|
760
|
-
font-family:
|
|
826
|
+
font-family:
|
|
827
|
+
'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
761
828
|
font-size: 0.85rem;
|
|
762
829
|
line-height: 1.6;
|
|
763
830
|
max-width: 100%;
|
|
@@ -806,13 +873,22 @@ export const DemoButtonLink = styled.a`
|
|
|
806
873
|
left: -100%;
|
|
807
874
|
width: 100%;
|
|
808
875
|
height: 100%;
|
|
809
|
-
background: linear-gradient(
|
|
876
|
+
background: linear-gradient(
|
|
877
|
+
90deg,
|
|
878
|
+
transparent,
|
|
879
|
+
rgba(255, 255, 255, 0.1),
|
|
880
|
+
transparent
|
|
881
|
+
);
|
|
810
882
|
animation: shine 2s infinite;
|
|
811
883
|
}
|
|
812
884
|
|
|
813
885
|
@keyframes shine {
|
|
814
|
-
0% {
|
|
815
|
-
|
|
886
|
+
0% {
|
|
887
|
+
left: -100%;
|
|
888
|
+
}
|
|
889
|
+
100% {
|
|
890
|
+
left: 100%;
|
|
891
|
+
}
|
|
816
892
|
}
|
|
817
893
|
|
|
818
894
|
&:hover {
|
|
@@ -33,8 +33,8 @@ export const Row = styled.div`
|
|
|
33
33
|
justify-content: flex-start;
|
|
34
34
|
align-items: center;
|
|
35
35
|
font-size: 20px;
|
|
36
|
-
word-wrap: break-word;
|
|
37
|
-
word-break: break-all;
|
|
36
|
+
word-wrap: break-word;
|
|
37
|
+
word-break: break-all;
|
|
38
38
|
overflow: hidden;
|
|
39
39
|
padding-bottom: 10px;
|
|
40
40
|
|