Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Footer: React.FC = () => {
<div className="container mx-auto px-8 flex justify-between items-center">
<div className="flex items-center gap-3 text-[10px] font-bold tracking-tight">
<span className="text-slate-500 dark:text-slate-400">
© 2025 EricHuang
© 2025 <span style={{ color: 'var(--brand-primary)' }}>EricHuang</span>
</span>
<div className="w-px h-2 bg-slate-300 dark:bg-slate-700" />
<span className="text-slate-400 dark:text-slate-500 uppercase tracking-widest">
Expand All @@ -27,7 +27,7 @@ const Footer: React.FC = () => {
href={GITHUB_URL}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-slate-500 dark:text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors text-[10px] font-bold"
className="flex items-center gap-1.5 text-slate-500 dark:text-slate-400 hover-brand transition-colors text-[10px] font-bold"
>
<Github className="w-3 h-3" />
<span>GitHub</span>
Expand Down
15 changes: 7 additions & 8 deletions components/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,13 @@ const MarkdownEditor: React.FC = () => {
/>
</div>

{/* Resizable Divider */}
<div
onMouseDown={startResizing}
className="w-1.5 h-full cursor-col-resize hover:bg-indigo-500/30 active:bg-indigo-500/50 transition-colors z-10 flex items-center justify-center group"
>
<div className="w-0.5 h-8 bg-slate-300 dark:bg-slate-700 group-hover:bg-indigo-500 rounded-full transition-colors" />
</div>

{/* Resizable Divider */}
<div
onMouseDown={startResizing}
className="w-1.5 h-full cursor-col-resize resizer-product transition-colors z-10 flex items-center justify-center group"
>
<div className="w-0.5 h-8 bg-slate-300 dark:bg-slate-700 group-hover:bg-product rounded-full transition-colors" />
</div>
<div style={{ width: `${100 - splitPercent}%` }} className="flex flex-col h-full">
<PreviewPane
parsedBlocks={parsedBlocks}
Expand Down
13 changes: 10 additions & 3 deletions components/editor/EditorHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,17 @@ export const EditorHeader: React.FC = () => {
return (
<header className="bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 px-8 py-4 flex justify-between items-center z-20 shadow-sm transition-colors">
<div className="flex items-center gap-4">
<div className="bg-slate-900 dark:bg-indigo-600 p-1 rounded-xl">
<div
className="p-1 rounded-xl"
style={{ backgroundColor: 'var(--product-primary)' }}
>
<img src={logoPath} alt="Logo" className="w-9 h-9" />
</div>
<div>
<h1 className="text-xl font-black text-slate-900 dark:text-white tracking-tight">
<h1
className="text-xl font-black tracking-tight"
style={{ color: 'var(--product-primary)' }}
>
{t('title')}
</h1>
<p className="text-[10px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-widest">{t('subtitle')}</p>
Expand All @@ -55,7 +61,8 @@ export const EditorHeader: React.FC = () => {
<IconButton
onClick={() => setIsAIModalOpen(true)}
title={t('aiPrompt')}
className="bg-transparent border-none hover:bg-white dark:hover:bg-slate-700 shadow-none text-indigo-600 dark:text-indigo-400"
className="bg-transparent border-none hover:bg-white dark:hover:bg-slate-700 shadow-none"
style={{ color: 'var(--product-primary)' }}
>
<Bot className="w-4 h-4" />
</IconButton>
Expand Down
3 changes: 2 additions & 1 deletion components/editor/EditorPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ export const EditorPane: React.FC<EditorPaneProps> = ({
<textarea
ref={textareaRef}
onScroll={onScroll}
className="flex-1 w-full p-10 resize-none focus:outline-none text-base leading-[1.8] text-slate-700 dark:text-slate-300 bg-transparent selection:bg-indigo-100 dark:selection:bg-indigo-900"
className="flex-1 w-full p-10 resize-none focus:outline-none text-base leading-[1.8] text-slate-700 dark:text-slate-300 bg-transparent selection-product"

style={{ fontFamily: UI_THEME.FONTS.PREVIEW }}
value={content}
onChange={handleChange}
Expand Down
17 changes: 10 additions & 7 deletions components/editor/PreviewRenderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ export const PreviewBlock: React.FC<{ block: ParsedBlock; showLineNumbers?: bool
case BlockType.HEADING_1:
return <h1 className="text-4xl font-black mb-12 mt-16 pb-4 border-b-4 border-slate-900 tracking-tight leading-tight"><RenderRichText text={block.content} /></h1>;
case BlockType.HEADING_2:
return <h2 className="text-2xl font-black mb-8 mt-12 tracking-tight flex items-center gap-3 before:w-2 before:h-8 before:bg-indigo-600"><RenderRichText text={block.content} /></h2>;
return <h2 className="text-2xl font-black mb-8 mt-12 tracking-tight flex items-center gap-3 before:w-2 before:h-8 before-bg-product"><RenderRichText text={block.content} /></h2>;
case BlockType.HEADING_3:
return <h3 className="text-xl font-bold mb-6 mt-10 text-slate-800 underline decoration-indigo-200 underline-offset-8 decoration-4"><RenderRichText text={block.content} /></h3>;
return <h3 className="text-xl font-bold mb-6 mt-10 text-slate-800 dark:text-slate-200 underline decoration-product underline-offset-8 decoration-4"><RenderRichText text={block.content} /></h3>;
case BlockType.CODE_BLOCK:
const codeLines = block.content.split('\n');
return (
Expand Down Expand Up @@ -138,14 +138,17 @@ export const PreviewBlock: React.FC<{ block: ParsedBlock; showLineNumbers?: bool
<div className={`
${isCenter ? 'max-w-[90%]' : 'max-w-[85%]'}
border-2 p-6 relative
${isRight ? 'border-dashed border-slate-900 bg-white text-right' :
isCenter ? 'border-double border-indigo-400 bg-indigo-50/30 text-center' :
'border-dotted border-slate-900 bg-slate-100 text-left'}
${isRight ? 'border-dashed border-slate-900 dark:border-slate-400 bg-white dark:bg-slate-900 text-right' :
isCenter ? 'border-double border-product bg-product-glow text-center' :
'border-dotted border-slate-900 dark:border-slate-400 bg-slate-100 dark:bg-slate-800 text-left'}
`}>
<div class={`absolute -top-3 ${isRight ? 'left-4' : isCenter ? 'left-1/2 -translate-x-1/2' : 'right-4'} bg-inherit px-2 text-[10px] font-black tracking-widest text-indigo-600 border border-slate-200 uppercase`}>
<div
class={`absolute -top-3 ${isRight ? 'left-4' : isCenter ? 'left-1/2 -translate-x-1/2' : 'right-4'} bg-inherit px-2 text-[10px] font-black tracking-widest border border-slate-200 dark:border-slate-700 uppercase`}
style={{ color: 'var(--product-primary)' }}
>
{block.role}
</div>
<div className="whitespace-pre-wrap leading-[1.8] text-slate-900"><RenderRichText text={block.content} /></div>
<div className="whitespace-pre-wrap leading-[1.8] text-slate-900 dark:text-slate-100"><RenderRichText text={block.content} /></div>
</div>
</div>
);
Expand Down
42 changes: 25 additions & 17 deletions components/editor/slash-command/SlashCommandMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,25 +106,33 @@ export const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({
const Icon = item.icon;

return (
<button
key={item.id}
ref={isSelected ? selectedItemRef : null}
className={`w-full flex items-center gap-3 px-3 py-2 text-left text-sm rounded-md transition-colors
${isSelected
? 'bg-indigo-50 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300'
: 'text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50'
}
<button
key={command.id}
onClick={() => {
onSelect(command.id);
}}
className={`
w-full flex items-center gap-3 px-3 py-2 text-sm text-left transition-all
${idx === selectedIndex
? 'bg-product-glow'
: 'hover:bg-slate-50 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-300'}
`}
style={idx === selectedIndex ? { color: 'var(--product-primary)' } : {}}
>
<div
className={`
p-1.5 rounded-lg border transition-colors
${idx === selectedIndex
? 'bg-white dark:bg-slate-900'
: 'bg-slate-100 dark:bg-slate-800 border-slate-200 dark:border-slate-700'}
`}
onClick={() => onSelect(item)}
style={idx === selectedIndex ? { borderColor: 'var(--product-primary)' } : {}}
>
<div className={`flex items-center justify-center w-8 h-8 rounded border
${isSelected
? 'bg-white dark:bg-indigo-900/50 border-indigo-200 dark:border-indigo-800 text-indigo-600 dark:text-indigo-400'
: 'bg-slate-50 dark:bg-slate-800 border-slate-200 dark:border-slate-700 text-slate-500 dark:text-slate-400'
}`}
>
<Icon size={16} />
</div>
{React.cloneElement(command.icon as React.ReactElement, {
className: "w-4 h-4",
style: idx === selectedIndex ? { color: 'var(--product-primary)' } : {}
})}
</div>
<div className="flex flex-col flex-1 min-w-0">
<span className="font-medium truncate">{item.label}</span>
{item.description && (
Expand Down
14 changes: 12 additions & 2 deletions components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,29 @@ export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
isLoading,
disabled,
style,
...props
}) => {
const baseStyles = "flex items-center justify-center gap-2 px-6 py-2.5 font-bold rounded-xl transition-all shadow-md active:scale-95 disabled:active:scale-100 disabled:cursor-not-allowed";

const variants = {
primary: "bg-slate-900 dark:bg-indigo-600 hover:bg-slate-800 dark:hover:bg-indigo-500 text-white disabled:bg-slate-300 dark:disabled:bg-slate-700",
primary: "text-white disabled:bg-slate-300 dark:disabled:bg-slate-700",
secondary: "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700",
danger: "bg-red-500 text-white hover:bg-red-600"
};

const primaryStyles = variant === 'primary' ? {
backgroundColor: 'var(--product-primary)',
boxShadow: '0 4px 12px var(--product-glow)',
} : {};

// Handle hover for primary via CSS or just keep it simple if we can't easily do hover in inline styles without a library
// Better yet, I can add a small CSS block in index.html for .btn-primary-product

return (
<button
className={`${baseStyles} ${variants[variant]} ${className}`}
className={`${baseStyles} ${variants[variant]} ${variant === 'primary' ? 'btn-product-primary' : ''} ${className}`}
style={{ ...primaryStyles, ...style }}
disabled={disabled || isLoading}
{...props}
>
Expand Down
11 changes: 9 additions & 2 deletions components/ui/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ export const IconButton: React.FC<IconButtonProps> = ({
children,
className = '',
active,
style,
...props
}) => {
const baseStyles = "p-2 rounded-lg border transition-all flex items-center justify-center";
const defaultStyles = "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-slate-700 hover:bg-slate-200 dark:hover:bg-slate-700";
const activeStyles = "bg-indigo-100 dark:bg-indigo-900/50 text-indigo-600 dark:text-indigo-400 border-indigo-200 dark:border-indigo-800";

const activeStyles = {
backgroundColor: 'var(--product-glow)',
color: 'var(--product-primary)',
borderColor: 'var(--product-primary)',
};

return (
<button
className={`${baseStyles} ${active ? activeStyles : defaultStyles} ${className}`}
className={`${baseStyles} ${active ? '' : defaultStyles} ${className}`}
style={active ? { ...activeStyles, ...style } : style}
{...props}
>
{children}
Expand Down
93 changes: 93 additions & 0 deletions globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* --- 設計系統核心變數 (Amber Anthracite v2.0 + Scholarly Blue) --- */
:root {
--font-main: "Consolas", "Microsoft JhengHei", "微軟正黑體", sans-serif;

/* Master Brand: Amber Anthracite v2.0 */
--bg-base: #FCF9F5;
--text-high: #2D2926;
--brand-primary: #A86434; /* Amber */

/* MD2DOC Product Layer: Scholarly Blue */
--product-primary: #3E5C76;
--product-hover: #527491;
--product-glow: rgba(62, 92, 118, 0.12);
}

[data-theme='dark'] {
--bg-base: #1A1816;
--text-high: #E0E0E0;
--brand-primary: #EFC69B; /* Soft Amber */

--product-primary: #6D9DC5;
--product-hover: #8AB3D6;
--product-glow: rgba(109, 157, 197, 0.2);
}

/* --- 基礎頁面樣式 --- */
body {
font-family: var(--font-main);
background-color: var(--bg-base);
color: var(--text-high);
margin: 0;
transition: background-color 0.3s ease, color 0.3s ease;
}

.manuscript-font {
font-family: var(--font-main);
}

code, pre, .font-mono {
font-family: "Consolas", monospace !important;
}

/* --- 自定義捲軸樣式 --- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg-base); }
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
[data-theme='dark'] ::-webkit-scrollbar-thumb {
background: #334155;
}
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }

/* --- 產品功能性 Helper Classes --- */

/* 按鈕主色調 */
.btn-product-primary {
background-color: var(--product-primary);
transition: all 0.2s ease;
}

.btn-product-primary:hover:not(:disabled) {
background-color: var(--product-hover);
transform: translateY(-1px);
box-shadow: 0 6px 16px var(--product-glow);
}

/* 文字選取顏色 */
.selection-product::selection {
background-color: var(--product-glow);
}

/* 預覽區 H2 裝飾線 */
.before-bg-product::before {
content: "";
display: block;
background-color: var(--product-primary) !important;
}

/* 分隔線調整工具 */
.resizer-product:hover {
background-color: var(--product-glow);
}

.resizer-product:active {
background-color: rgba(109, 157, 197, 0.4);
}

/* 品牌簽名 Hover */
.hover-brand:hover {
color: var(--brand-primary) !important;
}
42 changes: 13 additions & 29 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,37 +64,21 @@
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class'
darkMode: 'class',
theme: {
extend: {
colors: {
'brand-primary': 'var(--brand-primary)',
'product-primary': 'var(--product-primary)',
'product-hover': 'var(--product-hover)',
'product-glow': 'var(--product-glow)',
'bg-base': 'var(--bg-base)',
'text-high': 'var(--text-high)',
}
}
}
}
</script>
<style>
/* 核心排版策略:英數強制 Consolas,中文微軟正黑體 */
:root {
--font-main: "Consolas", "Microsoft JhengHei", "微軟正黑體", sans-serif;
}

body {
font-family: var(--font-main);
background-color: #f1f5f9;
margin: 0;
color: #1e293b;
}

.manuscript-font {
font-family: var(--font-main);
}

/* 程式碼塊強制等寬 */
code, pre, .font-mono {
font-family: "Consolas", monospace !important;
}

/* 自定義捲軸 */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: #f8fafc; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/buffer/6.0.3/buffer.min.js"></script>
<script>
// 關鍵:將 Buffer 掛載到全局,docx 庫才能運作
Expand Down
1 change: 1 addition & 0 deletions index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './globals.css';
import './services/i18n'; // Initialize i18n

const container = document.getElementById('root');
Expand Down