commit f80e5c0b5a59e110b89e26e7c5829fe37edb0de9
Author: RhenCloud
Date: Sat Dec 6 23:39:15 2025 +0800
feat: 初始化项目
- 初始化 Vue 3 + Vite 项目结构
- 添加核心组件 (Hero, About, Projects, Friends 等)
- 配置页面路由和基础样式
- 添加服务端 API 接口 (邮件发送, 好友请求)
- 添加项目配置文件 (vercel.json, vite.config.js)
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..facb12f
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,22 @@
+VITE_GITHUB_TOKEN=your-github-token
+
+# SMTP 服务器地址
+SMTP_HOST=smtp.example.com
+
+# SMTP 端口(不填写默认465)
+SMTP_PORT=465
+
+# SMTP 用户名(一般与SENDER_EMAIL相同)
+SMTP_USER=your-email@example.com
+
+# SMTP 密码
+SMTP_PASS=your-email-password
+
+# 收件邮箱地址
+ADMIN_EMAIL=admin@example.com
+
+# 发件邮箱地址(一般与SMTP_USER相同)
+SENDER_EMAIL=sender@example.com
+
+# 是否启用SSL
+SMTP_SECURE=true
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..14aaedd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,36 @@
+# dependencies
+node_modules/
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
+
+# builds
+dist/
+build/
+.out/
+.next/
+.vercel/
+.vite/
+
+# env & secrets
+.env
+.env.*
+!.env.example
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+logs/
+*.log
+
+# editors & system
+.vscode/
+.idea/
+.DS_Store
+Thumbs.db
+
+# tests
+tests/
+test*
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3b31524
--- /dev/null
+++ b/README.md
@@ -0,0 +1,168 @@
+# Cloud Home
+
+一个基于 Vue 的个人主页模板,内置友链申请、项目展示、友链随机展示、自定义配置,支持 Vercel 部署与邮件通知。
+
+## 特性
+
+- 🎨 个性化主页:头像、社交链接、技能、站点/项目列表可配置。
+- 🔗 友链模块:支持申请表单、邮件通知、随机顺序展示。
+- 📱 响应式设计:适配桌面与移动端。
+- ⚙️ Serverless 部署支持
+
+## 技术栈
+
+- 前端:Vue 3 + HTML + CSS
+- 构建:Vite
+- 部署:Vercel(静态构建 + Serverless Functions)
+
+## 配置指南
+
+### 站点配置文件 (`src/config/siteConfig.ts`)
+
+本项目的所有静态内容配置均集中在 `src/config/siteConfig.ts` 文件中。
+
+```typescript
+const siteConfig: SiteConfig = {
+ profile: {
+ name: "Example User", // 你的名字
+ title: "I'm a software developer.", // 你的简介,可为空
+ avatar: "avatar.webp", // 你的头像,可为public目录下的文件或外部链接
+ bio: "Hello World", // 你的喜欢的一句话,可为空
+ },
+
+ // 社交链接,预定义的社交链接可在 `src/components/SocialLink.vue` 中查阅
+ socialLinks: [
+ { name: "GitHub", url: "https://github.com/ExampleUser" },
+ { name: "Email", url: "mailto:you@example.com" },
+ { name: "Telegram", url: "https://t.me/ExampleUser" },
+ { name: "Bilibili", url: "https://space.bilibili.com/1502883335" },
+ { name: "Blog", url: "https://blog.example.com" },
+ ],
+
+ github: {
+ username: "ExampleUser", // 你的 GitHub 用户名
+ },
+
+ // 个人介绍卡片
+ about: [
+ { title: "Example", desc: "Example description", icon: "🧠" },
+ { title: "Example", desc: "Example description", icon: "🛠️" },
+ { title: "Example", desc: "Example description", icon: "🎬" },
+ { title: "Example", desc: "Example description", icon: "🎮" },
+ ],
+
+ siteMeta: {
+ title: "RhenCloud", // 网站标题
+ icon: "favicon.ico", // 网站图标,可为public目录下的文件或外部链接
+ },
+
+ // 技能图标展示,详见https://github.com/tandpfun/skill-icons#icons-list
+ skills: [
+ { title: "前端", items: ["css", "html", "javascript", "typescript", "vue"] },
+ { title: "后端 / 云", items: ["cpp", "cloudflare", "docker", "java", "mysql", "nodejs", "python", "vercel"] },
+ { title: "工具", items: ["ae", "au", "git", "github", "md", "ps", "pr", "vscode"] },
+ { title: "操作系统", items: ["arch", "linux", "windows"] },
+ ],
+
+ sites: [
+ {
+ name: "Example Site 1",
+ desc: "Example Site 1",
+ url: "https://example1.com",
+ },
+ {
+ name: "Example Site 2",
+ desc: "Example Site 2",
+ url: "https://example2.com",
+ },
+ ],
+
+ projects: [
+ { name: "Example Project 1", url: "https://github.com/ExampleUser/example-project-1", desc: "Example Project 1" },
+ { name: "Example Project 2", url: "https://github.com/ExampleUser/example-project-2", desc: "Example Project 2" },
+ ],
+
+ friends: [
+ {
+ name: "Example Site 1",
+ desc: "Example Site 1",
+ url: "https://example1.com",
+ avatar: "https://example1.com/avatar.png",
+ },
+ {
+ name: "Example Site 2",
+ desc: "Example Site 2",
+ url: "https://example2.com",
+ avatar: "https://example2.com/avatar.png",
+ },
+ ],
+
+ footer: {
+ beian: "备案号", // 备案号,留空则不显示
+ beianLink: "https://beian.miit.gov.cn/", // 备案号链接,一般无需修改
+ showHitokoto: true, // 是否显示一言
+ hitokotoType: "a&b&c&d&j", // 一言类型,详见 https://developer.hitokoto.cn/sentence/#%E5%8F%A5%E5%AD%90%E7%B1%BB%E5%9E%8B-%E5%8F%82%E6%95%B0
+ customHtml: '', // 自定义 HTML 代码,如统计代码等
+ },
+};
+```
+
+### 其他配置
+
+- **404 页面**:修改 `public/404.html` 来自定义 404 错误页面的样式与内容。
+- **友链展示逻辑**:`FriendsSection.vue` 默认使用随机顺序渲染 `siteConfig.friends`,如需固定顺序请修改该组件。
+
+## 环境变量(邮件发送)
+
+在 Vercel 控制台或本地 `.env` 配置:
+
+- `VITE_GITHUB_TOKEN`: 具有仓库读取权限的 GitHub Token,用于绕过 GitHub API 速率限制。
+- `SMTP_HOST`: 邮件服务器主机名
+- `SMTP_PORT`: 端口(如 465 或 587)
+- `SMTP_USER`: 发件人邮箱账号
+- `SMTP_PASS`: 邮箱授权码或密码
+- `MAIL_FROM`: 发件人地址(通常同 SMTP_USER)
+- `ADMIN_EMAIL`: 接收通知的邮箱地址
+- `SMTP_SECURE`:是否启用 SSL/TLS 加密(默认 `true`)
+
+## 本地开发
+
+```bash
+pnpm install
+pnpm dev
+```
+
+访问 `http://localhost:5173/`。
+
+## 构建
+
+```bash
+pnpm build
+```
+
+产物输出到 `dist/`。
+
+## 部署到 Vercel
+
+1. 导入仓库到 Vercel。
+2. 设置上文的 SMTP 环境变量。
+3. 保持 `vercel.json` 中 `distDir: "dist"`。
+4. 部署后,静态文件从 `dist/` 提供,API 走 `/api/send-mail`。
+
+## API
+
+- `POST /api/send-mail`:友链申请邮件发送。请求体示例:
+
+```json
+{
+ "name": "RhenCloud",
+ "url": "https://example.com",
+ "desc": "个人博客",
+ "email": "you@example.com",
+ "avatar": "https://example.com/avatar.png"
+}
+```
+
+## 许可
+
+MIT License.
diff --git a/api/send-mail.ts b/api/send-mail.ts
new file mode 100644
index 0000000..0413a3e
--- /dev/null
+++ b/api/send-mail.ts
@@ -0,0 +1,60 @@
+import type { VercelRequest, VercelResponse } from "@vercel/node";
+import nodemailer from "nodemailer";
+
+export default async function handler(req: VercelRequest, res: VercelResponse) {
+ // CORS 处理
+ // res.setHeader("Access-Control-Allow-Origin", "*");
+ // res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
+ // res.setHeader("Access-Control-Allow-Headers", "Content-Type");
+
+ // if (req.method === "OPTIONS") {
+ // return res.status(200).end();
+ // }
+
+ // if (req.method === "GET") {
+ // return res.status(200).json({ status: "ok", message: "use POST to send mail" });
+ // }
+
+ // if (req.method !== "POST") {
+ // return res.status(405).json({ error: "Method Not Allowed" });
+ // }
+
+ try {
+ const data = typeof req.body === "string" ? JSON.parse(req.body) : req.body;
+ const { name, email, site, desc, avatar } = data || {};
+ if (!name || !email || !desc) {
+ return res.status(400).json({ error: "Missing required fields: name, email, desc" });
+ }
+
+ const transporter = nodemailer.createTransport({
+ host: process.env.SMTP_HOST,
+ port: Number(process.env.SMTP_PORT || 465),
+ secure: Number(process.env.SMTP_PORT || 465) === 465,
+ auth: {
+ user: process.env.SMTP_USER,
+ pass: process.env.SMTP_PASS,
+ },
+ });
+
+ const info = await transporter.sendMail({
+ from: process.env.SENDER_EMAIL,
+ to: process.env.ADMIN_EMAIL,
+ subject: "友链申请 / 联系表单",
+ html: `
+
名称:${name}
+ 邮箱:${email}
+ 站点:${site || "未填写"}
+ 描述:${desc}
+ 头像:${avatar || "未填写"}
+ 时间:${new Date().toISOString()}
+ `,
+ });
+
+ return res.status(200).json({
+ message: "Mail sent",
+ id: info.messageId,
+ });
+ } catch (err) {
+ return res.status(500).json({ error: "Send mail failed", detail: (err as Error).message });
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..153041f
--- /dev/null
+++ b/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Cloud Home
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b6f91af
--- /dev/null
+++ b/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "cloud-home",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@vercel/node": "^5.5.15",
+ "express": "^5.2.1",
+ "he": "^1.2.0",
+ "nodemailer": "^7.0.11",
+ "vue": "^3.5.25",
+ "vue-router": "^4.6.3"
+ },
+ "devDependencies": {
+ "@types/express": "^5.0.6",
+ "@types/node": "^24.10.1",
+ "@types/nodemailer": "^7.0.4",
+ "@vitejs/plugin-vue": "^6.0.2",
+ "typescript": "^5.9.3",
+ "vite": "^7.2.6",
+ "vue-tsc": "^3.1.6"
+ }
+}
\ No newline at end of file
diff --git a/public/404.html b/public/404.html
new file mode 100644
index 0000000..6c825fc
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+ 404 - Not Found
+
+
+
+
+
+ 404
+ 页面不见了,或已被移除。
+ 返回首页
+
+
+
+
\ No newline at end of file
diff --git a/public/avatar.webp b/public/avatar.webp
new file mode 100644
index 0000000..1d61fad
Binary files /dev/null and b/public/avatar.webp differ
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..0358a03
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/src/components/AboutSection.vue b/src/components/AboutSection.vue
new file mode 100644
index 0000000..79d6c39
--- /dev/null
+++ b/src/components/AboutSection.vue
@@ -0,0 +1,67 @@
+
+
+ 个人简介
+ 关于我 · About Me
+
+
+
+ {{ item.icon }}
+
{{ item.title }}
+
+ {{ item.desc }}
+
+
+
+
+
+
+
+
diff --git a/src/components/FooterSection.vue b/src/components/FooterSection.vue
new file mode 100644
index 0000000..9d30654
--- /dev/null
+++ b/src/components/FooterSection.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
diff --git a/src/components/FriendsSection.vue b/src/components/FriendsSection.vue
new file mode 100644
index 0000000..58fc43e
--- /dev/null
+++ b/src/components/FriendsSection.vue
@@ -0,0 +1,311 @@
+
+
+ 友情链接
+ 欢迎互换友链 · Friends
+
+
+
+
+
+
+
+
+
![]()
+
{{ f.name }}
+
+
友链
+
+ {{ f.desc || "一个有趣的站点" }}
+ 访问 →
+
+
+
+
+
+
+
{{ dialogTitle }}
+
{{ dialogText }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/GithubSection.vue b/src/components/GithubSection.vue
new file mode 100644
index 0000000..b8caa2a
--- /dev/null
+++ b/src/components/GithubSection.vue
@@ -0,0 +1,132 @@
+
+
+ GitHub
+
+
常用语言
+
我常用的语言 · Languages
+
+
+
+
提交热力图
+
我的提交热力图 · Acitivity Heatmap
+
![GitHub Heatmap]()
+
+
+
+
+
+
+
diff --git a/src/components/HeroSection.vue b/src/components/HeroSection.vue
new file mode 100644
index 0000000..6a6a98e
--- /dev/null
+++ b/src/components/HeroSection.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
{{ profile.name }}
+
{{ profile.title }}
+
{{ profile.bio }}
+
+
+
+
+
+
+
diff --git a/src/components/PageSwitcher.vue b/src/components/PageSwitcher.vue
new file mode 100644
index 0000000..824f05f
--- /dev/null
+++ b/src/components/PageSwitcher.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
diff --git a/src/components/ProjectsSection.vue b/src/components/ProjectsSection.vue
new file mode 100644
index 0000000..19c2057
--- /dev/null
+++ b/src/components/ProjectsSection.vue
@@ -0,0 +1,86 @@
+
+
+ 项目作品
+ 一些正在维护或已发布的项目 · Projects
+
+
+
+
{{ p.name }}
+ 项目
+
+ {{ p.desc }}
+ 查看仓库 →
+
+
+
+
+
+
+
+
diff --git a/src/components/SitesSection.vue b/src/components/SitesSection.vue
new file mode 100644
index 0000000..a3ed6c6
--- /dev/null
+++ b/src/components/SitesSection.vue
@@ -0,0 +1,97 @@
+
+
+ 我的网站
+ 正在运行的站点 · Websites
+
+
+
+
+
{{ site.name }}
+
+
在线
+
+ {{ site.desc || "点击访问了解更多" }}
+ 查看 →
+
+
+
+
+
+
+
+
diff --git a/src/components/SkillsSection.vue b/src/components/SkillsSection.vue
new file mode 100644
index 0000000..b332a20
--- /dev/null
+++ b/src/components/SkillsSection.vue
@@ -0,0 +1,81 @@
+
+
+
+
技能专长
+
我常用的工具与技术 · Skills & Technologies
+
+
+
+
+
+
+
+
diff --git a/src/components/SocialLinks.vue b/src/components/SocialLinks.vue
new file mode 100644
index 0000000..65596ae
--- /dev/null
+++ b/src/components/SocialLinks.vue
@@ -0,0 +1,96 @@
+
+
+ 社交链接
+ 社交账号 · Links
+
+
+
+
+
+
+
diff --git a/src/config/siteConfig.ts b/src/config/siteConfig.ts
new file mode 100644
index 0000000..b75a145
--- /dev/null
+++ b/src/config/siteConfig.ts
@@ -0,0 +1,144 @@
+interface SiteConfig {
+ profile: {
+ name: string;
+ title: string;
+ avatar: string;
+ bio: string;
+ };
+ socialLinks: Array<{
+ name: string;
+ url: string;
+ }>;
+ github: {
+ username: string;
+ };
+ about: Array<{
+ title: string;
+ desc: string;
+ icon: string;
+ }>;
+ siteMeta: {
+ title: string;
+ icon: string;
+ };
+ skills: Array<{
+ title: string;
+ items: string[];
+ }>;
+ sites: Array<{
+ name: string;
+ desc: string;
+ url: string;
+ }>;
+ projects: Array<{
+ name: string;
+ url: string;
+ desc: string;
+ }>;
+ friends: Array<{
+ name: string;
+ desc: string;
+ url: string;
+ avatar: string;
+ }>;
+ footer: {
+ beian: string;
+ beianLink: string;
+ showHitokoto: boolean;
+ hitokotoType: string;
+ customHtml: string;
+ };
+}
+
+const siteConfig: SiteConfig = {
+ profile: {
+ name: "RhenCloud",
+ title: "I'm RhenCloud.",
+ avatar: "avatar.webp", // public/avatar.webp
+ bio: "趁世界还未重启之前 约一次爱恋",
+ },
+
+ socialLinks: [
+ { name: "GitHub", url: "https://github.com/RhenCloud" },
+ { name: "Email", url: "mailto:i@rhen.cloud" },
+ { name: "Telegram", url: "https://t.me/RhenCloud" },
+ { name: "Bilibili", url: "https://space.bilibili.com/1502883335" },
+ { name: "Blog", url: "https://blog.rhen.cloud" },
+ ],
+
+ github: {
+ username: "RhenCloud",
+ },
+
+ about: [
+ { title: "Pro-LGBT", desc: "我相信性别多样性是人们应有的自由和权利。", icon: "🧠" },
+ { title: "Developer", desc: "专注后端 / 云原生,热爱自动化与高可用。", icon: "🛠️" },
+ { title: "Anime Fan", desc: "二次元爱好者,享受故事与想象力。", icon: "🎬" },
+ { title: "Just For Fun", desc: "我喜欢尝试新鲜事物,折腾小众技术", icon: "🎮" },
+ ],
+
+ siteMeta: {
+ title: "RhenCloud",
+ icon: "favicon.ico", // public/favicon.ico
+ },
+
+ skills: [
+ { title: "前端", items: ["css", "html", "javascript", "typescript", "vue"] },
+ { title: "后端 / 云", items: ["cpp", "cloudflare", "docker", "java", "mysql", "nodejs", "python", "vercel"] },
+ { title: "工具", items: ["ae", "au", "git", "github", "md", "ps", "pr", "vscode"] },
+ { title: "操作系统", items: ["arch", "linux", "windows"] },
+ ],
+
+ sites: [
+ {
+ name: "个人主页",
+ desc: "个人主页",
+ url: "https://rhen.cloud",
+ },
+ {
+ name: "我的博客",
+ desc: "分享与记录",
+ url: "https://blog.rhen.cloud",
+ },
+ {
+ name: "来视奸我",
+ desc: "使用Sleepy项目搭建的视奸网站",
+ url: "https://sleepy.rhen.cloud",
+ },
+ {
+ name: "网站监控",
+ desc: "网站运行状态监控",
+ url: "https://status.rhen.cloud",
+ },
+ ],
+
+ projects: [
+ { name: "Cloud Home", url: "https://github.com/RhenCloud/cloud-home", desc: "个人主页模板" },
+ { name: "ILP", url: "https://github.com/RhenCloud/ILP", desc: "跨平台、多网站、模块化的小说下载器" },
+ { name: "ILP-C++", url: "https://github.com/RhenCloud/ILP-Cpp", desc: "跨平台、多网站、模块化的小说下载器" },
+ ],
+ friends: [
+ {
+ name: "wuxian",
+ desc: "wuxian's web",
+ url: "https://www.alxian.cn",
+ avatar: "https://www.alxian.cn/_next/image?url=%2Fimages%2Favatar.jpg&w=256&q=75",
+ },
+ {
+ name: "鈴奈咲桜のBlog",
+ desc: "一个普普通通的Blog",
+ url: "https://blog.sakura.ink",
+ avatar: "https://q2.qlogo.cn/headimg_dl?dst_uin=2731443459&spec=5",
+ },
+ ],
+
+ footer: {
+ beian: "津ICP备2025039003号-1",
+ beianLink: "https://beian.miit.gov.cn/",
+ showHitokoto: true,
+ hitokotoType: "a&b&c&d&j",
+ customHtml: '© 2025 RhenCloud',
+ },
+};
+
+export default siteConfig;
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..c8c4d1e
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,6 @@
+import { createApp } from "vue";
+import App from "./App.vue";
+import router from "./router";
+import "./styles.css";
+
+createApp(App).use(router).mount("#app");
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..7319b7d
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,19 @@
+import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
+import Home from "../views/Home.vue";
+import About from "../views/About.vue";
+import Sites from "../views/Sites.vue";
+import Projects from "../views/Projects.vue";
+import Friends from "../views/Friends.vue";
+
+const routes: RouteRecordRaw[] = [
+ { path: "/", name: "Home", component: Home, meta: { order: 0, label: "首页" } },
+ { path: "/about", name: "About", component: About, meta: { order: 1, label: "关于" } },
+ { path: "/sites", name: "Sites", component: Sites, meta: { order: 2, label: "网站" } },
+ { path: "/projects", name: "Projects", component: Projects, meta: { order: 3, label: "项目" } },
+ { path: "/friends", name: "Friends", component: Friends, meta: { order: 4, label: "友链" } },
+];
+
+export default createRouter({
+ history: createWebHistory(),
+ routes,
+});
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..68d91ef
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,92 @@
+:root {
+ color-scheme: light dark;
+ font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ background: #0f1629;
+ color: #e8eefc;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ background: radial-gradient(circle at 20% 20%, #1b2b4b, #0f1629);
+}
+
+a {
+ color: #7cc1ff;
+ text-decoration: none;
+}
+
+.page {
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 32px 16px 48px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.card {
+ background: rgba(255, 255, 255, 0.04);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 16px;
+ padding: 18px 20px;
+ backdrop-filter: blur(8px);
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25);
+}
+
+.hero {
+ display: grid;
+ grid-template-columns: 96px 1fr;
+ gap: 16px;
+ align-items: center;
+}
+
+.avatar {
+ width: 96px;
+ height: 96px;
+ border-radius: 50%;
+ object-fit: cover;
+ border: 2px solid rgba(255, 255, 255, 0.2);
+}
+
+h1,
+h2,
+h3 {
+ margin: 0 0 8px 0;
+}
+
+p {
+ margin: 6px 0;
+}
+
+.muted {
+ color: #a8b3cf;
+ font-size: 0.95rem;
+}
+
+.chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.chips a {
+ padding: 6px 12px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.08);
+}
+
+.list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.list li {
+ margin-bottom: 8px;
+}
+
+.footer {
+ text-align: center;
+ margin-top: 8px;
+}
diff --git a/src/views/About.vue b/src/views/About.vue
new file mode 100644
index 0000000..99df128
--- /dev/null
+++ b/src/views/About.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Friends.vue b/src/views/Friends.vue
new file mode 100644
index 0000000..cdbd8a4
--- /dev/null
+++ b/src/views/Friends.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/src/views/Home.vue b/src/views/Home.vue
new file mode 100644
index 0000000..ce69ab9
--- /dev/null
+++ b/src/views/Home.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Projects.vue b/src/views/Projects.vue
new file mode 100644
index 0000000..c313fb3
--- /dev/null
+++ b/src/views/Projects.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/src/views/Sites.vue b/src/views/Sites.vue
new file mode 100644
index 0000000..f06bb17
--- /dev/null
+++ b/src/views/Sites.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..68af4be
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,7 @@
+///
+
+declare module "*.vue" {
+ import type { DefineComponent } from "vue";
+ const component: DefineComponent<{}, {}, any>;
+ export default component;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..5bd2acc
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "preserve",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..2f699d8
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..624634b
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,10 @@
+{
+ "version": 2,
+ "framework": "vite",
+ "routes": [
+ {
+ "src": "/api/(.*)",
+ "dest": "/api/$1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..c5c15df
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+
+export default defineConfig({
+ plugins: [vue()],
+ server: { port: 5173 },
+});