Compare commits

...
11 Commits
Author SHA1 Message Date
jiawei 18e98a954d chore: exclude bundled font CSS from language stats 2026-08-30 17:30:00 +08:00
jiawei efe03af847 feat(chapter3): add useRef demo
Demonstrate retaining the previous counter value with a ref while updating state through a shadcn button.
2026-08-30 17:25:30 +08:00
jiawei 4823d13938 feat(chapter3): add effect hook demo
Demonstrate resize subscriptions with cleanup and show an async effect that updates the Ant Design button state.
2026-08-30 17:09:32 +08:00
jiawei 7e0e3527e9 fix(chapter3): use shadcn button variants in state demo
Replace the Ant Design dashed button type with the shadcn outline variant and include the state demo on the demo page.
2026-08-30 16:44:28 +08:00
jiawei f6d90efb0a feat(chapter3): replicate styled Next.js app setup
Add the App Router page group, nested layouts, Tailwind and shadcn styling, shared header components, local fonts and backgrounds, Ant Design demo support, and compatible lint scripts and dependencies.
2026-08-30 16:29:58 +08:00
jiawei 65805be81d [chapter3]: add config 2026-08-30 15:17:46 +08:00
jiawei 0aa57254d9 init chapter3 2026-08-30 15:09:19 +08:00
jiawei 1916e3e31a feat(chapter2): add Ant Design demo page
Add the demo route and Ant Design layout, include the missing page styles, and align global CSS and dependencies with the demo setup.
2026-08-30 10:53:19 +08:00
jiawei 7b03222922 fix(chapter2): align shadcn button with Tailwind prefix
Configure shadcn aliases and regenerate the Button component with the tw: utility prefix so its styles are emitted by Tailwind.
2026-08-30 10:26:59 +08:00
jiawei a1401c68f5 feat(chapter2): set up styled app shell
Add the App Router page and nested layout, configure Tailwind and shadcn styling, include local fonts and background assets, and install the supporting UI and lint dependencies.
2026-08-30 10:20:06 +08:00
jiawei 5ce6f24569 chore(chapter2): configure compatible lint tooling
Use the Next.js-compatible ESLint 9 toolchain, add CSS linting scripts and dependencies, and export the Stylelint config through a named constant.
2026-08-30 00:06:40 +08:00
100 changed files with 41281 additions and 124 deletions
+1
View File
@@ -0,0 +1 @@
**/src/app/styles/fonts/*.css linguist-vendored
+26
View File
@@ -0,0 +1,26 @@
public
node_modules
src/database/migrations
pnpm-lock.yaml
package-lock.json
docker
Dockerfile*
LICENSE
yarn-error.log
.next
.history
.dockerignore
.DS_Store
.eslintignore
.editorconfig
.gitignore
.prettierignore
.eslintcache
*.lock
*.png
*.jpg
*.jpeg
*.eot
*.ttf
*.woff
*.sql
+11
View File
@@ -0,0 +1,11 @@
const config = {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
proseWrap: 'never',
endOfLine: 'auto',
semi: true,
tabWidth: 4,
htmlWhitespaceSensitivity: 'strict',
};
export default config;
+25
View File
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/styles/tailwind.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": "tw"
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/app/_components/shadcn/ui",
"utils": "@/app/_components/shadcn/libs/utils",
"ui": "@/app/_components/shadcn/ui",
"lib": "@/app/_components/shadcn/libs",
"hooks": "@/app/_components/shadcn/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
+39 -6
View File
@@ -3,26 +3,59 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "rimraf .next && next dev --turbopack",
"build": "next build", "build": "rimraf .next && next build",
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "pnpm lint:es && pnpm lint:style",
"lint:es": "eslint . --fix",
"lint:style": "stylelint \"**/*.css\" --fix --cache --cache-location node_modules/.cache/stylelint"
}, },
"dependencies": { "dependencies": {
"@ant-design/cssinjs": "^2.1.2",
"@ant-design/nextjs-registry": "^1.3.0",
"antd": "^6.6.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.37.0",
"next": "16.3.3", "next": "16.3.3",
"radix-ui": "^1.6.7",
"react": "19.2.8", "react": "19.2.8",
"react-dom": "19.2.8" "react-dom": "19.2.8",
"shadcn": "^4.19.0",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint-react/eslint-plugin": "^5.18.6",
"@eslint/eslintrc": "^3.3.6",
"@next/eslint-plugin-next": "^16.3.3",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-compiler": "1.0.0",
"eslint": "^9", "eslint": "^9.39.5",
"eslint-config-next": "16.3.3", "eslint-config-next": "16.3.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-perfectionist": "^5.10.1",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.5",
"eslint-plugin-unused-imports": "^4.4.1",
"prettier": "^3.9.6",
"rimraf": "^6.1.3",
"stylelint": "^17.14.1",
"stylelint-config-css-modules": "^4.6.0",
"stylelint-config-recess-order": "^7.8.0",
"stylelint-config-standard": "^40.0.0",
"stylelint-prettier": "^5.0.3",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
}, },
"packageManager": "pnpm@10.34.5" "packageManager": "pnpm@10.34.5",
"imports": {
"#tailwind.css": "./src/app/styles/tailwind.css"
}
} }
+5386 -1
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
/* src/app/(pages)/layout.module.css */
@reference "#tailwind.css";
.layout {
@apply tw:min-h-full tw:w-full tw:flex tw:flex-col tw:bg-background tw:bg-cover tw:bg-fixed tw:bg-center tw:bg-no-repeat tw:bg-[url('../styles/images/bg-light.png')] tw:p-0 tw:m-0;
html.dark &,
html[data-theme='dark'] & {
background-image: url('../styles/images/bg-dark.png');
}
}
+19
View File
@@ -0,0 +1,19 @@
// src/app/(pages)/layout.tsx
import type { Metadata } from 'next';
import type { FC, PropsWithChildren } from 'react';
import { Header } from '../_components/header';
import $styles from './layout.module.css';
export const metadata: Metadata = {
title: 'pincman的博客',
description:
'pincman的个人博客,提供一些ts、react、node.js、php、golang相关的技术文档以及分享一些生活琐事',
};
const AppLayout: FC<PropsWithChildren> = ({ children }) => (
<div className={$styles.layout}>
<Header />
{children}
</div>
);
export default AppLayout;
+9
View File
@@ -0,0 +1,9 @@
/* src/app/(pages)/page.module.css */
@reference "#tailwind.css";
.block {
@apply tw:page-container tw:flex tw:flex-col tw:flex-none! tw:w-full tw:space-y-4 tw:p-5 tw:items-center tw:justify-center
tw:rounded-sm tw:backdrop-blur-sm tw:shadow-nysm tw:shadow-neutral-800 tw:duration-300
tw:text-lg tw:text-center tw:text-white tw:bg-black/30
tw:hover:shadow-nylg tw:font-lxgw;
}
+22
View File
@@ -0,0 +1,22 @@
// src/app/(pages)/page.tsx
import { FC } from 'react';
import { ArrowBigRight } from 'lucide-react';
import Link from 'next/link';
import $styles from './page.module.css';
import { Button } from '../_components/shadcn/ui/button';
const App: FC = () => (
<main className={$styles.container}>
<div className={$styles.block}>
欢迎来到3R教室,这是Nextjs课程的开始
<Button asChild>
<Link href="https://3rcd.com" target="_blank">
<ArrowBigRight /> 查看官网
</Link>
</Button>
</div>
</main>
);
export default App;
@@ -0,0 +1 @@
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 801 802"><defs><style>.cls-1{fill:#fff;}.cls-1,.cls-2{stroke:#000;stroke-miterlimit:10;}.cls-2{fill:#224740;}</style></defs><rect class="cls-1" x="0.5" y="0.5" width="800" height="801"/><path class="cls-2" d="M255.61,517.24,182.5,569.53c-16.57,13.4-68.15,8.09-74.37-.12-8.7-11.49-23.82-26.39-14-35.29s37.18-8.36,37.18-8.36l30.06-.58s19.51-4,27.92-4.89c11.23-1.23,13-3.43,10.56-5.93-.67.24-.48-.48-.48-.48-5.6-4.86-25.35-10.58-22.23-12.31l62-52.44c-1.87,1.12-163.29,65.48-163.29,65.48L62.67,526.27s-2.17-1.48-20.75,27.34c-2.5,3.88,37.87,28.51,37.87,28.51C209.06,671.81,328.12,475,353.3,420.12l-.24-2.29,5.15-5.58c18.53,15.86,32.57,37.32,45.87,45.14,20.4,12-.15-8.39,38.94,24.3s39.47,148.93,36.6,175.53-29.74,29.64-29.74,29.64,9.93-28.87,7.36-78.72c-2.57-49.16-81.43-51.44-83.41-51.51C471,602.21,394,738.52,394,738.52s35,4.34,93.39-56.92-25.93-226.14-20.1-253.09c2.69-12.37,95.24-19.54,95.24-19.54s117.86,1.61,120.76,5.78c5.4,7.7,9.11,22.5,4.08,46.84-5,23.83-78.09,90.15-81.16,93,2.29-.59,39.63-11.61,96.61-69.39,58.81-59.69-1.66-127.51-58.34-103.49S528,381.27,528,381.27c-7.7-8.51-11.89-18.92-13.46-30.47h0l-27.48-5.14,27.46-18.19h0c7.21-63.27,66.28-143.75,74.05-152.62,10.38-11.8,15.63-11.29,7.31-23-6.65-9.35-3-12.33-.71-13.37l.64-.2c-9.26,2.67-52.44,13.88-116.82,11.24-72-2.92-111-84.78-112-87.12l-2.21-2.86s-38.57,66-12.6,108.84c20.18,33.3,153.43,44.76,153.43,44.76s.82,14.78-9.7,21.19-32.69-2.77-72.55-15.43l-8.88-1.7c-11.23-1.51-36.36-13.58-64-40.62l-26.19,218,5.65,18-67.34,97.66c-2.6,2.47-2.6,2.47-6.91,7m68.6-122.67M407,285.14s13.38-5.44,25.85.58,19.25,21.2,19.25,21.2-13.37,5.47-25.84-.55S407,285.14,407,285.14Z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,11 @@
// src/app/_components/header/index.tsx
import type { FC } from 'react';
import { HeaderLogo } from './logo';
import $styles from './styles.module.css';
export const Header: FC = () => (
<header className={$styles.header}>
<HeaderLogo />
</header>
);
@@ -0,0 +1,17 @@
/* src/app/_components/header/logo.module.css */
@reference "#tailwind.css";
.link {
@apply tw:w-20 tw:h-20 tw:block tw:rounded-full tw:shadow-nymd tw:p-1 tw:bg-white;
transition-duration: 300ms;
animation: breathe-light 4s ease-in-out infinite;
&:hover {
@apply tw:shadow-nylg tw:shadow-amber-400;
transform: scale(1.2) rotate(360deg);
}
& > img {
@apply tw:rounded-full;
}
}
@@ -0,0 +1,20 @@
// src/app/_components/header/logo.tsx
import Image from 'next/image';
import Link from 'next/link';
import Avatar from './avatar.svg';
import $styles from './logo.module.css';
export const HeaderLogo = () => (
<Link href="/" className={$styles.link}>
<Image
src={Avatar}
alt="avatar logo"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
/>
</Link>
);
@@ -0,0 +1,6 @@
/* src/app/_components/header/styles.module.css */
@reference "#tailwind.css";
.header {
@apply tw:flex tw:justify-center tw:items-center tw:pt-6 tw:max-h-24 tw:flex-auto;
}
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
@@ -0,0 +1,67 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/app/_components/shadcn/libs/utils"
const buttonVariants = cva(
"tw:group/button tw:inline-flex tw:shrink-0 tw:items-center tw:justify-center tw:rounded-lg tw:border tw:border-transparent tw:bg-clip-padding tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:outline-none tw:select-none tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:active:not-aria-[haspopup]:translate-y-px tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",
{
variants: {
variant: {
default: "tw:bg-primary tw:text-primary-foreground tw:hover:bg-primary/80",
outline:
"tw:border-border tw:bg-background tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:border-input tw:dark:bg-input/30 tw:dark:hover:bg-input/50",
secondary:
"tw:bg-secondary tw:text-secondary-foreground tw:hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] tw:aria-expanded:bg-secondary tw:aria-expanded:text-secondary-foreground",
ghost:
"tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:hover:bg-muted/50",
destructive:
"tw:bg-destructive/10 tw:text-destructive tw:hover:bg-destructive/20 tw:focus-visible:border-destructive/40 tw:focus-visible:ring-destructive/20 tw:dark:bg-destructive/20 tw:dark:hover:bg-destructive/30 tw:dark:focus-visible:ring-destructive/40",
link: "tw:text-primary tw:underline-offset-4 tw:hover:underline",
},
size: {
default:
"tw:h-8 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pr-2 tw:has-data-[icon=inline-start]:pl-2",
xs: "tw:h-6 tw:gap-1 tw:rounded-[min(var(--radius-md),10px)] tw:px-2 tw:text-xs tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pr-1.5 tw:has-data-[icon=inline-start]:pl-1.5 tw:[&_svg:not([class*=size-])]:size-3",
sm: "tw:h-7 tw:gap-1 tw:rounded-[min(var(--radius-md),12px)] tw:px-2.5 tw:text-[0.8rem] tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pr-1.5 tw:has-data-[icon=inline-start]:pl-1.5 tw:[&_svg:not([class*=size-])]:size-3.5",
lg: "tw:h-9 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pr-2 tw:has-data-[icon=inline-start]:pl-2",
icon: "tw:size-8",
"icon-xs":
"tw:size-6 tw:rounded-[min(var(--radius-md),10px)] tw:in-data-[slot=button-group]:rounded-lg tw:[&_svg:not([class*=size-])]:size-3",
"icon-sm":
"tw:size-7 tw:rounded-[min(var(--radius-md),12px)] tw:in-data-[slot=button-group]:rounded-lg",
"icon-lg": "tw:size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
@@ -0,0 +1,103 @@
import * as React from "react"
import { cn } from "@/app/_components/shadcn/libs/utils"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn(
"font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
className
)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-(--card-spacing)", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
className
)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
+11
View File
@@ -0,0 +1,11 @@
/* src/app/(pages)/layout.module.css */
@reference "#tailwind.css";
.layout {
@apply tw:min-h-full tw:w-full tw:flex tw:flex-col tw:bg-background tw:bg-cover tw:bg-fixed tw:bg-center tw:bg-no-repeat tw:bg-[url('../styles/images/bg-light.png')] tw:p-0 tw:m-0;
html.dark &,
html[data-theme='dark'] & {
background-image: url('../styles/images/bg-dark.png');
}
}
+34
View File
@@ -0,0 +1,34 @@
// src/app/demo/layout.tsx
'use client';
import { FC, PropsWithChildren } from 'react';
// src/app/demo/layout.tsx
import { px2remTransformer, StyleProvider } from '@ant-design/cssinjs';
import { AntdRegistry } from '@ant-design/nextjs-registry';
import { App as AntdApp, ConfigProvider, theme } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import $styles from './layout.module.css';
const px2rem = px2remTransformer();
const DemoLayout: FC<PropsWithChildren> = ({ children }) => (
<AntdRegistry>
<ConfigProvider
locale={zhCN}
theme={{
algorithm: theme.defaultAlgorithm,
// 启用css变量
cssVar: {},
hashed: false,
token: {},
}}
>
<AntdApp>
<StyleProvider transformers={[px2rem]}>
<div className={$styles.layout}>{children}</div>
</StyleProvider>
</AntdApp>
</ConfigProvider>
</AntdRegistry>
);
export default DemoLayout;
+9
View File
@@ -0,0 +1,9 @@
@reference "#tailwind.css";
.demo {
@apply tw:flex tw:flex-auto tw:flex-wrap tw:items-center tw:justify-center;
& > .container {
@apply tw:bg-neutral-100/40 tw:shadow-black/20 tw:backdrop-blur-sm tw:shadow-md tw:rounded-md tw:p-5 tw:m-5 tw:min-w-[20rem];
}
}
+23
View File
@@ -0,0 +1,23 @@
// src/app/demo/page.tsx
// ...
import { FC } from 'react';
import { Button } from 'antd';
import $styles from './page.module.css';
const DemoPage: FC = () => (
<div className={$styles.demo}>
<div className={$styles.container}>
<h2 className="tw:text-center">First React App</h2>
<div className="tw:flex tw:flex-col tw:items-center">
<div className="tw:my-5 tw:flex-auto">
<Button variant="solid" href="https://3rcd.com" target="_blank">
3R教室
</Button>
</div>
</div>
</div>
</div>
);
export default DemoPage;
-26
View File
@@ -1,26 +0,0 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
+11 -21
View File
@@ -1,29 +1,19 @@
import type { Metadata } from "next"; // src/app/layout.tsx
import { Geist, Geist_Mono } from "next/font/google"; import type { Metadata } from 'next';
import "./globals.css";
const geistSans = Geist({ import './styles/index.css';
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({ import type { FC, PropsWithChildren } from 'react';
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: 'nextapp',
description: "Generated by create next app", description: '3r教室Next.js全栈开发课程',
}; };
export default function RootLayout({ children }: LayoutProps<"/">) { const RootLayout: FC<PropsWithChildren> = ({ children }) => (
return ( <html lang="en">
<html <body>{children}</body>
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
</html> </html>
); );
}
export default RootLayout;
-69
View File
@@ -1,69 +0,0 @@
import Image from "next/image";
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert h-5 w-[100px]"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the{" "}
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
page.tsx
</code>{" "}
file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert h-[14px] w-4"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={14}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</div>
);
}
+8
View File
@@ -0,0 +1,8 @@
/* src/app/styles/app.css */
.lucide {
@apply tw:w-4 tw:h-4 tw:stroke-[1px];
}
.ant-app {
@apply tw:h-[100vh] tw:w-full tw:p-0 tw:m-0;
}
+55
View File
@@ -0,0 +1,55 @@
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 300;
src:
url('/fonts/firacode/woff2/FiraCode-Light.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Light.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 400;
src:
url('/fonts/firacode/woff2/FiraCode-Regular.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Regular.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 500;
src:
url('/fonts/firacode/woff2/FiraCode-Medium.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Medium.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 600;
src:
url('/fonts/firacode/woff2/FiraCode-SemiBold.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-SemiBold.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 700;
src:
url('/fonts/firacode/woff2/FiraCode-Bold.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Bold.woff') format('woff');
}
@font-face {
font-family: 'Fira Code VF';
font-style: normal;
/* font-weight requires a range: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide#Using_a_variable_font_font-face_changes */
font-weight: 300 700;
src:
url('/fonts/firacode/woff2/FiraCode-VF.woff2') format('woff2-variations'),
url('/fonts/firacode/woff/FiraCode-VF.woff') format('woff-variations');
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

+5
View File
@@ -0,0 +1,5 @@
@import './fonts/fira-code.css';
@import './fonts/lxgwwenkai-regular.css';
@import './fonts/lxgwwenkaimono-regular.css';
@import './tailwind.css';
@import './app.css';
+118
View File
@@ -0,0 +1,118 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(100% 0 0deg);
--foreground: oklch(14.1% 0.005 285.823deg);
--card: oklch(100% 0 0deg);
--card-foreground: oklch(14.1% 0.005 285.823deg);
--popover: oklch(100% 0 0deg);
--popover-foreground: oklch(14.1% 0.005 285.823deg);
--primary: oklch(21% 0.006 285.885deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(96.7% 0.001 286.375deg);
--secondary-foreground: oklch(21% 0.006 285.885deg);
--muted: oklch(96.7% 0.001 286.375deg);
--muted-foreground: oklch(55.2% 0.016 285.938deg);
--accent: oklch(96.7% 0.001 286.375deg);
--accent-foreground: oklch(21% 0.006 285.885deg);
--destructive: oklch(57.7% 0.245 27.325deg);
--border: oklch(92% 0.004 286.32deg);
--input: oklch(92% 0.004 286.32deg);
--ring: oklch(70.5% 0.015 286.067deg);
--chart-1: oklch(64.6% 0.222 41.116deg);
--chart-2: oklch(60% 0.118 184.704deg);
--chart-3: oklch(39.8% 0.07 227.392deg);
--chart-4: oklch(82.8% 0.189 84.429deg);
--chart-5: oklch(76.9% 0.188 70.08deg);
--sidebar: oklch(98.5% 0 0deg);
--sidebar-foreground: oklch(14.1% 0.005 285.823deg);
--sidebar-primary: oklch(21% 0.006 285.885deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(96.7% 0.001 286.375deg);
--sidebar-accent-foreground: oklch(21% 0.006 285.885deg);
--sidebar-border: oklch(92% 0.004 286.32deg);
--sidebar-ring: oklch(70.5% 0.015 286.067deg);
}
.dark {
--background: oklch(14.1% 0.005 285.823deg);
--foreground: oklch(98.5% 0 0deg);
--card: oklch(21% 0.006 285.885deg);
--card-foreground: oklch(98.5% 0 0deg);
--popover: oklch(21% 0.006 285.885deg);
--popover-foreground: oklch(98.5% 0 0deg);
--primary: oklch(92% 0.004 286.32deg);
--primary-foreground: oklch(21% 0.006 285.885deg);
--secondary: oklch(27.4% 0.006 286.033deg);
--secondary-foreground: oklch(98.5% 0 0deg);
--muted: oklch(27.4% 0.006 286.033deg);
--muted-foreground: oklch(70.5% 0.015 286.067deg);
--accent: oklch(27.4% 0.006 286.033deg);
--accent-foreground: oklch(98.5% 0 0deg);
--destructive: oklch(70.4% 0.191 22.216deg);
--border: oklch(100% 0 0deg / 10%);
--input: oklch(100% 0 0deg / 15%);
--ring: oklch(55.2% 0.016 285.938deg);
--chart-1: oklch(48.8% 0.243 264.376deg);
--chart-2: oklch(69.6% 0.17 162.48deg);
--chart-3: oklch(76.9% 0.188 70.08deg);
--chart-4: oklch(62.7% 0.265 303.9deg);
--chart-5: oklch(64.5% 0.246 16.439deg);
--sidebar: oklch(21% 0.006 285.885deg);
--sidebar-foreground: oklch(98.5% 0 0deg);
--sidebar-primary: oklch(48.8% 0.243 264.376deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(27.4% 0.006 286.033deg);
--sidebar-accent-foreground: oklch(98.5% 0 0deg);
--sidebar-border: oklch(100% 0 0deg / 10%);
--sidebar-ring: oklch(55.2% 0.016 285.938deg);
}
@layer base {
* {
@apply tw:border-border;
}
body {
@apply tw:bg-background tw:text-foreground;
}
}
+136
View File
@@ -0,0 +1,136 @@
/* src/app/styles/tailwind.css */
@layer theme, base, antd, components, utilities;
@import 'tailwindcss' prefix(tw);
@import 'tw-animate-css';
@import './tailwind/base.css';
@import './tailwind/utilities.css';
@import './tailwind/theme.css';
@import './tailwind/shadcn.css';
@custom-variant dark (&:is(.dark, .dark *, [data-theme='dark'], [data-theme='dark'] *));
@theme inline {
--font-heading: var(--font-sans);
--font-sans: var(--font-sans);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply tw:border-border tw:outline-ring/50;
}
body {
@apply tw:bg-background tw:text-foreground;
}
html {
@apply tw:font-sans;
}
}
+50
View File
@@ -0,0 +1,50 @@
/********************************************************* 自定义tailwind基本样式 *********************************************************/
@layer base {
html {
font-size: 100%;
}
/* 以下为自定义样式 */
html,
body {
@apply tw:font-base tw:h-[100vh] tw:w-full tw:p-0 tw:m-0;
}
body {
@apply tw:text-sm;
}
h1 {
@apply tw:text-3xl;
}
h2 {
@apply tw:text-2xl;
}
h3 {
@apply tw:text-xl;
}
h4 {
@apply tw:text-lg;
}
h5 {
@apply tw:text-sm;
}
h6 {
@apply tw:text-xs;
}
button,
[role='button'] {
cursor: pointer;
}
:disabled {
cursor: default;
}
}
+118
View File
@@ -0,0 +1,118 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(100% 0 0deg);
--foreground: oklch(14.1% 0.005 285.823deg);
--card: oklch(100% 0 0deg);
--card-foreground: oklch(14.1% 0.005 285.823deg);
--popover: oklch(100% 0 0deg);
--popover-foreground: oklch(14.1% 0.005 285.823deg);
--primary: oklch(21% 0.006 285.885deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(96.7% 0.001 286.375deg);
--secondary-foreground: oklch(21% 0.006 285.885deg);
--muted: oklch(96.7% 0.001 286.375deg);
--muted-foreground: oklch(55.2% 0.016 285.938deg);
--accent: oklch(96.7% 0.001 286.375deg);
--accent-foreground: oklch(21% 0.006 285.885deg);
--destructive: oklch(57.7% 0.245 27.325deg);
--border: oklch(92% 0.004 286.32deg);
--input: oklch(92% 0.004 286.32deg);
--ring: oklch(70.5% 0.015 286.067deg);
--chart-1: oklch(64.6% 0.222 41.116deg);
--chart-2: oklch(60% 0.118 184.704deg);
--chart-3: oklch(39.8% 0.07 227.392deg);
--chart-4: oklch(82.8% 0.189 84.429deg);
--chart-5: oklch(76.9% 0.188 70.08deg);
--sidebar: oklch(98.5% 0 0deg);
--sidebar-foreground: oklch(14.1% 0.005 285.823deg);
--sidebar-primary: oklch(21% 0.006 285.885deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(96.7% 0.001 286.375deg);
--sidebar-accent-foreground: oklch(21% 0.006 285.885deg);
--sidebar-border: oklch(92% 0.004 286.32deg);
--sidebar-ring: oklch(70.5% 0.015 286.067deg);
}
.dark {
--background: oklch(14.1% 0.005 285.823deg);
--foreground: oklch(98.5% 0 0deg);
--card: oklch(21% 0.006 285.885deg);
--card-foreground: oklch(98.5% 0 0deg);
--popover: oklch(21% 0.006 285.885deg);
--popover-foreground: oklch(98.5% 0 0deg);
--primary: oklch(92% 0.004 286.32deg);
--primary-foreground: oklch(21% 0.006 285.885deg);
--secondary: oklch(27.4% 0.006 286.033deg);
--secondary-foreground: oklch(98.5% 0 0deg);
--muted: oklch(27.4% 0.006 286.033deg);
--muted-foreground: oklch(70.5% 0.015 286.067deg);
--accent: oklch(27.4% 0.006 286.033deg);
--accent-foreground: oklch(98.5% 0 0deg);
--destructive: oklch(70.4% 0.191 22.216deg);
--border: oklch(100% 0 0deg / 10%);
--input: oklch(100% 0 0deg / 15%);
--ring: oklch(55.2% 0.016 285.938deg);
--chart-1: oklch(48.8% 0.243 264.376deg);
--chart-2: oklch(69.6% 0.17 162.48deg);
--chart-3: oklch(76.9% 0.188 70.08deg);
--chart-4: oklch(62.7% 0.265 303.9deg);
--chart-5: oklch(64.5% 0.246 16.439deg);
--sidebar: oklch(21% 0.006 285.885deg);
--sidebar-foreground: oklch(98.5% 0 0deg);
--sidebar-primary: oklch(48.8% 0.243 264.376deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(27.4% 0.006 286.033deg);
--sidebar-accent-foreground: oklch(98.5% 0 0deg);
--sidebar-border: oklch(100% 0 0deg / 10%);
--sidebar-ring: oklch(55.2% 0.016 285.938deg);
}
@layer base {
* {
@apply tw:border-border;
}
body {
@apply tw:bg-background tw:text-foreground;
}
}
@@ -0,0 +1,33 @@
/********************************************************* 自定义tailwind主题扩展 *********************************************************/
@theme {
--breakpoint-xs: 30rem;
--breakpoint-sm: 36rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 62rem;
--breakpoint-xl: 75rem;
--breakpoint-2xl: 87.5rem;
--shadow-nysm: 0 0 2px 0 rgb(0 0 0 / 5%);
--shadow-ny: 0 0 3px 0 rgb(0 0 0 / 10%), 0 0 2px - 1px rgb(0 0 0 / 10%);
--shadow-nymd: 0 0 6px -1px rgb(0 0 0 / 10%), 0 0 4px -2px rgb(0 0 0 / 10%);
--shadow-nylg: 0 0 15px -3px rgb(0 0 0 / 10%), 0 0 6px -4px rgb(0 0 0 / 10%);
--shadow-spread: 0 5px 40px rgb(0 0 0 / 10%);
/* 默认字体 */
--font-base:
-apple-system, blinkmacsystemfont, tahoma, arial, 'Hiragino Sans GB', 'Microsoft YaHei',
'WenQuanYi Micro Hei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
/* 霞鹜文楷字体 */
--font-lxgw:
'LXGW WenKai', '楷体', 'STKaiti', -apple-system, blinkmacsystemfont, tahoma, arial,
'Hiragino Sans GB', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/* 默认代码字体 */
--font-code:
'Fira Code', -apple-system, blinkmacsystemfont, tahoma, arial, 'Hiragino Sans GB',
'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
@@ -0,0 +1,107 @@
/********************************************************* 自定义tailwind工具类 *********************************************************/
@utility page-item {
@apply tw:flex tw:flex-auto tw:flex-col tw:mt-20 tw:items-center;
}
/* 页面包装容器 */
@utility page-container {
@apply tw:max-w-[90%] tw:md:max-w-[80%] tw:lg:max-w-[70%] tw:xl:max-w-[60%] tw:flex-auto tw:mb-5;
}
/* 页面包装容器 */
/* 空白页样式,一般用于404和error页面 */
@utility page-blank {
@apply tw:bg-white/80 tw:backdrop-blur-sm tw:h-full tw:w-full tw:drop-shadow-lg tw:rounded-md tw:flex tw:justify-center tw:items-center;
html.dark & {
@apply tw:bg-black/50;
}
}
/* 全透明图标按钮 */
@utility btn-icon-transparent {
@apply tw:block tw:w-auto! tw:border-none! tw:bg-transparent! tw:shadow-none! tw:ring-0! tw:hover:bg-transparent! tw:focus-visible:ring-0!;
}
@utility page-block {
@apply tw:bg-zinc-100/80;
html.dark & {
@apply tw:bg-zinc-800/50;
}
}
@utility page-block-hover {
@apply tw:hover:bg-white/90;
html.dark & {
@apply tw:hover:bg-black/80;
}
}
/* 只显示一行文字, 溢出部分以省略号代替 */
@utility ellips {
@apply tw:inline-block tw:overflow-hidden tw:max-w-full tw:whitespace-nowrap tw:text-ellipsis tw:break-all;
}
/* 鼠标移动到文字或链接出现动画下划线 */
@utility animate-decoration {
padding-bottom: 2px;
background: linear-gradient(hsl(var(--foreground)), hsl(var(--foreground))) 0% 100% / 0% 1px
no-repeat;
transition: background-size ease-out 200ms;
&:not(:focus):hover {
background-size: 100% 1px;
}
}
/* 粗下划线 */
@utility animate-decoration-lg {
&:not(:focus):hover {
background-size: 100% 2px;
}
}
/* 取消下划线 */
@utility none-animate-decoration {
background-size: 0 !important;
transition: none !important;
&:hover,
&:not(:focus):hover {
background-size: 100% 0 !important;
}
}
/* 透明滚动条 */
@utility transparent-scrollbar {
&::-webkit-scrollbar {
@apply tw:w-1 tw:h-1;
}
&::-webkit-scrollbar-track {
@apply tw:bg-gray-100;
}
html.dark &::-webkit-scrollbar-track {
@apply tw:bg-zinc-800;
}
&::-webkit-scrollbar-thumb {
@apply tw:bg-gray-300 tw:rounded;
}
html.dark &::-webkit-scrollbar-thumb {
@apply tw:bg-zinc-600;
}
&::-webkit-scrollbar-corner {
@apply tw:bg-gray-100;
}
html.dark &::-webkit-scrollbar-corner {
@apply tw:bg-zinc-800;
}
}
+93
View File
@@ -0,0 +1,93 @@
/** @type {import('stylelint').Config} */
const stylelintConfig = {
extends: [
'stylelint-config-standard',
'stylelint-config-css-modules',
'stylelint-config-recess-order',
'stylelint-prettier/recommended',
],
rules: {
'import-notation': 'string', // 使用string方式引入其它css文件,而不是url()
'selector-type-no-unknown': null,
'selector-class-pattern': null,
'custom-property-pattern': null,
'no-duplicate-selectors': null, // 取消禁止重复定义,这样可以在css module中单独定义变量
'block-no-empty': null, // 禁止出现空块
'declaration-empty-line-before': 'never',
'declaration-block-no-duplicate-properties': true, // 在声明的块中中禁止出现重复的属性
'declaration-block-no-redundant-longhand-properties': true, // 禁止使用可以缩写却不缩写的属性
'shorthand-property-no-redundant-values': true, // 禁止在简写属性中使用冗余值
'color-hex-length': 'short', // 指定十六进制颜色是否使用缩写
'comment-no-empty': true, // 禁止空注释
'font-family-name-quotes': 'always-unless-keyword', // 指定字体名称是否需要使用引号引起来 | 期待每一个不是关键字的字体名都使用引号引起来
// 'font-weight-notation': 'numeric', // 要求使用数字或命名的 (可能的情况下) font-weight 值
'function-url-quotes': 'always', // 要求或禁止 url 使用引号
'property-no-vendor-prefix': true, // 禁止属性使用浏览器引擎前缀
'value-no-vendor-prefix': true, // 禁止给值添加浏览器引擎前缀
'selector-no-vendor-prefix': true, // 禁止使用浏览器引擎前缀
'no-descending-specificity': null, // 禁止低优先级的选择器出现在高优先级的选择器之后
'at-rule-no-deprecated': null, // 禁止使用未知指令报错
'at-rule-no-unknown': null, // 禁止使用未知指令报错
'property-no-unknown': [
true,
{
ignoreProperties: [
// CSS Modules composition
// https://github.com/css-modules/css-modules#composition
'composes',
],
},
],
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: [
// CSS Modules :global scope
// https://github.com/css-modules/css-modules#exceptions
'global',
'local',
],
},
],
'rule-empty-line-before': [
// 要求或禁止在规则声明之前有空行
'always-multi-line',
{
except: ['first-nested'],
ignore: ['after-comment'],
},
],
'at-rule-empty-line-before': [
// 要求或禁止在 at 规则之前有空行
'always',
{
except: ['blockless-after-same-name-blockless', 'first-nested'],
ignore: ['after-comment'],
},
],
'comment-empty-line-before': [
// 要求或禁止在注释之前有空行
'always',
{
except: ['first-nested'],
ignore: ['stylelint-commands'],
},
],
},
ignoreFiles: [
'public',
'node_modules',
'build',
'.history',
'.next',
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
],
};
export default stylelintConfig;
+26
View File
@@ -0,0 +1,26 @@
public
node_modules
src/database/migrations
pnpm-lock.yaml
package-lock.json
docker
Dockerfile*
LICENSE
yarn-error.log
.next
.history
.dockerignore
.DS_Store
.eslintignore
.editorconfig
.gitignore
.prettierignore
.eslintcache
*.lock
*.png
*.jpg
*.jpeg
*.eot
*.ttf
*.woff
*.sql
+41
View File
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+11
View File
@@ -0,0 +1,11 @@
const config = {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
proseWrap: 'never',
endOfLine: 'auto',
semi: true,
tabWidth: 4,
htmlWhitespaceSensitivity: 'strict',
};
export default config;
+36
View File
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+25
View File
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/styles/tailwind.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/app/_components/shadcn/ui",
"utils": "@/app/_components/shadcn/libs/utils",
"ui": "@/app/_components/shadcn/ui",
"lib": "@/app/_components/shadcn/libs",
"hooks": "@/app/_components/shadcn/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
+8
View File
@@ -0,0 +1,8 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactCompiler: true,
};
export default nextConfig;
+65
View File
@@ -0,0 +1,65 @@
{
"name": "chapter3",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "rimraf .next && next dev --turbopack",
"build": "rimraf .next && next build",
"start": "next start",
"lint": "pnpm lint:es && pnpm lint:style",
"lint:es": "eslint . --fix",
"lint:style": "stylelint \"**/*.css\" --fix --cache --cache-location node_modules/.cache/stylelint"
},
"dependencies": {
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@ant-design/cssinjs": "^2.1.2",
"@ant-design/nextjs-registry": "^1.3.0",
"antd": "^6.6.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"immer": "^11.1.18",
"lucide-react": "^1.37.0",
"lodash": "^4.18.1",
"next": "16.3.3",
"radix-ui": "^1.6.7",
"react": "19.2.8",
"react-dom": "19.2.8",
"shadcn": "^4.19.0",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@eslint-react/eslint-plugin": "^5.18.6",
"@eslint/eslintrc": "^3.3.6",
"@next/eslint-plugin-next": "^16.3.3",
"@tailwindcss/postcss": "^4",
"@types/lodash": "^4.17.25",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0",
"eslint": "^9.39.5",
"eslint-config-next": "16.3.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-perfectionist": "^5.10.1",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.5",
"eslint-plugin-unused-imports": "^4.4.1",
"prettier": "^3.9.6",
"rimraf": "^6.1.3",
"stylelint": "^17.14.1",
"stylelint-config-css-modules": "^4.6.0",
"stylelint-config-recess-order": "^7.8.0",
"stylelint-config-standard": "^40.0.0",
"stylelint-prettier": "^5.0.3",
"tailwindcss": "^4",
"typescript": "^5"
},
"packageManager": "pnpm@10.34.5",
"imports": {
"#tailwind.css": "./src/app/styles/tailwind.css"
}
}
+9621
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
ignoredBuiltDependencies:
- sharp
- unrs-resolver
+7
View File
@@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
+1
View File
@@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

@@ -0,0 +1,11 @@
/* src/app/(pages)/layout.module.css */
@reference "#tailwind.css";
.layout {
@apply min-h-full w-full flex flex-col bg-background bg-cover bg-fixed bg-center bg-no-repeat bg-[url('../styles/images/bg-light.png')] p-0 m-0;
html.dark &,
html[data-theme='dark'] & {
background-image: url('../styles/images/bg-dark.png');
}
}
+19
View File
@@ -0,0 +1,19 @@
// src/app/(pages)/layout.tsx
import type { Metadata } from 'next';
import type { FC, PropsWithChildren } from 'react';
import { Header } from '../_components/header';
import $styles from './layout.module.css';
export const metadata: Metadata = {
title: 'pincman的博客',
description:
'pincman的个人博客,提供一些ts、react、node.js、php、golang相关的技术文档以及分享一些生活琐事',
};
const AppLayout: FC<PropsWithChildren> = ({ children }) => (
<div className={$styles.layout}>
<Header />
{children}
</div>
);
export default AppLayout;
+9
View File
@@ -0,0 +1,9 @@
/* src/app/(pages)/page.module.css */
@reference "#tailwind.css";
.block {
@apply page-container flex flex-col flex-none! w-full space-y-4 p-5 items-center justify-center
rounded-sm backdrop-blur-sm shadow-nysm shadow-neutral-800 duration-300
text-lg text-center text-white bg-black/30
hover:shadow-nylg font-lxgw;
}
+22
View File
@@ -0,0 +1,22 @@
// src/app/(pages)/page.tsx
import { FC } from 'react';
import { ArrowBigRight } from 'lucide-react';
import Link from 'next/link';
import $styles from './page.module.css';
import { Button } from '../_components/shadcn/ui/button';
const App: FC = () => (
<main className={$styles.container}>
<div className={$styles.block}>
欢迎来到3R教室,这是Nextjs课程的开始
<Button asChild>
<Link href="https://3rcd.com" target="_blank">
<ArrowBigRight /> 查看官网
</Link>
</Button>
</div>
</main>
);
export default App;
@@ -0,0 +1 @@
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 801 802"><defs><style>.cls-1{fill:#fff;}.cls-1,.cls-2{stroke:#000;stroke-miterlimit:10;}.cls-2{fill:#224740;}</style></defs><rect class="cls-1" x="0.5" y="0.5" width="800" height="801"/><path class="cls-2" d="M255.61,517.24,182.5,569.53c-16.57,13.4-68.15,8.09-74.37-.12-8.7-11.49-23.82-26.39-14-35.29s37.18-8.36,37.18-8.36l30.06-.58s19.51-4,27.92-4.89c11.23-1.23,13-3.43,10.56-5.93-.67.24-.48-.48-.48-.48-5.6-4.86-25.35-10.58-22.23-12.31l62-52.44c-1.87,1.12-163.29,65.48-163.29,65.48L62.67,526.27s-2.17-1.48-20.75,27.34c-2.5,3.88,37.87,28.51,37.87,28.51C209.06,671.81,328.12,475,353.3,420.12l-.24-2.29,5.15-5.58c18.53,15.86,32.57,37.32,45.87,45.14,20.4,12-.15-8.39,38.94,24.3s39.47,148.93,36.6,175.53-29.74,29.64-29.74,29.64,9.93-28.87,7.36-78.72c-2.57-49.16-81.43-51.44-83.41-51.51C471,602.21,394,738.52,394,738.52s35,4.34,93.39-56.92-25.93-226.14-20.1-253.09c2.69-12.37,95.24-19.54,95.24-19.54s117.86,1.61,120.76,5.78c5.4,7.7,9.11,22.5,4.08,46.84-5,23.83-78.09,90.15-81.16,93,2.29-.59,39.63-11.61,96.61-69.39,58.81-59.69-1.66-127.51-58.34-103.49S528,381.27,528,381.27c-7.7-8.51-11.89-18.92-13.46-30.47h0l-27.48-5.14,27.46-18.19h0c7.21-63.27,66.28-143.75,74.05-152.62,10.38-11.8,15.63-11.29,7.31-23-6.65-9.35-3-12.33-.71-13.37l.64-.2c-9.26,2.67-52.44,13.88-116.82,11.24-72-2.92-111-84.78-112-87.12l-2.21-2.86s-38.57,66-12.6,108.84c20.18,33.3,153.43,44.76,153.43,44.76s.82,14.78-9.7,21.19-32.69-2.77-72.55-15.43l-8.88-1.7c-11.23-1.51-36.36-13.58-64-40.62l-26.19,218,5.65,18-67.34,97.66c-2.6,2.47-2.6,2.47-6.91,7m68.6-122.67M407,285.14s13.38-5.44,25.85.58,19.25,21.2,19.25,21.2-13.37,5.47-25.84-.55S407,285.14,407,285.14Z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,11 @@
// src/app/_components/header/index.tsx
import type { FC } from 'react';
import { HeaderLogo } from './logo';
import $styles from './styles.module.css';
export const Header: FC = () => (
<header className={$styles.header}>
<HeaderLogo />
</header>
);
@@ -0,0 +1,17 @@
/* src/app/_components/header/logo.module.css */
@reference "#tailwind.css";
.link {
@apply w-20 h-20 block rounded-full shadow-nymd p-1 bg-white;
transition-duration: 300ms;
animation: breathe-light 4s ease-in-out infinite;
&:hover {
@apply shadow-nylg shadow-amber-400;
transform: scale(1.2) rotate(360deg);
}
& > img {
@apply rounded-full;
}
}
@@ -0,0 +1,20 @@
// src/app/_components/header/logo.tsx
import Image from 'next/image';
import Link from 'next/link';
import Avatar from './avatar.svg';
import $styles from './logo.module.css';
export const HeaderLogo = () => (
<Link href="/" className={$styles.link}>
<Image
src={Avatar}
alt="avatar logo"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
/>
</Link>
);
@@ -0,0 +1,6 @@
/* src/app/_components/header/styles.module.css */
@reference "#tailwind.css";
.header {
@apply flex justify-center items-center pt-6 max-h-24 flex-auto;
}
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
@@ -0,0 +1,67 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/app/_components/shadcn/libs/utils"
const buttonVariants = cva(
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/80",
outline:
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost:
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default:
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
icon: "size-8",
"icon-xs":
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
"icon-sm":
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
"icon-lg": "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
@@ -0,0 +1,103 @@
import * as React from "react"
import { cn } from "@/app/_components/shadcn/libs/utils"
function Card({
className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn(
"font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
className
)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-(--card-spacing)", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
className
)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
@@ -0,0 +1,60 @@
// src/app/demo/_components/effect.tsx
// ...
'use client';
import { Button } from 'antd';
import { type FC, useEffect, useState } from 'react';
import $styles from './style.module.css';
const EffectDemo: FC = () => {
const [ghost, setGhost] = useState<boolean>(false);
const [width, setWidth] = useState(0);
const [red, setRed] = useState<boolean>(false);
const toggleGhostBtn = () => setGhost(!ghost);
useEffect(() => {
const resizeHandle = () => {
setWidth(window.innerWidth);
console.log('浏览器宽度改变');
};
const frameId = requestAnimationFrame(resizeHandle);
window.addEventListener('resize', resizeHandle);
return () => {
cancelAnimationFrame(frameId);
window.removeEventListener('resize', resizeHandle);
};
}, []);
useEffect(() => {
(
async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolve('done');
}, 1000);
});
setRed(ghost);
}
)();
}, [ghost]);
useEffect(() => {
console.log('只在第一次或重新渲染时触发');
}, []);
return (
<div className={$styles.container}>
<h2 className="text-center">useEffect Demo</h2>
<p className="py-5 text-center">{ghost ? 'ghost' : '普通'}按钮</p>
<div className="flex flex-col justify-center">
<Button type="primary" onClick={toggleGhostBtn} ghost={ghost} danger={red}>
切换按钮样式
</Button>
<p className="pt-5 text-center">宽度为: {width}</p>
</div>
</div>
);
};
export default EffectDemo;
+31
View File
@@ -0,0 +1,31 @@
// src/app/demo/_components/ref.tsx
'use client';
import { type FC, useEffect, useState, useRef } from 'react';
import { Button } from '@/app/_components/shadcn/ui/button';
import clsx from 'clsx';
import $styles from './style.module.css';
const RefDemo: FC = () => {
const [count, setCount] = useState(0);
const inited = useRef(count);
useEffect(() => {
if (inited.current !== count) {
inited.current = count;
console.log('changed');
}
}, [count]);
return (
<div className={clsx($styles.container, 'w-80')}>
<h2 className="text-center">useRef Demo</h2>
<p className="py-5 text-center">{count}</p>
<div className="flex justify-around">
<Button onClick={() => setCount(Math.ceil(Math.random() * 10))} variant="outline">
变化
</Button>
</div>
</div>
);
};
export default RefDemo;
@@ -0,0 +1,32 @@
// src/app/demo/_components/state.tsx
'use client';
import { Button } from '@/app/_components/shadcn/ui/button';
import { FC, useState } from 'react';
import clsx from 'clsx';
import $styles from './style.module.css';
const StateDemo: FC = () => {
const [count, setCount] = useState(1);
const [isShow, toggleShow] = useState(true);
return (
<div className={clsx($styles.container, 'w-[20rem]')}>
<h2 className="text-center">useState Demo</h2>
{isShow && <p className="text-center py-5">{count}</p>}
<div className="flex justify-around">
<Button onClick={() => setCount(count + 1)} variant="outline">
增加
</Button>
<Button onClick={() => setCount(count - 1)} variant="outline">
减少
</Button>
<Button onClick={() => toggleShow(!isShow)} variant="outline">
{isShow ? '显示' : '隐藏'}
</Button>
</div>
</div>
);
};
export default StateDemo;
@@ -0,0 +1,11 @@
@reference "#tailwind.css";
.container {
@apply bg-neutral-100/40 shadow-black/20 backdrop-blur-sm shadow-md rounded-md p-5 m-5 min-w-[20rem];
}
html.dark {
.container {
@apply bg-neutral-800/40 shadow-slate-100/30;
}
}
+11
View File
@@ -0,0 +1,11 @@
/* src/app/(pages)/layout.module.css */
@reference "#tailwind.css";
.layout {
@apply min-h-full w-full flex flex-col bg-background bg-cover bg-fixed bg-center bg-no-repeat bg-[url('../styles/images/bg-light.png')] p-0 m-0;
html.dark &,
html[data-theme='dark'] & {
background-image: url('../styles/images/bg-dark.png');
}
}
+34
View File
@@ -0,0 +1,34 @@
// src/app/demo/layout.tsx
'use client';
import { FC, PropsWithChildren } from 'react';
// src/app/demo/layout.tsx
import { px2remTransformer, StyleProvider } from '@ant-design/cssinjs';
import { AntdRegistry } from '@ant-design/nextjs-registry';
import { App as AntdApp, ConfigProvider, theme } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import $styles from './layout.module.css';
const px2rem = px2remTransformer();
const DemoLayout: FC<PropsWithChildren> = ({ children }) => (
<AntdRegistry>
<ConfigProvider
locale={zhCN}
theme={{
algorithm: theme.defaultAlgorithm,
// 启用css变量
cssVar: {},
hashed: false,
token: {},
}}
>
<AntdApp>
<StyleProvider transformers={[px2rem]}>
<div className={$styles.layout}>{children}</div>
</StyleProvider>
</AntdApp>
</ConfigProvider>
</AntdRegistry>
);
export default DemoLayout;
+9
View File
@@ -0,0 +1,9 @@
@reference "#tailwind.css";
.demo {
@apply flex flex-auto flex-wrap items-center justify-center;
& > .container {
@apply bg-neutral-100/40 shadow-black/20 backdrop-blur-sm shadow-md rounded-md p-5 m-5 min-w-[20rem];
}
}
+35
View File
@@ -0,0 +1,35 @@
// src/app/demo/page.tsx
// ...
import { FC } from 'react';
import { Button } from 'antd';
import StateDemo from './_components/state';
import EffectDemo from './_components/effect';
import RefDemo from './_components/ref';
import $styles from './page.module.css';
const DemoPage: FC = () => (
<div className={$styles.demo}>
<div className={$styles.container}>
<h2 className="text-center">First React App</h2>
<div className="flex flex-col items-center">
<div className="my-5 flex-auto">
<Button variant="solid" href="https://3rcd.com" target="_blank">
3R教室
</Button>
</div>
<div className={$styles.demo}>
<StateDemo />
</div>
<div className={$styles.demo}>
<EffectDemo />
</div>
<div className={$styles.demo}>
<RefDemo />
</div>
</div>
</div>
</div>
);
export default DemoPage;
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+19
View File
@@ -0,0 +1,19 @@
// src/app/layout.tsx
import type { Metadata } from 'next';
import './styles/index.css';
import type { FC, PropsWithChildren } from 'react';
export const metadata: Metadata = {
title: 'nextapp',
description: '3r教室Next.js全栈开发课程',
};
const RootLayout: FC<PropsWithChildren> = ({ children }) => (
<html lang="en">
<body>{children}</body>
</html>
);
export default RootLayout;
+8
View File
@@ -0,0 +1,8 @@
/* src/app/styles/app.css */
.lucide {
@apply w-4 h-4 stroke-[1px];
}
.ant-app {
@apply h-[100vh] w-full p-0 m-0;
}
+55
View File
@@ -0,0 +1,55 @@
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 300;
src:
url('/fonts/firacode/woff2/FiraCode-Light.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Light.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 400;
src:
url('/fonts/firacode/woff2/FiraCode-Regular.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Regular.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 500;
src:
url('/fonts/firacode/woff2/FiraCode-Medium.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Medium.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 600;
src:
url('/fonts/firacode/woff2/FiraCode-SemiBold.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-SemiBold.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 700;
src:
url('/fonts/firacode/woff2/FiraCode-Bold.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Bold.woff') format('woff');
}
@font-face {
font-family: 'Fira Code VF';
font-style: normal;
/* font-weight requires a range: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide#Using_a_variable_font_font-face_changes */
font-weight: 300 700;
src:
url('/fonts/firacode/woff2/FiraCode-VF.woff2') format('woff2-variations'),
url('/fonts/firacode/woff/FiraCode-VF.woff') format('woff-variations');
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

+5
View File
@@ -0,0 +1,5 @@
@import './fonts/fira-code.css';
@import './fonts/lxgwwenkai-regular.css';
@import './fonts/lxgwwenkaimono-regular.css';
@import './tailwind.css';
@import './app.css';
+118
View File
@@ -0,0 +1,118 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(100% 0 0deg);
--foreground: oklch(14.1% 0.005 285.823deg);
--card: oklch(100% 0 0deg);
--card-foreground: oklch(14.1% 0.005 285.823deg);
--popover: oklch(100% 0 0deg);
--popover-foreground: oklch(14.1% 0.005 285.823deg);
--primary: oklch(21% 0.006 285.885deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(96.7% 0.001 286.375deg);
--secondary-foreground: oklch(21% 0.006 285.885deg);
--muted: oklch(96.7% 0.001 286.375deg);
--muted-foreground: oklch(55.2% 0.016 285.938deg);
--accent: oklch(96.7% 0.001 286.375deg);
--accent-foreground: oklch(21% 0.006 285.885deg);
--destructive: oklch(57.7% 0.245 27.325deg);
--border: oklch(92% 0.004 286.32deg);
--input: oklch(92% 0.004 286.32deg);
--ring: oklch(70.5% 0.015 286.067deg);
--chart-1: oklch(64.6% 0.222 41.116deg);
--chart-2: oklch(60% 0.118 184.704deg);
--chart-3: oklch(39.8% 0.07 227.392deg);
--chart-4: oklch(82.8% 0.189 84.429deg);
--chart-5: oklch(76.9% 0.188 70.08deg);
--sidebar: oklch(98.5% 0 0deg);
--sidebar-foreground: oklch(14.1% 0.005 285.823deg);
--sidebar-primary: oklch(21% 0.006 285.885deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(96.7% 0.001 286.375deg);
--sidebar-accent-foreground: oklch(21% 0.006 285.885deg);
--sidebar-border: oklch(92% 0.004 286.32deg);
--sidebar-ring: oklch(70.5% 0.015 286.067deg);
}
.dark {
--background: oklch(14.1% 0.005 285.823deg);
--foreground: oklch(98.5% 0 0deg);
--card: oklch(21% 0.006 285.885deg);
--card-foreground: oklch(98.5% 0 0deg);
--popover: oklch(21% 0.006 285.885deg);
--popover-foreground: oklch(98.5% 0 0deg);
--primary: oklch(92% 0.004 286.32deg);
--primary-foreground: oklch(21% 0.006 285.885deg);
--secondary: oklch(27.4% 0.006 286.033deg);
--secondary-foreground: oklch(98.5% 0 0deg);
--muted: oklch(27.4% 0.006 286.033deg);
--muted-foreground: oklch(70.5% 0.015 286.067deg);
--accent: oklch(27.4% 0.006 286.033deg);
--accent-foreground: oklch(98.5% 0 0deg);
--destructive: oklch(70.4% 0.191 22.216deg);
--border: oklch(100% 0 0deg / 10%);
--input: oklch(100% 0 0deg / 15%);
--ring: oklch(55.2% 0.016 285.938deg);
--chart-1: oklch(48.8% 0.243 264.376deg);
--chart-2: oklch(69.6% 0.17 162.48deg);
--chart-3: oklch(76.9% 0.188 70.08deg);
--chart-4: oklch(62.7% 0.265 303.9deg);
--chart-5: oklch(64.5% 0.246 16.439deg);
--sidebar: oklch(21% 0.006 285.885deg);
--sidebar-foreground: oklch(98.5% 0 0deg);
--sidebar-primary: oklch(48.8% 0.243 264.376deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(27.4% 0.006 286.033deg);
--sidebar-accent-foreground: oklch(98.5% 0 0deg);
--sidebar-border: oklch(100% 0 0deg / 10%);
--sidebar-ring: oklch(55.2% 0.016 285.938deg);
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
+136
View File
@@ -0,0 +1,136 @@
/* src/app/styles/tailwind.css */
@layer theme, base, antd, components, utilities;
@import 'tailwindcss';
@import 'tw-animate-css';
@import './tailwind/base.css';
@import './tailwind/utilities.css';
@import './tailwind/theme.css';
@import './tailwind/shadcn.css';
@custom-variant dark (&:is(.dark, .dark *, [data-theme='dark'], [data-theme='dark'] *));
@theme inline {
--font-heading: var(--font-sans);
--font-sans: var(--font-sans);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
}
}
+50
View File
@@ -0,0 +1,50 @@
/********************************************************* 自定义tailwind基本样式 *********************************************************/
@layer base {
html {
font-size: 100%;
}
/* 以下为自定义样式 */
html,
body {
@apply font-base h-[100vh] w-full p-0 m-0;
}
body {
@apply text-sm;
}
h1 {
@apply text-3xl;
}
h2 {
@apply text-2xl;
}
h3 {
@apply text-xl;
}
h4 {
@apply text-lg;
}
h5 {
@apply text-sm;
}
h6 {
@apply text-xs;
}
button,
[role='button'] {
cursor: pointer;
}
:disabled {
cursor: default;
}
}
+118
View File
@@ -0,0 +1,118 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(100% 0 0deg);
--foreground: oklch(14.1% 0.005 285.823deg);
--card: oklch(100% 0 0deg);
--card-foreground: oklch(14.1% 0.005 285.823deg);
--popover: oklch(100% 0 0deg);
--popover-foreground: oklch(14.1% 0.005 285.823deg);
--primary: oklch(21% 0.006 285.885deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(96.7% 0.001 286.375deg);
--secondary-foreground: oklch(21% 0.006 285.885deg);
--muted: oklch(96.7% 0.001 286.375deg);
--muted-foreground: oklch(55.2% 0.016 285.938deg);
--accent: oklch(96.7% 0.001 286.375deg);
--accent-foreground: oklch(21% 0.006 285.885deg);
--destructive: oklch(57.7% 0.245 27.325deg);
--border: oklch(92% 0.004 286.32deg);
--input: oklch(92% 0.004 286.32deg);
--ring: oklch(70.5% 0.015 286.067deg);
--chart-1: oklch(64.6% 0.222 41.116deg);
--chart-2: oklch(60% 0.118 184.704deg);
--chart-3: oklch(39.8% 0.07 227.392deg);
--chart-4: oklch(82.8% 0.189 84.429deg);
--chart-5: oklch(76.9% 0.188 70.08deg);
--sidebar: oklch(98.5% 0 0deg);
--sidebar-foreground: oklch(14.1% 0.005 285.823deg);
--sidebar-primary: oklch(21% 0.006 285.885deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(96.7% 0.001 286.375deg);
--sidebar-accent-foreground: oklch(21% 0.006 285.885deg);
--sidebar-border: oklch(92% 0.004 286.32deg);
--sidebar-ring: oklch(70.5% 0.015 286.067deg);
}
.dark {
--background: oklch(14.1% 0.005 285.823deg);
--foreground: oklch(98.5% 0 0deg);
--card: oklch(21% 0.006 285.885deg);
--card-foreground: oklch(98.5% 0 0deg);
--popover: oklch(21% 0.006 285.885deg);
--popover-foreground: oklch(98.5% 0 0deg);
--primary: oklch(92% 0.004 286.32deg);
--primary-foreground: oklch(21% 0.006 285.885deg);
--secondary: oklch(27.4% 0.006 286.033deg);
--secondary-foreground: oklch(98.5% 0 0deg);
--muted: oklch(27.4% 0.006 286.033deg);
--muted-foreground: oklch(70.5% 0.015 286.067deg);
--accent: oklch(27.4% 0.006 286.033deg);
--accent-foreground: oklch(98.5% 0 0deg);
--destructive: oklch(70.4% 0.191 22.216deg);
--border: oklch(100% 0 0deg / 10%);
--input: oklch(100% 0 0deg / 15%);
--ring: oklch(55.2% 0.016 285.938deg);
--chart-1: oklch(48.8% 0.243 264.376deg);
--chart-2: oklch(69.6% 0.17 162.48deg);
--chart-3: oklch(76.9% 0.188 70.08deg);
--chart-4: oklch(62.7% 0.265 303.9deg);
--chart-5: oklch(64.5% 0.246 16.439deg);
--sidebar: oklch(21% 0.006 285.885deg);
--sidebar-foreground: oklch(98.5% 0 0deg);
--sidebar-primary: oklch(48.8% 0.243 264.376deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(27.4% 0.006 286.033deg);
--sidebar-accent-foreground: oklch(98.5% 0 0deg);
--sidebar-border: oklch(100% 0 0deg / 10%);
--sidebar-ring: oklch(55.2% 0.016 285.938deg);
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@@ -0,0 +1,33 @@
/********************************************************* 自定义tailwind主题扩展 *********************************************************/
@theme {
--breakpoint-xs: 30rem;
--breakpoint-sm: 36rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 62rem;
--breakpoint-xl: 75rem;
--breakpoint-2xl: 87.5rem;
--shadow-nysm: 0 0 2px 0 rgb(0 0 0 / 5%);
--shadow-ny: 0 0 3px 0 rgb(0 0 0 / 10%), 0 0 2px - 1px rgb(0 0 0 / 10%);
--shadow-nymd: 0 0 6px -1px rgb(0 0 0 / 10%), 0 0 4px -2px rgb(0 0 0 / 10%);
--shadow-nylg: 0 0 15px -3px rgb(0 0 0 / 10%), 0 0 6px -4px rgb(0 0 0 / 10%);
--shadow-spread: 0 5px 40px rgb(0 0 0 / 10%);
/* 默认字体 */
--font-base:
-apple-system, blinkmacsystemfont, tahoma, arial, 'Hiragino Sans GB', 'Microsoft YaHei',
'WenQuanYi Micro Hei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
/* 霞鹜文楷字体 */
--font-lxgw:
'LXGW WenKai', '楷体', 'STKaiti', -apple-system, blinkmacsystemfont, tahoma, arial,
'Hiragino Sans GB', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/* 默认代码字体 */
--font-code:
'Fira Code', -apple-system, blinkmacsystemfont, tahoma, arial, 'Hiragino Sans GB',
'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
@@ -0,0 +1,107 @@
/********************************************************* 自定义tailwind工具类 *********************************************************/
@utility page-item {
@apply flex flex-auto flex-col mt-20 items-center;
}
/* 页面包装容器 */
@utility page-container {
@apply max-w-[90%] md:max-w-[80%] lg:max-w-[70%] xl:max-w-[60%] flex-auto mb-5;
}
/* 页面包装容器 */
/* 空白页样式,一般用于404和error页面 */
@utility page-blank {
@apply bg-white/80 backdrop-blur-sm h-full w-full drop-shadow-lg rounded-md flex justify-center items-center;
html.dark & {
@apply bg-black/50;
}
}
/* 全透明图标按钮 */
@utility btn-icon-transparent {
@apply block w-auto! border-none! bg-transparent! shadow-none! ring-0! hover:bg-transparent! focus-visible:ring-0!;
}
@utility page-block {
@apply bg-zinc-100/80;
html.dark & {
@apply bg-zinc-800/50;
}
}
@utility page-block-hover {
@apply hover:bg-white/90;
html.dark & {
@apply hover:bg-black/80;
}
}
/* 只显示一行文字, 溢出部分以省略号代替 */
@utility ellips {
@apply inline-block overflow-hidden max-w-full whitespace-nowrap text-ellipsis break-all;
}
/* 鼠标移动到文字或链接出现动画下划线 */
@utility animate-decoration {
padding-bottom: 2px;
background: linear-gradient(hsl(var(--foreground)), hsl(var(--foreground))) 0% 100% / 0% 1px
no-repeat;
transition: background-size ease-out 200ms;
&:not(:focus):hover {
background-size: 100% 1px;
}
}
/* 粗下划线 */
@utility animate-decoration-lg {
&:not(:focus):hover {
background-size: 100% 2px;
}
}
/* 取消下划线 */
@utility none-animate-decoration {
background-size: 0 !important;
transition: none !important;
&:hover,
&:not(:focus):hover {
background-size: 100% 0 !important;
}
}
/* 透明滚动条 */
@utility transparent-scrollbar {
&::-webkit-scrollbar {
@apply w-1 h-1;
}
&::-webkit-scrollbar-track {
@apply bg-gray-100;
}
html.dark &::-webkit-scrollbar-track {
@apply bg-zinc-800;
}
&::-webkit-scrollbar-thumb {
@apply bg-gray-300 rounded;
}
html.dark &::-webkit-scrollbar-thumb {
@apply bg-zinc-600;
}
&::-webkit-scrollbar-corner {
@apply bg-gray-100;
}
html.dark &::-webkit-scrollbar-corner {
@apply bg-zinc-800;
}
}
+93
View File
@@ -0,0 +1,93 @@
/** @type {import('stylelint').Config} */
const stylelintConfig = {
extends: [
'stylelint-config-standard',
'stylelint-config-css-modules',
'stylelint-config-recess-order',
'stylelint-prettier/recommended',
],
rules: {
'import-notation': 'string', // 使用string方式引入其它css文件,而不是url()
'selector-type-no-unknown': null,
'selector-class-pattern': null,
'custom-property-pattern': null,
'no-duplicate-selectors': null, // 取消禁止重复定义,这样可以在css module中单独定义变量
'block-no-empty': null, // 禁止出现空块
'declaration-empty-line-before': 'never',
'declaration-block-no-duplicate-properties': true, // 在声明的块中中禁止出现重复的属性
'declaration-block-no-redundant-longhand-properties': true, // 禁止使用可以缩写却不缩写的属性
'shorthand-property-no-redundant-values': true, // 禁止在简写属性中使用冗余值
'color-hex-length': 'short', // 指定十六进制颜色是否使用缩写
'comment-no-empty': true, // 禁止空注释
'font-family-name-quotes': 'always-unless-keyword', // 指定字体名称是否需要使用引号引起来 | 期待每一个不是关键字的字体名都使用引号引起来
// 'font-weight-notation': 'numeric', // 要求使用数字或命名的 (可能的情况下) font-weight 值
'function-url-quotes': 'always', // 要求或禁止 url 使用引号
'property-no-vendor-prefix': true, // 禁止属性使用浏览器引擎前缀
'value-no-vendor-prefix': true, // 禁止给值添加浏览器引擎前缀
'selector-no-vendor-prefix': true, // 禁止使用浏览器引擎前缀
'no-descending-specificity': null, // 禁止低优先级的选择器出现在高优先级的选择器之后
'at-rule-no-deprecated': null, // 禁止使用未知指令报错
'at-rule-no-unknown': null, // 禁止使用未知指令报错
'property-no-unknown': [
true,
{
ignoreProperties: [
// CSS Modules composition
// https://github.com/css-modules/css-modules#composition
'composes',
],
},
],
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: [
// CSS Modules :global scope
// https://github.com/css-modules/css-modules#exceptions
'global',
'local',
],
},
],
'rule-empty-line-before': [
// 要求或禁止在规则声明之前有空行
'always-multi-line',
{
except: ['first-nested'],
ignore: ['after-comment'],
},
],
'at-rule-empty-line-before': [
// 要求或禁止在 at 规则之前有空行
'always',
{
except: ['blockless-after-same-name-blockless', 'first-nested'],
ignore: ['after-comment'],
},
],
'comment-empty-line-before': [
// 要求或禁止在注释之前有空行
'always',
{
except: ['first-nested'],
ignore: ['stylelint-commands'],
},
],
},
ignoreFiles: [
'public',
'node_modules',
'build',
'.history',
'.next',
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
],
};
export default stylelintConfig;
+34
View File
@@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}