Commit 90edd27f authored by fisherdaddy's avatar fisherdaddy

chore: 更新样式

parent f6603ad5
node_modules/
package-lock.json
dist/
src/.DS_Store
!robots.txt
\ No newline at end of file
src/.DS_Store
\ No newline at end of file
......@@ -73,12 +73,12 @@ function Header() {
<NavLink to="/image-tools" onClick={handleNavClick}>
{t('image-tools')}
</NavLink>
<NavLink to="/blog" onClick={handleNavClick}>
{t('blog')}
</NavLink>
<NavLink to="/ai-products" onClick={handleNavClick}>
{t('ai-products')}
</NavLink>
<NavLink to="/blog" onClick={handleNavClick}>
{t('blog')}
</NavLink>
</div>
<div className="right-container">
<LanguageSelector />
......
This diff is collapsed.
......@@ -7,19 +7,26 @@ import SEO from '../components/SEO';
const InputText = styled.textarea`
width: 100%;
height: 200px;
font-size: 14px;
padding: 10px;
border: none;
border-bottom: 1px solid #e0e0e0;
font-size: 15px;
padding: 16px;
border: 1px solid rgba(99, 102, 241, 0.1);
border-radius: 12px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
box-sizing: border-box;
outline: none;
resize: none;
transition: all 0.3s ease;
line-height: 1.5;
&:focus {
border-color: rgba(99, 102, 241, 0.3);
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
}
@media (min-width: 768px) {
width: 35%;
width: 40%;
height: 100%;
border-bottom: none;
border-right: 1px solid #e0e0e0;
}
`;
......@@ -27,67 +34,103 @@ const PreviewContainer = styled.div`
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 12px;
border: 1px solid rgba(99, 102, 241, 0.1);
padding: 16px;
box-sizing: border-box;
@media (min-width: 768px) {
width: 65%;
width: 58%;
height: 100%;
}
`;
const ToggleButton = styled.span`
cursor: pointer;
color: #666;
font-weight: bold;
margin-right: 5px;
const ButtonGroup = styled.div`
display: flex;
gap: 8px;
position: absolute;
top: 12px;
right: 12px;
`;
const Key = styled.span`
color: #881391;
`;
const ActionButton = styled.button`
background: rgba(99, 102, 241, 0.1);
border: none;
border-radius: 6px;
padding: 6px 12px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #6366F1;
transition: all 0.3s ease;
const Value = styled.span`
color: #1a1aa6;
&:hover {
background: rgba(99, 102, 241, 0.2);
}
&.active {
background: #6366F1;
color: white;
}
svg {
width: 14px;
height: 14px;
}
`;
const JsonList = styled.ul`
list-style-type: none;
padding-left: 20px;
margin: 0;
const RelativePreviewContainer = styled(PreviewContainer)`
position: relative;
`;
const CopyButton = styled.button`
position: absolute;
top: 10px;
right: 10px;
background-color: transparent;
const ToggleButton = styled.button`
background: none;
border: none;
cursor: pointer;
padding: 5px;
display: flex;
color: #6366F1;
font-size: 13px;
padding: 2px 6px;
margin-right: 6px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.3s, color 0.3s;
transition: all 0.2s ease;
&:hover {
opacity: 1;
background: rgba(99, 102, 241, 0.1);
}
svg {
width: 16px;
height: 16px;
}
`;
&.copied {
color: #34a853; // Google green color for success feedback
}
const JsonList = styled.ul`
list-style-type: none;
padding-left: 24px;
margin: 0;
font-size: 15px;
line-height: 1.6;
`;
const RelativePreviewContainer = styled(PreviewContainer)`
position: relative;
const Key = styled.span`
color: #6366F1;
font-weight: 500;
font-size: 15px;
`;
const Value = styled.span`
color: #374151;
font-size: 15px;
&:not(:last-child) {
margin-right: 4px;
}
`;
function JsonFormatter() {
......@@ -95,6 +138,7 @@ function JsonFormatter() {
const [input, setInput] = useState('');
const [parsedJson, setParsedJson] = useState(null);
const [isCopied, setIsCopied] = useState(false);
const [isCompressed, setIsCompressed] = useState(false);
useEffect(() => {
try {
......@@ -107,7 +151,9 @@ function JsonFormatter() {
const handleCopy = () => {
if (parsedJson) {
const formattedJson = JSON.stringify(parsedJson, null, 2);
const formattedJson = isCompressed
? JSON.stringify(parsedJson)
: JSON.stringify(parsedJson, null, 2);
navigator.clipboard.writeText(formattedJson).then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
......@@ -115,9 +161,13 @@ function JsonFormatter() {
}
};
const toggleCompression = () => {
setIsCompressed(!isCompressed);
};
return (
<>
<SEO
<SEO
title={t('tools.jsonFormatter.title')}
description={t('tools.jsonFormatter.description')}
/>
......@@ -133,19 +183,43 @@ function JsonFormatter() {
{parsedJson ? (
<>
<Preview>
<JsonView data={parsedJson} />
</Preview>
<CopyButton onClick={handleCopy} className={isCopied ? 'copied' : ''}>
{isCopied ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
{isCompressed ? (
<pre style={{ margin: 0, whiteSpace: 'nowrap', overflowX: 'auto' }}>
{JSON.stringify(parsedJson)}
</pre>
) : (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
<JsonView data={parsedJson} />
)}
</CopyButton>
</Preview>
<ButtonGroup>
<ActionButton
onClick={toggleCompression}
className={isCompressed ? 'active' : ''}
>
{isCompressed ? (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M4 9h16v2H4V9zm0 4h16v2H4v-2z"/>
</svg>
) : (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 13H5v-2h14v2z"/>
</svg>
)}
{isCompressed ? '展开' : '压缩'}
</ActionButton>
<ActionButton onClick={handleCopy} className={isCopied ? 'active' : ''}>
{isCopied ? (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
) : (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
)}
{isCopied ? '已复制' : '复制'}
</ActionButton>
</ButtonGroup>
</>
) : (
<Preview>{t('tools.jsonFormatter.invalidJson')}</Preview>
......@@ -164,10 +238,19 @@ function JsonView({ data }) {
return (
<div>
<ToggleButton onClick={() => setIsExpanded(!isExpanded)}>
{isExpanded ? '[-]' : '[+]'}
{isExpanded ? (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 13H5v-2h14v2z"/>
</svg>
) : (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
)}
</ToggleButton>
{!isExpanded && <span>Array</span>}
{isExpanded && (
{!isExpanded ? (
<span style={{ color: '#6B7280', fontSize: '15px' }}>Array [{data.length}]</span>
) : (
<JsonList>
[
{data.map((item, index) => (
......@@ -182,19 +265,29 @@ function JsonView({ data }) {
</div>
);
} else if (typeof data === 'object' && data !== null) {
const entries = Object.entries(data);
return (
<div>
<ToggleButton onClick={() => setIsExpanded(!isExpanded)}>
{isExpanded ? '{-}' : '{+}'}
{isExpanded ? (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 13H5v-2h14v2z"/>
</svg>
) : (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
)}
</ToggleButton>
{!isExpanded && <span>Object</span>}
{isExpanded && (
{!isExpanded ? (
<span style={{ color: '#6B7280', fontSize: '15px' }}>Object {`{${entries.length}}`}</span>
) : (
<JsonList>
{'{'}
{Object.entries(data).map(([key, value], index, array) => (
{entries.map(([key, value], index) => (
<li key={key}>
<Key>"{key}"</Key>: <JsonView data={value} />
{index < array.length - 1 && ','}
{index < entries.length - 1 && ','}
</li>
))}
{'}'}
......
......@@ -4,26 +4,25 @@ import events from '../data/openai-releases.json';
import SEO from '../components/SEO';
import { useTranslation } from '../js/i18n';
const timeline = () => {
const Timeline = () => {
const { t } = useTranslation();
return (
<>
<SEO
<SEO
title={t('tools.openAITimeline.title')}
description={t('tools.openAITimeline.description')}
/>
<div className="container">
<div className="timeline-title">OpenAI 产品发布时间线</div>
<h1 className="timeline-title">{t('tools.openAITimeline.title')}</h1>
<ul className="timeline">
{events.map((item, index) => (
<li className="event" key={index}>
<div className="event-content">
<div className="event-date">{item.date}</div>
<div className="event-title">{item.title}</div>
<div class="event-feature">{item.feature}</div>
<div class="event-description">{item.description}</div>
<div className="event-feature">{item.feature}</div>
<div className="event-description">{item.description}</div>
</div>
</li>
))}
......@@ -33,4 +32,4 @@ const timeline = () => {
);
};
export default timeline;
export default Timeline;
......@@ -2,26 +2,28 @@
import React, { useState } from 'react';
import '../styles/PricingChart.css';
const ChartLegend = ({ onLegendClick, highlightedBarTypes }) => (
<div className="legend">
<div
className="legend-item"
onClick={() => onLegendClick('input')}
style={{ cursor: 'pointer', opacity: highlightedBarTypes.input ? 1 : 0.5 }}
>
<div className="legend-color input-color"></div>
<span>Input price</span>
</div>
<div
className="legend-item"
onClick={() => onLegendClick('output')}
style={{ cursor: 'pointer', opacity: highlightedBarTypes.output ? 1 : 0.5 }}
>
<div className="legend-color output-color"></div>
<span>Output price</span>
const ChartLegend = ({ onLegendClick, highlightedBarTypes }) => {
return (
<div className="legend">
<div
className="legend-item"
onClick={() => onLegendClick('input')}
style={{ cursor: 'pointer', opacity: highlightedBarTypes.input ? 1 : 0.5 }}
>
<div className="legend-color input-color"></div>
<span>Input Price</span>
</div>
<div
className="legend-item"
onClick={() => onLegendClick('output')}
style={{ cursor: 'pointer', opacity: highlightedBarTypes.output ? 1 : 0.5 }}
>
<div className="legend-color output-color"></div>
<span>Output Price</span>
</div>
</div>
</div>
);
);
};
const ChartBar = ({ price, type, maxPrice, highlighted }) => {
const getBarHeight = () => {
......
......@@ -3,21 +3,21 @@ import PricingChart from '../components/PricingChart';
import OpenaiPricing from '../data/openai-pricing.json';
import LLMPricing from '../data/llm-pricing.json';
import VisionPricing from '../data/vision-model-pricing.json';
import { useTranslation } from '../js/i18n';
import SEO from '../components/SEO';
const PricingCharts = () => {
const { t } = useTranslation();
const lastUpdateTime = '2024-11-06 21:30'; // 硬编码的更新时间
const lastUpdateTime = '2024-11-06 21:30';
return (
<>
<SEO
title={t('tools.modelPrice.title')}
description={t('tools.modelPrice.description')}
title="AI Model Pricing Comparison"
description="Compare prices of different AI models"
/>
<div className="pricing-charts-container">
<div className="update-time">数据最后更新时间: {lastUpdateTime}</div>
<div className="update-time">
Last Updated: {lastUpdateTime}
</div>
<PricingChart data={OpenaiPricing} />
<PricingChart data={LLMPricing} />
<PricingChart data={VisionPricing} />
......
import React, { useState, useCallback } from 'react';
import { Title, Wrapper, Container, InputText, Preview } from '../js/SharedStyles';
import { Title, Wrapper, Container, Preview } from '../js/SharedStyles';
import styled from 'styled-components';
import { useTranslation } from '../js/i18n';
import SEO from './SEO';
const EncoderDecoderContainer = styled(Container)`
flex-direction: column;
gap: 16px;
`;
const StyledInputText = styled(InputText)`
height: 100px;
margin-bottom: 20px;
@media (min-width: 768px) {
height: 100px;
width: 100%;
}
`;
const PreviewWrapper = styled.div`
const StyledInputText = styled.textarea`
width: 100%;
height: 120px;
font-size: 15px;
padding: 16px;
border: 1px solid rgba(99, 102, 241, 0.1);
border-radius: 12px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
box-sizing: border-box;
outline: none;
resize: none;
transition: all 0.3s ease;
line-height: 1.5;
&:focus {
border-color: rgba(99, 102, 241, 0.3);
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
}
`;
const Label = styled.label`
font-weight: 500;
font-size: 14px;
color: #5f6368;
color: #374151;
margin-bottom: 8px;
display: block;
letter-spacing: 0.1px;
`;
const StyledPreview = styled(Preview)`
background-color: #f8f9fa;
padding: 12px 40px 12px 12px; // 增加右侧 padding 为按钮留出空间
border-radius: 8px;
border: 1px solid #dadce0;
font-size: 14px;
color: #202124;
min-height: 24px; // 确保即使内容为空,也有足够的高度容纳按钮
const ModeSwitcher = styled.div`
margin-bottom: 8px;
select {
padding: 8px 12px;
border-radius: 8px;
border: 1px solid rgba(99, 102, 241, 0.1);
font-size: 14px;
color: #374151;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
cursor: pointer;
transition: all 0.3s ease;
&:focus {
border-color: rgba(99, 102, 241, 0.3);
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
outline: none;
}
}
`;
const ResultContainer = styled.div`
......@@ -45,43 +66,47 @@ const ResultContainer = styled.div`
width: 100%;
`;
const CopyButton = styled.button`
const StyledPreview = styled.div`
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 12px;
border: 1px solid rgba(99, 102, 241, 0.1);
padding: 16px;
font-size: 15px;
color: #374151;
min-height: 24px;
line-height: 1.5;
position: relative;
`;
const ActionButton = styled.button`
position: absolute;
top: 8px;
right: 8px;
background-color: transparent;
top: 12px;
right: 12px;
background: rgba(99, 102, 241, 0.1);
border: none;
border-radius: 6px;
padding: 6px 12px;
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.3s, color 0.3s;
gap: 6px;
font-size: 13px;
color: #6366F1;
transition: all 0.3s ease;
&:hover {
opacity: 1;
}
svg {
width: 16px;
height: 16px;
background: rgba(99, 102, 241, 0.2);
}
&.copied {
color: #34a853; // 成功复制后的反馈颜色
&.active {
background: #6366F1;
color: white;
}
`;
const ModeSwitcher = styled.div`
margin-bottom: 20px;
select {
padding: 8px;
border-radius: 4px;
border: 1px solid #dadce0;
font-size: 14px;
color: #202124;
svg {
width: 14px;
height: 14px;
}
`;
......@@ -138,29 +163,41 @@ function UrlEncoderDecoder() {
<option value="decode">{t('tools.urlEncodeDecode.decode')}</option>
</select>
</ModeSwitcher>
<StyledInputText
id="urlInput"
placeholder={mode === 'decode' ? t('tools.urlDecode.inputLabel') : t('tools.urlEncode.inputLabel')}
value={input}
onChange={handleInputChange}
/>
<PreviewWrapper>
<Label>{mode === 'decode' ? t('tools.urlDecode.resultLabel') : t('tools.urlEncode.resultLabel')}</Label>
<div>
<Label>
{mode === 'decode' ? t('tools.urlDecode.inputLabel') : t('tools.urlEncode.inputLabel')}
</Label>
<StyledInputText
value={input}
onChange={handleInputChange}
placeholder={mode === 'decode' ? t('tools.urlDecode.inputLabel') : t('tools.urlEncode.inputLabel')}
/>
</div>
<div>
<Label>
{mode === 'decode' ? t('tools.urlDecode.resultLabel') : t('tools.urlEncode.resultLabel')}
</Label>
<ResultContainer>
<StyledPreview>{resultText}</StyledPreview>
<CopyButton onClick={handleCopy} className={isCopied ? 'copied' : ''}>
<ActionButton
onClick={handleCopy}
className={isCopied ? 'active' : ''}
>
{isCopied ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
)}
</CopyButton>
{isCopied ? t('tools.jsonFormatter.copiedMessage') : t('tools.jsonFormatter.copyButton')}
</ActionButton>
</ResultContainer>
</PreviewWrapper>
</div>
</EncoderDecoderContainer>
</Wrapper>
</>
......
export default {
tools: {
modelPrice: {
title: "AI Model Pricing Comparison",
description: "Compare prices of different AI models",
lastUpdate: "Last Updated",
inputPrice: "Input Price",
outputPrice: "Output Price",
openai: {
title: "OpenAI Models Pricing",
subtitle: "Price per 1K tokens"
}
}
}
};
\ No newline at end of file
......@@ -89,7 +89,10 @@
"base64InputPlaceholder": "Paste Base64 string here",
"imageResult": "Image Result",
"invalidBase64": "Invalid Base64 string",
"download": "Download Image"
"download": "Download Image",
"dragOrClick": "Drag and drop or click to upload",
"fileName": "File name",
"fileSize": "File size"
},
"fisherai": {
"title": "FisherAI",
......
export default {
tools: {
modelPrice: {
title: "AIモデル価格比較",
description: "異なるAIモデルの価格を比較",
lastUpdate: "最終更新",
inputPrice: "入力価格",
outputPrice: "出力価格",
openai: {
title: "OpenAIモデル価格",
subtitle: "1Kトークンあたりの価格"
}
}
}
};
\ No newline at end of file
......@@ -81,15 +81,18 @@
"description": "紙に書くのと同等の効果を生成します"
},
"imageBase64Converter": {
"title": "画像とBase64コンバーター",
"description": "画像とBase64の相互変換",
"imageToBase64": "画像をBase64に変換",
"base64Result": "Base64の結果",
"base64ToImage": "Base64を画像に変換",
"base64InputPlaceholder": "ここにBase64文字列を貼り付け",
"imageResult": "画像の結果",
"invalidBase64": "無効なBase64文字列",
"download": "画像をダウンロード"
"title": "Base64 と画像の変換",
"description": "画像と Base64 の相互変換",
"imageToBase64": "画像を Base64 に変換",
"base64Result": "Base64 結果",
"base64ToImage": "Base64 を画像に変換",
"base64InputPlaceholder": "ここに Base64 文字列を貼り付けてください",
"imageResult": "画像結果",
"invalidBase64": "無効な Base64 文字列",
"download": "画像をダウンロード",
"dragOrClick": "画像をドラッグまたはクリックしてアップロード",
"fileName": "ファイル名",
"fileSize": "ファイルサイズ"
},
"fisherai": {
"title": "FisherAI",
......
export default {
tools: {
modelPrice: {
title: "AI 모델 가격 비교",
description: "다양한 AI 모델의 가격 비교",
lastUpdate: "마지막 업데이트",
inputPrice: "입력 가격",
outputPrice: "출력 가격",
openai: {
title: "OpenAI 모델 가격",
subtitle: "1K 토큰당 가격"
}
}
}
};
\ No newline at end of file
......@@ -90,7 +90,10 @@
"base64InputPlaceholder": "여기에 Base64 문자열을 붙여넣기",
"imageResult": "이미지 결과",
"invalidBase64": "잘못된 Base64 문자열",
"download": "이미지 다운로드"
"download": "이미지 다운로드",
"dragOrClick": "드래그 앤 드롭 또는 클릭하여 업로드",
"fileName": "파일 이름",
"fileSize": "파일 크기"
},
"fisherai": {
"title": "FisherAI",
......
export default {
tools: {
modelPrice: {
title: "AI模型价格对比",
description: "比较不同AI模型的价格",
lastUpdate: "数据最后更新时间",
inputPrice: "输入价格",
outputPrice: "输出价格",
openai: {
title: "OpenAI模型价格",
subtitle: "每千字符价格"
}
}
}
};
\ No newline at end of file
......@@ -88,7 +88,10 @@
"base64InputPlaceholder": "在此粘贴 Base64 字符串",
"imageResult": "图片结果",
"invalidBase64": "无效的 Base64 字符串",
"download": "下载图片"
"download": "下载图片",
"dragOrClick": "拖拽或点击上传图片",
"fileName": "文件名",
"fileSize": "文件大小"
},
"fisherai": {
"title": "FisherAI",
......
.pricing-chart {
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
padding: 2.5rem;
background: linear-gradient(145deg, rgba(255,255,255,0.05) 0%, rgba(240,242,245,0.1) 100%);
border-radius: 16px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}
.chart-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.5rem;
font-size: 1.75rem;
background: linear-gradient(90deg, #4285f4, #7c4dff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 1rem;
}
.chart-subtitle {
font-size: 1rem;
color: #666;
font-weight: normal;
margin-bottom: 1rem;
font-size: 1.1rem;
color: var(--text-secondary);
margin-bottom: 1.5rem;
}
.legend {
......@@ -26,6 +32,15 @@
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255,255,255,0.1);
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.legend-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.legend-color {
......@@ -83,7 +98,7 @@
position: absolute;
left: 0;
right: 0;
border-top: 1px solid #e0e0e0;
border-top: 1px dashed rgba(224,224,224,0.3);
}
.chart-column {
......@@ -110,15 +125,17 @@
width: 15px; /* 减小柱状图的宽度 */
position: relative;
border-radius: 4px 4px 0 0;
transition: height 0.3s ease;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: linear-gradient(180deg, rgba(66,133,244,0.9), rgba(66,133,244,0.7));
box-shadow: 0 4px 12px rgba(66,133,244,0.2);
}
.input-bar {
background-color: #4285f4;
background: linear-gradient(180deg, #4285f4, #2b579a);
}
.output-bar {
background-color: #7c4dff;
background: linear-gradient(180deg, #7c4dff, #4a2b9a);
margin-left: 5px;
}
......@@ -145,6 +162,12 @@
width: 20px; /* 缩小Logo尺寸 */
height: 20px;
object-fit: contain;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
transition: transform 0.3s ease;
}
.provider-logo:hover {
transform: scale(1.1);
}
.provider-name {
......@@ -164,14 +187,15 @@
}
.pricing-charts-container {
padding: 20px;
background-color: #f9f9f9;
padding: 2rem;
background: var(--bg-primary);
min-height: 100vh;
}
.update-time {
text-align: right;
font-size: 0.9rem;
color: #555;
margin-bottom: 15px;
font-style: italic;
background: rgba(255,255,255,0.05);
padding: 0.5rem 1rem;
border-radius: 8px;
margin-bottom: 2rem;
display: inline-block;
}
.container {
font-family: 'Helvetica Neue', Arial, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
background-color: #ffffff;
padding: 2rem;
position: relative;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.95));
min-height: 100vh;
}
.container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
linear-gradient(90deg, rgba(99, 102, 241, 0.05) 1px, transparent 1px),
linear-gradient(rgba(99, 102, 241, 0.05) 1px, transparent 1px);
background-size: 20px 20px;
pointer-events: none;
z-index: -1;
}
.timeline-title {
text-align: center;
color: #4560f5;
margin-bottom: 40px;
font-size: 60px;
font-size: 2.5rem;
margin-bottom: 3rem;
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
letter-spacing: -0.02em;
}
.timeline {
......@@ -21,40 +41,51 @@
list-style: none;
}
.timeline:before {
.timeline::before {
content: "";
position: absolute;
top: 0;
left: 50%;
width: 2px;
height: 100%;
background: #4560f5;
background: linear-gradient(180deg, #6366F1 0%, #4F46E5 100%);
transform: translateX(-50%);
}
.event {
position: relative;
margin-bottom: 50px;
margin-bottom: 3rem;
}
.event:before {
.event::before {
content: "";
position: absolute;
top: 0;
left: 50%;
width: 20px;
height: 20px;
background: #4560f5;
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
border-radius: 50%;
transform: translateX(-50%);
box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);
z-index: 2;
}
.event-content {
position: relative;
width: 45%;
padding: 15px;
background: #fff;
border-radius: 5px;
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
padding: 1.5rem;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(99, 102, 241, 0.1);
border: 1px solid rgba(99, 102, 241, 0.1);
transition: all 0.3s ease;
}
.event-content:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(99, 102, 241, 0.15);
border-color: rgba(99, 102, 241, 0.3);
}
.event:nth-child(odd) .event-content {
......@@ -62,27 +93,51 @@
}
.event:nth-child(even) .event-content {
left: 0%;
left: 0;
}
.event-date {
font-weight: bold;
color: #4560f5;
margin-bottom: 5px;
font-weight: 600;
color: #6366F1;
margin-bottom: 0.5rem;
font-size: 1.1rem;
}
.event-title {
font-weight: bold;
margin-bottom: 5px;
font-weight: 700;
margin-bottom: 0.5rem;
font-size: 1.3rem;
color: #1a1a1a;
}
.event-feature {
color: #4F46E5;
margin-bottom: 0.5rem;
font-size: 1rem;
font-weight: 500;
}
.event-description {
font-size: 0.9em;
color: #666;
margin-top: 5px;
color: #4B5563;
font-size: 0.95rem;
line-height: 1.6;
}
.event-feature {
font-style: italic;
color: #888;
margin-top: 3px;
@media (max-width: 768px) {
.timeline::before {
left: 30px;
}
.event::before {
left: 30px;
}
.event-content {
width: calc(100% - 80px);
left: 80px !important;
}
.timeline-title {
font-size: 2rem;
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment