ADD:增加AIdemo

This commit is contained in:
lilan 2026-04-28 16:48:40 +08:00
parent ad0db44e3c
commit 5eca37cfdc
32 changed files with 11576 additions and 20 deletions

5859
design/ai/aidemo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
{
"name": "llm-frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.1"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"vite": "^5.0.8"
}
}

View File

@ -0,0 +1,824 @@
import React, { useState, useRef, useEffect } from 'react';
import axios from 'axios';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
function App() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [files, setFiles] = useState([]);
const [model, setModel] = useState('bailian-token-plan/qwen3.6-plus');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [previewContent, setPreviewContent] = useState('');
const [thinking, setThinking] = useState(false);
const [previewLoading, setPreviewLoading] = useState(false);
const [generatedFiles, setGeneratedFiles] = useState([]);
const [activePreview, setActivePreview] = useState(null);
const [previewMode, setPreviewMode] = useState('code'); // 'code' 'preview'
const [sessions, setSessions] = useState([]);
const [currentSession, setCurrentSession] = useState(null);
const [sessionInput, setSessionInput] = useState('');
const [showSessionModal, setShowSessionModal] = useState(false);
const [sessionMessagesCache, setSessionMessagesCache] = useState({}); //
const chatRef = useRef(null);
const modelOptions = [
{ value: 'bailian-token-plan/qwen3.6-plus', label: 'Qwen3.6 Plus (百炼)' },
{ value: 'bailian-token-plan/MiniMax-M2.5', label: 'MiniMax M2.5 (百炼)' },
{ value: 'bailian-token-plan/glm-5', label: 'GLM-5 (百炼)' },
{ value: 'bailian-token-plan/deepseek-v3.2', label: 'DeepSeek V3.2 (百炼)' },
{ value: 'ollama/qwen3-coder-next:latest', label: 'Qwen3 Coder (Ollama)' }
];
const scrollToBottom = () => {
if (chatRef.current) {
chatRef.current.scrollTop = chatRef.current.scrollHeight;
}
};
//
const loadSessions = async () => {
try {
const response = await axios.get('/api/sessions');
setSessions(response.data.sessions);
} catch (err) {
console.error('加载会话列表失败:', err);
setError('加载会话列表失败');
}
};
//
const createSession = async (title) => {
try {
const response = await axios.post('/api/sessions', { title });
const newSession = response.data;
setSessions(prev => [newSession, ...prev]);
setCurrentSession(newSession);
setMessages([
{
role: 'assistant',
content: '你好!我是基于 OpenCode 的大模型助手。请问有什么我可以帮助你的吗?你可以:\n1. 直接输入问题进行对话\n2. 上传文件进行分析\n3. 切换不同的模型\n\n例如你可以问我"如何使用 Python 实现快速排序?" 或者上传一个 PDF 文件让我分析其中的内容。',
generatedFiles: []
}
]);
setGeneratedFiles([]);
setShowSessionModal(false);
setSessionInput('');
} catch (err) {
console.error('创建会话失败:', err);
setError('创建会话失败');
}
};
//
const switchSession = async (session) => {
try {
setIsLoading(true);
setCurrentSession(session);
//
if (sessionMessagesCache[session.id]) {
setMessages(sessionMessagesCache[session.id]);
} else {
//
const response = await axios.get(`/api/sessions/${session.id}/messages`);
const sessionMessages = response.data.messages.map(msg => ({
role: msg.role,
content: msg.content,
generatedFiles: extractGeneratedFiles(msg.content)
}));
const messagesToSet = sessionMessages.length > 0 ? sessionMessages : [
{
role: 'assistant',
content: '你好!我是基于 OpenCode 的大模型助手。请问有什么我可以帮助你的吗?你可以:\n1. 直接输入问题进行对话\n2. 上传文件进行分析\n3. 切换不同的模型\n\n例如你可以问我"如何使用 Python 实现快速排序?" 或者上传一个 PDF 文件让我分析其中的内容。',
generatedFiles: []
}
];
setMessages(messagesToSet);
//
setSessionMessagesCache(prev => ({
...prev,
[session.id]: messagesToSet
}));
}
setGeneratedFiles([]);
} catch (err) {
console.error('切换会话失败:', err);
setError('切换会话失败');
} finally {
setIsLoading(false);
}
};
//
const deleteSession = async (sessionId) => {
try {
await axios.delete(`/api/sessions/${sessionId}`);
setSessions(prev => prev.filter(session => session.id !== sessionId));
if (currentSession && currentSession.id === sessionId) {
setCurrentSession(null);
setMessages([
{
role: 'assistant',
content: '你好!我是基于 OpenCode 的大模型助手。请问有什么我可以帮助你的吗?你可以:\n1. 直接输入问题进行对话\n2. 上传文件进行分析\n3. 切换不同的模型\n\n例如你可以问我"如何使用 Python 实现快速排序?" 或者上传一个 PDF 文件让我分析其中的内容。',
generatedFiles: []
}
]);
setGeneratedFiles([]);
}
} catch (err) {
console.error('删除会话失败:', err);
setError('删除会话失败');
}
};
const sendMessage = async () => {
if (!input.trim() && files.length === 0) return;
setIsLoading(true);
setError('');
setThinking(true);
try {
const [modelType, modelName] = model.split('/');
const modelId = model;
const newMessage = {
role: 'user',
content: input,
files: files.map(file => ({
name: file.name,
size: file.size,
type: file.type
}))
};
setMessages(prev => [...prev, newMessage]);
setInput('');
//
if (currentSession) {
setSessionMessagesCache(cache => ({
...cache,
[currentSession.id]: [...(cache[currentSession.id] || []), newMessage]
}));
}
let assistantContent = '';
if (files.length > 0) {
const formData = new FormData();
files.forEach(file => formData.append('files', file));
formData.append('messages', JSON.stringify([{ role: 'user', content: input }]));
formData.append('model_type', modelType);
formData.append('model_name', modelName);
const response = await axios.post('/api/chat/with-files', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
assistantContent = response.data.content;
} else {
console.log('发送消息时的会话ID:', currentSession?.id);
const response = await axios.post('/api/v1/chat/completions', {
messages: [{ role: 'user', content: input }],
model: modelId,
stream: false,
session_id: currentSession?.id
});
assistantContent = response.data.choices[0].message.content;
}
const assistantMessage = {
role: 'assistant',
content: assistantContent,
generatedFiles: extractGeneratedFiles(assistantContent)
};
if (currentSession) {
setSessionMessagesCache(cache => ({
...cache,
[currentSession.id]: [...(cache[currentSession.id] || []), assistantMessage]
}));
}
setMessages(prev => [...prev, assistantMessage]);
if (assistantMessage.generatedFiles.length > 0) {
setGeneratedFiles(prev => [...prev, ...assistantMessage.generatedFiles]);
}
setFiles([]);
} catch (err) {
console.error('发送消息失败:', err);
setError('发送消息失败,请重试');
setMessages(prev => prev.slice(0, -1));
setInput(input);
} finally {
setIsLoading(false);
setThinking(false);
}
};
const extractGeneratedFiles = (content) => {
const files = [];
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
const fileNameRegex = /^(?:file:?\s*)?[`"]?([\w./-]+\.(?:html|css|js|jsx|ts|tsx|py|sh|json|md))[`"]?/gim;
const fileExistsRegex = /The files ([\w./-]+\.(?:html|css|js|jsx|ts|tsx|py|sh|json|md)) and ([\w./-]+\.(?:html|css|js|jsx|ts|tsx|py|sh|json|md)) already exist and are complete/g;
const fileCreatedRegex = /已创建\s+([\w./-]+\.(?:html|css|js|jsx|ts|tsx|py|sh|json|md))(?:\s+文件)?/g;
const filePathRegex = /`([\w./-]+\.(?:html|css|js|jsx|ts|tsx|py|sh|json|md))`/g;
const listFileRegex = /-\s*\*{0,2}([\w./-]+\.(?:html|css|js|jsx|ts|tsx|py|sh|json|md))\*{0,2}\s*-/g;
//
let match;
while ((match = codeBlockRegex.exec(content)) !== null) {
const language = match[1] || 'text';
const code = match[2].trim();
const fileNameMatch = content.substring(0, match.index).match(fileNameRegex);
const fileName = fileNameMatch
? fileNameMatch[fileNameMatch.length - 1].replace(/[`"]/g, '')
: `generated_code.${language === 'html' ? 'html' : language === 'javascript' ? 'js' : language}`;
files.push({
name: fileName,
language: language,
content: code
});
}
//
match = fileExistsRegex.exec(content);
if (match) {
const file1 = match[1];
const file2 = match[2];
//
[file1, file2].forEach(fileName => {
const language = fileName.split('.').pop();
files.push({
name: fileName,
language: language,
content: `// 已存在的文件: ${fileName}`,
exists: true
});
});
}
//
while ((match = fileCreatedRegex.exec(content)) !== null) {
const fileName = match[1];
const language = fileName.split('.').pop();
files.push({
name: fileName,
language: language,
content: `// 已创建的文件: ${fileName}`,
exists: true
});
}
// `index.html`
while ((match = filePathRegex.exec(content)) !== null) {
let fileName = match[1];
//
fileName = fileName.split('/').pop();
fileName = fileName.split('\\').pop();
const language = fileName.split('.').pop();
//
const fileExists = files.some(file => file.name === fileName);
if (!fileExists) {
files.push({
name: fileName,
language: language,
content: `// 文件: ${fileName}`,
exists: true
});
}
}
// "- **square.html** - "
while ((match = listFileRegex.exec(content)) !== null) {
const fileName = match[1];
const language = fileName.split('.').pop();
//
const fileExists = files.some(file => file.name === fileName);
if (!fileExists) {
files.push({
name: fileName,
language: language,
content: `// 文件: ${fileName}`,
exists: true
});
}
}
return files;
};
const previewFile = async (file) => {
try {
const maxFileSize = 10 * 1024 * 1024;
if (file.size > maxFileSize) {
setError('文件大小超过限制最大10MB');
return;
}
const allowedTypes = ['text/plain', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
if (!allowedTypes.includes(file.type)) {
setError('不支持的文件类型请上传文本、PDF或Word文件');
return;
}
setPreviewLoading(true);
setError('');
setPreviewContent('');
const formData = new FormData();
formData.append('files', file);
formData.append('messages', JSON.stringify([{ role: 'user', content: '请预览此文件内容,提取关键信息并以清晰的格式呈现' }]));
formData.append('model_type', model.split('/')[0]);
formData.append('model_name', model.split('/')[1]);
const response = await axios.post('/api/chat/with-files', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 30000
});
setPreviewContent(response.data.content);
} catch (err) {
console.error('预览文件失败:', err);
if (err.code === 'ECONNABORTED') {
setError('预览文件超时,请重试');
} else {
setError('预览文件失败,请重试');
}
} finally {
setPreviewLoading(false);
}
};
const handleFileUpload = (e) => {
const selectedFiles = Array.from(e.target.files);
setFiles(prev => [...prev, ...selectedFiles]);
};
const removeFile = (index) => {
setFiles(prev => prev.filter((_, i) => i !== index));
};
const handleModelChange = async (e) => {
const newModel = e.target.value;
const [modelType, modelName] = newModel.split('/');
try {
await axios.post('/api/model/switch', {
model_type: modelType,
model_name: modelName
});
setModel(newModel);
} catch (err) {
console.error('切换模型失败:', err);
setError('切换模型失败,请重试');
}
};
const openPreview = async (file) => {
if (file.exists) {
//
try {
setPreviewLoading(true);
// 使 /api/api/file/ /api/file/
//
let filePath = currentSession ? `${currentSession.id}/${file.name}` : file.name;
const response = await axios.get(`/api/api/file/${encodeURIComponent(filePath)}`);
//
let updatedFile = {
...file,
content: response.data.content,
size: response.data.size,
name: response.data.name
};
// HTMLCSS
if (file.language === 'html' || file.name.endsWith('.html')) {
updatedFile.content = await inlineCssFiles(updatedFile.content, filePath);
}
setActivePreview(updatedFile);
} catch (err) {
console.error('获取文件内容失败:', err);
setError('获取文件内容失败');
setActivePreview(file); // 使
} finally {
setPreviewLoading(false);
}
} else {
//
setActivePreview(file);
}
};
// CSSHTML
const inlineCssFiles = async (htmlContent, htmlFilePath) => {
try {
// HTMLCSS
const cssRegex = /<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"[^>]*>/g;
let match;
let updatedHtml = htmlContent;
while ((match = cssRegex.exec(htmlContent)) !== null) {
const cssHref = match[1];
// CSS
if (!cssHref.startsWith('http://') && !cssHref.startsWith('https://') && !cssHref.startsWith('/')) {
// CSS
const htmlDir = htmlFilePath.substring(0, htmlFilePath.lastIndexOf('/') + 1);
const cssFilePath = htmlDir + cssHref;
// CSS
const cssResponse = await axios.get(`/api/api/file/${encodeURIComponent(cssFilePath)}`);
const cssContent = cssResponse.data.content;
// linkstyle
const linkTag = match[0];
const styleTag = `<style>${cssContent}</style>`;
updatedHtml = updatedHtml.replace(linkTag, styleTag);
}
}
return updatedHtml;
} catch (err) {
console.error('内联CSS文件失败:', err);
return htmlContent; //
}
};
const closePreview = () => {
setActivePreview(null);
};
useEffect(() => {
//
loadSessions();
if (messages.length === 0) {
setMessages([
{
role: 'assistant',
content: '你好!我是基于 OpenCode 的大模型助手。请问有什么我可以帮助你的吗?你可以:\n1. 直接输入问题进行对话\n2. 上传文件进行分析\n3. 切换不同的模型\n\n例如你可以问我"如何使用 Python 实现快速排序?" 或者上传一个 PDF 文件让我分析其中的内容。',
generatedFiles: []
}
]);
}
}, []);
useEffect(() => {
scrollToBottom();
}, [messages, thinking]);
// useEffect
// useEffect(() => {
// //
// loadSessions();
// }, [sessions.length]);
return (
<div className="App">
<div className="layout">
{/* 会话管理面板 */}
<div className="layout__sessions">
<div className="sessions-header">
<h2>会话</h2>
<button
className="create-session-btn"
onClick={() => setShowSessionModal(true)}
disabled={isLoading}
>
+ 新建会话
</button>
</div>
<div className="sessions-list">
{console.log('渲染会话列表:', sessions)}
{sessions.length === 0 ? (
<div className="no-sessions">
没有会话请创建一个新会话
</div>
) : (
sessions.map(session => (
<div
key={session.id}
className={`session-item ${currentSession && currentSession.id === session.id ? 'active' : ''}`}
>
<div
className="session-info"
onClick={() => switchSession(session)}
>
<div className="session-title">{session.title}</div>
<div className="session-time">
{new Date(session.updated_at).toLocaleString()}
</div>
</div>
<button
className="session-delete"
onClick={(e) => {
e.stopPropagation();
if (window.confirm('确定要删除这个会话吗?')) {
deleteSession(session.id);
}
}}
>
×
</button>
</div>
))
)}
</div>
</div>
<div className="layout__preview">
<div className="preview-header">
<h2>预览区</h2>
<select
className="model-selector"
value={model}
onChange={handleModelChange}
disabled={isLoading}
>
{modelOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div className="preview-content">
{activePreview ? (
<div className="code-preview">
<div className="code-preview__header">
<div>
<span className="code-preview__filename">{activePreview.name}</span>
{activePreview.size && (
<span className="code-preview__filesize">({(activePreview.size / 1024).toFixed(2)} KB)</span>
)}
</div>
<div className="code-preview__controls">
<button
className={`preview-mode-btn ${previewMode === 'code' ? 'active' : ''}`}
onClick={() => setPreviewMode('code')}
>
源码
</button>
{activePreview.language === 'html' && (
<button
className={`preview-mode-btn ${previewMode === 'preview' ? 'active' : ''}`}
onClick={() => setPreviewMode('preview')}
>
预览
</button>
)}
<button className="code-preview__close" onClick={closePreview}>×</button>
</div>
</div>
{previewMode === 'code' ? (
<SyntaxHighlighter
style={vscDarkPlus}
language={activePreview.language || 'html'}
showLineNumbers
>
{activePreview.content}
</SyntaxHighlighter>
) : (
<div className="html-preview">
<iframe
srcDoc={activePreview.content}
title="HTML Preview"
sandbox="allow-scripts"
className="html-preview__iframe"
/>
</div>
)}
</div>
) : previewContent ? (
<div className="preview-markdown">
<ReactMarkdown
components={{
code: ({ node, inline, className, children, ...props }) => {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
style={vscDarkPlus}
language={match[1]}
PreTag="div"
{...props}
>
{children}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
}
}}
>
{previewContent}
</ReactMarkdown>
</div>
) : (
<div className="preview-placeholder">
<p>选择左侧聊天中生成的文件进行预览</p>
{generatedFiles.length > 0 && (
<div className="generated-files-list">
<h3>生成的文件:</h3>
{generatedFiles.map((file, index) => (
<button
key={index}
className="file-preview-btn"
onClick={() => openPreview(file)}
>
{file.name}
</button>
))}
</div>
)}
</div>
)}
</div>
</div>
<div className="layout__chat">
<div className="chat-container" ref={chatRef}>
{messages.map((msg, index) => (
<div
key={index}
className={`message ${msg.role === 'user' ? 'user-message' : 'assistant-message'}`}
>
<div className="message-content">
<ReactMarkdown
components={{
code: ({ node, inline, className, children, ...props }) => {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
style={vscDarkPlus}
language={match[1]}
PreTag="div"
{...props}
>
{children}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
}
}}
>
{msg.content}
</ReactMarkdown>
</div>
{msg.files && msg.files.length > 0 && (
<div className="message-files">
{msg.files.map((file, fileIndex) => (
<div key={fileIndex} className="file-item">
<span className="file-name">{file.name}</span>
<span className="file-size">({(file.size / 1024).toFixed(2)} KB)</span>
</div>
))}
</div>
)}
{msg.generatedFiles && msg.generatedFiles.length > 0 && (
<div className="message-generated-files">
<span className="generated-files-label">生成的文件:</span>
{msg.generatedFiles.map((file, fileIndex) => (
<button
key={fileIndex}
className="file-preview-btn"
onClick={() => openPreview(file)}
>
{file.name}
</button>
))}
</div>
)}
</div>
))}
{thinking && (
<div className="message assistant-message">
<div className="thinking">正在思考...</div>
</div>
)}
</div>
{error && <div className="error">{error}</div>}
<div className="upload-section">
<input
type="file"
multiple
onChange={handleFileUpload}
disabled={isLoading}
/>
{files.length > 0 && (
<div className="file-list">
<h4>已选择的文件</h4>
{files.map((file, index) => (
<div key={index} className="file-item">
<span>{file.name}</span>
<button
onClick={() => previewFile(file)}
disabled={isLoading || previewLoading}
>
{previewLoading ? '预览中...' : '预览'}
</button>
<button
onClick={() => removeFile(index)}
disabled={isLoading}
>
删除
</button>
</div>
))}
</div>
)}
</div>
<div className="input-section">
<textarea
placeholder="输入消息..."
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={isLoading}
onKeyPress={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}}
/>
<button
onClick={sendMessage}
disabled={isLoading || (!input.trim() && files.length === 0)}
>
发送
</button>
</div>
</div>
{/* 创建会话模态框 */}
{showSessionModal && (
<div className="modal-overlay">
<div className="modal">
<div className="modal-header">
<h3>创建新会话</h3>
<button
className="modal-close"
onClick={() => setShowSessionModal(false)}
>
×
</button>
</div>
<div className="modal-body">
<input
type="text"
placeholder="会话标题"
value={sessionInput}
onChange={(e) => setSessionInput(e.target.value)}
className="session-title-input"
/>
</div>
<div className="modal-footer">
<button
className="modal-cancel"
onClick={() => setShowSessionModal(false)}
>
取消
</button>
<button
className="modal-confirm"
onClick={() => createSession(sessionInput)}
disabled={!sessionInput.trim()}
>
创建
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
}
export default App;

View File

@ -0,0 +1,171 @@
.contact {
padding: 6rem 0;
background: #0a0a0a;
}
.contact-content {
display: grid;
grid-template-columns: 1fr 1.5fr;
gap: 3rem;
}
.contact-info {
background: #1a1a1a;
border-radius: 20px;
padding: 2.5rem;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.contact-info-title {
color: #fff;
font-size: 1.5rem;
margin: 0 0 0.5rem;
}
.contact-info-text {
color: #888;
margin: 0 0 2rem;
line-height: 1.6;
}
.info-items {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-bottom: 2rem;
}
.info-item {
display: flex;
align-items: center;
gap: 1rem;
}
.info-icon {
width: 45px;
height: 45px;
background: rgba(255, 106, 0, 0.1);
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
color: #ff6a00;
font-size: 1.2rem;
}
.info-text {
display: flex;
flex-direction: column;
}
.info-text strong {
color: #fff;
font-size: 0.95rem;
}
.info-text span {
color: #888;
font-size: 0.9rem;
}
.social-links {
display: flex;
gap: 0.8rem;
}
.social-link {
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
color: #888;
text-decoration: none;
font-weight: 600;
font-size: 0.85rem;
transition: all 0.3s ease;
}
.social-link:hover {
background: #ff6a00;
color: #fff;
}
.contact-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
color: #ccc;
font-size: 0.9rem;
font-weight: 500;
}
.form-group input,
.form-group textarea {
background: #1a1a1a;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 0.9rem 1.2rem;
color: #fff;
font-size: 1rem;
transition: all 0.3s ease;
font-family: inherit;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #ff6a00;
box-shadow: 0 0 0 3px rgba(255, 106, 0, 0.1);
}
.form-group input::placeholder,
.form-group textarea::placeholder {
color: #555;
}
.submit-btn {
background: linear-gradient(135deg, #ff6a00, #ff8533);
color: #fff;
border: none;
padding: 1rem 2rem;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 106, 0, 0.4);
}
.arrow {
transition: transform 0.3s ease;
}
.submit-btn:hover .arrow {
transform: translateX(5px);
}
@media (max-width: 992px) {
.contact-content {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,136 @@
import { useState } from 'react'
import './Contact.css'
const Contact = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
})
const handleChange = (e) => {
setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }))
}
const handleSubmit = (e) => {
e.preventDefault()
alert('Message sent! I will get back to you soon.')
setFormData({ name: '', email: '', subject: '', message: '' })
}
return (
<section id="contact" className="contact">
<div className="section-container">
<div className="section-header">
<span className="section-tag">Get In Touch</span>
<h2 className="section-title">Let&apos;s Work <span className="highlight">Together</span></h2>
<p className="section-subtitle">
Have a project in mind? I&apos;d love to hear about it. Send me a message and let&apos;s create something amazing.
</p>
</div>
<div className="contact-content">
<div className="contact-info">
<h3 className="contact-info-title">Contact Information</h3>
<p className="contact-info-text">
Fill out the form and I&apos;ll get back to you within 24 hours.
</p>
<div className="info-items">
<div className="info-item">
<span className="info-icon">&#9993;</span>
<div className="info-text">
<strong>Email</strong>
<span>sarah@designpro.com</span>
</div>
</div>
<div className="info-item">
<span className="info-icon">&#9990;</span>
<div className="info-text">
<strong>Phone</strong>
<span>+1 (555) 123-4567</span>
</div>
</div>
<div className="info-item">
<span className="info-icon">&#9873;</span>
<div className="info-text">
<strong>Location</strong>
<span>San Francisco, CA</span>
</div>
</div>
</div>
<div className="social-links">
<a href="#" className="social-link" aria-label="LinkedIn">in</a>
<a href="#" className="social-link" aria-label="Twitter">X</a>
<a href="#" className="social-link" aria-label="Dribbble">Dr</a>
<a href="#" className="social-link" aria-label="Behance">Be</a>
</div>
</div>
<form className="contact-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="name">Full Name</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
placeholder="John Doe"
required
/>
</div>
<div className="form-group">
<label htmlFor="email">Email Address</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="john@example.com"
required
/>
</div>
<div className="form-group">
<label htmlFor="subject">Subject</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
placeholder="Project Inquiry"
required
/>
</div>
<div className="form-group">
<label htmlFor="message">Message</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
placeholder="Tell me about your project..."
rows={5}
required
></textarea>
</div>
<button type="submit" className="submit-btn">
Send Message
<span className="arrow">&#10148;</span>
</button>
</form>
</div>
</div>
</section>
)
}
export default Contact

