refactor: 重新设计Popup页面,简洁现代美观

- 采用Clean Modernity设计哲学
- 优化视觉层次和间距
- 使用indigo/violet渐变主题色
- 简化模式切换按钮
- 美化复选框和下拉选择框
- 添加平滑动画效果
- 自定义滚动条样式
- 更新docs/popup-design-philosophy.md

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ddshi 2026-01-23 15:47:38 +08:00
parent f83795eeb2
commit 6045cbe6ad
6 changed files with 282 additions and 128 deletions

View File

@ -0,0 +1,15 @@
# Design Philosophy: Clean Modernity
## 视觉哲学宣言
**简约而不简单**。现代设计的本质不是添加,而是减法。每一个元素的存在都应有其意义,每一处空白都是设计的呼吸。去除一切不必要的装饰,让功能本身成为美学。
**清晰的视觉层次**。用户视线应自然流动,从最重要的元素流向次要元素。通过尺寸、颜色、间距和对比度建立层次,无需多余标注。信息自明,用户无需思考。
**克制的色彩运用**。以单色或双色调为主,点缀恰到好处的强调色。色彩用于引导注意力和表达状态,而非装饰。柔和的灰度背景承载内容,明亮的强调色突出行动。
**微妙的圆角与阴影**。圆润的边角传递友好与亲和,恰到好处的阴影创造深度与层次。避免生硬的直角和夸张的投影,追求自然的光影效果。
**精致的细节把控**。即使在16像素的图标中每一个像素都经过考量。按钮的点击区域、字体的行高、元素的对齐——这些看不见的细节决定了整体的品质感。博物馆级的工艺标准意味着卓越。
**留白的艺术**。空间本身就是一种元素。足够的留白让内容得以呼吸,让界面看起来从容不迫。拥挤的界面传达焦虑,宽敞的界面传递自信。

View File

@ -11,3 +11,55 @@ body {
padding: 0;
width: 320px;
}
/* 动画 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-fade-in {
animation: fadeIn 0.2s ease-out;
}
.animate-scale-in {
animation: scaleIn 0.2s ease-out;
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
.dark ::-webkit-scrollbar-thumb {
background: #4b5563;
}
.dark ::-webkit-scrollbar-thumb:hover {
background: #6b7280;
}

View File

@ -68,7 +68,6 @@ export function Popup() {
});
await copyToClipboard(markdown);
showStatus('success', '已复制到剪贴板!');
// 1秒后关闭弹窗
setTimeout(() => {
window.close();
}, 1000);
@ -89,8 +88,6 @@ export function Popup() {
const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
// 使用chrome.downloads API
const filename = `${extractedContent.title.replace(/[<>:"/\\|?*]/g, '_').substring(0, 100)}.md`;
await chrome.downloads.download({
url: url,
@ -100,7 +97,6 @@ export function Popup() {
URL.revokeObjectURL(url);
showStatus('success', '开始下载...');
// 1秒后关闭弹窗
setTimeout(() => {
window.close();
}, 1000);
@ -141,51 +137,74 @@ export function Popup() {
if (settingsLoading) {
return (
<div className="w-80 p-4 flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<div className="w-80 h-64 flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-2 border-indigo-500 border-t-transparent"></div>
</div>
);
}
return (
<div className="w-80 p-4 bg-white dark:bg-gray-900">
{/* 标题 */}
<div className="flex items-center gap-2 mb-4">
<svg className="w-8 h-8 text-green-500" viewBox="0 0 24 24" fill="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm-1 2l5 5h-5V4zM6 20V4h6v6h6v10H6z"/>
<path d="M8 12h8v2H8zm0 4h8v2H8z"/>
</svg>
<h1 className="text-xl font-bold text-gray-900 dark:text-white">ReadMD</h1>
<div className="w-80 bg-gray-50 dark:bg-gray-900">
{/* 头部 */}
<div className="px-5 py-5 bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center shadow-lg shadow-indigo-500/30">
<svg className="w-5 h-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
<path d="M14 2v6h6"/>
<path d="M8 13h8"/>
<path d="M8 17h8"/>
</svg>
</div>
<div>
<h1 className="text-lg font-semibold text-gray-900 dark:text-white">ReadMD</h1>
<p className="text-xs text-gray-500 dark:text-gray-400"> Markdown</p>
</div>
</div>
</div>
{/* 提取按钮 */}
<ExtractButton
loading={status.type === 'loading'}
onExtract={handleExtract}
mode={mode}
onModeChange={setMode}
/>
{/* 主要内容区 */}
<div className="p-5 space-y-4">
{/* 状态消息 */}
{status.message && (
<div className={`p-3 rounded-lg text-sm font-medium transition-all duration-300 ${
status.type === 'success' ? 'bg-emerald-50 text-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-400' :
status.type === 'error' ? 'bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400' :
status.type === 'loading' ? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400' :
'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
}`}>
{status.message}
</div>
)}
{/* 状态消息 */}
{status.message && (
<div className={`mt-3 p-2 rounded-lg text-sm ${
status.type === 'success' ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' :
status.type === 'error' ? 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400' :
status.type === 'loading' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400' :
'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300'
}`}>
{status.message}
</div>
)}
{/* 提取按钮 */}
<ExtractButton
loading={status.type === 'loading'}
onExtract={handleExtract}
mode={mode}
onModeChange={setMode}
/>
{/* 分隔线 */}
<hr className="my-4 border-gray-200 dark:border-gray-700" />
{/* 选项面板 */}
<OptionsPanel
settings={settings}
onSettingsChange={saveSettings}
/>
</div>
{/* 选项面板 */}
<OptionsPanel
settings={settings}
onSettingsChange={saveSettings}
/>
{/* 页脚 */}
<div className="px-5 py-4 bg-white dark:bg-gray-800 border-t border-gray-100 dark:border-gray-700">
<button
onClick={() => chrome.runtime.openOptionsPage()}
className="w-full py-2.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors flex items-center justify-center gap-2"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 2.0 1 83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</button>
</div>
{/* 预览弹窗 */}
{showPreview && extractedContent && (
@ -196,20 +215,6 @@ export function Popup() {
onDownload={handleDownload}
/>
)}
{/* 页脚 */}
<div className="mt-4 text-center text-xs text-gray-500 dark:text-gray-400">
<a
href="#"
onClick={(e) => {
e.preventDefault();
chrome.runtime.openOptionsPage();
}}
className="hover:text-gray-700 dark:hover:text-gray-200"
>
</a>
</div>
</div>
);
}

View File

