From 2a65533cc719539887546872bd380c115e918af8 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Wed, 20 May 2026 00:55:24 +0800 Subject: [PATCH] feat: add various Waterfall card components and enhance main.css with theme variables and styles --- app/assets/css/main.css | 42 +++- app/components/index/WaterfallCard.vue | 150 +++++++++++ app/components/index/WaterfallImageCard.vue | 99 ++++++++ app/components/index/WaterfallPortfolioCard.vue | 151 +++++++++++ app/components/index/WaterfallProjectCard.vue | 172 +++++++++++++ app/components/index/WaterfallTextCard.vue | 63 +++++ app/pages/index/index.vue | 321 +++++++++++++++++++++++- server/api/cards.get.ts | 140 +++++++++++ 8 files changed, 1133 insertions(+), 5 deletions(-) create mode 100644 app/components/index/WaterfallCard.vue create mode 100644 app/components/index/WaterfallImageCard.vue create mode 100644 app/components/index/WaterfallPortfolioCard.vue create mode 100644 app/components/index/WaterfallProjectCard.vue create mode 100644 app/components/index/WaterfallTextCard.vue create mode 100644 server/api/cards.get.ts diff --git a/app/assets/css/main.css b/app/assets/css/main.css index a461c50..7a43b22 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -1 +1,41 @@ -@import "tailwindcss"; \ No newline at end of file +@import "tailwindcss"; + +@theme { + --color-canvas: #faf9f5; + --color-surface-card: #efe9de; + --color-surface-soft: #f5f0e8; + --color-surface-dark: #181715; + --color-surface-dark-elevated: #252320; + --color-surface-dark-soft: #1f1e1b; + --color-primary: #cc785c; + --color-primary-active: #a9583e; + --color-ink: #141413; + --color-body: #3d3d3a; + --color-body-strong: #252523; + --color-muted: #6c6a64; + --color-muted-soft: #8e8b82; + --color-hairline: #e6dfd8; + --color-hairline-soft: #ebe6df; +} + +body { + background-color: var(--color-canvas); + color: var(--color-body); + font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +:root { + --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); + --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1); + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/app/components/index/WaterfallCard.vue b/app/components/index/WaterfallCard.vue new file mode 100644 index 0000000..1c82456 --- /dev/null +++ b/app/components/index/WaterfallCard.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/app/components/index/WaterfallImageCard.vue b/app/components/index/WaterfallImageCard.vue new file mode 100644 index 0000000..93a5f05 --- /dev/null +++ b/app/components/index/WaterfallImageCard.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/app/components/index/WaterfallPortfolioCard.vue b/app/components/index/WaterfallPortfolioCard.vue new file mode 100644 index 0000000..685e283 --- /dev/null +++ b/app/components/index/WaterfallPortfolioCard.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/app/components/index/WaterfallProjectCard.vue b/app/components/index/WaterfallProjectCard.vue new file mode 100644 index 0000000..f726775 --- /dev/null +++ b/app/components/index/WaterfallProjectCard.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/app/components/index/WaterfallTextCard.vue b/app/components/index/WaterfallTextCard.vue new file mode 100644 index 0000000..3d8b6c4 --- /dev/null +++ b/app/components/index/WaterfallTextCard.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/app/pages/index/index.vue b/app/pages/index/index.vue index ff2ebaf..8f3ef3b 100644 --- a/app/pages/index/index.vue +++ b/app/pages/index/index.vue @@ -1,10 +1,323 @@ \ No newline at end of file + + + + diff --git a/server/api/cards.get.ts b/server/api/cards.get.ts new file mode 100644 index 0000000..20e181a --- /dev/null +++ b/server/api/cards.get.ts @@ -0,0 +1,140 @@ +type CardType = 'text' | 'image' | 'image-text' | 'portfolio' | 'project' + +interface CardData { + id: number + type: CardType + image?: string + images?: string[] + title: string + description?: string + tags?: string[] + aspectRatio: number +} + +const images = [ + 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&h=800&fit=crop', + 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=600&h=500&fit=crop', + 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=600&h=700&fit=crop', + 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=600&h=900&fit=crop', + 'https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=600&h=550&fit=crop', + 'https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=600&h=650&fit=crop', + 'https://images.unsplash.com/photo-1501854140801-50d01698950b?w=600&h=750&fit=crop', + 'https://images.unsplash.com/photo-1518173946687-a1e4e09e68b7?w=600&h=850&fit=crop', + 'https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=600&h=580&fit=crop', + 'https://images.unsplash.com/photo-1505144808419-1957a94ca61e?w=600&h=720&fit=crop', + 'https://images.unsplash.com/photo-1465146344425-f00d5f5c8f07?w=600&h=600&fit=crop', + 'https://images.unsplash.com/photo-1490730141103-6cac27aaab94?w=600&h=780&fit=crop', +] + +const portfolioSets = [ + [images[0], images[1], images[4], images[5]], + [images[2], images[3], images[6], images[7]], + [images[8], images[9], images[10], images[11]], + [images[1], images[3], images[8], images[10]], + [images[0], images[2], images[6], images[9]], + [images[4], images[5], images[7], images[11]], +] + +const titles = [ + '山间晨雾', '海岸余晖', '林深见鹿', '星河滚烫', + '秋日私语', '雪落无声', '荒漠孤烟', '江南水乡', + '云海翻涌', '古城旧梦', '樱花吹雪', '冰川纪行', +] + +const descriptions = [ + '清晨第一缕阳光穿透薄雾,洒在青翠的山谷间', + '海浪轻抚沙滩,余晖将天际染成珊瑚般的暖色调', + '密林深处,光影斑驳,万物有灵且美', + '浩瀚星空下,篝火噼啪作响,银河横亘天际', + '金黄落叶铺满小径,秋风携来果实成熟的芬芳', + '白雪覆盖大地,万物归于宁静与纯粹', + '大漠无垠,风沙雕刻出千年的纹路', + '小桥流水,烟雨朦胧中白墙黛瓦静静伫立', + '云层如海浪般在山巅之下翻涌奔腾', + '石板路上回响着千年前的足音,城墙斑驳诉说时光', + '粉色花瓣如雪般飘落,短暂而绚烂', + '万年冰层透着幽蓝的光,仿佛地球深处的呼吸', +] + +const textContents = [ + { title: '关于灵感', description: '灵感从不拜访懒人。它像一只羞怯的鸟,只在你俯首案前、笔耕不辍时,悄然落在你的肩头。那些看似神来之笔的瞬间,不过是千百次练习后的水到渠成。' }, + { title: '时间之河', description: '时间是一条沉默的河流,我们都是河中的石头。水流磨平了棱角,却让内里的纹理愈发清晰。每一道纹路都是一个故事,每一次冲刷都是一次重生。' }, + { title: '留白之美', description: '最好的设计往往藏在未说之处。一幅山水画的力量不在墨色,而在无墨之间的云雾与山岚。界面如此,人生亦然——空间不是空缺,是呼吸的节拍。' }, + { title: '光与影', description: '没有阴影,光便失去了意义。创作的本质不是消除黑暗,而是让明暗相互成全。一幅好的摄影作品,阴影里藏着比亮处更多的故事。' }, + { title: '旅途随笔', description: '旅行不是从一个地方到另一个地方,而是从一种视角切换到另一种视角。同一轮明月,在不同的经度里,会映出不同的乡愁。' }, + { title: '匠人之心', description: '一把好刀,从锻打到淬火,每一步都不可省略。手艺人知道:没有什么捷径能绕过时间的沉淀。代码与器物,皆是时间的艺术。' }, + { title: '草木人间', description: '每一片叶子都是一个小小的宇宙——脉络如河流,叶肉如大地。阳光穿过叶片的时候,像是神在端详自己的指纹。我们不过是这绿色剧场里的观众。' }, + { title: '夜的独白', description: '凌晨三点,城市终于安静下来。窗外零星灯火像是失眠者彼此交换的暗号。在这万籁俱寂的时刻,思绪反而最清晰——或许黑夜才是思考者真正的白昼。' }, +] + +const projectTags = [ + ['Vue', 'TypeScript', 'Nuxt'], + ['React', 'Next.js', 'Tailwind'], + ['Node.js', 'PostgreSQL', 'Docker'], + ['Figma', 'Design System', 'UI/UX'], + ['Python', 'FastAPI', 'MongoDB'], + ['Swift', 'SwiftUI', 'iOS'], + ['Go', 'gRPC', 'Kubernetes'], + ['Rust', 'WebAssembly', 'Canvas'], + ['Three.js', 'WebGL', 'GLSL'], + ['Svelte', 'SvelteKit', 'Prisma'], + ['Astro', 'MDX', 'Contentful'], + ['Flutter', 'Dart', 'Firebase'], +] + +const typeCycle: CardType[] = [ + 'image-text', 'text', 'image-text', 'portfolio', + 'image-text', 'image', 'image-text', 'project', + 'text', 'image-text', 'portfolio', 'image-text', + 'image', 'image-text', 'project', 'text', + 'image-text', 'portfolio', 'image-text', 'image', +] + +function genItem(globalIdx: number): CardData { + const type = typeCycle[globalIdx % typeCycle.length] + const idx = globalIdx % 12 + const base: Pick = { + id: globalIdx + 1, + type, + title: titles[idx], + aspectRatio: 0.6 + Math.random() * 0.7, + } + + switch (type) { + case 'text': { + const tc = textContents[globalIdx % textContents.length] + return { ...base, title: tc.title, description: tc.description, aspectRatio: 0.6 + Math.random() * 0.5 } + } + case 'image': + return { ...base, image: images[idx], description: undefined, aspectRatio: 0.65 + Math.random() * 0.55 } + case 'image-text': + return { ...base, image: images[idx], description: descriptions[idx], aspectRatio: 0.6 + Math.random() * 0.7 } + case 'portfolio': { + const set = portfolioSets[globalIdx % portfolioSets.length] + const ar = 0.85 + Math.random() * 0.15 + return { ...base, images: set, description: descriptions[idx], aspectRatio: ar } + } + case 'project': + return { + ...base, + image: images[idx], + description: descriptions[idx], + tags: projectTags[globalIdx % projectTags.length], + aspectRatio: 0.65 + Math.random() * 0.45, + } + } +} + +export default defineEventHandler(async (event) => { + const query = getQuery(event) + const page = Math.max(1, parseInt(String(query.page || '1'))) + const pageSize = Math.min(30, Math.max(1, parseInt(String(query.pageSize || '12')))) + + const start = (page - 1) * pageSize + const items = Array.from({ length: pageSize }, (_, i) => genItem(start + i)) + + const total = 60 + const hasMore = start + pageSize < total + + return { items, hasMore, page } +})