diff --git a/.agent/skills/git-pr-description/SKILL.md b/.agent/skills/git-pr-description/SKILL.md
index 4aba276..97620fb 100644
--- a/.agent/skills/git-pr-description/SKILL.md
+++ b/.agent/skills/git-pr-description/SKILL.md
@@ -103,13 +103,21 @@ git diff master..HEAD
## ⚠️ 修改的內容
-依模組 / 元件分組列出改動:
-
-### [元件或模組名稱]
-- 具體改了什麼、為什麼這樣改
-
-### [另一個元件或模組名稱]
-- 具體改了什麼
+依功能與需求分組:
+- **功能名稱 / 需求項目**:說明此組變更的業務目標
+- **修改方向**:簡述(效能、修復、樣式等)
+- **內容**:列出具體修改點,**禁止**出現任何檔案路徑(包含相對路徑),一律改用功能描述,例如「新增手風琴展開動畫」而非「修改 `src/components/FAQ.jsx`」
+
+### [功能名稱 / 需求項目]
+- **修改方向**:...
+- **內容**:
+ - 具體修改點 1(純功能描述)
+ - 具體修改點 2(純功能描述)
+
+### [另一個功能名稱]
+- **修改方向**:...
+- **內容**:
+ - ...
## 🧪 測試步驟
@@ -150,9 +158,16 @@ git diff master..HEAD
- 不要在 code block 外面加額外的 `📝 PR Title:` 等前綴,直接輸出可複製的 markdown
- code block 內的第一行為 PR Title,空一行後接 Description
- 使用者可要求調整任何部分後再複製使用
+- **重要**:Description 中**禁止**出現任何檔案路徑(包含相對路徑),一律改用純功能描述。
---
+## 🛑 格式嚴格規範
+
+- **禁止任何 Markdown 連結格式**:`[文字](...)`
+- **禁止任何 URI / scheme**:比如 `file://`、`cci:`
+- **禁止出現任何檔案路徑**:不論相對或絕對路徑,一律不出現在 Description 中,改以純功能描述取代
+
## 邊界情況處理
- **存在未提交的變更**:提醒使用者先提交或 stash,避免遺漏
diff --git a/.agent/skills/git-pr-description/references/pr-template.md b/.agent/skills/git-pr-description/references/pr-template.md
index 4cb76c7..2a75958 100644
--- a/.agent/skills/git-pr-description/references/pr-template.md
+++ b/.agent/skills/git-pr-description/references/pr-template.md
@@ -11,15 +11,25 @@
## ⚠️ 修改的內容
-
-
-### [元件 / 模組名稱]
+
-- 變更說明
+### [功能名稱 / 需求項目]
+- **修改方向**:簡述調整目的 (例如:優化效能、修復邏輯錯誤、調整樣式)
+- **內容**:
+ - 具體修改點 1(純功能描述)
+ - 具體修改點 2(純功能描述)
-### [元件 / 模組名稱]
+### [另一個功能名稱]
+- **修改方向**:...
+- **內容**:
+ - ...
-- 變更說明
## 🧪 測試步驟
diff --git a/src/App.jsx b/src/App.jsx
index 3a7c65b..5ec99d5 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -4,8 +4,10 @@ import SocialProof from './components/SocialProof';
import Features from './components/Features';
import UseCases from './components/UseCases';
import Pricing from './components/Pricing';
+import FAQ from './components/FAQ';
import CallToAction from './components/CallToAction';
import Footer from './components/Footer';
+import CookieConsent from './components/CookieConsent';
function App() {
return (
@@ -17,9 +19,11 @@ function App() {
+
+
);
}
diff --git a/src/components/CookieConsent.jsx b/src/components/CookieConsent.jsx
new file mode 100644
index 0000000..da43e4d
--- /dev/null
+++ b/src/components/CookieConsent.jsx
@@ -0,0 +1,60 @@
+import React, { useState, useEffect } from 'react';
+
+const CookieConsent = () => {
+ const [showBanner, setShowBanner] = useState(false);
+ const [animateIn, setAnimateIn] = useState(false);
+
+ useEffect(() => {
+ const consent = localStorage.getItem('cookie-consent');
+ if (!consent) {
+ setShowBanner(true);
+ // Small delay to allow browser to paint before adding class for transition
+ setTimeout(() => setAnimateIn(true), 50);
+ }
+ }, []);
+
+ const handleAccept = (type) => {
+ localStorage.setItem('cookie-consent', type);
+ setAnimateIn(false); // Triggers exit animation
+
+ // Wait for animation to finish before removing from DOM
+ setTimeout(() => {
+ setShowBanner(false);
+ }, 400);
+ };
+
+ if (!showBanner) return null;
+
+ return (
+
+
+
+
+
我們重視您的隱私
+
+ 我們使用 Cookie 來改善您的瀏覽體驗、提供個人化內容並分析網站流量。
+ 點擊「接受所有」即表示您同意我們使用 Cookie。
+ 了解更多
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CookieConsent;
diff --git a/src/components/FAQ.jsx b/src/components/FAQ.jsx
new file mode 100644
index 0000000..840955a
--- /dev/null
+++ b/src/components/FAQ.jsx
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+
+const FAQ_DATA = [
+ {
+ id: 1,
+ question: "SalesPilot CRM 與其他工具有什麼不同?",
+ answer: "SalesPilot 專為高成長團隊設計。不同於傳統 CRM 笨重且難以使用,我們專注於自動化、速度以及美觀的使用者介面,讓您的團隊真正享受使用的過程。"
+ },
+ {
+ id: 2,
+ question: "有提供免費試用嗎?",
+ answer: "有的!我們提供 14 天免費試用,您可以完整體驗所有 Pro 功能。無需綁定信用卡即可開始使用,讓您無風險地探索我們的平台。"
+ },
+ {
+ id: 3,
+ question: "我可以從目前的 CRM 匯入資料嗎?",
+ answer: "當然可以。我們提供 CSV 和 Excel 檔案的一鍵匯入工具。此外,我們也提供 Salesforce、HubSpot 和 Pipedrive 的直接遷移工具,讓切換過程無縫接軌。"
+ },
+ {
+ id: 4,
+ question: "我的資料安全嗎?",
+ answer: "安全性是我們的首要考量。我們使用企業級 AES-256 加密技術保護靜態資料,並使用 TLS 1.3 保護傳輸中的資料。我們符合 SOC 2 Type II 標準,並定期進行安全稽核。"
+ },
+ {
+ id: 5,
+ question: "你們有提供與其他工具的整合嗎?",
+ answer: "是的,SalesPilot 可與超過 2,000+ 個應用程式連接。我們與 Slack、Gmail、Outlook、Zoom 和 Stripe 有原生整合,並完全支援 Zapier,以自動化您的整個工作流程。"
+ },
+ {
+ id: 6,
+ question: "如果我需要升級或降級方案怎麼辦?",
+ answer: "您可以隨時從後台儀表板更改您的方案。費用將根據使用天數自動按比例調整,您只需支付實際使用的部分。"
+ }
+];
+
+const FAQ = () => {
+ const [openItems, setOpenItems] = useState([]);
+
+ const toggleItem = (id) => {
+ setOpenItems(prev => {
+ if (prev.includes(id)) {
+ return prev.filter(item => item !== id);
+ } else {
+ return [...prev, id];
+ }
+ });
+ };
+
+ return (
+
+
+
+
支援中心
+
常見問答
+
關於 SalesPilot CRM 的所有疑問。找不到您需要的答案嗎?歡迎與我們友善的團隊聯繫。
+
+
+
+ {FAQ_DATA.map((item) => {
+ const isOpen = openItems.includes(item.id);
+ return (
+
+
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default FAQ;
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 5e205a8..44bb73d 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -1,8 +1,10 @@
import { useState } from 'react';
+import { useTheme } from '../context/ThemeContext';
import { NAV_LINKS, BRAND } from '../data/navigation';
-function Navbar() {
+const Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false);
+ const { theme, toggleTheme } = useTheme();
return (
@@ -39,9 +41,18 @@ function Navbar() {
))}
-
- 預約 Demo
-
+
diff --git a/src/context/ThemeContext.jsx b/src/context/ThemeContext.jsx
new file mode 100644
index 0000000..8e6130a
--- /dev/null
+++ b/src/context/ThemeContext.jsx
@@ -0,0 +1,44 @@
+import React, { createContext, useContext, useEffect, useState } from 'react';
+
+const ThemeContext = createContext();
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
+
+export const ThemeProvider = ({ children }) => {
+ const [theme, setTheme] = useState(() => {
+ // Check localStorage first
+ const storedTheme = localStorage.getItem('theme');
+ if (storedTheme) {
+ return storedTheme;
+ }
+ // Fallback to system preference
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
+ return 'light';
+ }
+ return 'dark';
+ });
+
+ useEffect(() => {
+ const root = document.documentElement;
+ // Set the data-theme attribute
+ root.setAttribute('data-theme', theme);
+ // Persist to localStorage
+ localStorage.setItem('theme', theme);
+ }, [theme]);
+
+ const toggleTheme = () => {
+ setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/data/navigation.js b/src/data/navigation.js
index c257c0e..9145893 100644
--- a/src/data/navigation.js
+++ b/src/data/navigation.js
@@ -3,6 +3,7 @@ export const NAV_LINKS = [
{ label: '應用場景', href: '#use-cases' },
{ label: '方案價格', href: '#pricing' },
{ label: '客戶見證', href: '#social-proof' },
+ { label: '常見問答', href: '#faq' },
];
export const BRAND = {
diff --git a/src/index.css b/src/index.css
index 507bc0c..53828dc 100644
--- a/src/index.css
+++ b/src/index.css
@@ -82,6 +82,42 @@
/* Layout */
--container-max: 1200px;
--navbar-height: 72px;
+ --navbar-bg: rgba(10, 14, 26, 0.8);
+
+
+ /* Component Specific */
+ --hero-title-gradient: linear-gradient(135deg, #fff 0%, var(--color-primary-light) 50%, var(--color-accent-light) 100%);
+}
+
+[data-theme="light"] {
+ /* Colors - Light Mode Overrides */
+ --color-bg: #ffffff;
+ --color-bg-elevated: #f8fafc;
+ --color-bg-card: #ffffff;
+ --color-bg-card-hover: #f1f5f9;
+ --color-surface: #e2e8f0;
+
+ --color-primary: #4f46e5;
+ /* Slightly darker for better contrast on white */
+ --color-primary-light: #6366f1;
+ --color-primary-dark: #4338ca;
+ --color-primary-glow: rgba(79, 70, 229, 0.15);
+
+ --color-text: #0f172a;
+ --color-text-secondary: #475569;
+ --color-text-muted: #64748b;
+ --color-text-inverse: #f8fafc;
+
+ --color-border: #e2e8f0;
+ --color-border-hover: #cbd5e1;
+
+ --shadow-sm: 0 1px 2px 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);
+ --shadow-glow: 0 0 20px var(--color-primary-glow);
+
+ --hero-title-gradient: linear-gradient(135deg, var(--color-primary-dark) 0%, var(--color-primary) 50%, var(--color-accent) 100%);
+ --navbar-bg: rgba(255, 255, 255, 0.8);
}
/* --- Reset & Base --- */
@@ -106,6 +142,16 @@ body {
background: var(--color-bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ transition: color 0.3s ease, background-color 0.3s ease, border-color 0.3s ease;
+}
+
+/* Apply transitions to major containers for theme switching */
+.navbar,
+.features__card,
+.hero__mockup,
+.social-proof,
+.footer {
+ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}
img {
@@ -118,7 +164,8 @@ a {
text-decoration: none;
}
-ul, ol {
+ul,
+ol {
list-style: none;
}
@@ -256,7 +303,7 @@ button {
right: 0;
height: var(--navbar-height);
z-index: 1000;
- background: rgba(10, 14, 26, 0.8);
+ background: var(--navbar-bg);
backdrop-filter: blur(16px);
border-bottom: 1px solid var(--color-border);
}
@@ -320,6 +367,34 @@ button {
width: 100%;
}
+.navbar__actions {
+ display: flex;
+ align-items: center;
+ gap: var(--space-4);
+}
+
+.theme-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border-radius: var(--radius-full);
+ background: transparent;
+ color: var(--color-text);
+ font-size: var(--text-lg);
+ transition: all var(--transition-base);
+ border: 1px solid var(--color-border);
+ cursor: pointer;
+}
+
+.theme-toggle:hover {
+ background: var(--color-bg-card-hover);
+ border-color: var(--color-primary-light);
+ color: var(--color-primary);
+ transform: rotate(15deg);
+}
+
.navbar__toggle {
display: none;
flex-direction: column;
@@ -369,7 +444,28 @@ button {
.navbar__cta {
width: 100%;
- margin-top: var(--space-4);
+ }
+
+ .navbar__actions {
+ flex-direction: column;
+ width: 100%;
+ align-items: flex-start;
+ }
+
+ .theme-toggle {
+ width: 100%;
+ justify-content: flex-start;
+ padding: var(--space-2) var(--space-4);
+ border-radius: var(--radius-md);
+ border: none;
+ font-size: var(--text-base);
+ }
+
+ .theme-toggle::after {
+ content: '切換主題';
+ margin-left: var(--space-2);
+ font-size: var(--text-sm);
+ font-weight: 500;
}
}
@@ -404,8 +500,15 @@ button {
}
@keyframes heroGlow {
- 0% { transform: translate(0, 0) scale(1); opacity: 0.6; }
- 100% { transform: translate(-40px, 40px) scale(1.15); opacity: 0.9; }
+ 0% {
+ transform: translate(0, 0) scale(1);
+ opacity: 0.6;
+ }
+
+ 100% {
+ transform: translate(-40px, 40px) scale(1.15);
+ opacity: 0.9;
+ }
}
.hero__grid {
@@ -432,7 +535,7 @@ button {
font-weight: 800;
line-height: 1.15;
margin-bottom: var(--space-6);
- background: linear-gradient(135deg, #fff 0%, var(--color-primary-light) 50%, var(--color-accent-light) 100%);
+ background: var(--hero-title-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
@@ -493,8 +596,15 @@ button {
}
@keyframes mockupFloat {
- 0%, 100% { transform: translateY(0); }
- 50% { transform: translateY(-12px); }
+
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+
+ 50% {
+ transform: translateY(-12px);
+ }
}
.hero__mockup-header {
@@ -513,9 +623,20 @@ button {
opacity: 0.4;
}
-.hero__mockup-dot:first-child { background: #ef4444; opacity: 0.7; }
-.hero__mockup-dot:nth-child(2) { background: #f59e0b; opacity: 0.7; }
-.hero__mockup-dot:nth-child(3) { background: #10b981; opacity: 0.7; }
+.hero__mockup-dot:first-child {
+ background: #ef4444;
+ opacity: 0.7;
+}
+
+.hero__mockup-dot:nth-child(2) {
+ background: #f59e0b;
+ opacity: 0.7;
+}
+
+.hero__mockup-dot:nth-child(3) {
+ background: #10b981;
+ opacity: 0.7;
+}
.hero__mockup-body {
display: flex;
@@ -578,10 +699,21 @@ button {
opacity: 0.7;
}
-.hero__mockup-card--blue { background: linear-gradient(135deg, #3b82f6, #6366f1); }
-.hero__mockup-card--purple { background: linear-gradient(135deg, #8b5cf6, #a855f7); }
-.hero__mockup-card--amber { background: linear-gradient(135deg, #f59e0b, #ef4444); }
-.hero__mockup-card--green { background: linear-gradient(135deg, #10b981, #06b6d4); }
+.hero__mockup-card--blue {
+ background: linear-gradient(135deg, #3b82f6, #6366f1);
+}
+
+.hero__mockup-card--purple {
+ background: linear-gradient(135deg, #8b5cf6, #a855f7);
+}
+
+.hero__mockup-card--amber {
+ background: linear-gradient(135deg, #f59e0b, #ef4444);
+}
+
+.hero__mockup-card--green {
+ background: linear-gradient(135deg, #10b981, #06b6d4);
+}
@media (max-width: 968px) {
.hero__inner {
@@ -841,7 +973,7 @@ button {
direction: rtl;
}
-.use-cases__item--reverse > * {
+.use-cases__item--reverse>* {
direction: ltr;
}
@@ -925,6 +1057,7 @@ button {
}
@media (max-width: 768px) {
+
.use-cases__item,
.use-cases__item--reverse {
grid-template-columns: 1fr;
@@ -1243,3 +1376,183 @@ button {
grid-template-columns: 1fr;
}
}
+
+/* ========================================
+ FAQ
+ ======================================== */
+.faq {
+ padding: var(--space-20) 0;
+ background: var(--color-bg);
+}
+
+.faq__grid {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-4);
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.faq__item {
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-lg);
+ background: var(--color-bg-card);
+ overflow: hidden;
+ transition: all var(--transition-base);
+}
+
+.faq__item--open {
+ border-color: var(--color-primary-glow);
+ background: var(--color-bg-elevated);
+}
+
+.faq__question {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-6);
+ text-align: left;
+ background: transparent;
+ cursor: pointer;
+ transition: background var(--transition-fast);
+}
+
+.faq__question:hover {
+ background: var(--color-bg-card-hover);
+}
+
+.faq__question-text {
+ font-size: var(--text-lg);
+ font-weight: 600;
+ color: var(--color-text);
+ line-height: 1.4;
+ padding-right: var(--space-4);
+}
+
+.faq__icon {
+ font-size: var(--text-xl);
+ font-weight: 400;
+ color: var(--color-primary-light);
+ min-width: 24px;
+ text-align: center;
+ transition: transform var(--transition-base);
+}
+
+.faq__answer-wrapper {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height var(--transition-slow), opacity var(--transition-slow);
+ opacity: 0;
+}
+
+.faq__item--open .faq__answer-wrapper {
+ max-height: 500px;
+ /* Adjust based on content length */
+ opacity: 1;
+}
+
+.faq__answerWrapperInner {
+ padding: 0 var(--space-6) var(--space-6);
+}
+
+.faq__answer {
+ color: var(--color-text-secondary);
+ line-height: 1.7;
+}
+
+@media (max-width: 640px) {
+ .faq__question {
+ padding: var(--space-4);
+ }
+
+ .faq__question-text {
+ font-size: var(--text-base);
+ }
+
+ .faq__answerWrapperInner {
+ padding: 0 var(--space-4) var(--space-4);
+ }
+}
+
+/* ========================================
+COOKIE CONSENT
+========================================*/
+.cookie-consent {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(17, 24, 39, 0.95);
+ /* --color-bg-elevated with opacity */
+ backdrop-filter: blur(12px);
+ border-top: 1px solid var(--color-border);
+ padding: var(--space-6) 0;
+ z-index: 9999;
+ transform: translateY(100%);
+ transition: transform 400ms cubic-bezier(0.16, 1, 0.3, 1);
+ box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.2);
+}
+
+.cookie-consent--visible {
+ transform: translateY(0);
+}
+
+.cookie-consent__inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-8);
+}
+
+.cookie-consent__content {
+ flex: 1;
+}
+
+.cookie-consent__title {
+ font-size: var(--text-lg);
+ font-weight: 700;
+ margin-bottom: var(--space-2);
+ color: var(--color-text);
+}
+
+.cookie-consent__text {
+ font-size: var(--text-sm);
+ color: var(--color-text-secondary);
+ line-height: 1.6;
+ max-width: 800px;
+}
+
+.cookie-consent__link {
+ color: var(--color-primary-light);
+ text-decoration: underline;
+ margin-left: var(--space-1);
+ transition: color var(--transition-fast);
+}
+
+.cookie-consent__link:hover {
+ color: var(--color-primary);
+}
+
+.cookie-consent__actions {
+ display: flex;
+ gap: var(--space-3);
+ flex-shrink: 0;
+}
+
+@media (max-width: 768px) {
+ .cookie-consent {
+ padding: var(--space-6) 0;
+ }
+
+ .cookie-consent__inner {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--space-6);
+ }
+
+ .cookie-consent__actions {
+ width: 100%;
+ justify-content: flex-end;
+ }
+}
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
index 644bee6..167b005 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,10 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
+import { ThemeProvider } from './context/ThemeContext';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
-
+
+
+
,
);