View File

@ -0,0 +1,134 @@
.experience {
padding: 6rem 0;
background: #0a0a0a;
position: relative;
overflow: hidden;
}
.timeline {
position: relative;
max-width: 900px;
margin: 0 auto;
padding: 2rem 0;
}
.timeline-line {
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(180deg, #ff6a00, rgba(255, 106, 0, 0.1));
transform: translateX(-50%);
}
.timeline-item {
display: flex;
margin-bottom: 3rem;
position: relative;
}
.timeline-item.left {
justify-content: flex-start;
padding-right: calc(50% + 40px);
}
.timeline-item.right {
justify-content: flex-end;
padding-left: calc(50% + 40px);
}
.timeline-dot {
position: absolute;
left: 50%;
top: 20px;
width: 16px;
height: 16px;
background: #ff6a00;
border-radius: 50%;
transform: translateX(-50%);
box-shadow: 0 0 20px rgba(255, 106, 0, 0.4);
}
.timeline-dot::before {
content: '';
position: absolute;
inset: -4px;
border-radius: 50%;
border: 2px solid rgba(255, 106, 0, 0.3);
}
.timeline-content {
background: #1a1a1a;
border-radius: 16px;
padding: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.05);
transition: all 0.3s ease;
}
.timeline-content:hover {
border-color: rgba(255, 106, 0, 0.2);
transform: translateY(-4px);
}
.timeline-period {
display: inline-block;
background: rgba(255, 106, 0, 0.1);
color: #ff6a00;
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
margin-bottom: 0.8rem;
}
.timeline-title {
font-size: 1.3rem;
color: #fff;
margin: 0 0 0.3rem;
}
.timeline-company {
display: block;
color: #888;
font-size: 0.95rem;
margin-bottom: 0.8rem;
}
.timeline-description {
color: #aaa;
font-size: 0.9rem;
line-height: 1.6;
margin: 0 0 1rem;
}
.timeline-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.timeline-tag {
background: rgba(255, 255, 255, 0.05);
color: #ccc;
padding: 0.3rem 0.7rem;
border-radius: 6px;
font-size: 0.8rem;
}
@media (max-width: 768px) {
.timeline-line {
left: 20px;
}
.timeline-item.left,
.timeline-item.right {
padding-left: 50px;
padding-right: 0;
justify-content: flex-start;
}
.timeline-dot {
left: 20px;
}
}

