This commit is contained in:
2025-11-10 16:54:07 +08:00
commit b2a99d2a66
25 changed files with 9960 additions and 0 deletions

29
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,29 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules/
.svelte-kit/
.vite/
dist/
build/
*.log

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

290
README.md Normal file
View File

@@ -0,0 +1,290 @@
# My App Store
一个基于苹果 App Store 源码架构构建的现代化前端应用,使用 SvelteKit + TypeScript 开发。
## ✨ 特性
- 🎨 **现代化 UI 设计** - 基于苹果设计语言的组件系统
- 🌍 **多语言支持** - 内置国际化系统,支持 8 种语言
- 🎭 **主题系统** - 支持亮色/暗色主题,多种颜色预设
-**无障碍优先** - 完整的 ARIA 支持和键盘导航
- 📱 **响应式设计** - 适配所有设备尺寸
-**高性能** - 基于 Svelte 的极致性能优化
- 🔧 **TypeScript** - 完整的类型安全
- 🧩 **模块化架构** - 可复用的组件和工具库
## 🏗️ 架构设计
### 核心系统
- **路由系统** - 基于 SvelteKit 的文件路由 + 自定义路由状态管理
- **状态管理** - Svelte stores + 自定义状态管理器
- **主题系统** - CSS 变量 + 动态主题切换
- **国际化** - 基于 Intl API 的完整 i18n 解决方案
- **组件库** - 从苹果源码提取并现代化改造的组件系统
### 目录结构
```
src/
├── lib/
│ ├── components/ # 组件库
│ │ └── base/ # 基础组件 (Button, Modal, etc.)
│ ├── stores/ # 状态管理
│ │ ├── router.ts # 路由状态
│ │ └── theme.ts # 主题状态
│ ├── i18n/ # 国际化
│ ├── styles/ # 样式系统
│ │ └── variables.scss # 设计系统变量
│ └── utils/ # 工具函数
├── routes/ # 页面路由
│ ├── +layout.svelte # 全局布局
│ └── +page.svelte # 首页
└── app.html # HTML 模板
```
## 🚀 快速开始
### 环境要求
- Node.js 18+
- npm 或 pnpm
### 安装依赖
```bash
cd my-app-store
npm install
```
### 开发模式
```bash
npm run dev
```
访问 http://localhost:5173
### 构建生产版本
```bash
npm run build
```
### 预览生产版本
```bash
npm run preview
```
## 🎨 组件系统
### Button 组件
支持多种样式变体和尺寸:
```svelte
<Button variant="primary" size="lg">
<span slot="icon-before">🚀</span>
Get Started
</Button>
```
**变体类型:**
- `primary` - 主要按钮
- `secondary` - 次要按钮
- `tertiary` - 第三级按钮
- `pill` - 药丸按钮
- `text` - 文本按钮
- `alert` - 警告按钮
**尺寸:**
- `sm` - 小尺寸 (32px)
- `md` - 中等尺寸 (40px)
- `lg` - 大尺寸 (48px)
### Modal 组件
功能完整的模态框组件:
```svelte
<Modal size="md" ariaLabel="Settings Modal">
<h2>Settings</h2>
<p>Modal content here...</p>
</Modal>
```
**特性:**
- 焦点陷阱和键盘导航
- 多种尺寸选项
- 自动 polyfill 支持
- 无障碍友好
## 🌍 国际化
### 支持的语言
- 🇺🇸 English
- 🇨🇳 中文
- 🇯🇵 日本語
- 🇰🇷 한국어
- 🇪🇸 Español
- 🇫🇷 Français
- 🇩🇪 Deutsch
- 🇸🇦 العربية
### 使用翻译
```svelte
<script>
import { t } from '~/lib/i18n';
</script>
<h1>{$t('navigation.home')}</h1>
```
### 添加新语言
1.`src/lib/i18n/index.ts` 中添加语言配置
2. 添加对应的翻译内容
3. 更新 `supportedLocales` 数组
## 🎭 主题系统
### 预设主题
- Apple - 苹果蓝
- Google - 谷歌蓝
- GitHub - 深色主题
- Spotify - 绿色主题
### 自定义主题
```typescript
import { theme } from '~/lib/stores/theme';
// 设置主色调
theme.setPrimaryColor('#FF6B6B');
// 应用预设
theme.applyPreset('spotify');
// 切换主题模式
theme.setMode('dark');
```
### CSS 变量
主题系统基于 CSS 变量,支持动态切换:
```scss
.my-component {
background-color: var(--color-primary);
color: var(--color-gray-900);
border-radius: var(--border-radius-md);
}
```
## 📱 响应式设计
使用移动优先的响应式设计:
```scss
.component {
// 移动端样式
padding: var(--spacing-sm);
@media (min-width: 768px) {
// 平板和桌面样式
padding: var(--spacing-md);
}
}
```
## ♿ 无障碍支持
- 完整的 ARIA 标签
- 键盘导航支持
- 焦点管理
- 屏幕阅读器友好
- 高对比度模式
- 减少动画选项
## 🔧 开发工具
### 代码检查
```bash
npm run lint
```
### 类型检查
```bash
npm run check
```
### 代码格式化
```bash
npm run format
```
## 📦 部署
### Vercel
```bash
npm i -g vercel
vercel
```
### Netlify
```bash
npm run build
# 上传 build 目录
```
### Docker
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "build"]
```
## 🤝 贡献指南
1. Fork 项目
2. 创建特性分支 (`git checkout -b feature/amazing-feature`)
3. 提交更改 (`git commit -m 'Add amazing feature'`)
4. 推送到分支 (`git push origin feature/amazing-feature`)
5. 开启 Pull Request
## 📄 许可证
本项目基于 MIT 许可证开源。
## 🙏 致谢
- 感谢苹果公司提供的优秀设计灵感
- 感谢 Svelte 团队的出色框架
- 感谢所有开源贡献者
## 📞 联系方式
如有问题或建议,请通过以下方式联系:
- 创建 Issue
- 发送邮件
- 社交媒体
---
**注意:** 本项目仅用于学习和研究目的,不用于商业用途。所有设计灵感来源于公开可访问的资源。

4462
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "my-app-store",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "vite build",
"dev": "vite dev",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"sass": "^1.69.5",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"svelte-i18n": "^3.7.4"
}
}

12
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

12
src/app.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en" %sveltekit.theme%>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" %sveltekit.theme%>
<div style="display: contents" %sveltekit.theme%>%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,309 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { makeSafeTick } from '$lib/utils/makeSafeTick';
const dispatch = createEventDispatcher();
const handleButtonClick = () => {
dispatch('buttonClick');
};
// 按钮类型定义 - 基于苹果设计规范简化
type ButtonType =
| 'primary' // 原 buttonA
| 'secondary' // 原 buttonB
| 'tertiary' // 原 buttonD
| 'alert' // 原 alertButton
| 'alert-secondary' // 原 alertButtonSecondary
| 'pill' // 原 pillButton
| 'text' // 原 textButton
| 'social' // 原 socialProfileButton
| null;
export let variant: ButtonType = 'primary';
export let makeFocused = false;
export let ariaLabel: string | null = null;
export let type: 'button' | 'submit' = 'button';
export let disabled = false;
export let buttonElement: HTMLButtonElement | null = null;
export let size: 'sm' | 'md' | 'lg' = 'md';
export let fullWidth = false;
function handleKeyUp(e: KeyboardEvent) {
if (e.key === 'Enter' || e.key === 'Escape') {
handleButtonClick();
}
}
const safeTick = makeSafeTick();
onMount(async () => {
await safeTick(async (tick) => {
await tick();
if (makeFocused && buttonElement) {
buttonElement.focus();
}
});
});
</script>
<div
class="button-wrapper"
class:primary={variant === 'primary'}
class:secondary={variant === 'secondary'}
class:tertiary={variant === 'tertiary'}
class:alert={variant === 'alert'}
class:alert-secondary={variant === 'alert-secondary'}
class:pill={variant === 'pill'}
class:text={variant === 'text'}
class:social={variant === 'social'}
class:size-sm={size === 'sm'}
class:size-md={size === 'md'}
class:size-lg={size === 'lg'}
class:full-width={fullWidth}
data-testid="button-wrapper"
>
<button
on:click={handleButtonClick}
data-testid="button"
aria-label={ariaLabel}
bind:this={buttonElement}
on:keyup={handleKeyUp}
class:link={variant === 'text'}
{type}
{disabled}
>
{#if $$slots['icon-before']}
<div class="button__icon button__icon--before">
<slot name="icon-before" />
</div>
{/if}
<slot />
{#if $$slots['icon-after']}
<div class="button__icon button__icon--after">
<slot name="icon-after" />
</div>
{/if}
</button>
</div>
<style lang="scss">
.button-wrapper {
display: inline-block;
width: auto;
&.full-width {
width: 100%;
}
button {
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: var(--border-radius-md);
font-family: var(--font-family-primary);
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
white-space: nowrap;
&:focus {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
&[disabled] {
opacity: var(--buttonDisabledOpacity, 0.5);
cursor: not-allowed;
@media (prefers-color-scheme: dark) {
opacity: var(--buttonDisabledOpacityDark, 1);
background-color: var(--buttonDisabledBGColorDark, rgba(255, 255, 255, 0.5));
color: var(--buttonDisabledTextColorDark, var(--color-gray-400));
}
}
}
}
// 尺寸变体
.size-sm button {
height: 32px;
padding: 0 12px;
font-size: var(--font-size-sm);
min-width: 80px;
}
.size-md button {
height: 40px;
padding: 0 16px;
font-size: var(--font-size-md);
min-width: 100px;
}
.size-lg button {
height: 48px;
padding: 0 24px;
font-size: var(--font-size-lg);
min-width: 120px;
}
// 主要按钮样式
.primary button {
background-color: var(--color-primary);
color: white;
&:hover:not([disabled]) {
background-color: color-mix(in srgb, var(--color-primary) 90%, black);
}
&:active:not([disabled]) {
background-color: color-mix(in srgb, var(--color-primary) 80%, black);
}
}
// 次要按钮样式
.secondary button {
background-color: transparent;
color: var(--color-primary);
border: 1px solid var(--color-primary);
&:hover:not([disabled]) {
background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
}
&:active:not([disabled]) {
background-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
}
}
// 第三级按钮样式
.tertiary button {
background-color: var(--color-gray-100);
color: var(--color-gray-700);
&:hover:not([disabled]) {
background-color: var(--color-gray-200);
}
&:active:not([disabled]) {
background-color: var(--color-gray-300);
}
@media (prefers-color-scheme: dark) {
background-color: var(--color-gray-700);
color: var(--color-gray-200);
&:hover:not([disabled]) {
background-color: var(--color-gray-600);
}
&:active:not([disabled]) {
background-color: var(--color-gray-500);
}
}
}
// 警告按钮样式
.alert button {
background-color: var(--color-error);
color: white;
border-radius: var(--border-radius-sm);
&:hover:not([disabled]) {
background-color: color-mix(in srgb, var(--color-error) 90%, black);
}
}
.alert-secondary button {
background-color: var(--color-gray-100);
color: var(--color-error);
&:hover:not([disabled]) {
background-color: var(--color-gray-200);
}
@media (prefers-color-scheme: dark) {
background-color: var(--color-gray-700);
}
}
// 药丸按钮样式
.pill button {
background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
color: var(--color-primary);
border-radius: var(--border-radius-full);
height: 32px;
min-width: 90px;
&:hover:not([disabled]) {
background-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
}
}
// 文本按钮样式
.text button {
background-color: transparent;
color: var(--color-primary);
padding: 8px 12px;
min-width: auto;
&:hover:not([disabled]) {
background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
}
&.link {
text-decoration: none;
}
}
// 社交按钮样式
.social button {
background-color: var(--color-primary);
color: white;
border-radius: var(--border-radius-lg);
height: auto;
padding: 12px 24px;
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
}
// 图标样式
.button__icon {
display: flex;
align-items: center;
justify-content: center;
fill: currentColor;
height: 1em;
width: 1em;
&--before {
margin-right: 0.5em;
}
&--after {
margin-left: 0.5em;
}
}
// 全宽样式
.full-width {
width: 100%;
button {
width: 100%;
}
}
// 响应式调整
@media (max-width: 768px) {
.button-wrapper:not(.full-width) {
width: 100%;
button {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,302 @@
<script lang="ts">
import { onMount, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let modalTriggerElement: HTMLElement | null = null;
export let error: boolean = false;
export let dialogId: string = '';
export let dialogClassNames: string = '';
export let disableScrim: boolean = false;
export let showOnMount: boolean = false;
export let preventDefaultClose: boolean = false;
export let ariaLabelledBy: string | null = null;
export let ariaLabel: string | null = null;
export let size: 'sm' | 'md' | 'lg' | 'xl' | 'full' = 'md';
let ariaHidden: boolean = true;
let dialogElement: HTMLDialogElement;
let needsPolyfill: boolean = false;
let isDialogInShadow: boolean = false;
export function showModal() {
// 防止背景滚动
document.body.classList.add('modal-open');
// 处理 polyfill 情况
if (needsPolyfill) {
isDialogInShadow = isInShadow(dialogElement);
if (!isDialogInShadow) {
document.body.appendChild(dialogElement);
}
}
ariaHidden = false;
dialogElement.showModal();
// 焦点管理
const firstFocusable = dialogElement.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
) as HTMLElement;
firstFocusable?.focus();
}
export function close() {
document.body.classList.remove('modal-open');
// 清理 polyfill 添加的元素
if (needsPolyfill && !isDialogInShadow) {
try {
document.body.removeChild(dialogElement);
} catch (e) {
// 元素可能已经被移除
}
}
ariaHidden = true;
dialogElement.close();
// 恢复焦点到触发元素
modalTriggerElement?.focus();
}
function handleClose(e: Event) {
if (preventDefaultClose) {
e.preventDefault();
} else {
close();
}
dispatch('close');
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape' && !preventDefaultClose) {
close();
}
// 焦点陷阱
if (e.key === 'Tab') {
trapFocus(e);
}
}
function trapFocus(e: KeyboardEvent) {
const focusableElements = dialogElement.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
) as NodeListOf<HTMLElement>;
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey) {
if (document.activeElement === firstElement) {
lastElement?.focus();
e.preventDefault();
}
} else {
if (document.activeElement === lastElement) {
firstElement?.focus();
e.preventDefault();
}
}
}
function isInShadow(node: HTMLElement | ParentNode | null): boolean {
while (node) {
if (node.toString() === '[object ShadowRoot]') {
return true;
}
node = node.parentNode;
}
return false;
}
onMount(async () => {
// 检查是否需要 dialog polyfill
needsPolyfill = !('showModal' in dialogElement);
if (needsPolyfill) {
try {
const { default: dialogPolyfill } = await import('dialog-polyfill');
dialogPolyfill.registerDialog(dialogElement);
dialogElement.classList.add('dialog-polyfill');
} catch (error) {
console.warn('Dialog polyfill failed to load:', error);
}
}
if (showOnMount) {
showModal();
}
});
</script>
<svelte:window on:keydown={handleKeydown} />
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<dialog
data-testid="modal-dialog"
class="modal"
class:error
class:no-scrim={disableScrim}
class:size-sm={size === 'sm'}
class:size-md={size === 'md'}
class:size-lg={size === 'lg'}
class:size-xl={size === 'xl'}
class:size-full={size === 'full'}
class:needs-polyfill={needsPolyfill}
class={dialogClassNames}
id={dialogId}
bind:this={dialogElement}
on:click|self={handleClose}
on:close={handleClose}
on:cancel={handleClose}
aria-labelledby={ariaLabelledBy}
aria-label={ariaLabel}
aria-hidden={ariaHidden}
>
<div class="modal__content">
<slot {handleClose} />
</div>
</dialog>
<style lang="scss">
:global(body.modal-open) {
overflow: hidden;
}
.modal {
border: none;
border-radius: var(--border-radius-lg);
padding: 0;
background: transparent;
max-width: 90vw;
max-height: 90vh;
box-shadow: var(--shadow-lg);
&::backdrop {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
&.no-scrim::backdrop {
background-color: transparent;
backdrop-filter: none;
}
&.error {
border: 2px solid var(--color-error);
}
// 尺寸变体
&.size-sm {
width: 320px;
min-height: 200px;
}
&.size-md {
width: 480px;
min-height: 300px;
}
&.size-lg {
width: 640px;
min-height: 400px;
}
&.size-xl {
width: 800px;
min-height: 500px;
}
&.size-full {
width: 95vw;
height: 95vh;
max-width: none;
max-height: none;
}
// 响应式调整
@media (max-width: 768px) {
&:not(.size-full) {
width: 95vw;
max-width: 400px;
}
}
}
.modal__content {
background-color: var(--color-gray-50);
border-radius: var(--border-radius-lg);
padding: var(--spacing-lg);
width: 100%;
height: 100%;
overflow-y: auto;
@media (prefers-color-scheme: dark) {
background-color: var(--color-gray-800);
color: var(--color-gray-100);
}
}
// Polyfill 样式支持
:global(.needs-polyfill) {
position: absolute;
left: 0;
right: 0;
width: fit-content;
height: fit-content;
margin: auto;
border: none;
padding: 0;
background: transparent;
color: inherit;
display: block;
&:not([open]) {
display: none;
}
& + .backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
&._dialog_overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
}
}
// 动画效果
.modal {
animation: modalFadeIn 0.2s ease-out;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: scale(0.95) translateY(-10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
// 焦点样式
.modal :global(*:focus) {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
</style>

326
src/lib/i18n/index.ts Normal file
View File

@@ -0,0 +1,326 @@
import { writable, derived } from 'svelte/store';
import { browser } from '$app/environment';
export interface Locale {
code: string;
name: string;
flag: string;
rtl?: boolean;
}
export interface Translation {
[key: string]: string | Translation;
}
export interface I18nState {
currentLocale: Locale;
translations: Record<string, Translation>;
isLoading: boolean;
error: Error | null;
}
// 支持的语言列表
export const supportedLocales: Locale[] = [
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'zh', name: '中文', flag: '🇨🇳' },
{ code: 'ja', name: '日本語', flag: '🇯🇵' },
{ code: 'ko', name: '한국어', flag: '🇰🇷' },
{ code: 'es', name: 'Español', flag: '🇪🇸' },
{ code: 'fr', name: 'Français', flag: '🇫🇷' },
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' },
{ code: 'ar', name: 'العربية', flag: '🇸🇦', rtl: true }
];
// 默认翻译内容
const defaultTranslations: Record<string, Translation> = {
en: {
common: {
loading: 'Loading...',
error: 'Error',
retry: 'Retry',
cancel: 'Cancel',
confirm: 'Confirm',
save: 'Save',
delete: 'Delete',
edit: 'Edit',
close: 'Close',
back: 'Back',
next: 'Next',
previous: 'Previous',
search: 'Search',
filter: 'Filter',
sort: 'Sort',
more: 'More'
},
navigation: {
home: 'Home',
apps: 'Apps',
games: 'Games',
search: 'Search',
account: 'Account'
},
buttons: {
download: 'Download',
install: 'Install',
update: 'Update',
open: 'Open',
share: 'Share',
favorite: 'Favorite',
rate: 'Rate'
}
},
zh: {
common: {
loading: '加载中...',
error: '错误',
retry: '重试',
cancel: '取消',
confirm: '确认',
save: '保存',
delete: '删除',
edit: '编辑',
close: '关闭',
back: '返回',
next: '下一步',
previous: '上一步',
search: '搜索',
filter: '筛选',
sort: '排序',
more: '更多'
},
navigation: {
home: '首页',
apps: '应用',
games: '游戏',
search: '搜索',
account: '账户'
},
buttons: {
download: '下载',
install: '安装',
update: '更新',
open: '打开',
share: '分享',
favorite: '收藏',
rate: '评分'
}
}
};
// 检测浏览器语言
function detectBrowserLocale(): string {
if (!browser) return 'en';
const browserLang = navigator.language || 'en';
const langCode = browserLang.split('-')[0];
// 检查是否支持该语言
const supported = supportedLocales.find(locale => locale.code === langCode);
return supported ? langCode : 'en';
}
// 创建 i18n store
function createI18n() {
const defaultLocale = supportedLocales.find(l => l.code === 'en')!;
const initialState: I18nState = {
currentLocale: defaultLocale,
translations: defaultTranslations,
isLoading: false,
error: null
};
const { subscribe, set, update } = writable(initialState);
return {
subscribe,
// 初始化 i18n
init: async () => {
const detectedLocale = detectBrowserLocale();
const locale = supportedLocales.find(l => l.code === detectedLocale) || defaultLocale;
update(state => ({ ...state, isLoading: true }));
try {
await loadLocale(locale.code);
update(state => ({
...state,
currentLocale: locale,
isLoading: false
}));
// 设置 HTML 属性
if (browser) {
document.documentElement.lang = locale.code;
document.documentElement.dir = locale.rtl ? 'rtl' : 'ltr';
}
} catch (error) {
update(state => ({
...state,
isLoading: false,
error: error as Error
}));
}
},
// 切换语言
setLocale: async (localeCode: string) => {
const locale = supportedLocales.find(l => l.code === localeCode);
if (!locale) {
throw new Error(`Unsupported locale: ${localeCode}`);
}
update(state => ({ ...state, isLoading: true, error: null }));
try {
await loadLocale(localeCode);
update(state => ({
...state,
currentLocale: locale,
isLoading: false
}));
// 更新 HTML 属性
if (browser) {
document.documentElement.lang = locale.code;
document.documentElement.dir = locale.rtl ? 'rtl' : 'ltr';
// 保存到 localStorage
localStorage.setItem('preferred-locale', localeCode);
}
} catch (error) {
update(state => ({
...state,
isLoading: false,
error: error as Error
}));
}
},
// 添加翻译
addTranslations: (localeCode: string, translations: Translation) => {
update(state => ({
...state,
translations: {
...state.translations,
[localeCode]: {
...state.translations[localeCode],
...translations
}
}
}));
},
// 清除错误
clearError: () => {
update(state => ({ ...state, error: null }));
}
};
}
// 加载语言包
async function loadLocale(localeCode: string): Promise<void> {
// 如果已经有默认翻译,直接返回
if (defaultTranslations[localeCode]) {
return;
}
// 模拟异步加载翻译文件
// 在实际应用中,这里会从服务器或本地文件加载翻译
await new Promise(resolve => setTimeout(resolve, 100));
// 这里可以添加动态加载翻译文件的逻辑
console.log(`Loading translations for ${localeCode}`);
}
// 创建 i18n 实例
export const i18n = createI18n();
// 派生状态
export const currentLocale = derived(i18n, $i18n => $i18n.currentLocale);
export const translations = derived(i18n, $i18n => $i18n.translations);
export const isI18nLoading = derived(i18n, $i18n => $i18n.isLoading);
// 翻译函数
export const t = derived(
[currentLocale, translations],
([$currentLocale, $translations]) => {
return (key: string, params?: Record<string, string | number>): string => {
const localeTranslations = $translations[$currentLocale.code] || $translations.en;
const value = getNestedValue(localeTranslations, key);
if (typeof value !== 'string') {
console.warn(`Translation not found for key: ${key}`);
return key;
}
// 参数替换
if (params) {
return value.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
return params[paramKey]?.toString() || match;
});
}
return value;
};
}
);
// 获取嵌套对象的值
function getNestedValue(obj: any, path: string): any {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : undefined;
}, obj);
}
// 格式化数字
export const formatNumber = derived(currentLocale, $currentLocale => {
return (value: number, options?: Intl.NumberFormatOptions): string => {
if (!browser) return value.toString();
try {
return new Intl.NumberFormat($currentLocale.code, options).format(value);
} catch {
return value.toString();
}
};
});
// 格式化日期
export const formatDate = derived(currentLocale, $currentLocale => {
return (date: Date, options?: Intl.DateTimeFormatOptions): string => {
if (!browser) return date.toISOString();
try {
return new Intl.DateTimeFormat($currentLocale.code, options).format(date);
} catch {
return date.toLocaleDateString();
}
};
});
// 格式化相对时间
export const formatRelativeTime = derived(currentLocale, $currentLocale => {
return (value: number, unit: Intl.RelativeTimeFormatUnit): string => {
if (!browser) return `${value} ${unit}`;
try {
const rtf = new Intl.RelativeTimeFormat($currentLocale.code, { numeric: 'auto' });
return rtf.format(value, unit);
} catch {
return `${value} ${unit}`;
}
};
});
// 初始化 i18n在浏览器环境中
if (browser) {
// 从 localStorage 恢复用户偏好
const savedLocale = localStorage.getItem('preferred-locale');
if (savedLocale && supportedLocales.find(l => l.code === savedLocale)) {
i18n.setLocale(savedLocale);
} else {
i18n.init();
}
}

223
src/lib/stores/router.ts Normal file
View File

@@ -0,0 +1,223 @@
import { writable, derived } from 'svelte/store';
import { browser } from '$app/environment';
export interface Page {
id: string;
title: string;
url: string;
data?: any;
meta?: {
description?: string;
keywords?: string[];
image?: string;
};
}
export interface RouterState {
currentPage: Page | null;
isLoading: boolean;
error: Error | null;
history: Page[];
}
// 创建路由状态
function createRouter() {
const initialState: RouterState = {
currentPage: null,
isLoading: false,
error: null,
history: []
};
const { subscribe, set, update } = writable(initialState);
return {
subscribe,
// 导航到新页面
navigate: async (url: string, options?: { replace?: boolean; data?: any }) => {
if (!browser) return;
update(state => ({ ...state, isLoading: true, error: null }));
try {
const page = await loadPage(url, options?.data);
update(state => {
const newHistory = options?.replace
? state.history
: [...state.history, page];
return {
...state,
currentPage: page,
isLoading: false,
history: newHistory
};
});
// 更新浏览器历史
if (options?.replace) {
history.replaceState({ page }, page.title, url);
} else {
history.pushState({ page }, page.title, url);
}
// 更新页面标题
document.title = page.title;
} catch (error) {
update(state => ({
...state,
isLoading: false,
error: error as Error
}));
}
},
// 返回上一页
goBack: () => {
if (browser && window.history.length > 1) {
window.history.back();
}
},
// 前进到下一页
goForward: () => {
if (browser) {
window.history.forward();
}
},
// 重新加载当前页面
reload: async () => {
update(state => {
if (state.currentPage) {
return { ...state, isLoading: true, error: null };
}
return state;
});
const currentState = get(router);
if (currentState.currentPage) {
try {
const page = await loadPage(currentState.currentPage.url);
update(state => ({
...state,
currentPage: page,
isLoading: false
}));
} catch (error) {
update(state => ({
...state,
isLoading: false,
error: error as Error
}));
}
}
},
// 清除错误状态
clearError: () => {
update(state => ({ ...state, error: null }));
},
// 设置当前页面(用于 SSR
setCurrentPage: (page: Page) => {
update(state => ({
...state,
currentPage: page,
isLoading: false,
error: null
}));
}
};
}
// 页面加载函数
async function loadPage(url: string, data?: any): Promise<Page> {
// 模拟页面加载延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 这里应该根据 URL 加载对应的页面数据
// 在实际应用中,这可能涉及 API 调用或路由匹配
const pageId = extractPageId(url);
return {
id: pageId,
title: `Page ${pageId}`,
url,
data,
meta: {
description: `Description for ${pageId}`,
keywords: [pageId, 'app', 'store']
}
};
}
// 从 URL 提取页面 ID
function extractPageId(url: string): string {
const segments = url.split('/').filter(Boolean);
return segments[segments.length - 1] || 'home';
}
// 获取当前 store 值的辅助函数
function get<T>(store: { subscribe: (fn: (value: T) => void) => () => void }): T {
let value: T;
const unsubscribe = store.subscribe(v => value = v);
unsubscribe();
return value!;
}
// 创建路由实例
export const router = createRouter();
// 派生状态
export const currentPage = derived(router, $router => $router.currentPage);
export const isLoading = derived(router, $router => $router.isLoading);
export const routerError = derived(router, $router => $router.error);
export const canGoBack = derived(router, $router => $router.history.length > 1);
// 浏览器历史事件处理
if (browser) {
window.addEventListener('popstate', (event) => {
if (event.state?.page) {
router.setCurrentPage(event.state.page);
} else {
// 如果没有状态,重新加载当前 URL 的页面
loadPage(window.location.pathname + window.location.search)
.then(page => router.setCurrentPage(page))
.catch(error => console.error('Failed to load page on popstate:', error));
}
});
}
// 路由守卫类型
export type RouteGuard = (to: Page, from: Page | null) => boolean | Promise<boolean>;
// 路由守卫管理
class RouteGuardManager {
private guards: RouteGuard[] = [];
addGuard(guard: RouteGuard) {
this.guards.push(guard);
return () => {
const index = this.guards.indexOf(guard);
if (index > -1) {
this.guards.splice(index, 1);
}
};
}
async checkGuards(to: Page, from: Page | null): Promise<boolean> {
for (const guard of this.guards) {
const result = await guard(to, from);
if (!result) {
return false;
}
}
return true;
}
}
export const routeGuards = new RouteGuardManager();

346
src/lib/stores/theme.ts Normal file
View File

@@ -0,0 +1,346 @@
import { writable, derived } from 'svelte/store';
import { browser } from '$app/environment';
export type ThemeMode = 'light' | 'dark' | 'auto';
export interface ThemeConfig {
mode: ThemeMode;
primaryColor: string;
accentColor: string;
borderRadius: 'none' | 'sm' | 'md' | 'lg' | 'xl';
fontScale: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
reducedMotion: boolean;
highContrast: boolean;
}
export interface ThemeState {
config: ThemeConfig;
resolvedMode: 'light' | 'dark';
isSystemDark: boolean;
}
// 默认主题配置
const defaultThemeConfig: ThemeConfig = {
mode: 'auto',
primaryColor: '#007AFF',
accentColor: '#5856D6',
borderRadius: 'md',
fontScale: 'md',
reducedMotion: false,
highContrast: false
};
// 主题预设
export const themePresets = {
apple: {
primaryColor: '#007AFF',
accentColor: '#5856D6'
},
google: {
primaryColor: '#4285F4',
accentColor: '#34A853'
},
github: {
primaryColor: '#24292e',
accentColor: '#0366d6'
},
spotify: {
primaryColor: '#1DB954',
accentColor: '#1ed760'
}
};
// 检测系统主题偏好
function detectSystemTheme(): boolean {
if (!browser) return false;
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
// 检测用户偏好
function detectUserPreferences(): Partial<ThemeConfig> {
if (!browser) return {};
const preferences: Partial<ThemeConfig> = {};
// 检测动画偏好
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
preferences.reducedMotion = true;
}
// 检测对比度偏好
if (window.matchMedia('(prefers-contrast: high)').matches) {
preferences.highContrast = true;
}
return preferences;
}
// 创建主题 store
function createTheme() {
const initialState: ThemeState = {
config: defaultThemeConfig,
resolvedMode: 'light',
isSystemDark: false
};
const { subscribe, set, update } = writable(initialState);
return {
subscribe,
// 初始化主题
init: () => {
if (!browser) return;
// 从 localStorage 恢复配置
const savedConfig = localStorage.getItem('theme-config');
let config = defaultThemeConfig;
if (savedConfig) {
try {
config = { ...defaultThemeConfig, ...JSON.parse(savedConfig) };
} catch (error) {
console.warn('Failed to parse saved theme config:', error);
}
}
// 应用用户系统偏好
const userPreferences = detectUserPreferences();
config = { ...config, ...userPreferences };
const isSystemDark = detectSystemTheme();
const resolvedMode = config.mode === 'auto'
? (isSystemDark ? 'dark' : 'light')
: config.mode;
update(() => ({
config,
resolvedMode,
isSystemDark
}));
// 应用主题到 DOM
applyTheme(config, resolvedMode);
// 监听系统主题变化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleSystemThemeChange = (e: MediaQueryListEvent) => {
update(state => {
const newIsSystemDark = e.matches;
const newResolvedMode = state.config.mode === 'auto'
? (newIsSystemDark ? 'dark' : 'light')
: state.config.mode;
if (newResolvedMode !== state.resolvedMode) {
applyTheme(state.config, newResolvedMode);
}
return {
...state,
isSystemDark: newIsSystemDark,
resolvedMode: newResolvedMode
};
});
};
mediaQuery.addEventListener('change', handleSystemThemeChange);
// 返回清理函数
return () => {
mediaQuery.removeEventListener('change', handleSystemThemeChange);
};
},
// 设置主题模式
setMode: (mode: ThemeMode) => {
update(state => {
const newConfig = { ...state.config, mode };
const newResolvedMode = mode === 'auto'
? (state.isSystemDark ? 'dark' : 'light')
: mode;
applyTheme(newConfig, newResolvedMode);
saveConfig(newConfig);
return {
...state,
config: newConfig,
resolvedMode: newResolvedMode
};
});
},
// 设置主色调
setPrimaryColor: (color: string) => {
update(state => {
const newConfig = { ...state.config, primaryColor: color };
applyTheme(newConfig, state.resolvedMode);
saveConfig(newConfig);
return { ...state, config: newConfig };
});
},
// 设置强调色
setAccentColor: (color: string) => {
update(state => {
const newConfig = { ...state.config, accentColor: color };
applyTheme(newConfig, state.resolvedMode);
saveConfig(newConfig);
return { ...state, config: newConfig };
});
},
// 应用主题预设
applyPreset: (presetName: keyof typeof themePresets) => {
const preset = themePresets[presetName];
if (!preset) return;
update(state => {
const newConfig = { ...state.config, ...preset };
applyTheme(newConfig, state.resolvedMode);
saveConfig(newConfig);
return { ...state, config: newConfig };
});
},
// 设置圆角大小
setBorderRadius: (radius: ThemeConfig['borderRadius']) => {
update(state => {
const newConfig = { ...state.config, borderRadius: radius };
applyTheme(newConfig, state.resolvedMode);
saveConfig(newConfig);
return { ...state, config: newConfig };
});
},
// 设置字体缩放
setFontScale: (scale: ThemeConfig['fontScale']) => {
update(state => {
const newConfig = { ...state.config, fontScale: scale };
applyTheme(newConfig, state.resolvedMode);
saveConfig(newConfig);
return { ...state, config: newConfig };
});
},
// 切换减少动画
toggleReducedMotion: () => {
update(state => {
const newConfig = { ...state.config, reducedMotion: !state.config.reducedMotion };
applyTheme(newConfig, state.resolvedMode);
saveConfig(newConfig);
return { ...state, config: newConfig };
});
},
// 切换高对比度
toggleHighContrast: () => {
update(state => {
const newConfig = { ...state.config, highContrast: !state.config.highContrast };
applyTheme(newConfig, state.resolvedMode);
saveConfig(newConfig);
return { ...state, config: newConfig };
});
},
// 重置主题
reset: () => {
const isSystemDark = detectSystemTheme();
const resolvedMode = defaultThemeConfig.mode === 'auto'
? (isSystemDark ? 'dark' : 'light')
: defaultThemeConfig.mode;
update(() => ({
config: defaultThemeConfig,
resolvedMode,
isSystemDark
}));
applyTheme(defaultThemeConfig, resolvedMode);
if (browser) {
localStorage.removeItem('theme-config');
}
}
};
}
// 应用主题到 DOM
function applyTheme(config: ThemeConfig, resolvedMode: 'light' | 'dark') {
if (!browser) return;
const root = document.documentElement;
// 设置主题模式
root.setAttribute('data-theme', resolvedMode);
root.classList.toggle('dark', resolvedMode === 'dark');
// 设置颜色
root.style.setProperty('--color-primary', config.primaryColor);
root.style.setProperty('--color-accent', config.accentColor);
// 设置圆角
const radiusMap = {
none: '0px',
sm: '4px',
md: '8px',
lg: '12px',
xl: '16px'
};
root.style.setProperty('--border-radius-base', radiusMap[config.borderRadius]);
// 设置字体缩放
const fontScaleMap = {
xs: '0.875',
sm: '0.9375',
md: '1',
lg: '1.125',
xl: '1.25'
};
root.style.setProperty('--font-scale', fontScaleMap[config.fontScale]);
// 设置动画偏好
root.style.setProperty('--transition-duration', config.reducedMotion ? '0ms' : '250ms');
// 设置对比度
root.classList.toggle('high-contrast', config.highContrast);
}
// 保存配置到 localStorage
function saveConfig(config: ThemeConfig) {
if (!browser) return;
try {
localStorage.setItem('theme-config', JSON.stringify(config));
} catch (error) {
console.warn('Failed to save theme config:', error);
}
}
// 创建主题实例
export const theme = createTheme();
// 派生状态
export const themeMode = derived(theme, $theme => $theme.config.mode);
export const resolvedThemeMode = derived(theme, $theme => $theme.resolvedMode);
export const primaryColor = derived(theme, $theme => $theme.config.primaryColor);
export const accentColor = derived(theme, $theme => $theme.config.accentColor);
export const isDark = derived(theme, $theme => $theme.resolvedMode === 'dark');
// 主题切换辅助函数
export const toggleTheme = () => {
theme.setMode(resolvedThemeMode.subscribe(mode => {
theme.setMode(mode === 'dark' ? 'light' : 'dark');
}));
};
// 初始化主题(在浏览器环境中)
if (browser) {
theme.init();
}

View File

@@ -0,0 +1,121 @@
/* 设计系统变量 */
:root {
/* 颜色系统 - 基于苹果设计语言 */
--color-primary: #007AFF;
--color-secondary: #5856D6;
--color-success: #34C759;
--color-warning: #FF9500;
--color-error: #FF3B30;
/* 灰度系统 */
--color-gray-50: #F9FAFB;
--color-gray-100: #F3F4F6;
--color-gray-200: #E5E7EB;
--color-gray-300: #D1D5DB;
--color-gray-400: #9CA3AF;
--color-gray-500: #6B7280;
--color-gray-600: #4B5563;
--color-gray-700: #374151;
--color-gray-800: #1F2937;
--color-gray-900: #111827;
/* 系统颜色 - 适配苹果原有变量 */
--systemBlue: var(--color-primary);
--systemGray: var(--color-gray-500);
--systemQuinary: var(--color-gray-100);
--systemTertiary: var(--color-gray-400);
--systemTertiary-onLight: var(--color-gray-600);
/* 按钮相关变量 */
--keyColorBG: var(--color-primary);
--buttonBackgroundColor: var(--keyColorBG);
--buttonTextColor: white;
--buttonDisabledOpacity: 0.5;
--buttonDisabledBGColor: var(--color-gray-200);
--buttonDisabledTextColor: var(--color-gray-400);
/* 间距系统 */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
/* 字体系统 */
--font-family-primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-md: 16px;
--font-size-lg: 18px;
--font-size-xl: 20px;
--font-size-2xl: 24px;
/* 字体权重 */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* 行高 */
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* 圆角 */
--border-radius-sm: 4px;
--border-radius-md: 8px;
--border-radius-lg: 12px;
--border-radius-xl: 16px;
--border-radius-full: 9999px;
/* 阴影 */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
/* 过渡动画 */
--transition-fast: 150ms ease-in-out;
--transition-normal: 250ms ease-in-out;
--transition-slow: 350ms ease-in-out;
}
/* 暗色模式 */
@media (prefers-color-scheme: dark) {
:root {
--color-primary: #0A84FF;
--color-gray-50: #1F2937;
--color-gray-100: #374151;
--color-gray-200: #4B5563;
--color-gray-300: #6B7280;
--color-gray-400: #9CA3AF;
--color-gray-500: #D1D5DB;
--color-gray-600: #E5E7EB;
--color-gray-700: #F3F4F6;
--color-gray-800: #F9FAFB;
--color-gray-900: #FFFFFF;
--buttonDisabledOpacityDark: 1;
--buttonDisabledBGColorDark: rgba(255, 255, 255, 0.5);
--buttonDisabledTextColorDark: var(--systemTertiary-onLight);
}
}
/* 全局重置和基础样式 */
* {
box-sizing: border-box;
}
body {
font-family: var(--font-family-primary);
line-height: var(--line-height-normal);
color: var(--color-gray-900);
background-color: var(--color-gray-50);
margin: 0;
padding: 0;
}
/* SCSS 变量用于断点 */
$breakpoint-small: 640px;
$breakpoint-medium: 768px;
$breakpoint-large: 1024px;

View File

@@ -0,0 +1,19 @@
/**
* 创建一个安全的 tick 函数,用于在组件挂载后安全地执行操作
* 这是对苹果原有 @amp/web-app-components 中 makeSafeTick 的替代实现
*/
export function makeSafeTick() {
return async (callback: (tick: () => Promise<void>) => Promise<void>) => {
// 创建一个 tick 函数,它会在下一个微任务中执行
const tick = () => new Promise<void>(resolve => {
// 使用 setTimeout 确保在下一个事件循环中执行
setTimeout(resolve, 0);
});
try {
await callback(tick);
} catch (error) {
console.error('SafeTick callback error:', error);
}
};
}

439
src/routes/+layout.svelte Normal file
View File

@@ -0,0 +1,439 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
let isDarkMode = false;
let currentPath = '/';
let currentCategory: string | null = null;
const primaryNav = [
{ label: 'Today', href: '/', icon: 'today' },
{ label: 'Projects', href: '/projects', icon: 'projects' },
{ label: 'About', href: '/about', icon: 'about' },
{ label: 'Contact', href: '/contact', icon: 'contact' }
];
const categoryNav = [
{ label: 'Brand Design', href: '/projects?category=Brand Design' },
{ label: 'UI/UX Design', href: '/projects?category=UI/UX Design' },
{ label: 'Web Design', href: '/projects?category=Web Design' },
{ label: 'Graphic Design', href: '/projects?category=Graphic Design' },
{ label: 'Photography', href: '/projects?category=Photography' },
{ label: 'Print Design', href: '/projects?category=Print Design' }
];
function toggleTheme() {
isDarkMode = !isDarkMode;
document.documentElement.classList.toggle('dark', isDarkMode);
}
function navigateTo(href: string) {
goto(href);
}
$: currentPath = $page.url.pathname;
$: currentCategory = $page.url.searchParams.get('category');
onMount(() => {
// 检测系统主题偏好
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
isDarkMode = prefersDark;
document.documentElement.classList.toggle('dark', isDarkMode);
});
</script>
<svelte:head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="LiuBai Design - A modern creative studio experience built with SvelteKit" />
</svelte:head>
<div class="app-shell">
<aside class="sidebar">
<header class="sidebar__header">
<div class="sidebar__store-icon" aria-hidden="true">🎨</div>
<div class="sidebar__heading">
<span class="sidebar__title">LiuBai Design</span>
<span class="sidebar__subtitle">Creative Studio</span>
</div>
</header>
<div class="sidebar__search">
<span class="sidebar__search-icon" aria-hidden="true">🔍</span>
<input type="search" placeholder="Search" aria-label="Search LiuBai Design" />
</div>
<nav class="sidebar__nav">
<div class="sidebar__section" aria-label="Primary navigation">
{#each primaryNav as item}
<button
class:active={item.href === '/' ? currentPath === '/' : currentPath.startsWith(item.href)}
on:click={() => navigateTo(item.href)}
>
<span class="sidebar__bullet" aria-hidden="true"></span>
{item.label}
</button>
{/each}
</div>
<div class="sidebar__section" aria-label="Design categories">
<span class="sidebar__section-label">Design Categories</span>
{#each categoryNav as item}
<button
class:active={currentCategory === item.label}
on:click={() => navigateTo(item.href)}
>
{item.label}
</button>
{/each}
</div>
</nav>
<footer class="sidebar__footer">
<button on:click={toggleTheme} aria-label="Toggle theme">
<span class="sidebar__avatar" aria-hidden="true">{isDarkMode ? '☀️' : '🌙'}</span>
{isDarkMode ? 'Light' : 'Dark'}
</button>
<button aria-label="Account">
<span class="sidebar__avatar" aria-hidden="true">🧑‍💻</span>
Account
</button>
</footer>
</aside>
<main class="content">
<slot />
</main>
</div>
<style>
:global(html) {
height: 100%;
scroll-behavior: smooth;
}
:global(body) {
margin: 0;
padding: 0;
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* 亮色模式 */
:global(body) {
background: #f5f5f7;
color: #1d1d1f;
}
/* 暗色模式 */
:global(.dark body) {
background: radial-gradient(circle at top left, rgba(46, 46, 82, 0.6), transparent 40%),
radial-gradient(circle at bottom right, rgba(36, 36, 60, 0.5), transparent 45%),
#050506;
color: #ffffff;
}
.app-shell {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
min-height: 100vh;
}
.sidebar {
display: grid;
grid-template-rows: auto auto 1fr auto;
gap: 32px;
padding: 32px 24px;
background: rgba(255, 255, 255, 0.8);
border-right: 1px solid rgba(0, 0, 0, 0.08);
backdrop-filter: blur(30px);
box-shadow: 12px 0 40px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
:global(.dark) .sidebar {
background: rgba(10, 10, 18, 0.9);
border-right: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 12px 0 40px rgba(0, 0, 0, 0.35);
}
.sidebar__header {
display: flex;
gap: 12px;
align-items: center;
}
.sidebar__store-icon {
width: 36px;
height: 36px;
border-radius: 12px;
background: linear-gradient(135deg, #4f46e5 0%, #9333ea 100%);
display: grid;
place-items: center;
font-size: 20px;
color: white;
}
.sidebar__heading {
display: flex;
flex-direction: column;
color: rgba(0, 0, 0, 0.9);
transition: color 0.3s ease;
}
:global(.dark) .sidebar__heading {
color: rgba(255, 255, 255, 0.9);
}
.sidebar__title {
font-size: 15px;
font-weight: 600;
letter-spacing: 0.02em;
}
.sidebar__subtitle {
font-size: 13px;
color: rgba(255, 255, 255, 0.55);
}
.sidebar__search {
position: relative;
display: flex;
align-items: center;
}
.sidebar__search-icon {
position: absolute;
left: 14px;
font-size: 14px;
color: rgba(0, 0, 0, 0.4);
transition: color 0.3s ease;
}
:global(.dark) .sidebar__search-icon {
color: rgba(255, 255, 255, 0.4);
}
.sidebar__search input {
width: 100%;
padding: 10px 14px 10px 34px;
border-radius: 12px;
border: none;
background: rgba(0, 0, 0, 0.08);
color: rgba(0, 0, 0, 0.88);
font-size: 13px;
transition: all 0.3s ease;
}
:global(.dark) .sidebar__search input {
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.88);
}
.sidebar__search input:focus {
outline: none;
background: rgba(0, 0, 0, 0.12);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.35);
}
:global(.dark) .sidebar__search input:focus {
background: rgba(255, 255, 255, 0.12);
box-shadow: 0 0 0 2px rgba(147, 197, 253, 0.35);
}
.sidebar__nav {
display: flex;
flex-direction: column;
gap: 36px;
}
.sidebar__section {
display: grid;
gap: 4px;
}
.sidebar__section-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: rgba(0, 0, 0, 0.35);
margin-bottom: 8px;
transition: color 0.3s ease;
}
:global(.dark) .sidebar__section-label {
color: rgba(255, 255, 255, 0.35);
}
.sidebar__section button {
position: relative;
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-radius: 12px;
border: none;
background: transparent;
color: rgba(0, 0, 0, 0.66);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
:global(.dark) .sidebar__section button {
color: rgba(255, 255, 255, 0.66);
}
.sidebar__section button:hover {
color: rgba(0, 0, 0, 0.9);
background: rgba(0, 0, 0, 0.06);
}
:global(.dark) .sidebar__section button:hover {
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.06);
}
.sidebar__section button.active {
color: #ffffff;
background: linear-gradient(135deg, rgba(88, 88, 255, 0.45), rgba(138, 92, 255, 0.45));
}
.sidebar__bullet {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.4);
transition: background 0.3s ease;
}
:global(.dark) .sidebar__bullet {
background: rgba(255, 255, 255, 0.4);
}
.sidebar__section button.active .sidebar__bullet {
background: #ffffff;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.8);
}
.sidebar__footer button {
width: 100%;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.08);
background: rgba(0, 0, 0, 0.03);
color: rgba(0, 0, 0, 0.72);
font-size: 13px;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 8px;
}
:global(.dark) .sidebar__footer button {
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
color: rgba(255, 255, 255, 0.72);
}
.sidebar__footer button:hover {
border-color: rgba(0, 0, 0, 0.2);
background: rgba(0, 0, 0, 0.08);
color: #000000;
}
:global(.dark) .sidebar__footer button:hover {
border-color: rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.08);
color: #ffffff;
}
.sidebar__avatar {
width: 28px;
height: 28px;
border-radius: 9px;
background: rgba(0, 0, 0, 0.1);
display: grid;
place-items: center;
transition: background 0.3s ease;
}
:global(.dark) .sidebar__avatar {
background: rgba(255, 255, 255, 0.1);
}
.content {
position: relative;
overflow-y: auto;
padding: 48px 56px 64px;
background: linear-gradient(180deg, rgba(245, 245, 247, 0.9) 0%, rgba(240, 240, 242, 0.92) 55%, rgba(235, 235, 237, 0.96) 100%);
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
:global(.dark) .content {
background: linear-gradient(180deg, rgba(16, 16, 28, 0.9) 0%, rgba(8, 8, 18, 0.92) 55%, rgba(6, 6, 12, 0.96) 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.content::-webkit-scrollbar {
width: 10px;
}
.content::-webkit-scrollbar-track {
background: rgba(200, 200, 200, 0.7);
}
:global(.dark) .content::-webkit-scrollbar-track {
background: rgba(12, 12, 18, 0.7);
}
.content::-webkit-scrollbar-thumb {
background: rgba(150, 150, 150, 0.45);
border-radius: 6px;
}
:global(.dark) .content::-webkit-scrollbar-thumb {
background: rgba(110, 110, 145, 0.45);
}
@media (max-width: 1024px) {
.app-shell {
grid-template-columns: 240px 1fr;
}
.content {
padding: 36px 32px 48px;
}
}
@media (max-width: 860px) {
.app-shell {
grid-template-columns: 1fr;
}
.sidebar {
position: sticky;
top: 0;
z-index: 10;
grid-template-rows: auto;
grid-auto-flow: column;
overflow-x: auto;
padding: 20px 24px;
gap: 24px;
}
.sidebar__nav,
.sidebar__footer {
display: none;
}
.content {
padding: 24px;
}
}
</style>

700
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,700 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
// 特色项目数据
const featuredProjects = [
{
id: 1,
title: 'Creative Brand Identity',
subtitle: 'Complete visual system for modern brands',
badge: 'FEATURED PROJECT',
category: 'Brand Design',
description: 'A comprehensive brand identity system featuring logo design, color palettes, typography, and brand guidelines.',
bgColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%), radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.15) 0%, transparent 50%)',
textColor: 'white',
image: '🎨',
buttonText: 'View Project'
},
{
id: 2,
title: 'Mobile App Interface',
subtitle: 'Modern UI/UX for digital experiences',
badge: 'UI/UX DESIGN',
category: 'Digital Design',
description: 'Intuitive mobile interface design with focus on user experience and modern aesthetics.',
bgColor: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%), radial-gradient(circle at 20% 80%, rgba(240, 147, 251, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.15) 0%, transparent 50%)',
textColor: 'white',
image: '📱',
buttonText: 'Explore'
}
];
const smallProjects = [
{
id: 3,
name: 'Web Portfolio',
category: 'Web Design',
icon: '💻',
bgColor: '#4f46e5'
},
{
id: 4,
name: 'Print Design',
category: 'Graphic Design',
icon: '📄',
bgColor: '#059669'
},
{
id: 5,
name: 'Photography',
category: 'Visual Arts',
icon: '📸',
bgColor: '#dc2626'
},
{
id: 6,
name: 'Illustration',
category: 'Digital Art',
icon: '🖼️',
bgColor: '#7c3aed'
}
];
function navigateToProject(projectId: number) {
goto(`/projects/${projectId}`);
}
function navigateToProjects() {
goto('/projects');
}
function navigateToContact() {
goto('/contact');
}
onMount(() => {
console.log('Home page mounted');
});
</script>
<svelte:head>
<title>LiuBai Design - Creative Studio</title>
<meta name="description" content="LiuBai Design - Discover amazing creative works and design solutions" />
</svelte:head>
<div class="today-page">
<!-- 页面标题 -->
<header class="page-header">
<h1 class="page-title">Today</h1>
<p class="page-subtitle">Discover our latest creative works and design solutions</p>
</header>
<!-- 主要特色项目 -->
<section class="hero-section">
<div class="hero-card" style="background: {featuredProjects[0].bgColor}" on:click={() => navigateToProject(featuredProjects[0].id)} role="button" tabindex="0">
<div class="hero-content">
<div class="hero-badge">{featuredProjects[0].badge}</div>
<h2 class="hero-title">{featuredProjects[0].title}</h2>
<p class="hero-description">{featuredProjects[0].description}</p>
<div class="hero-project-info">
<div class="project-icon">{featuredProjects[0].image}</div>
<div class="project-details">
<h3 class="project-name">{featuredProjects[0].subtitle}</h3>
<p class="project-category">{featuredProjects[0].category}</p>
</div>
<button class="project-button" on:click|stopPropagation={() => navigateToProject(featuredProjects[0].id)}>{featuredProjects[0].buttonText}</button>
</div>
</div>
</div>
</section>
<!-- 项目展示区域 -->
<section class="projects-section">
<div class="section-header">
<h2 class="section-title">Featured Works</h2>
<p class="section-subtitle">Explore our creative portfolio</p>
</div>
<div class="projects-grid">
<!-- 大型项目卡片 -->
<div class="large-project-card" style="background: {featuredProjects[1].bgColor}" on:click={() => navigateToProject(featuredProjects[1].id)} role="button" tabindex="0">
<div class="card-content">
<div class="card-badge">{featuredProjects[1].badge}</div>
<h3 class="card-title">{featuredProjects[1].title}</h3>
<p class="card-description">{featuredProjects[1].description}</p>
<div class="card-project-info">
<div class="project-icon small">{featuredProjects[1].image}</div>
<div class="project-details">
<h4 class="project-name">{featuredProjects[1].subtitle}</h4>
<p class="project-category">{featuredProjects[1].category}</p>
</div>
<button class="project-button small" on:click|stopPropagation={() => navigateToProject(featuredProjects[1].id)}>{featuredProjects[1].buttonText}</button>
</div>
</div>
</div>
<!-- 小型项目卡片网格 -->
<div class="small-projects-grid">
{#each smallProjects as project}
<div class="small-project-card" on:click={navigateToProjects} role="button" tabindex="0">
<div class="small-card-icon" style="background-color: {project.bgColor}">
{project.icon}
</div>
<h4 class="small-card-title">{project.name}</h4>
<p class="small-card-category">{project.category}</p>
<button class="small-card-button" on:click|stopPropagation={navigateToProjects}>View</button>
</div>
{/each}
</div>
</div>
</section>
<!-- 服务展示 -->
<section class="services-section">
<div class="section-header">
<h2 class="section-title">Our Services</h2>
<p class="section-subtitle">What we can create for you</p>
</div>
<div class="services-grid">
<div class="service-card" on:click={navigateToContact} role="button" tabindex="0">
<div class="service-icon">🎨</div>
<h3 class="service-title">Brand Identity</h3>
<p class="service-description">Complete visual identity systems that tell your brand's story</p>
</div>
<div class="service-card" on:click={navigateToContact} role="button" tabindex="0">
<div class="service-icon">💻</div>
<h3 class="service-title">Web Design</h3>
<p class="service-description">Modern, responsive websites that engage and convert</p>
</div>
<div class="service-card" on:click={navigateToContact} role="button" tabindex="0">
<div class="service-icon">📱</div>
<h3 class="service-title">UI/UX Design</h3>
<p class="service-description">Intuitive interfaces that users love to interact with</p>
</div>
<div class="service-card" on:click={navigateToContact} role="button" tabindex="0">
<div class="service-icon">📸</div>
<h3 class="service-title">Photography</h3>
<p class="service-description">Professional photography for brands and products</p>
</div>
</div>
</section>
</div>
<style>
.today-page {
max-width: 1200px;
margin: 0 auto;
padding: 0;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.page-header {
margin-bottom: 32px;
}
.page-title {
font-size: 48px;
font-weight: 700;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .page-title {
color: #f5f5f7;
}
.page-subtitle {
font-size: 18px;
color: #6B7280;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .page-subtitle {
color: #9CA3AF;
}
/* Hero Section */
.hero-section {
margin-bottom: 48px;
}
.hero-card {
border-radius: 24px;
padding: 48px;
color: white;
position: relative;
overflow: hidden;
min-height: 400px;
display: flex;
align-items: flex-end;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.hero-card:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
}
.hero-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 50%);
pointer-events: none;
}
.hero-content {
width: 100%;
z-index: 2;
}
.hero-badge {
display: inline-block;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
animation: slideInUp 0.8s ease-out 0.2s both;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.hero-title {
font-size: 36px;
font-weight: 700;
margin-bottom: 16px;
line-height: 1.2;
animation: slideInUp 0.8s ease-out 0.4s both;
}
.hero-description {
font-size: 18px;
margin-bottom: 32px;
opacity: 0.9;
line-height: 1.5;
animation: slideInUp 0.8s ease-out 0.6s both;
}
.hero-project-info {
display: flex;
align-items: center;
gap: 16px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
padding: 16px 24px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.2);
animation: slideInUp 0.8s ease-out 0.8s both;
transition: all 0.3s ease;
}
.hero-project-info:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.4);
transform: translateY(-2px);
}
.project-icon {
width: 64px;
height: 64px;
background: rgba(255, 255, 255, 0.2);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
}
.project-icon.small {
width: 48px;
height: 48px;
font-size: 24px;
border-radius: 12px;
}
.project-details {
flex: 1;
}
.project-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.project-category {
font-size: 14px;
opacity: 0.8;
margin: 0;
}
.project-button {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 12px 24px;
border-radius: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
position: relative;
overflow: hidden;
}
.project-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.project-button:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.project-button:hover::before {
width: 300px;
height: 300px;
}
.project-button:active {
transform: translateY(0);
}
.project-button.small {
padding: 8px 16px;
font-size: 14px;
}
/* Projects Section */
.projects-section {
margin-bottom: 48px;
}
.section-header {
margin-bottom: 32px;
}
.section-title {
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .section-title {
color: #f5f5f7;
}
.section-subtitle {
font-size: 16px;
color: #6B7280;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .section-subtitle {
color: #9CA3AF;
}
.projects-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.large-project-card {
border-radius: 20px;
padding: 32px;
color: white;
min-height: 300px;
display: flex;
align-items: flex-end;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.large-project-card:hover {
transform: translateY(-6px) scale(1.02);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.large-project-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 50%);
pointer-events: none;
}
.card-content {
width: 100%;
}
.card-badge {
display: inline-block;
background: rgba(255, 255, 255, 0.2);
padding: 6px 12px;
border-radius: 16px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.card-title {
font-size: 24px;
font-weight: 700;
margin-bottom: 12px;
line-height: 1.2;
}
.card-description {
font-size: 14px;
margin-bottom: 24px;
opacity: 0.9;
line-height: 1.4;
}
.card-project-info {
display: flex;
align-items: center;
gap: 12px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
padding: 12px 16px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.small-projects-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.small-project-card {
background: white;
padding: 24px;
border-radius: 16px;
text-align: center;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
animation: slideInUp 0.6s ease-out both;
}
.small-project-card:nth-child(1) { animation-delay: 0.1s; }
.small-project-card:nth-child(2) { animation-delay: 0.2s; }
.small-project-card:nth-child(3) { animation-delay: 0.3s; }
.small-project-card:nth-child(4) { animation-delay: 0.4s; }
:global(.dark) .small-project-card {
background: rgba(28, 28, 30, 0.8);
color: #f5f5f7;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.small-project-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
:global(.dark) .small-project-card:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
}
.small-card-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin: 0 auto 16px;
color: white;
}
.small-card-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.small-card-category {
font-size: 14px;
color: #6B7280;
margin-bottom: 16px;
transition: color 0.3s ease;
}
:global(.dark) .small-card-category {
color: #9CA3AF;
}
.small-card-button {
background: #007AFF;
color: white;
border: none;
padding: 8px 16px;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
width: 100%;
}
.small-card-button:hover {
background: #0056CC;
transform: translateY(-1px);
}
/* Services Section */
.services-section {
margin-bottom: 48px;
}
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
}
.service-card {
background: white;
padding: 32px 24px;
border-radius: 16px;
text-align: center;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
animation: slideInUp 0.6s ease-out both;
}
.service-card:nth-child(1) { animation-delay: 0.2s; }
.service-card:nth-child(2) { animation-delay: 0.3s; }
.service-card:nth-child(3) { animation-delay: 0.4s; }
.service-card:nth-child(4) { animation-delay: 0.5s; }
:global(.dark) .service-card {
background: rgba(28, 28, 30, 0.8);
color: #f5f5f7;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.service-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
:global(.dark) .service-card:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
}
.service-icon {
font-size: 48px;
margin-bottom: 16px;
}
.service-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
}
.service-description {
font-size: 14px;
color: #6B7280;
line-height: 1.5;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .service-description {
color: #9CA3AF;
}
/* 响应式设计 */
@media (max-width: 768px) {
.hero-card {
padding: 32px 24px;
min-height: 300px;
}
.hero-title {
font-size: 28px;
}
.hero-description {
font-size: 16px;
}
.hero-project-info {
flex-direction: column;
text-align: center;
gap: 12px;
}
.projects-grid {
grid-template-columns: 1fr;
}
.small-projects-grid {
grid-template-columns: 1fr;
}
.services-grid {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -0,0 +1,587 @@
<script lang="ts">
import { onMount } from 'svelte';
const skills = [
{ name: 'Brand Identity', level: 95, icon: '🎨' },
{ name: 'UI/UX Design', level: 90, icon: '📱' },
{ name: 'Web Development', level: 85, icon: '💻' },
{ name: 'Print Design', level: 88, icon: '📄' },
{ name: 'Photography', level: 80, icon: '📸' },
{ name: 'Illustration', level: 75, icon: '🖼️' }
];
const timeline = [
{
year: '2024',
title: 'Founded LiuBai Design',
description: 'Established creative studio focusing on modern brand identity and digital experiences.'
},
{
year: '2023',
title: 'Senior Designer at Tech Corp',
description: 'Led design team for major product launches and brand redesign initiatives.'
},
{
year: '2022',
title: 'Freelance Designer',
description: 'Worked with various startups and established companies on branding and web design projects.'
},
{
year: '2021',
title: 'Design Degree Completed',
description: 'Graduated with honors in Visual Communication Design from Design University.'
}
];
onMount(() => {
console.log('About page mounted');
});
</script>
<svelte:head>
<title>About - LiuBai Design</title>
<meta name="description" content="Learn about LiuBai Design studio and our creative approach" />
</svelte:head>
<div class="about-page">
<!-- Hero Section -->
<section class="hero-section">
<div class="hero-content">
<div class="hero-avatar">
<div class="avatar-icon">🎨</div>
</div>
<h1 class="hero-title">About LiuBai Design</h1>
<p class="hero-description">
We are a creative studio passionate about crafting meaningful brand experiences
and digital solutions that connect with people and drive results.
</p>
</div>
</section>
<!-- Story Section -->
<section class="story-section">
<div class="section-header">
<h2 class="section-title">Our Story</h2>
</div>
<div class="story-content">
<p class="story-text">
Founded in 2024, LiuBai Design emerged from a passion for creating visual experiences
that not only look beautiful but also serve a purpose. We believe that great design
is the perfect balance between aesthetics and functionality.
</p>
<p class="story-text">
Our approach combines strategic thinking with creative execution, ensuring that every
project we undertake delivers both visual impact and measurable results for our clients.
</p>
</div>
</section>
<!-- Skills Section -->
<section class="skills-section">
<div class="section-header">
<h2 class="section-title">What We Do</h2>
<p class="section-subtitle">Our areas of expertise</p>
</div>
<div class="skills-grid">
{#each skills as skill, index}
<div class="skill-card" style="animation-delay: {index * 0.1}s">
<div class="skill-icon">{skill.icon}</div>
<h3 class="skill-name">{skill.name}</h3>
<div class="skill-bar">
<div class="skill-progress" style="width: {skill.level}%"></div>
</div>
<span class="skill-level">{skill.level}%</span>
</div>
{/each}
</div>
</section>
<!-- Timeline Section -->
<section class="timeline-section">
<div class="section-header">
<h2 class="section-title">Our Journey</h2>
<p class="section-subtitle">Key milestones in our creative journey</p>
</div>
<div class="timeline">
{#each timeline as item, index}
<div class="timeline-item" style="animation-delay: {index * 0.2}s">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="timeline-year">{item.year}</div>
<h3 class="timeline-title">{item.title}</h3>
<p class="timeline-description">{item.description}</p>
</div>
</div>
{/each}
</div>
</section>
<!-- Values Section -->
<section class="values-section">
<div class="section-header">
<h2 class="section-title">Our Values</h2>
</div>
<div class="values-grid">
<div class="value-card">
<div class="value-icon">💡</div>
<h3 class="value-title">Innovation</h3>
<p class="value-description">
We constantly explore new design trends and technologies to deliver cutting-edge solutions.
</p>
</div>
<div class="value-card">
<div class="value-icon">🤝</div>
<h3 class="value-title">Collaboration</h3>
<p class="value-description">
We work closely with our clients as partners to understand their vision and goals.
</p>
</div>
<div class="value-card">
<div class="value-icon"></div>
<h3 class="value-title">Excellence</h3>
<p class="value-description">
We strive for perfection in every detail and never compromise on quality.
</p>
</div>
</div>
</section>
<!-- Contact CTA -->
<section class="cta-section">
<div class="cta-content">
<h2 class="cta-title">Ready to Work Together?</h2>
<p class="cta-description">
Let's create something amazing together. Get in touch to discuss your project.
</p>
<button class="cta-button" on:click={() => window.location.href = '/contact'}>
Get In Touch
</button>
</div>
</section>
</div>
<style>
.about-page {
max-width: 1200px;
margin: 0 auto;
padding: 0;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Hero Section */
.hero-section {
text-align: center;
margin-bottom: 64px;
}
.hero-avatar {
width: 120px;
height: 120px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 32px;
position: relative;
overflow: hidden;
}
.hero-avatar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%);
}
.avatar-icon {
font-size: 48px;
z-index: 2;
}
.hero-title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .hero-title {
color: #f5f5f7;
}
.hero-description {
font-size: 20px;
color: #6B7280;
line-height: 1.6;
max-width: 600px;
margin: 0 auto;
transition: color 0.3s ease;
}
:global(.dark) .hero-description {
color: #9CA3AF;
}
/* Section Headers */
.section-header {
text-align: center;
margin-bottom: 48px;
}
.section-title {
font-size: 36px;
font-weight: 700;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .section-title {
color: #f5f5f7;
}
.section-subtitle {
font-size: 16px;
color: #6B7280;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .section-subtitle {
color: #9CA3AF;
}
/* Story Section */
.story-section {
margin-bottom: 80px;
}
.story-content {
max-width: 800px;
margin: 0 auto;
}
.story-text {
font-size: 18px;
color: #374151;
line-height: 1.7;
margin-bottom: 24px;
transition: color 0.3s ease;
}
:global(.dark) .story-text {
color: #D1D5DB;
}
/* Skills Section */
.skills-section {
margin-bottom: 80px;
}
.skills-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
.skill-card {
background: white;
padding: 32px 24px;
border-radius: 20px;
text-align: center;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
animation: slideInUp 0.6s ease-out both;
}
:global(.dark) .skill-card {
background: rgba(28, 28, 30, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
.skill-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.skill-icon {
font-size: 48px;
margin-bottom: 16px;
}
.skill-name {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .skill-name {
color: #f5f5f7;
}
.skill-bar {
width: 100%;
height: 8px;
background: #f3f4f6;
border-radius: 4px;
overflow: hidden;
margin-bottom: 8px;
}
:global(.dark) .skill-bar {
background: #374151;
}
.skill-progress {
height: 100%;
background: linear-gradient(90deg, #007AFF 0%, #00C7FF 100%);
border-radius: 4px;
transition: width 1s ease-out 0.5s;
}
.skill-level {
font-size: 14px;
font-weight: 600;
color: #007AFF;
}
/* Timeline Section */
.timeline-section {
margin-bottom: 80px;
}
.timeline {
max-width: 600px;
margin: 0 auto;
position: relative;
}
.timeline::before {
content: '';
position: absolute;
left: 20px;
top: 0;
bottom: 0;
width: 2px;
background: #e5e7eb;
}
:global(.dark) .timeline::before {
background: #374151;
}
.timeline-item {
position: relative;
padding-left: 60px;
margin-bottom: 48px;
animation: slideInLeft 0.6s ease-out both;
}
.timeline-marker {
position: absolute;
left: 12px;
top: 8px;
width: 16px;
height: 16px;
background: #007AFF;
border-radius: 50%;
border: 3px solid white;
box-shadow: 0 0 0 3px #e5e7eb;
}
:global(.dark) .timeline-marker {
border-color: rgba(28, 28, 30, 0.8);
box-shadow: 0 0 0 3px #374151;
}
.timeline-year {
font-size: 14px;
font-weight: 600;
color: #007AFF;
margin-bottom: 8px;
}
.timeline-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .timeline-title {
color: #f5f5f7;
}
.timeline-description {
font-size: 16px;
color: #6B7280;
line-height: 1.5;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .timeline-description {
color: #9CA3AF;
}
/* Values Section */
.values-section {
margin-bottom: 80px;
}
.values-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 32px;
}
.value-card {
text-align: center;
padding: 32px 24px;
}
.value-icon {
font-size: 64px;
margin-bottom: 24px;
}
.value-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 16px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .value-title {
color: #f5f5f7;
}
.value-description {
font-size: 16px;
color: #6B7280;
line-height: 1.6;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .value-description {
color: #9CA3AF;
}
/* CTA Section */
.cta-section {
text-align: center;
padding: 64px 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 24px;
color: white;
margin-bottom: 48px;
}
.cta-title {
font-size: 36px;
font-weight: 700;
margin-bottom: 16px;
}
.cta-description {
font-size: 18px;
margin-bottom: 32px;
opacity: 0.9;
}
.cta-button {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 16px 32px;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.cta-button:hover {
background: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.hero-title {
font-size: 32px;
}
.section-title {
font-size: 28px;
}
.skills-grid {
grid-template-columns: 1fr;
}
.values-grid {
grid-template-columns: 1fr;
}
.timeline {
margin-left: 20px;
}
.timeline-item {
padding-left: 40px;
}
.cta-section {
padding: 48px 24px;
}
.cta-title {
font-size: 28px;
}
}
</style>

View File

@@ -0,0 +1,667 @@
<script lang="ts">
import { onMount } from 'svelte';
let formData = {
name: '',
email: '',
company: '',
project: '',
budget: '',
message: ''
};
let isSubmitting = false;
let submitMessage = '';
const budgetOptions = [
'Under $5,000',
'$5,000 - $10,000',
'$10,000 - $25,000',
'$25,000 - $50,000',
'Over $50,000'
];
const projectTypes = [
'Brand Identity',
'Web Design',
'Mobile App',
'Print Design',
'Photography',
'Other'
];
async function handleSubmit() {
isSubmitting = true;
// 模拟表单提交
try {
await new Promise(resolve => setTimeout(resolve, 2000));
// 这里应该是实际的表单提交逻辑
console.log('Form submitted:', formData);
submitMessage = 'Thank you for your message! We\'ll get back to you within 24 hours.';
// 重置表单
formData = {
name: '',
email: '',
company: '',
project: '',
budget: '',
message: ''
};
} catch (error) {
submitMessage = 'Sorry, there was an error sending your message. Please try again.';
} finally {
isSubmitting = false;
}
}
onMount(() => {
console.log('Contact page mounted');
});
</script>
<svelte:head>
<title>Contact - LiuBai Design</title>
<meta name="description" content="Get in touch with LiuBai Design for your next creative project" />
</svelte:head>
<div class="contact-page">
<!-- Hero Section -->
<section class="hero-section">
<div class="hero-content">
<h1 class="hero-title">Let's Create Something Amazing</h1>
<p class="hero-description">
Ready to bring your vision to life? We'd love to hear about your project
and discuss how we can help you achieve your goals.
</p>
</div>
</section>
<div class="contact-content">
<!-- Contact Form -->
<div class="form-section">
<div class="form-header">
<h2 class="form-title">Tell Us About Your Project</h2>
<p class="form-description">
Fill out the form below and we'll get back to you within 24 hours.
</p>
</div>
{#if submitMessage}
<div class="submit-message" class:success={submitMessage.includes('Thank you')} class:error={submitMessage.includes('error')}>
{submitMessage}
</div>
{/if}
<form class="contact-form" on:submit|preventDefault={handleSubmit}>
<div class="form-row">
<div class="form-group">
<label for="name" class="form-label">Name *</label>
<input
type="text"
id="name"
bind:value={formData.name}
class="form-input"
required
placeholder="Your full name"
/>
</div>
<div class="form-group">
<label for="email" class="form-label">Email *</label>
<input
type="email"
id="email"
bind:value={formData.email}
class="form-input"
required
placeholder="your@email.com"
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="company" class="form-label">Company</label>
<input
type="text"
id="company"
bind:value={formData.company}
class="form-input"
placeholder="Your company name"
/>
</div>
<div class="form-group">
<label for="project" class="form-label">Project Type</label>
<select id="project" bind:value={formData.project} class="form-select">
<option value="">Select project type</option>
{#each projectTypes as type}
<option value={type}>{type}</option>
{/each}
</select>
</div>
</div>
<div class="form-group">
<label for="budget" class="form-label">Budget Range</label>
<select id="budget" bind:value={formData.budget} class="form-select">
<option value="">Select budget range</option>
{#each budgetOptions as budget}
<option value={budget}>{budget}</option>
{/each}
</select>
</div>
<div class="form-group">
<label for="message" class="form-label">Project Details *</label>
<textarea
id="message"
bind:value={formData.message}
class="form-textarea"
required
placeholder="Tell us about your project, goals, timeline, and any specific requirements..."
rows="6"
></textarea>
</div>
<button
type="submit"
class="submit-button"
disabled={isSubmitting}
>
{#if isSubmitting}
<span class="loading-spinner"></span>
Sending...
{:else}
Send Message
{/if}
</button>
</form>
</div>
<!-- Contact Info -->
<div class="info-section">
<div class="info-card">
<h3 class="info-title">Get In Touch</h3>
<div class="contact-methods">
<div class="contact-method">
<div class="method-icon">📧</div>
<div class="method-content">
<div class="method-label">Email</div>
<div class="method-value">hello@liubaidesign.com</div>
</div>
</div>
<div class="contact-method">
<div class="method-icon">📱</div>
<div class="method-content">
<div class="method-label">Phone</div>
<div class="method-value">+1 (555) 123-4567</div>
</div>
</div>
<div class="contact-method">
<div class="method-icon">📍</div>
<div class="method-content">
<div class="method-label">Location</div>
<div class="method-value">San Francisco, CA</div>
</div>
</div>
</div>
</div>
<div class="info-card">
<h3 class="info-title">What Happens Next?</h3>
<div class="process-steps">
<div class="process-step">
<div class="step-number">1</div>
<div class="step-content">
<div class="step-title">We Review</div>
<div class="step-description">We'll review your project details and requirements</div>
</div>
</div>
<div class="process-step">
<div class="step-number">2</div>
<div class="step-content">
<div class="step-title">We Connect</div>
<div class="step-description">Schedule a call to discuss your vision and goals</div>
</div>
</div>
<div class="process-step">
<div class="step-number">3</div>
<div class="step-content">
<div class="step-title">We Propose</div>
<div class="step-description">Receive a detailed proposal and timeline</div>
</div>
</div>
</div>
</div>
<div class="info-card">
<h3 class="info-title">Follow Us</h3>
<div class="social-links">
<a href="#" class="social-link">
<div class="social-icon">📘</div>
<span>Facebook</span>
</a>
<a href="#" class="social-link">
<div class="social-icon">📷</div>
<span>Instagram</span>
</a>
<a href="#" class="social-link">
<div class="social-icon">🐦</div>
<span>Twitter</span>
</a>
<a href="#" class="social-link">
<div class="social-icon">💼</div>
<span>LinkedIn</span>
</a>
</div>
</div>
</div>
</div>
</div>
<style>
.contact-page {
max-width: 1200px;
margin: 0 auto;
padding: 0;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Hero Section */
.hero-section {
text-align: center;
margin-bottom: 64px;
}
.hero-title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .hero-title {
color: #f5f5f7;
}
.hero-description {
font-size: 20px;
color: #6B7280;
line-height: 1.6;
max-width: 600px;
margin: 0 auto;
transition: color 0.3s ease;
}
:global(.dark) .hero-description {
color: #9CA3AF;
}
/* Contact Content */
.contact-content {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 48px;
}
/* Form Section */
.form-section {
background: white;
padding: 48px;
border-radius: 24px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
:global(.dark) .form-section {
background: rgba(28, 28, 30, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
.form-header {
margin-bottom: 32px;
}
.form-title {
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .form-title {
color: #f5f5f7;
}
.form-description {
font-size: 16px;
color: #6B7280;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .form-description {
color: #9CA3AF;
}
.submit-message {
padding: 16px;
border-radius: 12px;
margin-bottom: 24px;
font-weight: 500;
}
.submit-message.success {
background: #dcfce7;
color: #166534;
border: 1px solid #bbf7d0;
}
.submit-message.error {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}
:global(.dark) .submit-message.success {
background: rgba(34, 197, 94, 0.1);
color: #22c55e;
border-color: rgba(34, 197, 94, 0.3);
}
:global(.dark) .submit-message.error {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border-color: rgba(239, 68, 68, 0.3);
}
/* Form Styles */
.contact-form {
display: flex;
flex-direction: column;
gap: 24px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-label {
font-size: 14px;
font-weight: 600;
color: #374151;
transition: color 0.3s ease;
}
:global(.dark) .form-label {
color: #D1D5DB;
}
.form-input,
.form-select,
.form-textarea {
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background: white;
color: #1d1d1f;
}
:global(.dark) .form-input,
:global(.dark) .form-select,
:global(.dark) .form-textarea {
background: rgba(55, 65, 81, 0.5);
border-color: #4B5563;
color: #f5f5f7;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: #007AFF;
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 120px;
}
.submit-button {
background: #007AFF;
color: white;
border: none;
padding: 18px 32px;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.submit-button:hover:not(:disabled) {
background: #0056CC;
transform: translateY(-2px);
}
.submit-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Info Section */
.info-section {
display: flex;
flex-direction: column;
gap: 24px;
}
.info-card {
background: white;
padding: 32px 24px;
border-radius: 20px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
:global(.dark) .info-card {
background: rgba(28, 28, 30, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
.info-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .info-title {
color: #f5f5f7;
}
/* Contact Methods */
.contact-methods {
display: flex;
flex-direction: column;
gap: 16px;
}
.contact-method {
display: flex;
align-items: center;
gap: 12px;
}
.method-icon {
font-size: 24px;
width: 40px;
text-align: center;
}
.method-label {
font-size: 12px;
font-weight: 600;
color: #9CA3AF;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.method-value {
font-size: 14px;
font-weight: 500;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .method-value {
color: #f5f5f7;
}
/* Process Steps */
.process-steps {
display: flex;
flex-direction: column;
gap: 16px;
}
.process-step {
display: flex;
align-items: flex-start;
gap: 12px;
}
.step-number {
width: 32px;
height: 32px;
background: #007AFF;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
flex-shrink: 0;
}
.step-title {
font-size: 14px;
font-weight: 600;
color: #1d1d1f;
margin-bottom: 4px;
transition: color 0.3s ease;
}
:global(.dark) .step-title {
color: #f5f5f7;
}
.step-description {
font-size: 12px;
color: #6B7280;
line-height: 1.4;
transition: color 0.3s ease;
}
:global(.dark) .step-description {
color: #9CA3AF;
}
/* Social Links */
.social-links {
display: flex;
flex-direction: column;
gap: 12px;
}
.social-link {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
color: #1d1d1f;
text-decoration: none;
transition: all 0.3s ease;
}
:global(.dark) .social-link {
color: #f5f5f7;
}
.social-link:hover {
color: #007AFF;
transform: translateX(4px);
}
.social-icon {
font-size: 20px;
width: 32px;
text-align: center;
}
/* 响应式设计 */
@media (max-width: 768px) {
.contact-content {
grid-template-columns: 1fr;
gap: 32px;
}
.form-section {
padding: 32px 24px;
}
.form-row {
grid-template-columns: 1fr;
}
.hero-title {
font-size: 32px;
}
.form-title {
font-size: 24px;
}
}
</style>

View File

@@ -0,0 +1,410 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
// 项目数据
const projects = [
{
id: 1,
title: 'Creative Brand Identity',
category: 'Brand Design',
description: 'Complete visual identity system for modern brands',
image: '🎨',
tags: ['Logo Design', 'Brand Guidelines', 'Typography'],
status: 'Completed',
client: 'Tech Startup',
year: '2024'
},
{
id: 2,
title: 'Mobile App Interface',
category: 'UI/UX Design',
description: 'Modern mobile interface with focus on user experience',
image: '📱',
tags: ['Mobile Design', 'User Experience', 'Prototyping'],
status: 'In Progress',
client: 'E-commerce Platform',
year: '2024'
},
{
id: 3,
title: 'Web Portfolio',
category: 'Web Design',
description: 'Responsive portfolio website with modern aesthetics',
image: '💻',
tags: ['Web Development', 'Responsive Design', 'Animation'],
status: 'Completed',
client: 'Creative Agency',
year: '2023'
},
{
id: 4,
title: 'Print Design Collection',
category: 'Graphic Design',
description: 'Various print materials including brochures and posters',
image: '📄',
tags: ['Print Design', 'Layout', 'Typography'],
status: 'Completed',
client: 'Local Business',
year: '2023'
}
];
let selectedCategory = 'All';
const categories = ['All', 'Brand Design', 'UI/UX Design', 'Web Design', 'Graphic Design'];
$: filteredProjects = selectedCategory === 'All'
? projects
: projects.filter(project => project.category === selectedCategory);
function navigateToProject(projectId: number) {
goto(`/projects/${projectId}`);
}
function selectCategory(category: string) {
selectedCategory = category;
// 更新URL参数
const url = new URL(window.location.href);
if (category === 'All') {
url.searchParams.delete('category');
} else {
url.searchParams.set('category', category);
}
goto(url.pathname + url.search, { replaceState: true });
}
onMount(() => {
// 从URL参数读取分类
const urlCategory = $page.url.searchParams.get('category');
if (urlCategory && categories.includes(urlCategory)) {
selectedCategory = urlCategory;
}
console.log('Projects page mounted');
});
</script>
<svelte:head>
<title>Projects - LiuBai Design</title>
<meta name="description" content="Explore our creative projects and design portfolio" />
</svelte:head>
<div class="projects-page">
<header class="page-header">
<h1 class="page-title">Projects</h1>
<p class="page-subtitle">Explore our creative works and design solutions</p>
</header>
<!-- 分类筛选 -->
<div class="filter-section">
<div class="filter-tabs">
{#each categories as category}
<button
class="filter-tab"
class:active={selectedCategory === category}
on:click={() => selectCategory(category)}
>
{category}
</button>
{/each}
</div>
</div>
<!-- 项目网格 -->
<div class="projects-grid">
{#each filteredProjects as project (project.id)}
<div class="project-card" on:click={() => navigateToProject(project.id)} role="button" tabindex="0">
<div class="project-image">
<div class="project-icon">{project.image}</div>
<div class="project-status" class:completed={project.status === 'Completed'}>
{project.status}
</div>
</div>
<div class="project-content">
<div class="project-category">{project.category}</div>
<h3 class="project-title">{project.title}</h3>
<p class="project-description">{project.description}</p>
<div class="project-meta">
<span class="project-client">{project.client}</span>
<span class="project-year">{project.year}</span>
</div>
<div class="project-tags">
{#each project.tags as tag}
<span class="tag">{tag}</span>
{/each}
</div>
</div>
</div>
{/each}
</div>
{#if filteredProjects.length === 0}
<div class="empty-state">
<div class="empty-icon">🔍</div>
<h3>No projects found</h3>
<p>Try selecting a different category</p>
</div>
{/if}
</div>
<style>
.projects-page {
max-width: 1200px;
margin: 0 auto;
padding: 0;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.page-header {
margin-bottom: 32px;
}
.page-title {
font-size: 48px;
font-weight: 700;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .page-title {
color: #f5f5f7;
}
.page-subtitle {
font-size: 18px;
color: #6B7280;
margin: 0;
transition: color 0.3s ease;
}
:global(.dark) .page-subtitle {
color: #9CA3AF;
}
.filter-section {
margin-bottom: 32px;
}
.filter-tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.filter-tab {
padding: 12px 24px;
border: 2px solid #e5e7eb;
background: white;
border-radius: 25px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
color: #374151;
}
:global(.dark) .filter-tab {
background: rgba(28, 28, 30, 0.8);
border-color: #374151;
color: #9CA3AF;
}
.filter-tab:hover {
border-color: #007AFF;
color: #007AFF;
}
.filter-tab.active {
background: #007AFF;
border-color: #007AFF;
color: white;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
margin-bottom: 48px;
}
.project-card {
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
cursor: pointer;
animation: slideInUp 0.6s ease-out both;
}
:global(.dark) .project-card {
background: rgba(28, 28, 30, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.project-card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
:global(.dark) .project-card:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
}
.project-image {
height: 200px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.project-icon {
font-size: 64px;
}
.project-status {
position: absolute;
top: 16px;
right: 16px;
padding: 6px 12px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 12px;
font-size: 12px;
font-weight: 600;
color: white;
text-transform: uppercase;
}
.project-status.completed {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
}
.project-content {
padding: 24px;
}
.project-category {
font-size: 12px;
font-weight: 600;
color: #007AFF;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.project-title {
font-size: 20px;
font-weight: 700;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .project-title {
color: #f5f5f7;
}
.project-description {
font-size: 14px;
color: #6B7280;
line-height: 1.5;
margin-bottom: 16px;
transition: color 0.3s ease;
}
:global(.dark) .project-description {
color: #9CA3AF;
}
.project-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
font-size: 12px;
color: #9CA3AF;
}
.project-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag {
padding: 4px 8px;
background: #f3f4f6;
border-radius: 8px;
font-size: 11px;
font-weight: 500;
color: #374151;
transition: all 0.3s ease;
}
:global(.dark) .tag {
background: #374151;
color: #9CA3AF;
}
.empty-state {
text-align: center;
padding: 64px 24px;
}
.empty-icon {
font-size: 64px;
margin-bottom: 16px;
}
.empty-state h3 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .empty-state h3 {
color: #f5f5f7;
}
.empty-state p {
color: #6B7280;
transition: color 0.3s ease;
}
:global(.dark) .empty-state p {
color: #9CA3AF;
}
/* 响应式设计 */
@media (max-width: 768px) {
.projects-grid {
grid-template-columns: 1fr;
}
.filter-tabs {
justify-content: center;
}
.filter-tab {
padding: 10px 20px;
font-size: 14px;
}
}
</style>

View File

@@ -0,0 +1,569 @@
<script lang="ts">
import { page } from '$app/stores';
import { onMount } from 'svelte';
// 项目数据实际应用中应该从API获取
const projectsData = {
'1': {
id: 1,
title: 'Creative Brand Identity',
category: 'Brand Design',
description: 'A comprehensive brand identity system featuring logo design, color palettes, typography, and brand guidelines for a modern tech startup.',
image: '🎨',
client: 'Tech Startup',
year: '2024',
duration: '3 months',
status: 'Completed',
tags: ['Logo Design', 'Brand Guidelines', 'Typography', 'Color Theory'],
challenge: 'Create a modern, memorable brand identity that reflects innovation and trustworthiness while appealing to both B2B and B2C audiences.',
solution: 'Developed a clean, geometric logo with a vibrant color palette and comprehensive brand guidelines that ensure consistency across all touchpoints.',
results: 'Increased brand recognition by 150% and improved customer engagement across digital platforms.',
gallery: ['🎨', '🖼️', '📊', '📱', '💻', '📄']
},
'2': {
id: 2,
title: 'Mobile App Interface',
category: 'UI/UX Design',
description: 'Modern mobile interface design with focus on user experience and accessibility for an e-commerce platform.',
image: '📱',
client: 'E-commerce Platform',
year: '2024',
duration: '4 months',
status: 'In Progress',
tags: ['Mobile Design', 'User Experience', 'Prototyping', 'Accessibility'],
challenge: 'Design an intuitive shopping experience that reduces cart abandonment and improves conversion rates.',
solution: 'Created a streamlined user flow with clear navigation, optimized product discovery, and simplified checkout process.',
results: 'Currently in development phase with promising user testing results showing 40% improvement in task completion.',
gallery: ['📱', '🛒', '💳', '🔍', '⭐', '📊']
},
'3': {
id: 3,
title: 'Web Portfolio',
category: 'Web Design',
description: 'Responsive portfolio website with modern aesthetics and smooth animations for a creative agency.',
image: '💻',
client: 'Creative Agency',
year: '2023',
duration: '2 months',
status: 'Completed',
tags: ['Web Development', 'Responsive Design', 'Animation', 'Performance'],
challenge: 'Create a visually stunning portfolio that showcases work effectively while maintaining fast loading times.',
solution: 'Implemented a clean, grid-based layout with optimized images and smooth CSS animations.',
results: 'Achieved 95+ PageSpeed score and increased client inquiries by 200%.',
gallery: ['💻', '🎨', '📸', '🎬', '📱', '⚡']
},
'4': {
id: 4,
title: 'Print Design Collection',
category: 'Graphic Design',
description: 'Various print materials including brochures, posters, and business cards for local businesses.',
image: '📄',
client: 'Local Business',
year: '2023',
duration: '1 month',
status: 'Completed',
tags: ['Print Design', 'Layout', 'Typography', 'Brand Consistency'],
challenge: 'Create cohesive print materials that work across different formats and maintain brand consistency.',
solution: 'Developed a flexible design system that adapts to various print formats while maintaining visual hierarchy.',
results: 'Improved brand perception and increased foot traffic by 30%.',
gallery: ['📄', '📰', '🏷️', '📋', '📊', '🎨']
}
};
$: projectId = $page.params.id;
$: project = projectsData[projectId];
function goBack() {
window.history.back();
}
onMount(() => {
if (!project) {
// 如果项目不存在,重定向到项目列表页
window.location.href = '/projects';
}
});
</script>
<svelte:head>
<title>{project?.title || 'Project'} - LiuBai Design</title>
<meta name="description" content={project?.description || 'Project details'} />
</svelte:head>
{#if project}
<div class="project-detail">
<!-- 返回按钮 -->
<button class="back-button" on:click={goBack}>
← Back to Projects
</button>
<!-- 项目头部 -->
<header class="project-header">
<div class="project-hero">
<div class="project-hero-image">
<div class="hero-icon">{project.image}</div>
</div>
<div class="project-hero-content">
<div class="project-category">{project.category}</div>
<h1 class="project-title">{project.title}</h1>
<p class="project-description">{project.description}</p>
<div class="project-meta-grid">
<div class="meta-item">
<span class="meta-label">Client</span>
<span class="meta-value">{project.client}</span>
</div>
<div class="meta-item">
<span class="meta-label">Year</span>
<span class="meta-value">{project.year}</span>
</div>
<div class="meta-item">
<span class="meta-label">Duration</span>
<span class="meta-value">{project.duration}</span>
</div>
<div class="meta-item">
<span class="meta-label">Status</span>
<span class="meta-value status" class:completed={project.status === 'Completed'}>
{project.status}
</span>
</div>
</div>
<div class="project-tags">
{#each project.tags as tag}
<span class="tag">{tag}</span>
{/each}
</div>
</div>
</div>
</header>
<!-- 项目内容 -->
<div class="project-content">
<div class="content-section">
<h2>Challenge</h2>
<p>{project.challenge}</p>
</div>
<div class="content-section">
<h2>Solution</h2>
<p>{project.solution}</p>
</div>
<div class="content-section">
<h2>Results</h2>
<p>{project.results}</p>
</div>
<div class="content-section">
<h2>Gallery</h2>
<div class="gallery-grid">
{#each project.gallery as item, index}
<div class="gallery-item" style="animation-delay: {index * 0.1}s">
<div class="gallery-icon">{item}</div>
</div>
{/each}
</div>
</div>
</div>
<!-- 相关项目 -->
<div class="related-projects">
<h2>More Projects</h2>
<div class="related-grid">
{#each Object.values(projectsData).filter(p => p.id !== project.id).slice(0, 3) as relatedProject}
<div class="related-card" on:click={() => window.location.href = `/projects/${relatedProject.id}`}>
<div class="related-image">
<div class="related-icon">{relatedProject.image}</div>
</div>
<div class="related-content">
<div class="related-category">{relatedProject.category}</div>
<h3 class="related-title">{relatedProject.title}</h3>
</div>
</div>
{/each}
</div>
</div>
</div>
{:else}
<div class="loading">
<div class="loading-spinner">🔄</div>
<p>Loading project...</p>
</div>
{/if}
<style>
.project-detail {
max-width: 1200px;
margin: 0 auto;
padding: 0;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.back-button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 0;
background: none;
border: none;
color: #007AFF;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 32px;
}
.back-button:hover {
color: #0056CC;
transform: translateX(-4px);
}
.project-header {
margin-bottom: 48px;
}
.project-hero {
display: grid;
grid-template-columns: 300px 1fr;
gap: 48px;
align-items: start;
}
.project-hero-image {
aspect-ratio: 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.project-hero-image::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 50%);
}
.hero-icon {
font-size: 120px;
z-index: 2;
}
.project-category {
font-size: 14px;
font-weight: 600;
color: #007AFF;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.project-title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
color: #1d1d1f;
line-height: 1.2;
transition: color 0.3s ease;
}
:global(.dark) .project-title {
color: #f5f5f7;
}
.project-description {
font-size: 18px;
color: #6B7280;
line-height: 1.6;
margin-bottom: 32px;
transition: color 0.3s ease;
}
:global(.dark) .project-description {
color: #9CA3AF;
}
.project-meta-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
margin-bottom: 32px;
}
.meta-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.meta-label {
font-size: 12px;
font-weight: 600;
color: #9CA3AF;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.meta-value {
font-size: 16px;
font-weight: 600;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .meta-value {
color: #f5f5f7;
}
.meta-value.status {
color: #f59e0b;
}
.meta-value.status.completed {
color: #22c55e;
}
.project-tags {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.tag {
padding: 8px 16px;
background: #f3f4f6;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
color: #374151;
transition: all 0.3s ease;
}
:global(.dark) .tag {
background: #374151;
color: #9CA3AF;
}
.project-content {
margin-bottom: 64px;
}
.content-section {
margin-bottom: 48px;
}
.content-section h2 {
font-size: 32px;
font-weight: 700;
margin-bottom: 16px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .content-section h2 {
color: #f5f5f7;
}
.content-section p {
font-size: 16px;
color: #6B7280;
line-height: 1.7;
transition: color 0.3s ease;
}
:global(.dark) .content-section p {
color: #9CA3AF;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 16px;
}
.gallery-item {
aspect-ratio: 1;
background: white;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
animation: slideInUp 0.6s ease-out both;
}
:global(.dark) .gallery-item {
background: rgba(28, 28, 30, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
.gallery-item:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.gallery-icon {
font-size: 48px;
}
.related-projects {
margin-bottom: 48px;
}
.related-projects h2 {
font-size: 32px;
font-weight: 700;
margin-bottom: 24px;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .related-projects h2 {
color: #f5f5f7;
}
.related-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
.related-card {
background: white;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
cursor: pointer;
}
:global(.dark) .related-card {
background: rgba(28, 28, 30, 0.8);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
.related-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.related-image {
height: 150px;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
display: flex;
align-items: center;
justify-content: center;
}
.related-icon {
font-size: 48px;
}
.related-content {
padding: 20px;
}
.related-category {
font-size: 12px;
font-weight: 600;
color: #007AFF;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.related-title {
font-size: 18px;
font-weight: 600;
color: #1d1d1f;
transition: color 0.3s ease;
}
:global(.dark) .related-title {
color: #f5f5f7;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
text-align: center;
}
.loading-spinner {
font-size: 48px;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.project-hero {
grid-template-columns: 1fr;
gap: 24px;
text-align: center;
}
.project-hero-image {
max-width: 200px;
margin: 0 auto;
}
.hero-icon {
font-size: 80px;
}
.project-title {
font-size: 32px;
}
.project-meta-grid {
grid-template-columns: 1fr;
}
.gallery-grid {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
.related-grid {
grid-template-columns: 1fr;
}
}
</style>

42
start.md Normal file
View File

@@ -0,0 +1,42 @@
# 🚀 快速启动指南
## 安装依赖
```bash
npm install
```
## 启动开发服务器
```bash
npm run dev
```
然后在浏览器中访问 http://localhost:5173
## 可用的脚本
- `npm run dev` - 启动开发服务器
- `npm run build` - 构建生产版本
- `npm run preview` - 预览生产版本
- `npm run check` - 类型检查
- `npm run lint` - 代码检查
- `npm run format` - 代码格式化
## 项目特性
✅ 现代化 UI 组件系统
✅ 多主题支持(亮色/暗色)
✅ 国际化支持8种语言
✅ 响应式设计
✅ TypeScript 支持
✅ 无障碍友好
## 开始开发
1.`src/routes/` 中添加新页面
2.`src/lib/components/` 中创建新组件
3.`src/lib/stores/` 中管理状态
4.`src/lib/i18n/` 中添加翻译
享受开发吧!🎉

12
svelte.config.js Normal file
View File

@@ -0,0 +1,12 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter()
}
};
export default config;

16
tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"paths": {
"~/*": ["./src/*"]
}
}
}

18
vite.config.ts Normal file
View File

@@ -0,0 +1,18 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "src/lib/styles/variables.scss" as *;'
}
}
},
resolve: {
alias: {
'~': '/src'
}
}
});