@ -9,23 +9,23 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
return (
<div className="space-y-3">
{/* 提取模式切换 */}
<div className="flex gap-2 p-2 bg-gray-100 dark:bg-gray-800 rounded-lg">
<div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-800/50 rounded-lg">
<button
onClick={() => onModeChange('auto')}
className={`flex-1 py-2 px-3 text-sm rounded-md transition-colors ${
className={`flex-1 py-2 text-sm font-medium rounded-md transition-all duration-200 ${
mode === 'auto'
? 'bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
</button>
<button
onClick={() => onModeChange('selection')}
className={`flex-1 py-2 px-3 text-sm rounded-md transition-colors ${
className={`flex-1 py-2 text-sm font-medium rounded-md transition-all duration-200 ${
mode === 'selection'
? 'bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
@ -36,10 +36,10 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
<button
onClick={onExtract}
disabled={loading}
className={`w-full py-3 px-4 rounded-lg font-medium text-white transition-all ${
className={`w-full py-3 px-4 rounded-xl font-medium text-white transition-all duration-200 ${
loading
? 'bg-gray-400 cursor-not-allowed'
: 'bg-green-500 hover:bg-green-600 active:scale-95'
? 'bg-gray-300 dark:bg-gray-600 cursor-not-allowed'
: 'bg-gradient-to-r from-indigo-500 to-violet-600 hover:from-indigo-600 hover:to-violet-700 shadow-lg shadow-indigo-500/30 hover:shadow-indigo-500/40 active:scale-[0.98]'
}`}
>
{loading ? (
@ -51,7 +51,12 @@ export function ExtractButton({ loading, onExtract, mode, onModeChange }: Extrac
...
</span>
) : (
'提取并导出Markdown'
<span className="flex items-center justify-center gap-2">
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
Markdown
</span>
)}
</button>
</div>

View File

@ -7,52 +7,104 @@ interface OptionsPanelProps {
export function OptionsPanel({ settings, onSettingsChange }: OptionsPanelProps) {
return (
<div className="space-y-3">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300"></h4>
<div className="space-y-4">
<h4 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider"></h4>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={settings.includeTitle}
onChange={(e) => onSettingsChange({ includeTitle: e.target.checked })}
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500"
/>
<span className="text-sm text-gray-700 dark:text-gray-300"></span>
</label>
<div className="space-y-3">
{/* 包含标题 */}
<label className="flex items-center gap-3 cursor-pointer group">
<div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
settings.includeTitle
? 'border-indigo-500 bg-indigo-500'
: 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
}`}>
{settings.includeTitle && (
<svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<path d="M5 13l4 4L19 7"/>
</svg>
)}
</div>
<input
type="checkbox"
checked={settings.includeTitle}
onChange={(e) => onSettingsChange({ includeTitle: e.target.checked })}
className="hidden"
/>
<span className="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white transition-colors">
</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={settings.includeUrl}
onChange={(e) => onSettingsChange({ includeUrl: e.target.checked })}
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500"
/>
<span className="text-sm text-gray-700 dark:text-gray-300"></span>
</label>
{/* 包含原文链接 */}
<label className="flex items-center gap-3 cursor-pointer group">
<div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
settings.includeUrl
? 'border-indigo-500 bg-indigo-500'
: 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
}`}>
{settings.includeUrl && (
<svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<path d="M5 13l4 4L19 7"/>
</svg>
)}
</div>
<input
type="checkbox"
checked={settings.includeUrl}
onChange={(e) => onSettingsChange({ includeUrl: e.target.checked })}
className="hidden"
/>
<span className="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white transition-colors">
</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={settings.simplifyMode}
onChange={(e) => onSettingsChange({ simplifyMode: e.target.checked })}
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500"
/>
<span className="text-sm text-gray-700 dark:text-gray-300">广</span>
</label>
{/* 简化模式 */}
<label className="flex items-center gap-3 cursor-pointer group">
<div className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all duration-200 ${
settings.simplifyMode
? 'border-indigo-500 bg-indigo-500'
: 'border-gray-300 dark:border-gray-600 group-hover:border-indigo-400'
}`}>
{settings.simplifyMode && (
<svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<path d="M5 13l4 4L19 7"/>
</svg>
)}
</div>
<input
type="checkbox"
checked={settings.simplifyMode}
onChange={(e) => onSettingsChange({ simplifyMode: e.target.checked })}
className="hidden"
/>
<span className="text-sm text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white transition-colors">
</span>
</label>
</div>
{/* 图片处理 */}
<div className="pt-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
<label className="block text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
</label>
<select
value={settings.imageMode}
onChange={(e) => onSettingsChange({ imageMode: e.target.value as UserSettings['imageMode'] })}
className="w-full py-2 px-3 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="url">URL链接</option>
<option value="base64">Base64</option>
<option value="download"></option>
</select>
<div className="relative">
<select
value={settings.imageMode}
onChange={(e) => onSettingsChange({ imageMode: e.target.value as UserSettings['imageMode'] })}
className="w-full py-2.5 px-3 text-sm appearance-none bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-gray-900 dark:text-white focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all duration-200 cursor-pointer"
>
<option value="url"> URL </option>
<option value="base64"> Base64</option>
<option value="download"></option>
</select>
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-gray-400">
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M19 9l-7 7-7-7"/>
</svg>
</div>
</div>
</div>
</div>
);

View File

@ -20,72 +20,97 @@ export function PreviewModal({ content, onClose, onCopy, onDownload }: PreviewMo
});
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-full max-w-lg mx-4 max-h-[80vh] flex flex-col">
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4 animate-fade-in">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-lg overflow-hidden animate-scale-in">
{/* 标题栏 */}
<div className="flex items-center justify-between p-4 border-b dark:border-gray-700">
<h3 className="font-semibold text-gray-900 dark:text-white"></h3>
<div className="flex items-center justify-between px-5 py-4 border-b border-gray-100 dark:border-gray-700">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center">
<svg className="w-4 h-4 text-indigo-600 dark:text-indigo-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
</div>
<div>
<h3 className="font-semibold text-gray-900 dark:text-white"></h3>
<p className="text-xs text-gray-500 dark:text-gray-400 truncate max-w-[200px]">{content.title}</p>
</div>
</div>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
className="w-8 h-8 rounded-lg text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all flex items-center justify-center"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
{/* 内容预览 */}
<div className="flex-1 overflow-auto p-4">
<div className="text-sm text-gray-600 dark:text-gray-400 mb-2">{content.title}</div>
<div className="p-5 max-h-72 overflow-auto">
{/* 切换按钮 */}
<div className="flex gap-2 mb-3">
<div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-700/50 rounded-lg mb-4">
<button
onClick={() => setShowRaw(false)}
className={`px-3 py-1 text-xs rounded ${!showRaw ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700'}`}
className={`flex-1 py-2 text-xs font-medium rounded-md transition-all duration-200 ${
!showRaw
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
</button>
<button
onClick={() => setShowRaw(true)}
className={`px-3 py-1 text-xs rounded ${showRaw ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700'}`}
className={`flex-1 py-2 text-xs font-medium rounded-md transition-all duration-200 ${
showRaw
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Markdown
</button>
</div>
{showRaw ? (
<pre className="text-xs bg-gray-100 dark:bg-gray-800 p-3 rounded overflow-auto max-h-64 whitespace-pre-wrap">
<pre className="text-xs bg-gray-50 dark:bg-gray-900 p-4 rounded-xl overflow-auto max-h-56 whitespace-pre-wrap font-mono text-gray-700 dark:text-gray-300">
{markdown}
</pre>
) : (
<div
className="prose prose-sm max-w-none dark:prose-invert overflow-auto max-h-64"
className="prose prose-sm max-w-none dark:prose-invert overflow-auto max-h-56 text-gray-600 dark:text-gray-400"
dangerouslySetInnerHTML={{
__html: content.markdown
.replace(/^# .+$/m, '') // 移除标题
.replace(/> .+$/gm, '') // 移除引用
.replace(/```[\s\S]*?```/g, '[代码块]') // 简化代码块
.replace(/\n/g, '<br/>') // 换行
.replace(/^# .+$/m, '')
.replace(/> .+$/gm, '')
.replace(/```[\s\S]*?```/g, '[代码块]')
.replace(/\n/g, '<br/>')
}}
/>
)}
</div>
{/* 操作按钮 */}
<div className="p-4 border-t dark:border-gray-700 flex gap-2">
<div className="px-5 py-4 bg-gray-50 dark:bg-gray-800/50 border-t border-gray-100 dark:border-gray-700 flex gap-3">
<button
onClick={onCopy}
className="flex-1 py-2 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg text-sm font-medium transition-colors"
className="flex-1 py-2.5 px-4 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 rounded-xl text-sm font-medium border border-gray-200 dark:border-gray-600 transition-all duration-200 flex items-center justify-center gap-2"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
</button>
<button
onClick={onDownload}
className="flex-1 py-2 px-4 bg-green-500 hover:bg-green-600 text-white rounded-lg text-sm font-medium transition-colors"
className="flex-1 py-2.5 px-4 bg-gradient-to-r from-indigo-500 to-violet-600 hover:from-indigo-600 hover:to-violet-700 text-white rounded-xl text-sm font-medium shadow-lg shadow-indigo-500/25 transition-all duration-200 flex items-center justify-center gap-2"
>
MD文件
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
</button>
</div>
</div>