View File

@ -0,0 +1,70 @@
import './Experience.css'
const experiences = [
{
period: '2022 - Present',
title: 'Senior UI/UX Designer',
company: 'TechFlow Studio',
description: 'Leading design team for enterprise SaaS products, creating design systems and improving user engagement by 40%.',
tags: ['Design Systems', 'Leadership', 'SaaS']
},
{
period: '2019 - 2022',
title: 'UI/UX Designer',
company: 'Creative Digital Agency',
description: 'Designed 100+ websites and web applications for clients across healthcare, fintech, and e-commerce sectors.',
tags: ['Web Design', 'Mobile Apps', 'Branding']
},
{
period: '2017 - 2019',
title: 'Visual Designer',
company: 'Pixel Perfect Co.',
description: 'Created brand identities, marketing materials, and digital assets for startups and established brands.',
tags: ['Branding', 'Print Design', 'Visual Identity']
},
{
period: '2014 - 2017',
title: 'Junior Web Designer',
company: 'StartUp Hub',
description: 'Built responsive websites and landing pages for early-stage startups, learning rapid prototyping and agile design.',
tags: ['Responsive Design', 'Prototyping', 'Startups']
}
]
const Experience = () => {
return (
<section id="experience" className="experience">
<div className="section-container">
<div className="section-header">
<span className="section-tag">My Journey</span>
<h2 className="section-title">Work <span className="highlight">Experience</span></h2>
<p className="section-subtitle">
A decade of crafting digital experiences for companies worldwide
</p>
</div>
<div className="timeline">
{experiences.map((exp, index) => (
<div key={index} className={`timeline-item ${index % 2 === 0 ? 'left' : 'right'}`}>
<div className="timeline-dot"></div>
<div className="timeline-content">
<span className="timeline-period">{exp.period}</span>
<h3 className="timeline-title">{exp.title}</h3>
<span className="timeline-company">{exp.company}</span>
<p className="timeline-description">{exp.description}</p>
<div className="timeline-tags">
{exp.tags.map((tag, i) => (
<span key={i} className="timeline-tag">{tag}</span>
))}
</div>
</div>
</div>
))}
<div className="timeline-line"></div>
</div>
</div>
</section>
)
}
export default Experience

View File

@ -0,0 +1,90 @@
.timeline {
max-width: 800px;
margin: 0 auto;
position: relative;
}
.timelineItem {
display: flex;
gap: 2rem;
margin-bottom: 3rem;
position: relative;
}
.timelineItem:last-child .dotWrapper {
padding-bottom: 0;
}
.dotWrapper {
flex-direction: column;
align-items: center;
padding-top: 8px;
}
.dot {
width: 16px;
height: 16px;
background: #ff6b22;
border-radius: 50%;
margin-bottom: 8px;
position: relative;
z-index: 2;
box-shadow: 0 0 20px rgba(255, 107, 34, 0.3);
}
.line {
width: 2px;
flex: 1;
min-height: 60px;
background: rgba(255, 107, 34, 0.15);
}
.timelineItem:last-child .line { display: none; }
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 1.5rem 2rem;
flex: 1;
transition: all 0.3s ease;
}
.card:hover {
border-color: rgba(255, 107, 34, 0.3);
transform: translateX(5px);
}
.year {
display: inline-block;
background: rgba(255, 107, 34, 0.1);
color: #ff8544;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.75rem;
}
.role {
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 0.25rem;
}
.company {
color: #a0a0a0;
font-size: 0.95rem;
margin-bottom: 0.75rem;
}
.desc {
color: #888888;
font-size: 0.9rem;
line-height: 1.7;
}
@media (max-width: 600px) {
.timelineItem { gap: 1rem; }
.card { padding: 1.25rem; }
}

View File

@ -0,0 +1,57 @@
import './Footer.css'
const Footer = () => {
return (
<footer className="footer">
<div className="section-container">
<div className="footer-content">
<div className="footer-main">
<div className="footer-logo">
<span className="logo-accent">Design</span>Pro
</div>
<p className="footer-desc">
Crafting intuitive digital experiences that connect brands with their audiences.
</p>
</div>
<div className="footer-links">
<h4>Quick Links</h4>
<ul>
<li><button onClick={() => document.getElementById('hero')?.scrollIntoView({ behavior: 'smooth' })}>Home</button></li>
<li><button onClick={() => document.getElementById('services')?.scrollIntoView({ behavior: 'smooth' })}>Services</button></li>
<li><button onClick={() => document.getElementById('portfolio')?.scrollIntoView({ behavior: 'smooth' })}>Portfolio</button></li>
<li><button onClick={() => document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' })}>Contact</button></li>
</ul>
</div>
<div className="footer-links">
<h4>Services</h4>
<ul>
<li>UI/UX Design</li>
<li>Web Design</li>
<li>Landing Pages</li>
<li>Brand Identity</li>
</ul>
</div>
<div className="footer-cta">
<h4>Ready to start?</h4>
<a href="mailto:sarah@designpro.com" className="cta-btn">Get in Touch</a>
</div>
</div>
<div className="footer-bottom">
<p>&copy; 2024 DesignPro. All rights reserved.</p>
<div className="footer-socials">
<a href="#">LinkedIn</a>
<a href="#">Twitter</a>
<a href="#">Dribbble</a>
<a href="#">Behance</a>
</div>
</div>
</div>
</footer>
)
}
export default Footer

View File

@ -0,0 +1,300 @@
.hero {
min-height: 100vh;
display: flex;
align-items: center;
position: relative;
overflow: hidden;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d0d0d 100%);
padding-top: 80px;
}
.hero-bg-shapes {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.shape {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.15;
}
.shape-1 {
width: 400px;
height: 400px;
background: #ff6a00;
top: -100px;
right: -100px;
}
.shape-2 {
width: 300px;
height: 300px;
background: #ff8533;
bottom: -50px;
left: -50px;
}
.shape-3 {
width: 200px;
height: 200px;
background: #ff4500;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.hero-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
position: relative;
z-index: 1;
}
.hero-content {
animation: fadeInUp 0.8s ease forwards;
}
.hero-title {
font-size: 3.5rem;
color: #fff;
margin: 0 0 0.5rem;
line-height: 1.1;
}
.highlight {
color: #ff6a00;
position: relative;
}
.hero-subtitle {
font-size: 1.5rem;
color: #ff8533;
margin: 0 0 1.5rem;
font-weight: 500;
}
.hero-description {
font-size: 1.1rem;
color: #aaa;
line-height: 1.8;
margin: 0 0 2rem;
max-width: 500px;
}
.hero-stats {
display: flex;
gap: 2rem;
margin-bottom: 2.5rem;
}
.stat-item {
text-align: center;
}
.stat-number {
display: block;
font-size: 2.5rem;
font-weight: 700;
color: #ff6a00;
line-height: 1;
}
.stat-label {
font-size: 0.85rem;
color: #888;
margin-top: 0.5rem;
display: block;
}
.hero-buttons {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.btn {
padding: 0.9rem 2rem;
border-radius: 50px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
border: none;
}
.btn-primary {
background: linear-gradient(135deg, #ff6a00, #ff8533);
color: #fff;
box-shadow: 0 4px 15px rgba(255, 106, 0, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 106, 0, 0.6);
}
.btn-secondary {
background: transparent;
color: #fff;
border: 2px solid rgba(255, 255, 255, 0.2);
}
.btn-secondary:hover {
border-color: #ff6a00;
color: #ff6a00;
}
.hero-image {
display: flex;
justify-content: center;
align-items: center;
position: relative;
animation: fadeInRight 0.8s ease forwards;
}
.avatar-wrapper {
position: relative;
width: 380px;
height: 380px;
}
.avatar-ring {
position: absolute;
inset: 0;
border-radius: 50%;
border: 3px solid #ff6a00;
animation: rotate 20s linear infinite;
}
.avatar-ring::before {
content: '';
position: absolute;
inset: 10px;
border-radius: 50%;
border: 2px dashed rgba(255, 106, 0, 0.3);
}
.avatar-placeholder {
position: absolute;
inset: 20px;
border-radius: 50%;
background: linear-gradient(135deg, #2a2a2a, #333);
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 0 40px rgba(255, 106, 0, 0.2);
}
.avatar-placeholder span {
font-size: 6rem;
font-weight: 700;
color: #ff6a00;
}
.floating-badge {
position: absolute;
background: rgba(30, 30, 30, 0.9);
border: 1px solid rgba(255, 106, 0, 0.3);
border-radius: 12px;
padding: 0.8rem 1.2rem;
display: flex;
align-items: center;
gap: 0.5rem;
color: #fff;
font-weight: 500;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
animation: float 3s ease-in-out infinite;
}
.badge-1 {
top: 20px;
right: -20px;
animation-delay: 0s;
}
.badge-2 {
bottom: 40px;
left: -30px;
animation-delay: 1.5s;
}
.badge-icon {
color: #ff6a00;
font-size: 1.2rem;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInRight {
from { opacity: 0; transform: translateX(30px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@media (max-width: 992px) {
.hero-container {
grid-template-columns: 1fr;
text-align: center;
}
.hero-description {
margin-left: auto;
margin-right: auto;
}
.hero-stats {
justify-content: center;
}
.hero-buttons {
justify-content: center;
}
.hero-title {
font-size: 2.8rem;
}
.avatar-wrapper {
width: 280px;
height: 280px;
}
.hero-image {
order: -1;
}
}
@media (max-width: 576px) {
.hero-title {
font-size: 2.2rem;
}
.hero-stats {
gap: 1rem;
}
.stat-number {
font-size: 1.8rem;
}
}

View File

@ -0,0 +1,69 @@
import './Hero.css'
const Hero = () => {
return (
<section id="hero" className="hero">
<div className="hero-bg-shapes">
<div className="shape shape-1"></div>
<div className="shape shape-2"></div>
<div className="shape shape-3"></div>
</div>
<div className="hero-container">
<div className="hero-content">
<h1 className="hero-title">
Hello, I&apos;m <span className="highlight">Sarah Chen</span>
</h1>
<p className="hero-subtitle">Senior Product Designer</p>
<p className="hero-description">
Crafting intuitive digital experiences that connect brands with their audiences.
Specializing in UI/UX design, web design, and high-converting landing pages.
</p>
<div className="hero-stats">
<div className="stat-item">
<span className="stat-number">10</span>
<span className="stat-label">Years Experience</span>
</div>
<div className="stat-item">
<span className="stat-number">450+</span>
<span className="stat-label">Clients Served</span>
</div>
<div className="stat-item">
<span className="stat-number">&#9733;5.0</span>
<span className="stat-label">Client Rating</span>
</div>
</div>
<div className="hero-buttons">
<button className="btn btn-primary" onClick={() => document.getElementById('portfolio')?.scrollIntoView({ behavior: 'smooth' })}>
View My Work
</button>
<button className="btn btn-secondary" onClick={() => document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' })}>
Let&apos;s Collaborate
</button>
</div>
</div>
<div className="hero-image">
<div className="avatar-wrapper">
<div className="avatar-ring"></div>
<div className="avatar-placeholder">
<span>SC</span>
</div>
</div>
<div className="floating-badge badge-1">
<span className="badge-icon">&#9733;</span>
<span>Top Rated</span>
</div>
<div className="floating-badge badge-2">
<span className="badge-icon">&#10003;</span>
<span>Available</span>
</div>
</div>
</div>
</section>
)
}
export default Hero

View File

@ -0,0 +1,266 @@
.hero {
min-height: 100vh;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
}
.bgGradient {
position: absolute;
inset: 0;
background: radial-gradient(ellipse at 60% 50%, rgba(255, 107, 34, 0.12) 0%, transparent 70%);
pointer-events: none;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 120px 20px 80px;
width: 100%;
display: grid;
grid-template-columns: 1fr auto;
gap: 3rem;
position: relative;
z-index: 1;
}
.content {
max-width: 700px;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.avatarWrapper {
position: relative;
display: inline-block;
margin-bottom: 2rem;
animation: fadeInUp 0.6s ease both;
}
.avatarRing {
position: absolute;
inset: -6px;
border-radius: 50%;
border: 3px solid #ff6b22;
opacity: 0.4;
}
.avatar {
width: 140px;
height: 140px;
border-radius: 50%;
background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
display: flex;
align-items: center;
justify-content: center;
border: 4px solid rgba(255, 107, 34, 0.3);
overflow: hidden;
}
.avatarLetter {
font-size: 3.5rem;
font-weight: 900;
background: linear-gradient(135deg, #ff6b22 0%, #ff8544 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.statusDot {
position: absolute;
bottom: 10px;
right: 10px;
width: 24px;
height: 24px;
background: #22c55e;
border-radius: 50%;
border: 4px solid #0a0a0a;
}
.badge {
display: inline-block;
background: rgba(255, 107, 34, 0.1);
color: #ff8544;
padding: 8px 16px;
border-radius: 50px;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 1.5rem;
border: 1px solid rgba(255, 107, 34, 0.2);
animation: fadeInUp 0.6s ease 0.1s both;
}
.mainTitle {
font-size: 3.5rem;
font-weight: 900;
line-height: 1.1;
margin-bottom: 0.5rem;
letter-spacing: -1px;
animation: fadeInUp 0.6s ease 0.2s both;
}
.orangeText {
background: linear-gradient(135deg, #ff6b22 0%, #ff8544 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subTitle {
font-size: 1.4rem;
font-weight: 400;
color: #a0a0a0;
margin-bottom: 1.5rem;
animation: fadeInUp 0.6s ease 0.3s both;
}
.subTitle span {
color: #ffffff;
font-weight: 600;
}
.description {
font-size: 1.1rem;
color: #888888;
line-height: 1.8;
margin-bottom: 2rem;
animation: fadeInUp 0.6s ease 0.4s both;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.statsSection {
display: flex;
gap: 3rem;
margin-bottom: 2.5rem;
animation: fadeInUp 0.6s ease 0.5s both;
}
.statItem {
text-align: center;
}
.statValue {
display: block;
font-size: 2rem;
font-weight: 800;
color: #ff6b22;
line-height: 1;
}
.statLabel {
font-size: 0.85rem;
color: #a0a0a0;
margin-top: 4px;
}
.buttonGroup {
display: flex;
gap: 1rem;
animation: fadeInUp 0.6s ease 0.6s both;
}
.btnPrimary {
background: linear-gradient(135deg, #ff6b22 0%, #ff8544 100%);
color: #fff;
padding: 16px 32px;
border-radius: 10px;
font-weight: 700;
font-size: 1.05rem;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btnPrimary:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(255, 107, 34, 0.4);
}
.btnSecondary {
background: rgba(255, 255, 255, 0.05);
color: #ffffff;
padding: 16px 32px;
border-radius: 10px;
font-weight: 700;
font-size: 1.05rem;
border: 2px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btnSecondary:hover {
border-color: #ff6b22;
color: #ff6b22;
background: rgba(255, 107, 34, 0.05);
}
.sideElements {
position: relative;
width: 280px;
height: 400px;
}
.floatingCard {
position: absolute;
background: rgba(20, 20, 20, 0.9);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
padding: 16px;
display: flex;
align-items: center;
gap: 12px;
animation: float 3s ease-in-out infinite;
}
.floatingCard:nth-child(2) {
animation-delay: 1.5s;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.floatingEmoji {
font-size: 1.8rem;
}
.floatingTitle {
font-size: 0.9rem;
font-weight: 700;
color: #ffffff;
}
.floatingText {
font-size: 0.8rem;
color: #888888;
}
@media (max-width: 900px) {
.container {
grid-template-columns: 1fr;
padding-top: 120px;
}
.sideElements { display: none; }
.mainTitle { font-size: 2.5rem; }
.statsSection { gap: 2rem; }
}
@media (max-width: 600px) {
.mainTitle { font-size: 2rem; }
.buttonGroup { flex-direction: column; }
.statsSection { flex-wrap: wrap; justify-content: center; }
}

View File

@ -0,0 +1,129 @@
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
padding: 1rem 0;
transition: all 0.3s ease;
background: transparent;
}
.navbar.scrolled {
background: rgba(20, 20, 20, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.3);
padding: 0.7rem 0;
}
.navbar-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.navbar-logo {
font-size: 1.8rem;
font-weight: 700;
cursor: pointer;
color: #fff;
}
.logo-accent {
color: #ff6a00;
}
.nav-links {
display: flex;
list-style: none;
gap: 2rem;
margin: 0;
padding: 0;
}
.nav-links button {
background: none;
border: none;
color: #ccc;
font-size: 0.95rem;
cursor: pointer;
padding: 0.5rem 0;
position: relative;
transition: color 0.3s ease;
}
.nav-links button::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background: #ff6a00;
transition: width 0.3s ease;
}
.nav-links button:hover {
color: #ff6a00;
}
.nav-links button:hover::after {
width: 100%;
}
.menu-toggle {
display: none;
flex-direction: column;
gap: 5px;
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
}
.menu-toggle span {
display: block;
width: 25px;
height: 2px;
background: #fff;
transition: all 0.3s ease;
}
.menu-toggle span.open:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.menu-toggle span.open:nth-child(2) {
opacity: 0;
}
.menu-toggle span.open:nth-child(3) {
transform: rotate(-45deg) translate(5px, -5px);
}
@media (max-width: 768px) {
.menu-toggle {
display: flex;
}
.nav-links {
position: fixed;
top: 0;
right: -100%;
width: 70%;
height: 100vh;
background: rgba(20, 20, 20, 0.98);
flex-direction: column;
justify-content: center;
align-items: center;
gap: 2rem;
transition: right 0.3s ease;
}
.nav-links.active {
right: 0;
}
}

View File

@ -0,0 +1,46 @@
import { useState, useEffect } from 'react'
import './Navbar.css'
const Navbar = () => {
const [scrolled, setScrolled] = useState(false)
const [menuOpen, setMenuOpen] = useState(false)
useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 50)
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
const scrollToSection = (id) => {
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' })
setMenuOpen(false)
}
return (
<nav className={`navbar ${scrolled ? 'scrolled' : ''}`}>
<div className="navbar-container">
<div className="navbar-logo" onClick={() => scrollToSection('hero')}>
<span className="logo-accent">Design</span>Pro
</div>
<button className="menu-toggle" onClick={() => setMenuOpen(!menuOpen)}>
<span className={menuOpen ? 'open' : ''}></span>
<span className={menuOpen ? 'open' : ''}></span>
<span className={menuOpen ? 'open' : ''}></span>
</button>
<ul className={`nav-links ${menuOpen ? 'active' : ''}`}>
{['hero', 'services', 'experience', 'portfolio', 'testimonials', 'contact'].map((section) => (
<li key={section}>
<button onClick={() => scrollToSection(section)}>
{section.charAt(0).toUpperCase() + section.slice(1)}
</button>
</li>
))}
</ul>
</div>
</nav>
)
}
export default Navbar

View File

@ -0,0 +1,156 @@
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: transparent;
transition: background 0.3s ease, box-shadow 0.3s ease;
}
.scrolled {
background: rgba(10, 10, 10, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.3);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 2rem;
}
.logo {
font-size: 1.5rem;
font-weight: 800;
letter-spacing: -0.5px;
flex-shrink: 0;
}
.logoIcon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: linear-gradient(135deg, #ff6b22 0%, #ff8544 100%);
border-radius: 8px;
font-size: 1.1rem;
margin-right: 6px;
vertical-align: middle;
color: white;
}
.logoAccent {
color: #ff6b22;
}
.navLinks {
display: flex;
gap: 2rem;
align-items: center;
flex: 1;
justify-content: center;
}
.navLinks a {
font-size: 0.95rem;
font-weight: 500;
color: #a0a0a0;
transition: color 0.3s ease;
position: relative;
}
.navLinks a:hover {
color: #ffffff;
}
.navLinks a::after {
content: '';
position: absolute;
bottom: -4px;
left: 0;
width: 0;
height: 2px;
background: #ff6b22;
transition: width 0.3s ease;
}
.navLinks a:hover::after {
width: 100%;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.ctaBtn {
background: linear-gradient(135deg, #ff6b22 0%, #ff8544 100%);
color: #fff;
padding: 10px 24px;
border-radius: 8px;
font-weight: 600;
font-size: 0.95rem;
transition: all 0.3s ease;
flex-shrink: 0;
animation: slideIn 0.6s ease 0.3s both;
}
.ctaBtn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 107, 34, 0.4);
}
.mobileToggle {
display: none;
flex-direction: column;
gap: 5px;
background: none;
padding: 5px;
}
.bar {
display: block;
width: 24px;
height: 2px;
background: #ffffff;
transition: all 0.3s ease;
border-radius: 2px;
}
.barOpen:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); }
.barOpen:nth-child(2) { opacity: 0; }
.barOpen:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); }
@media (max-width: 768px) {
.ctaBtn {
display: none;
}
.mobileToggle {
display: flex;
}
.navLinks {
position: fixed;
top: 60px;
left: 0;
right: 0;
background: rgba(10, 10, 10, 0.98);
flex-direction: column;
padding: 2rem;
gap: 1.5rem;
transform: translateX(100%);
transition: transform 0.3s ease;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.navLinks.open {
transform: translateY(0);
}
}

View File

@ -0,0 +1,261 @@
.portfolio {
padding: 6rem 0;
background: #0a0a0a;
}
.portfolio-filters {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 3rem;
flex-wrap: wrap;
}
.filter-btn {
padding: 0.6rem 1.5rem;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #888;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.filter-btn:hover {
border-color: #ff6a00;
color: #ff6a00;
}
.filter-btn.active {
background: #ff6a00;
border-color: #ff6a00;
color: #fff;
}
.portfolio-carousel {
position: relative;
margin-bottom: 3rem;
}
.carousel-nav {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
padding: 0 1rem;
pointer-events: none;
z-index: 10;
}
.nav-btn {
width: 50px;
height: 50px;
border-radius: 50%;
background: rgba(255, 106, 0, 0.2);
border: 1px solid rgba(255, 106, 0, 0.3);
color: #ff6a00;
font-size: 1.2rem;
cursor: pointer;
pointer-events: auto;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.nav-btn:hover {
background: #ff6a00;
color: #fff;
}
.carousel-content {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 2rem;
background: #1a1a1a;
border-radius: 20px;
padding: 2rem;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.project-preview {
border-radius: 12px;
padding: 2rem;
display: flex;
justify-content: center;
align-items: center;
}
.preview-mockup {
background: #0d0d0d;
border-radius: 12px;
overflow: hidden;
width: 100%;
max-width: 400px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.mockup-header {
padding: 0.8rem 1rem;
display: flex;
gap: 6px;
background: #1a1a1a;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dot.red { background: #ff5f56; }
.dot.yellow { background: #ffbd2e; }
.dot.green { background: #27c93f; }
.mockup-body {
padding: 2rem;
}
.mockup-title {
font-size: 1.5rem;
font-weight: 700;
margin: 0 0 1rem;
}
.mockup-tags {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.mockup-tag {
background: rgba(255, 255, 255, 0.05);
color: #888;
padding: 0.3rem 0.7rem;
border-radius: 5px;
font-size: 0.75rem;
}
.project-info {
display: flex;
flex-direction: column;
justify-content: center;
padding: 1rem;
}
.project-category {
color: #ff6a00;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.project-title {
font-size: 1.8rem;
color: #fff;
margin: 0 0 1rem;
}
.project-description {
color: #888;
line-height: 1.7;
margin: 0 0 1.5rem;
}
.project-tags {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.project-tag {
background: rgba(255, 255, 255, 0.05);
color: #ccc;
padding: 0.3rem 0.7rem;
border-radius: 5px;
font-size: 0.8rem;
}
.view-project {
align-self: flex-start;
}
.carousel-indicators {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 1.5rem;
}
.indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
border: none;
cursor: pointer;
transition: all 0.3s ease;
}
.indicator.active {
background: #ff6a00;
transform: scale(1.2);
}
.projects-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
.project-mini-card {
background: #1a1a1a;
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.05);
transition: all 0.3s ease;
}
.project-mini-card:hover {
transform: translateY(-4px);
border-color: rgba(255, 106, 0, 0.2);
}
.mini-gradient {
height: 120px;
}
.mini-title {
color: #fff;
padding: 1rem 1rem 0.3rem;
margin: 0;
font-size: 1.1rem;
}
.mini-category {
display: block;
color: #888;
font-size: 0.85rem;
padding: 0 1rem 1rem;
}
@media (max-width: 992px) {
.carousel-content {
grid-template-columns: 1fr;
}
.projects-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 576px) {
.carousel-nav {
display: none;
}
}

View File

@ -0,0 +1,160 @@
import { useState } from 'react'
import './Portfolio.css'
const projects = [
{
id: 1,
title: 'FinTech App Redesign',
category: 'UI/UX Design',
description: 'Complete redesign of a mobile banking application, increasing user engagement by 45%.',
tags: ['Mobile', 'FinTech', 'UX Research'],
color: '#ff6a00'
},
{
id: 2,
title: 'E-Commerce Landing Page',
category: 'Landing Page',
description: 'High-converting landing page for a fashion e-commerce brand, 3x conversion rate.',
tags: ['E-Commerce', 'Conversion', 'Responsive'],
color: '#ff8533'
},
{
id: 3,
title: 'SaaS Dashboard',
category: 'UI/UX Design',
description: 'Analytics dashboard for enterprise SaaS with complex data visualization.',
tags: ['Dashboard', 'Data Viz', 'Enterprise'],
color: '#ff4500'
},
{
id: 4,
title: 'Healthcare Platform',
category: 'Web Design',
description: 'Telemedicine platform connecting patients with healthcare providers seamlessly.',
tags: ['Healthcare', 'Platform', 'Accessibility'],
color: '#ff6a00'
},
{
id: 5,
title: 'Restaurant Booking Page',
category: 'Landing Page',
description: 'Elegant landing page for a premium restaurant chain with online reservation system.',
tags: ['Hospitality', 'Booking', 'Premium'],
color: '#ff8533'
},
{
id: 6,
title: 'EdTech Website',
category: 'Web Design',
description: 'Modern educational platform website with course catalog and student portal.',
tags: ['Education', 'LMS', 'User Portal'],
color: '#ff4500'
}
]
const categories = ['All', 'UI/UX Design', 'Web Design', 'Landing Page']
const Portfolio = () => {
const [activeCategory, setActiveCategory] = useState('All')
const [currentIndex, setCurrentIndex] = useState(0)
const filteredProjects = activeCategory === 'All'
? projects
: projects.filter(p => p.category === activeCategory)
const featuredProject = filteredProjects[currentIndex % filteredProjects.length]
const goTo = (direction) => {
setCurrentIndex(prev => {
const next = prev + direction
return next < 0 ? filteredProjects.length - 1 : next >= filteredProjects.length ? 0 : next
})
}
return (
<section id="portfolio" className="portfolio">
<div className="section-container">
<div className="section-header">
<span className="section-tag">My Work</span>
<h2 className="section-title">Featured <span className="highlight">Projects</span></h2>
<p className="section-subtitle">
A selection of projects that showcase my expertise and approach to design
</p>
</div>
<div className="portfolio-filters">
{categories.map(cat => (
<button
key={cat}
className={`filter-btn ${activeCategory === cat ? 'active' : ''}`}
onClick={() => { setActiveCategory(cat); setCurrentIndex(0); }}
>
{cat}
</button>
))}
</div>
<div className="portfolio-carousel">
<div className="carousel-nav">
<button className="nav-btn prev" onClick={() => goTo(-1)}>&#10094;</button>
<button className="nav-btn next" onClick={() => goTo(1)}>&#10095;</button>
</div>
<div className="carousel-content">
<div className="project-preview" style={{ background: `linear-gradient(135deg, ${featuredProject.color}20, ${featuredProject.color}05)` }}>
<div className="preview-mockup">
<div className="mockup-header">
<span className="dot red"></span>
<span className="dot yellow"></span>
<span className="dot green"></span>
</div>
<div className="mockup-body">
<div className="mockup-title" style={{ color: featuredProject.color }}>{featuredProject.title}</div>
<div className="mockup-tags">
{featuredProject.tags.map((tag, i) => (
<span key={i} className="mockup-tag">{tag}</span>
))}
</div>
</div>
</div>
</div>
<div className="project-info">
<span className="project-category">{featuredProject.category}</span>
<h3 className="project-title">{featuredProject.title}</h3>
<p className="project-description">{featuredProject.description}</p>
<div className="project-tags">
{featuredProject.tags.map((tag, i) => (
<span key={i} className="project-tag">{tag}</span>
))}
</div>
<button className="btn btn-primary view-project">View Case Study</button>
</div>
</div>
<div className="carousel-indicators">
{filteredProjects.map((_, i) => (
<button
key={i}
className={`indicator ${i === currentIndex ? 'active' : ''}`}
onClick={() => setCurrentIndex(i)}
/>
))}
</div>
</div>
<div className="projects-grid">
{filteredProjects.slice(0, 3).map(project => (
<div key={project.id} className="project-mini-card">
<div className="mini-gradient" style={{ background: `linear-gradient(135deg, ${project.color}, ${project.color}80)` }}></div>
<h4 className="mini-title">{project.title}</h4>
<span className="mini-category">{project.category}</span>
</div>
))}
</div>
</div>
</section>
)
}
export default Portfolio

View File

@ -0,0 +1,153 @@
.filters {
display: flex;
justify-content: center;
gap: 12px;
margin-bottom: 3rem;
flex-wrap: wrap;
}
.filterBtn {
background: rgba(255, 255, 255, 0.05);
color: #a0a0a0;
padding: 10px 24px;
border-radius: 30px;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s ease;
border: 1px solid transparent;
}
.filterBtn:hover {
color: #fff;
}
.filterBtn.active {
background: linear-gradient(135deg, #ff6b22 0%, #ff8544 100%);
color: #fff;
}
.carousel {
display: flex;
align-items: center;
gap: 1rem;
}
.arrow {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid var(--border-color);
background: var(--bg-card);
color: #fff;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.3s ease;
}
.arrow:hover:not(:disabled) {
border-color: #ff6b22;
color: #ff6b22;
}
.arrow:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.carouselInner {
display: flex;
gap: 2rem;
overflow: hidden;
flex: 1;
}
.projectCard {
flex: 0 0 calc((100% - 4rem) / 3);
border-radius: 16px;
overflow: hidden;
background: var(--bg-card);
border: 1px solid var(--border-color);
transition: all 0.3s ease;
}
.projectCard:hover {
transform: translateY(-6px);
box-shadow: var(--shadow-card-hover);
}
.projectPreview {
height: 200px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.projectHighlight {
font-size: 1.2rem;
font-weight: 700;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 2px;
}
.projectInfo {
padding: 1.5rem;
}
.projectCategory {
font-size: 0.75rem;
font-weight: 700;
color: #ff6b22;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 0.3rem;
}
.projectTitle {
font-size: 1.15rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.projectDesc {
font-size: 0.9rem;
color: #888888;
line-height: 1.6;
}
.dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 2rem;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--border-color);
cursor: pointer;
transition: all 0.3s ease;
}
.activeDot {
background: #ff6b22;
width: 24px;
border-radius: 5px;
}
@media (max-width: 900px) {
.projectCard {
flex: 0 0 100%;
}
.carouselInner {
gap: 1rem;
}
.arrow { display: none; }
}

View File

@ -0,0 +1,243 @@
.services {
padding: 6rem 0;
background: #141414;
}
.section-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
.section-header {
text-align: center;
margin-bottom: 4rem;
}
.section-tag {
display: inline-block;
background: rgba(255, 106, 0, 0.1);
color: #ff6a00;
padding: 0.4rem 1.2rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 1rem;
}
.section-title {
font-size: 2.8rem;
color: #fff;
margin: 0 0 1rem;
}
.section-subtitle {
color: #888;
font-size: 1.1rem;
max-width: 600px;
margin: 0 auto;
}
.services-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.service-card {
background: #1a1a1a;
border-radius: 20px;
padding: 2rem;
border: 1px solid rgba(255, 255, 255, 0.05);
transition: all 0.3s ease;
}
.service-card:hover {
transform: translateY(-8px);
border-color: rgba(255, 106, 0, 0.3);
box-shadow: 0 10px 30px rgba(255, 106, 0, 0.1);
}
.card-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.card-title {
font-size: 1.4rem;
color: #fff;
margin: 0 0 0.8rem;
}
.card-description {
color: #888;
font-size: 0.95rem;
line-height: 1.6;
margin: 0 0 1.5rem;
}
.card-preview {
background: #0d0d0d;
border-radius: 12px;
overflow: hidden;
margin-bottom: 1.5rem;
}
.preview-header {
padding: 0.6rem 1rem;
display: flex;
gap: 6px;
}
.preview-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.preview-dot.red { background: #ff5f56; }
.preview-dot.yellow { background: #ffbd2e; }
.preview-dot.green { background: #27c93f; }
.preview-content {
padding: 1rem;
}
.app-preview {
display: flex;
gap: 0.8rem;
}
.screen-mockup {
flex: 1;
height: 100px;
background: linear-gradient(180deg, #222, #1a1a1a);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
color: #555;
font-size: 0.7rem;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.website-preview {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.wp-header {
height: 30px;
background: rgba(255, 106, 0, 0.1);
border-radius: 6px;
display: flex;
align-items: center;
padding-left: 0.8rem;
color: #ff6a00;
font-size: 0.65rem;
}
.wp-hero {
height: 50px;
background: linear-gradient(90deg, rgba(255, 106, 0, 0.2), rgba(255, 106, 0, 0.05));
border-radius: 6px;
display: flex;
align-items: center;
padding-left: 0.8rem;
color: #888;
font-size: 0.7rem;
}
.wp-grid {
height: 40px;
background: #222;
border-radius: 6px;
display: flex;
align-items: center;
padding-left: 0.8rem;
color: #555;
font-size: 0.65rem;
}
.landing-preview {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.lp-hero {
height: 50px;
background: linear-gradient(135deg, #ff6a00, #ff8533);
border-radius: 6px;
display: flex;
align-items: center;
padding-left: 0.8rem;
color: #fff;
font-size: 0.75rem;
font-weight: 500;
}
.lp-features {
display: flex;
gap: 0.5rem;
}
.lp-features span {
flex: 1;
height: 40px;
background: #222;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #555;
font-size: 0.65rem;
}
.card-features {
list-style: none;
padding: 0;
margin: 0 0 1.5rem;
}
.card-features li {
color: #ccc;
font-size: 0.9rem;
padding: 0.4rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.check {
color: #ff6a00;
font-weight: bold;
}
.card-btn {
width: 100%;
padding: 0.8rem;
background: transparent;
border: 1px solid rgba(255, 106, 0, 0.3);
color: #ff6a00;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.card-btn:hover {
background: #ff6a00;
color: #fff;
}
@media (max-width: 992px) {
.services-grid {
grid-template-columns: 1fr;
}
.section-title {
font-size: 2.2rem;
}
}

View File

@ -0,0 +1,110 @@
import './Services.css'
const services = [
{
title: 'UI/UX Design',
description: 'User-centered design solutions that create seamless experiences and drive engagement across all digital touchpoints.',
icon: '📱',
preview: {
type: 'app',
screens: ['Dashboard', 'Profile', 'Settings']
},
features: ['User Research', 'Wireframing', 'Prototyping', 'Usability Testing']
},
{
title: 'Web Design',
description: 'Modern, responsive websites that combine aesthetic appeal with optimal functionality and performance.',
icon: '🌐',
preview: {
type: 'website',
elements: ['Header', 'Hero Section', 'Content Grid']
},
features: ['Responsive Design', 'CMS Integration', 'SEO Optimization', 'Performance']
},
{
title: 'Landing Pages',
description: 'High-converting landing pages designed to maximize leads, sales, and user engagement for your campaigns.',
icon: '🚀',
preview: {
type: 'landing',
elements: ['Hero CTA', 'Features', 'Testimonials']
},
features: ['A/B Testing', 'Conversion Optimization', 'Analytics', 'Fast Loading']
}
]
const Services = () => {
return (
<section id="services" className="services">
<div className="section-container">
<div className="section-header">
<span className="section-tag">What I Do</span>
<h2 className="section-title">My <span className="highlight">Services</span></h2>
<p className="section-subtitle">
Comprehensive design solutions tailored to elevate your brand and drive measurable results
</p>
</div>
<div className="services-grid">
{services.map((service, index) => (
<div key={index} className="service-card">
<div className="card-icon">{service.icon}</div>
<h3 className="card-title">{service.title}</h3>
<p className="card-description">{service.description}</p>
<div className="card-preview">
<div className="preview-header">
<span className="preview-dot red"></span>
<span className="preview-dot yellow"></span>
<span className="preview-dot green"></span>
</div>
<div className="preview-content">
{service.preview.type === 'app' && (
<div className="app-preview">
{service.preview.screens.map((screen, i) => (
<div key={i} className="screen-mockup">
<span>{screen}</span>
</div>
))}
</div>
)}
{service.preview.type === 'website' && (
<div className="website-preview">
<div className="wp-header">{service.preview.elements[0]}</div>
<div className="wp-hero">{service.preview.elements[1]}</div>
<div className="wp-grid">
{service.preview.elements[2]}
</div>
</div>
)}
{service.preview.type === 'landing' && (
<div className="landing-preview">
<div className="lp-hero">{service.preview.elements[0]}</div>
<div className="lp-features">
<span>{service.preview.elements[1]}</span>
<span>{service.preview.elements[2]}</span>
</div>
</div>
)}
</div>
</div>
<ul className="card-features">
{service.features.map((feature, i) => (
<li key={i}>
<span className="check">&#10003;</span>
{feature}
</li>
))}
</ul>
<button className="card-btn">Learn More</button>
</div>
))}
</div>
</div>
</section>
)
}
export default Services

View File

@ -0,0 +1,105 @@
.cardsGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 2rem;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.card:hover {
transform: translateY(-6px);
box-shadow: var(--shadow-card-hover);
border-color: rgba(255, 107, 34, 0.3);
}
.cardIcon {
width: 56px;
height: 56px;
background: rgba(255, 107, 34, 0.1);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.5rem;
}
.cardTitle {
font-size: 1.35rem;
font-weight: 700;
margin-bottom: 0.75rem;
}
.cardDesc {
color: #888888;
font-size: 0.95rem;
line-height: 1.7;
margin-bottom: 1.5rem;
}
.cardPreview {
border-radius: 12px;
overflow: hidden;
margin-bottom: 1.5rem;
}
.previewBlock1 {
height: 120px;
background: linear-gradient(135deg, #1a0c00 0%, #2d1800 50%, #1a0c00 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.previewBlock2 {
height: 120px;
background: linear-gradient(135deg, #0c0c1a 0%, #1a1a2d 50%, #0c0c1a 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.previewBlock3 {
height: 120px;
background: linear-gradient(135deg, #0c1a0c 0%, #1a2d1a 50%, #0c1a0c 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.previewLabel {
color: rgba(255, 107, 34, 0.7);
font-size: 0.85rem;
font-weight: 600;
}
.cardTags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.tag {
background: rgba(255, 107, 34, 0.08);
color: #ff8544;
padding: 5px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 600;
}
@media (max-width: 900px) {
.cardsGrid {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,102 @@
.testimonials {
padding: 6rem 0;
background: #141414;
}
.testimonials-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.testimonial-card {
background: #1a1a1a;
border-radius: 16px;
padding: 2rem;
border: 1px solid rgba(255, 255, 255, 0.05);
transition: all 0.3s ease;
}
.testimonial-card:hover {
border-color: rgba(255, 106, 0, 0.2);
transform: translateY(-4px);
}
.card-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.client-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background: linear-gradient(135deg, #ff6a00, #ff8533);
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-weight: 700;
font-size: 1.1rem;
}
.client-info {
display: flex;
flex-direction: column;
}
.client-name {
color: #fff;
margin: 0;
font-size: 1.05rem;
}
.client-role {
color: #888;
font-size: 0.85rem;
}
.rating {
display: flex;
gap: 0.2rem;
margin-bottom: 1rem;
}
.star {
color: #ff6a00;
font-size: 1.2rem;
}
.testimonial-text {
color: #ccc;
line-height: 1.7;
margin: 0 0 1.5rem;
font-style: italic;
}
.project-badge {
display: inline-block;
background: rgba(255, 106, 0, 0.1);
padding: 0.4rem 0.8rem;
border-radius: 6px;
}
.badge-label {
color: #888;
font-size: 0.8rem;
margin-right: 0.3rem;
}
.badge-text {
color: #ff6a00;
font-size: 0.8rem;
font-weight: 600;
}
@media (max-width: 992px) {
.testimonials-grid {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,76 @@
import './Testimonials.css'
const testimonials = [
{
name: 'Emily Rodriguez',
title: 'Marketing Director',
company: 'TechVentures Inc.',
avatar: 'ER',
rating: 5,
text: "Sarah transformed our product's user experience completely. Our engagement metrics improved by 60% after implementing her designs. Her attention to detail and user-centric approach made all the difference.",
project: 'SaaS Platform Redesign'
},
{
name: 'Marcus Chen',
title: 'CEO & Founder',
company: 'HealthSync App',
avatar: 'MC',
rating: 5,
text: "Working with Sarah was a game-changer for our healthcare app. She understood our complex requirements and delivered an intuitive design that our users love. Highly recommend!",
project: 'Mobile App Design'
},
{
name: 'Sophia Anderson',
title: 'E-commerce Manager',
company: 'LuxeStyle Fashion',
avatar: 'SA',
rating: 5,
text: "Our conversion rate tripled after Sarah redesigned our landing pages. She combines creativity with data-driven decisions to produce remarkable results. A true professional!",
project: 'Landing Page Optimization'
}
]
const Testimonials = () => {
return (
<section id="testimonials" className="testimonials">
<div className="section-container">
<div className="section-header">
<span className="section-tag">Testimonials</span>
<h2 className="section-title">What Clients <span className="highlight">Say</span></h2>
<p className="section-subtitle">
Real feedback from clients who have experienced the impact of great design
</p>
</div>
<div className="testimonials-grid">
{testimonials.map((item, index) => (
<div key={index} className="testimonial-card">
<div className="card-header">
<div className="client-avatar">{item.avatar}</div>
<div className="client-info">
<h4 className="client-name">{item.name}</h4>
<span className="client-role">{item.title}, {item.company}</span>
</div>
</div>
<div className="rating">
{Array.from({ length: item.rating }).map((_, i) => (
<span key={i} className="star">&#9733;</span>
))}
</div>
<p className="testimonial-text">&quot;{item.text}&quot;</p>
<div className="project-badge">
<span className="badge-label">Project:</span>
<span className="badge-text">{item.project}</span>
</div>
</div>
))}
</div>
</div>
</section>
)
}
export default Testimonials

View File

@ -0,0 +1,133 @@
.why-choose-me {
padding: 6rem 0;
background: #141414;
}
.why-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.why-description {
color: #aaa;
font-size: 1.1rem;
line-height: 1.8;
margin-bottom: 2rem;
}
.why-list {
list-style: none;
padding: 0;
margin: 0 0 2rem;
}
.why-list li {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.list-icon {
color: #ff6a00;
font-size: 1.2rem;
flex-shrink: 0;
}
.list-text {
display: flex;
flex-direction: column;
}
.list-text strong {
color: #fff;
font-size: 1.1rem;
}
.list-text span {
color: #888;
font-size: 0.9rem;
margin-top: 0.2rem;
}
.why-image {
position: relative;
}
.designer-illustration {
position: relative;
background: linear-gradient(135deg, #1a1a1a, #222);
border-radius: 20px;
padding: 3rem;
min-height: 300px;
border: 1px solid rgba(255, 106, 0, 0.1);
}
.illustration-bg {
position: absolute;
inset: 0;
background: radial-gradient(circle at 30% 70%, rgba(255, 106, 0, 0.1), transparent);
border-radius: 20px;
}
.illustration-content {
position: relative;
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
.illustration-tools {
display: flex;
gap: 1rem;
}
.tool {
padding: 0.8rem 1.5rem;
background: rgba(255, 106, 0, 0.15);
border: 1px solid rgba(255, 106, 0, 0.3);
border-radius: 10px;
color: #ff6a00;
font-weight: 600;
font-size: 0.9rem;
}
.tool-2 { background: rgba(255, 133, 51, 0.15); }
.tool-3 { background: rgba(255, 69, 0, 0.15); }
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-top: 1.5rem;
}
.stat-card {
background: #1a1a1a;
border-radius: 12px;
padding: 1.2rem;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.stat-highlight {
display: block;
font-size: 2rem;
font-weight: 700;
color: #ff6a00;
}
.stat-card .stat-label {
color: #888;
font-size: 0.85rem;
display: block;
margin-top: 0.3rem;
}
@media (max-width: 992px) {
.why-content {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,79 @@
import './WhyChooseMe.css'
const stats = [
{ number: '600+', label: 'Projects Completed' },
{ number: '50+', label: 'Industries Covered' },
{ number: '450+', label: 'Happy Clients' },
{ number: '15+', label: 'Design Awards' }
]
const WhyChooseMe = () => {
return (
<section className="why-choose-me">
<div className="section-container">
<div className="why-content">
<div className="why-text">
<span className="section-tag">Why Me</span>
<h2 className="section-title">Why Choose <span className="highlight">Me</span>?</h2>
<p className="why-description">
With over a decade of experience in digital design, I bring a unique combination
of creative vision, technical expertise, and business understanding to every project.
</p>
<ul className="why-list">
<li>
<span className="list-icon">&#10003;</span>
<div className="list-text">
<strong>User-Centered Approach</strong>
<span>Every design decision is backed by research and user feedback</span>
</div>
</li>
<li>
<span className="list-icon">&#10003;</span>
<div className="list-text">
<strong>Results-Driven Design</strong>
<span>Focused on metrics that matter - conversions, engagement, retention</span>
</div>
</li>
<li>
<span className="list-icon">&#10003;</span>
<div className="list-text">
<strong>End-to-End Service</strong>
<span>From concept to launch, handling every aspect of design</span>
</div>
</li>
</ul>
<button className="btn btn-primary" onClick={() => document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' })}>
Start a Project
</button>
</div>
<div className="why-image">
<div className="designer-illustration">
<div className="illustration-bg"></div>
<div className="illustration-content">
<div className="illustration-tools">
<span className="tool tool-1">Figma</span>
<span className="tool tool-2">Sketch</span>
<span className="tool tool-3">Adobe</span>
</div>
</div>
</div>
<div className="stats-grid">
{stats.map((stat, index) => (
<div key={index} className="stat-card">
<span className="stat-number stat-highlight">{stat.number}</span>
<span className="stat-label">{stat.label}</span>
</div>
))}
</div>
</div>
</div>
</div>
</section>
)
}
export default WhyChooseMe

View File

@ -0,0 +1,167 @@
.whySection {
background: linear-gradient(180deg, #0a0a0a 0%, #0f0f0f 100%);
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.leftContent .title {
font-size: 2.5rem;
font-weight: 800;
text-align: left;
margin-bottom: 1rem;
}
.subtitle {
color: #888888;
font-size: 1.05rem;
line-height: 1.8;
margin-bottom: 2rem;
}
.features {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 2rem;
}
.featureItem {
display: flex;
align-items: center;
gap: 12px;
font-size: 1rem;
color: #cccccc;
}
.checkmark {
color: #ff6b22;
font-weight: 700;
font-size: 1.2rem;
}
.btn {
background: linear-gradient(135deg, #ff6b22 0%, #ff8544 100%);
color: #fff;
padding: 16px 32px;
border-radius: 10px;
font-weight: 700;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(255, 107, 34, 0.4);
}
.designerImage {
position: relative;
display: flex;
justify-content: center;
margin-bottom: 2rem;
}
.designerPlaceholder {
width: 180px;
height: 180px;
border-radius: 20px;
background: linear-gradient(135deg, #1a1a1a 0%, #222 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px solid rgba(255, 107, 34, 0.2);
transform: rotate(-3deg);
}
.designerIcon {
font-size: 3rem;
}
.designerRole {
color: #ff8544;
font-size: 0.85rem;
font-weight: 600;
}
.glowRing {
position: absolute;
inset: -10px;
border: 2px solid rgba(255, 107, 34, 0.08);
border-radius: 24px;
transform: rotate(-3deg);
animation: pulse-ring 2s ease-in-out infinite;
}
@keyframes pulse-ring {
0%, 100% { opacity: 0.5; transform: rotate(-3deg) scale(1); }
50% { opacity: 1; transform: rotate(-3deg) scale(1.03); }
}
.statsGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.statCard {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.25rem;
text-align: center;
transition: all 0.3s ease;
}
.statCard:hover {
border-color: rgba(255, 107, 34, 0.3);
}
.statIcon {
display: block;
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.statValue {
display: block;
font-size: 1.5rem;
font-weight: 800;
color: #ff6b22;
}
.statLabel {
display: block;
font-size: 0.8rem;
color: #888888;
margin-top: 4px;
}
@media (max-width: 900px) {
.grid {
grid-template-columns: 1fr;
gap: 3rem;
}
.leftContent .title {
text-align: center;
font-size: 2rem;
}
.btn { display: block; text-align: center; }
}
@media (max-width: 500px) {
.statsGrid {
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
}
}

View File

@ -0,0 +1,672 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.App {
width: 100%;
height: 100vh;
overflow: hidden;
}
.layout {
display: flex;
height: 100vh;
width: 100%;
}
/* 会话管理面板 */
.layout__sessions {
width: 350px;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
border-right: 1px solid #ddd;
min-height: 100vh;
}
.sessions-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background-color: #1890ff;
color: white;
}
.sessions-header h2 {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.create-session-btn {
padding: 8px 12px;
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.create-session-btn:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.create-session-btn:disabled {
background-color: rgba(255, 255, 255, 0.1);
cursor: not-allowed;
}
.sessions-list {
flex: 1;
overflow-y: auto;
padding: 10px;
background-color: #f5f5f5;
min-height: 200px;
}
.session-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
margin-bottom: 8px;
background-color: white;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s;
}
.session-item:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
}
.session-item.active {
background-color: #e3f2fd;
border-left: 4px solid #1890ff;
}
.session-info {
flex: 1;
text-align: left;
}
.session-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.session-time {
font-size: 12px;
color: #999;
}
.session-delete {
background: none;
border: none;
color: #999;
font-size: 18px;
cursor: pointer;
padding: 4px;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.session-delete:hover {
background-color: #ff4d4f;
color: white;
}
.layout__preview {
flex: 1;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
border-right: 1px solid #ddd;
max-width: 60%;
}
.layout__chat {
width: 450px;
display: flex;
flex-direction: column;
background-color: white;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background-color: #1890ff;
color: white;
}
.preview-header h2 {
font-size: 18px;
font-weight: 600;
}
.model-selector {
padding: 8px 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.1);
color: white;
font-size: 14px;
cursor: pointer;
}
.model-selector option {
color: #333;
background-color: white;
}
.preview-content {
flex: 1;
overflow: auto;
padding: 20px;
}
.preview-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
}
.preview-placeholder p {
font-size: 16px;
margin-bottom: 20px;
}
.generated-files-list {
margin-top: 20px;
}
.generated-files-list h3 {
font-size: 14px;
color: #666;
margin-bottom: 10px;
}
.code-preview {
height: 100%;
display: flex;
flex-direction: column;
}
.code-preview__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background-color: #2d2d2d;
border-radius: 8px 8px 0 0;
}
.code-preview__filename {
color: #fff;
font-size: 14px;
font-family: 'Courier New', monospace;
}
.code-preview__filesize {
color: #999;
font-size: 12px;
margin-left: 8px;
font-family: 'Courier New', monospace;
}
.code-preview__controls {
display: flex;
align-items: center;
gap: 8px;
}
.preview-mode-btn {
padding: 4px 8px;
background-color: rgba(255, 255, 255, 0.1);
color: #999;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.preview-mode-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
}
.preview-mode-btn.active {
background-color: #1890ff;
color: #fff;
}
.code-preview__close {
background: none;
border: none;
color: #999;
font-size: 24px;
cursor: pointer;
padding: 0;
line-height: 1;
}
.code-preview__close:hover {
color: #fff;
}
.html-preview {
width: 100%;
height: 100%;
min-height: 400px;
background-color: white;
border-radius: 0 0 8px 8px;
overflow: hidden;
}
.html-preview__iframe {
width: 100%;
height: 100%;
border: none;
min-height: 400px;
}
.code-preview pre {
flex: 1;
margin: 0;
border-radius: 0 0 8px 8px;
overflow: auto;
}
.preview-markdown {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.chat-container {
flex: 1;
overflow-y: auto;
padding: 15px;
background-color: #f5f5f5;
}
.message {
margin-bottom: 15px;
padding: 10px 15px;
border-radius: 8px;
max-width: 85%;
}
.user-message {
background-color: #e3f2fd;
margin-left: auto;
}
.assistant-message {
background-color: white;
align-self: flex-start;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.message-content {
word-wrap: break-word;
}
.message-content h1,
.message-content h2,
.message-content h3,
.message-content h4,
.message-content h5,
.message-content h6 {
margin-top: 1em;
margin-bottom: 0.5em;
font-weight: 600;
}
.message-content h1 {
font-size: 1.5em;
}
.message-content h2 {
font-size: 1.3em;
}
.message-content h3 {
font-size: 1.1em;
}
.message-content p {
margin-bottom: 1em;
}
.message-content ul,
.message-content ol {
margin-bottom: 1em;
margin-left: 2em;
}
.message-content li {
margin-bottom: 0.5em;
}
.message-content blockquote {
border-left: 4px solid #ddd;
padding-left: 1em;
margin: 1em 0;
color: #666;
}
.message-content a {
color: #1890ff;
text-decoration: none;
}
.message-content a:hover {
text-decoration: underline;
}
.message-content pre {
background-color: #f5f5f5;
padding: 1em;
border-radius: 4px;
overflow-x: auto;
margin: 1em 0;
}
.message-files {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.message-files .file-item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 0;
font-size: 14px;
color: #666;
}
.message-files .file-name {
font-weight: 500;
}
.message-files .file-size {
font-size: 12px;
color: #999;
}
.message-generated-files {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.generated-files-label {
font-size: 14px;
color: #666;
}
.file-preview-btn {
padding: 6px 12px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: background-color 0.2s;
}
.file-preview-btn:hover {
background-color: #40a9ff;
}
.upload-section {
padding: 10px 15px;
background-color: white;
border-top: 1px solid #eee;
}
.upload-section input[type="file"] {
margin-bottom: 10px;
}
.file-list {
margin-top: 10px;
text-align: left;
}
.file-item {
display: flex;
align-items: center;
gap: 10px;
padding: 5px 0;
border-bottom: 1px solid #eee;
}
.file-item button {
background: none;
border: none;
color: #ff4d4f;
cursor: pointer;
font-size: 14px;
}
.file-item button:first-of-type {
color: #1890ff;
}
.input-section {
display: flex;
gap: 10px;
padding: 15px;
background-color: white;
border-top: 1px solid #eee;
}
.input-section textarea {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: none;
min-height: 60px;
font-size: 14px;
font-family: inherit;
}
.input-section button {
padding: 0 20px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background-color 0.2s;
}
.input-section button:hover {
background-color: #40a9ff;
}
.input-section button:disabled {
background-color: #d9d9d9;
cursor: not-allowed;
}
.thinking {
font-style: italic;
color: #666;
margin: 10px 0;
}
.error {
color: #ff4d4f;
margin: 10px 15px;
padding: 10px;
background-color: #fff1f0;
border: 1px solid #ffccc7;
border-radius: 4px;
font-size: 14px;
}
/* 创建会话模态框 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background-color: white;
border-radius: 8px;
width: 400px;
max-width: 90%;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
}
.modal-header h3 {
font-size: 16px;
font-weight: 600;
margin: 0;
color: #333;
}
.modal-close {
background: none;
border: none;
color: #999;
font-size: 20px;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-close:hover {
color: #333;
}
.modal-body {
padding: 20px;
}
.session-title-input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
font-family: inherit;
box-sizing: border-box;
}
.session-title-input:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 15px 20px;
background-color: #f5f5f5;
border-top: 1px solid #ddd;
}
.modal-cancel {
padding: 8px 16px;
background-color: white;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.modal-cancel:hover {
border-color: #1890ff;
color: #1890ff;
}
.modal-confirm {
padding: 8px 16px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.modal-confirm:hover {
background-color: #40a9ff;
}
.modal-confirm:disabled {
background-color: #d9d9d9;
cursor: not-allowed;
}

View File

@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@ -0,0 +1,572 @@
:root {
--background: #fdf8f3;
--background-secondary: #f5f0eb;
--foreground: #262626;
--accent: #e4a4bd;
--accent-foreground: #262626;
--card: #fdf8f3;
--card-hover: #e4a4bd;
--border: rgba(38, 38, 38, 0.05);
--border-subtle: rgba(38, 38, 38, 0.08);
--muted: rgba(38, 38, 38, 0.7);
--muted-light: rgba(38, 38, 38, 0.3);
--glass-bg: rgba(253, 248, 243, 0.8);
--transition-luxury: cubic-bezier(0.16, 1, 0.3, 1);
--font-family: 'League Spartan', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-family);
background-color: var(--background);
color: var(--foreground);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
/* ==================== NAVIGATION ==================== */
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 80px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 48px;
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
z-index: 1000;
}
.nav__brand {
font-size: 18px;
font-weight: 900;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--foreground);
}
.nav__menu {
display: flex;
gap: 32px;
list-style: none;
}
.nav__link {
font-size: 10px;
font-weight: 900;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--foreground);
text-decoration: none;
transition: color 0.6s var(--transition-luxury);
}
.nav__link:hover {
color: var(--accent);
}
.nav__cta {
background: var(--accent);
color: var(--accent-foreground);
padding: 12px 32px;
border-radius: 9999px;
font-size: 10px;
font-weight: 900;
letter-spacing: 0.2em;
text-transform: uppercase;
text-decoration: none;
transition: all 0.6s var(--transition-luxury);
}
.nav__cta:hover {
background: var(--foreground);
color: var(--background);
}
/* ==================== HERO SECTION ==================== */
.hero {
min-height: 100vh;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: center;
padding: 120px 48px 48px;
}
.hero__content {
display: flex;
flex-direction: column;
gap: 32px;
}
.hero__headline {
font-size: clamp(64px, 15vw, 180px);
font-weight: 800;
line-height: 0.8;
letter-spacing: -0.04em;
color: var(--foreground);
}
.hero__headline em {
font-style: italic;
font-weight: 300;
color: var(--accent);
}
.hero__subtext {
font-size: 18px;
font-weight: 400;
color: var(--muted);
max-width: 400px;
line-height: 1.6;
}
.hero__cta {
display: inline-flex;
align-items: center;
gap: 12px;
font-size: 12px;
font-weight: 900;
letter-spacing: 0.3em;
text-transform: uppercase;
color: var(--foreground);
text-decoration: none;
border-bottom: 2px solid var(--accent);
padding-bottom: 8px;
transition: all 0.6s var(--transition-luxury);
}
.hero__cta:hover {
color: var(--accent);
border-color: var(--foreground);
}
.hero__cta-arrow {
transition: transform 0.6s var(--transition-luxury);
}
.hero__cta:hover .hero__cta-arrow {
transform: translateX(8px);
}
.hero__image-wrapper {
position: relative;
}
.hero__image {
width: 100%;
aspect-ratio: 3/4;
border-radius: 24px;
overflow: hidden;
position: relative;
}
.hero__image img {
width: 100%;
height: 100%;
object-fit: cover;
filter: grayscale(100%);
transition: all 1s var(--transition-luxury);
}
.hero__image:hover img {
filter: grayscale(0%);
transform: scale(1.08);
}
.hero__badge {
position: absolute;
bottom: -40px;
left: -40px;
width: 160px;
height: 160px;
border-radius: 50%;
background: var(--accent);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
animation: bounce-slow 4s ease-in-out infinite;
}
.hero__badge-number {
font-size: 48px;
font-style: italic;
color: var(--accent-foreground);
line-height: 1;
}
.hero__badge-text {
font-size: 8px;
font-weight: 900;
letter-spacing: 0.3em;
text-transform: uppercase;
color: var(--accent-foreground);
}
/* ==================== SERVICES SECTION ==================== */
.services {
background: var(--background-secondary);
padding: 120px 48px;
}
.services__headline {
font-size: clamp(48px, 8vw, 96px);
font-weight: 800;
line-height: 0.8;
letter-spacing: -0.04em;
text-align: center;
margin-bottom: 80px;
color: var(--foreground);
}
.services__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0;
border: 1px solid var(--border-subtle);
}
.services__card {
padding: 40px;
background: var(--card);
border-right: 1px solid var(--border-subtle);
transition: all 1s var(--transition-luxury);
cursor: pointer;
}
.services__card:last-child {
border-right: none;
}
.services__card:hover {
background: var(--card-hover);
}
.services__icon {
font-size: 48px;
color: var(--accent);
margin-bottom: 24px;
transition: color 1s var(--transition-luxury);
}
.services__card:hover .services__icon {
color: var(--foreground);
}
.services__card-title {
font-size: 18px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 16px;
color: var(--foreground);
}
.services__card-text {
font-size: 14px;
color: var(--muted);
line-height: 1.6;
}
/* ==================== PORTFOLIO SECTION ==================== */
.portfolio {
padding: 120px 48px;
background: var(--background);
}
.portfolio__grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 48px;
}
.portfolio__item {
display: flex;
flex-direction: column;
gap: 24px;
}
.portfolio__item:nth-child(even) {
transform: translateY(100px);
}
.portfolio__image-wrapper {
position: relative;
border-radius: 16px;
overflow: hidden;
aspect-ratio: 3/4;
cursor: pointer;
}
.portfolio__image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
filter: grayscale(100%);
transition: all 1s var(--transition-luxury);
}
.portfolio__image-wrapper:hover img {
filter: grayscale(0%);
transform: scale(1.08);
}
.portfolio__overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.6s var(--transition-luxury);
}
.portfolio__image-wrapper:hover .portfolio__overlay {
opacity: 1;
}
.portfolio__overlay-circle {
width: 96px;
height: 96px;
border-radius: 50%;
background: #000;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 900;
letter-spacing: 0.1em;
color: #fff;
text-transform: uppercase;
}
.portfolio__category {
font-size: 10px;
font-weight: 900;
letter-spacing: 0.3em;
text-transform: uppercase;
color: var(--accent);
}
.portfolio__title {
font-size: 32px;
font-weight: 700;
color: var(--foreground);
}
.portfolio__meta {
display: flex;
align-items: center;
gap: 12px;
font-size: 12px;
color: var(--muted);
}
.portfolio__meta-separator {
color: var(--accent);
}
/* ==================== FOOTER ==================== */
.footer {
background: var(--background-secondary);
padding: 80px 48px 40px;
}
.footer__main {
display: grid;
grid-template-columns: 5fr 7fr;
gap: 48px;
margin-bottom: 60px;
}
.footer__brand {
display: flex;
flex-direction: column;
gap: 24px;
}
.footer__logo {
font-size: 24px;
font-weight: 900;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--foreground);
}
.footer__mission {
font-size: 14px;
color: var(--muted);
line-height: 1.6;
max-width: 300px;
}
.footer__columns {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 48px;
}
.footer__column-title {
font-size: 10px;
font-weight: 900;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 24px;
position: relative;
padding-bottom: 8px;
}
.footer__column-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
background: var(--accent);
transform: translateY(8px);
}
.footer__column-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 12px;
}
.footer__column-link {
font-size: 14px;
color: var(--muted);
text-decoration: none;
transition: color 0.6s var(--transition-luxury);
}
.footer__column-link:hover {
color: var(--foreground);
}
.footer__bottom {
border-top: 1px solid var(--border-subtle);
padding-top: 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.footer__copyright {
font-size: 9px;
color: var(--muted-light);
}
.footer__legal {
display: flex;
gap: 24px;
}
.footer__legal-link {
font-size: 9px;
color: var(--muted-light);
text-decoration: none;
transition: color 0.6s var(--transition-luxury);
}
.footer__legal-link:hover {
color: var(--foreground);
}
/* ==================== ANIMATIONS ==================== */
@keyframes bounce-slow {
0%, 100% {
transform: translateY(-5%);
}
50% {
transform: translateY(5%);
}
}
.reveal-up {
opacity: 0;
transform: translateY(40px);
transition: all 1s var(--transition-luxury);
}
.reveal-up.active {
opacity: 1;
transform: translateY(0);
}
/* ==================== RESPONSIVE ==================== */
@media (max-width: 1024px) {
.hero {
grid-template-columns: 1fr;
padding: 120px 24px 48px;
}
.services__grid {
grid-template-columns: 1fr;
}
.services__card {
border-right: none;
border-bottom: 1px solid var(--border-subtle);
}
.services__card:last-child {
border-bottom: none;
}
.portfolio__grid {
grid-template-columns: 1fr;
}
.portfolio__item:nth-child(even) {
transform: none;
}
.footer__main {
grid-template-columns: 1fr;
}
.nav__menu {
display: none;
}
}
@media (max-width: 640px) {
.nav {
padding: 0 24px;
}
.hero, .services, .portfolio, .footer {
padding-left: 24px;
padding-right: 24px;
}
.hero__badge {
width: 120px;
height: 120px;
bottom: -20px;
left: -20px;
}
.hero__badge-number {
font-size: 36px;
}
}

View File

@ -0,0 +1,317 @@
# 前端 API 接口文档
## 概述
本文档描述了前端可以调用的后端 API 接口,用于与 AI 服务进行交互。
## 基础 URL
- 本地开发:`http://localhost:8000`
- 生产环境js2.blockelite.cn
- 端口17032
- SSH账户root
- 密码zao3aiCh
- SSH账户vipuser
- 密码zao3aiCh
- SSH登录命令ssh root@js2.blockelite.cn -p 17032
## 接口列表
### 1. 会话管理
#### 1.1 获取所有会话
**请求**
- 方法:`GET`
- 路径:`/sessions`
- 认证:无
**响应**
```json
{
"sessions": [
{
"id": "string",
"title": "string",
"created_at": "number",
"updated_at": "number"
}
]
}
```
#### 1.2 创建新会话
**请求**
- 方法:`POST`
- 路径:`/sessions`
- 认证:无
- 请求体:
```json
{
"title": "string"
}
```
**响应**
```json
{
"id": "string",
"title": "string",
"created_at": "number",
"updated_at": "number"
}
```
#### 1.3 获取会话详情
**请求**
- 方法:`GET`
- 路径:`/sessions/{session_id}`
- 认证:无
**响应**
```json
{
"id": "string",
"title": "string",
"created_at": "number",
"updated_at": "number"
}
```
#### 1.4 获取会话消息
**请求**
- 方法:`GET`
- 路径:`/sessions/{session_id}/messages`
- 认证:无
**响应**
```json
{
"messages": [
{
"id": "string",
"session_id": "string",
"role": "user" | "assistant",
"content": "string",
"created_at": "number"
}
]
}
```
#### 1.5 发送消息
**请求**
- 方法:`POST`
- 路径:`/sessions/{session_id}/chat`
- 认证:无
- 请求体:
```json
{
"message": "string",
"model": "string",
"stream": "boolean"
}
```
**响应**
- 非流式:
```json
{
"id": "string",
"content": "string",
"created_at": "number"
}
```
- 流式SSE (Server-Sent Events)
#### 1.6 删除会话
**请求**
- 方法:`DELETE`
- 路径:`/sessions/{session_id}`
- 认证:无
**响应**
```json
{
"success": "boolean"
}
```
### 2. OpenAI 兼容接口
#### 2.1 聊天完成
**请求**
- 方法:`POST`
- 路径:`/v1/chat/completions`
- 认证:无
- 请求体:
```json
{
"model": "string",
"messages": [
{
"role": "user" | "assistant" | "system",
"content": "string"
}
],
"stream": "boolean",
"session_id": "string" (可选)
}
```
**响应**
- 非流式:
```json
{
"id": "string",
"object": "chat.completion",
"created": "number",
"model": "string",
"choices": [
{
"index": "number",
"message": {
"role": "assistant",
"content": "string"
},
"finish_reason": "string"
}
],
"usage": {
"prompt_tokens": "number",
"completion_tokens": "number",
"total_tokens": "number"
}
}
```
- 流式SSE (Server-Sent Events),格式为 OpenAI 兼容的 `data: [DONE]` 格式
### 3. 文件访问
#### 3.1 获取文件内容
**请求**
- 方法:`GET`
- 路径:`/api/file/{file_path}`
- 认证:无
**响应**
- 文件内容(根据文件类型返回相应的内容)
## 错误处理
所有 API 接口在遇到错误时,会返回以下格式的错误响应:
````json
{
"detail": "string"
}
## 示例请求
### 示例 1发送消息非流式
```bash
curl -X POST http://localhost:8000/sessions/{session_id}/chat \
-H "Content-Type: application/json" \
-d '{
"message": "你好",
"model": "bailian-token-plan/qwen3.6-plus",
"stream": false
}'
````
### 示例 2使用 OpenAI 兼容接口
```bash
curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "bailian-token-plan/qwen3.6-plus",
"messages": [
{
"role": "user",
"content": "你好"
}
],
"stream": false
}'
```
## 模型列表
可用的模型列表:
- `bailian-token-plan/qwen3.6-plus` (百炼)
- `bailian-token-plan/MiniMax-M2.5` (百炼)
- `bailian-token-plan/glm-5` (百炼)
- `bailian-token-plan/deepseek-v3.2` (百炼)
- `ollama/qwen3-coder-next:latest` (Ollama)
## 注意事项
1. **流式响应**:使用 `stream: true` 时,响应为 SSE 格式,前端需要使用 SSE 客户端库处理
2. **会话管理**:所有消息都与会话关联,确保在发送消息时指定正确的 `session_id`
3. **模型选择**:不同模型的能力和响应格式可能不同,请根据需要选择合适的模型
4. **错误处理**:前端应处理 API 错误,特别是网络错误和服务不可用的情况
5. **文件访问**:生成的文件会保存在会话目录中,通过 `/api/file/{session_id}/{filename}` 访问
## 部署配置
### 环境变量
前端部署时需要配置以下环境变量:
- `VITE_API_URL`:后端 API 地址,例如 `http://localhost:8000`
### 本地开发
1. 启动 opencode serve 服务:
```bash
OPENCODE_SERVER_PASSWORD=123456789 opencode serve --port 4096 --hostname 0.0.0.0
```
2. 启动后端服务:
```bash
uvicorn src.api.main:app --host 0.0.0.0 --port 8000
```
3. 启动前端服务:
```bash
cd frontend && npm run dev
```
### 容器部署
使用 docker-compose 部署:
```bash
docker-compose -f deploy/docker-compose.yml up -d
```
## 版本历史
- **v1.0.0**:初始版本,支持会话管理和 OpenAI 兼容接口
- **v1.1.0**:添加 opencode serve 模式支持
- **v1.2.0**:优化文件管理,支持会话级文件隔离

View File

@ -14,8 +14,59 @@ const ProjectDetailContent: React.FC = () => {
const [error, setError] = useState<string | null>(null);
const [iframeContent, setIframeContent] = useState<string>('');
const [zoom, setZoom] = useState(1);
const [annotations, setAnnotations] = useState<{ elementId: string; content: string }[]>([]);
const { setSelectedElement } = useEditor();
const handleAIEdit = async () => {
console.log('=== AI修改请求 ===');
console.log('项目Id:', id);
console.log('页面Id:', pageId);
console.log('标注内容:', annotations);
try {
setLoading(true);
const response = await fetch('/api/ai/edit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
projectId: id,
pageId: pageId,
annotations: annotations,
}),
});
if (!response.ok) {
throw new Error('API request failed');
}
const data = await response.json();
console.log('=== AI修改响应 ===');
console.log('返回的页面数据:', data);
if (data.content) {
setIframeContent(data.content);
}
} catch (err) {
console.log('=== AI修改模拟响应 ===');
console.log('项目Id:', id);
console.log('页面Id:', pageId);
console.log('标注内容:', annotations);
const mockResponse = `
<div>
<h1 class="editable-element" id="ai-title-${Date.now()}">AI </h1>
<p class="editable-element" id="ai-content-${Date.now()}">AI根据您的标注内容生成的新内容项目: ${id}, 页面: ${pageId}</p>
<p class="editable-element" id="ai-note-${Date.now()}">标注数量: ${annotations.length} </p>
</div>
`;
setIframeContent(mockResponse);
} finally {
setLoading(false);
}
};
const adjustZoom = (delta: number) => {
setZoom(prevZoom => {
const newZoom = Math.max(0.5, Math.min(2, prevZoom + delta));
@ -350,6 +401,15 @@ const ProjectDetailContent: React.FC = () => {
{/* 页面标题 */}
<div className="p-4 border-b border-gray-200 bg-gray-50 flex items-center justify-between">
<h2 className="text-lg font-medium text-gray-800"> - {id} / {pageId}</h2>
<button
onClick={handleAIEdit}
className="px-4 py-2 bg-gradient-to-r from-purple-500 to-indigo-500 text-white rounded-lg hover:from-purple-600 hover:to-indigo-600 transition-all shadow-md hover:shadow-lg flex items-center gap-2"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
AI修改
</button>
<div className="flex items-center gap-2">
<button
onClick={() => adjustZoom(-0.1)}

View File

@ -7,31 +7,31 @@ export const fetchProjectContent = async (projectId: string, pageId: string = '1
const projectContents: Record<string, Record<string, string>> = {
'1': {
'1': `
<div id="home-hero">
<div class="editable-element" id="home-hero">
<h1 class="editable-element" id="home-hero-title">Welcome to Our Website</h1>
<p class="editable-element" id="home-hero-description">This is the hero section of the home page.</p>
</div>
<div id="home-features">
<div class="editable-element" id="home-features">
<h2 class="editable-element" id="home-features-title">Our Features</h2>
<p class="editable-element" id="home-features-description">Check out our amazing features.</p>
</div>
`,
'2': `
<div id="about-header">
<div class="editable-element" id="about-header">
<h1 class="editable-element" id="about-header-title">About Us</h1>
<p class="editable-element" id="about-header-description">Learn more about our company.</p>
</div>
<div id="about-history">
<div class="editable-element" id="about-history">
<h2 class="editable-element" id="about-history-title">Our History</h2>
<p class="editable-element" id="about-history-description">A decade of innovation and growth.</p>
</div>
`,
'3': `
<div id="products-header">
<div class="editable-element" id="products-header">
<h1 class="editable-element" id="products-header-title">Our Products</h1>
<p class="editable-element" id="products-header-description">Discover our amazing products.</p>
</div>
<div id="products-list">
<div class="editable-element" id="products-list">
<h2 class="editable-element" id="products-list-title">Product Categories</h2>
<p class="editable-element" id="products-list-description">Browse our product categories.</p>
</div>
@ -39,31 +39,31 @@ export const fetchProjectContent = async (projectId: string, pageId: string = '1
},
'2': {
'1': `
<div id="home-hero">
<div class="editable-element" id="home-hero">
<h1 class="editable-element" id="home-hero-title">Welcome to Company B</h1>
<p class="editable-element" id="home-hero-description">Your partner in business solutions.</p>
</div>
<div id="home-services">
<div class="editable-element" id="home-services">
<h2 class="editable-element" id="home-services-title">Our Services</h2>
<p class="editable-element" id="home-services-description">Professional services for your business.</p>
</div>
`,
'2': `
<div id="services-header">
<div class="editable-element" id="services-header">
<h1 class="editable-element" id="services-header-title">Our Services</h1>
<p class="editable-element" id="services-header-description">Comprehensive solutions for all your needs.</p>
</div>
<div id="services-details">
<div class="editable-element" id="services-details">
<h2 class="editable-element" id="services-details-title">Service Details</h2>
<p class="editable-element" id="services-details-description">Learn more about each service we offer.</p>
</div>
`,
'3': `
<div id="contact-header">
<div class="editable-element" id="contact-header">
<h1 class="editable-element" id="contact-header-title">Contact Us</h1>
<p class="editable-element" id="contact-header-description">Get in touch with our team.</p>
</div>
<div id="contact-form">
<div class="editable-element" id="contact-form">
<h2 class="editable-element" id="contact-form-title">Send a Message</h2>
<p class="editable-element" id="contact-form-description">Fill out the form below to contact us.</p>
</div>
@ -71,31 +71,31 @@ export const fetchProjectContent = async (projectId: string, pageId: string = '1
},
'3': {
'1': `
<div id="home-hero">
<div class="editable-element" id="home-hero">
<h1 class="editable-element" id="home-hero-title">Welcome to Company C</h1>
<p class="editable-element" id="home-hero-description">Innovation and excellence.</p>
</div>
<div id="home-news">
<div class="editable-element" id="home-news">
<h2 class="editable-element" id="home-news-title">Latest News</h2>
<p class="editable-element" id="home-news-description">Stay updated with our latest developments.</p>
</div>
`,
'2': `
<div id="news-header">
<div class="editable-element" id="news-header">
<h1 class="editable-element" id="news-header-title">News & Updates</h1>
<p class="editable-element" id="news-header-description">Latest news from our company.</p>
</div>
<div id="news-list">
<div class="editable-element" id="news-list">
<h2 class="editable-element" id="news-list-title">News Articles</h2>
<p class="editable-element" id="news-list-description">Browse our latest news articles.</p>
</div>
`,
'3': `
<div id="support-header">
<div class="editable-element" id="support-header">
<h1 class="editable-element" id="support-header-title">Customer Support</h1>
<p class="editable-element" id="support-header-description">We're here to help you.</p>
</div>
<div id="support-resources">
<div class="editable-element" id="support-resources">
<h2 class="editable-element" id="support-resources-title">Support Resources</h2>
<p class="editable-element" id="support-resources-description">Find answers to common questions.</p>
</div>
@ -110,8 +110,8 @@ export const fetchProjectContent = async (projectId: string, pageId: string = '1
return `
<div class="editable-element" id="default-element">
<h1>Project ${projectId} - Page ${pageId}</h1>
<p>This is the default content for project ${projectId}, page ${pageId}.</p>
<h1 class="editable-element">Project ${projectId} - Page ${pageId}</h1>
<p class="editable-element">This is the default content for project ${projectId}, page ${pageId}.</p>
</div>
`;
};