Commit 515af0dc authored by fisherdaddy's avatar fisherdaddy

feature: 新增 URL 解码器 & 样式优化 & 支持多语言

parent 6b538bc4
...@@ -5,6 +5,7 @@ import JsonFormatter from './components/JsonFormatter'; ...@@ -5,6 +5,7 @@ import JsonFormatter from './components/JsonFormatter';
import Header from './components/Header'; import Header from './components/Header';
import Footer from './components/Footer'; import Footer from './components/Footer';
import TextToImage from './components/TextToImage'; import TextToImage from './components/TextToImage';
import UrlDecode from './components/UrlDecode';
function App(op) { function App(op) {
return ( return (
...@@ -16,6 +17,7 @@ function App(op) { ...@@ -16,6 +17,7 @@ function App(op) {
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/text2image" element={<TextToImage />} /> <Route path="/text2image" element={<TextToImage />} />
<Route path="/json-formatter" element={<JsonFormatter />} /> <Route path="/json-formatter" element={<JsonFormatter />} />
<Route path="/url-decode" element={<UrlDecode />} />
</Routes> </Routes>
</main> </main>
</div> </div>
......
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Title, Wrapper, Container, Preview } from './SharedStyles';
import { useTranslation } from '../js/i18n';
const Container = styled.div` const InputText = styled.textarea`
padding: 20px; width: 100%;
max-width: 1200px; height: 200px;
margin: 0 auto; font-size: 14px;
font-family: 'Arial', sans-serif; padding: 10px;
`; border: none;
border-bottom: 1px solid #e0e0e0;
const Title = styled.h2` box-sizing: border-box;
color: #333; outline: none;
margin-bottom: 20px; resize: none;
`;
const FlexContainer = styled.div` @media (min-width: 768px) {
display: flex; width: 35%;
gap: 20px; height: 100%;
border-bottom: none;
border-right: 1px solid #e0e0e0;
}
`; `;
const TextArea = styled.textarea` const PreviewContainer = styled.div`
width: 100%; width: 100%;
height: 400px; display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px; padding: 10px;
border: 1px solid #ddd; box-sizing: border-box;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
`;
const JsonContainer = styled.div` @media (min-width: 768px) {
flex: 1; width: 65%;
overflow: auto; height: 100%;
height: 400px; }
background-color: #f8f8f8;
padding: 10px;
border-radius: 4px;
`; `;
const ToggleButton = styled.span` const ToggleButton = styled.span`
...@@ -58,9 +57,43 @@ const JsonList = styled.ul` ...@@ -58,9 +57,43 @@ const JsonList = styled.ul`
margin: 0; margin: 0;
`; `;
const CopyButton = styled.button`
position: absolute;
top: 10px;
right: 10px;
background-color: transparent;
border: none;
cursor: pointer;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.3s, color 0.3s;
&:hover {
opacity: 1;
}
svg {
width: 16px;
height: 16px;
}
&.copied {
color: #34a853; // Google green color for success feedback
}
`;
const RelativePreviewContainer = styled(PreviewContainer)`
position: relative;
`;
function JsonFormatter() { function JsonFormatter() {
const { t } = useTranslation();
const [input, setInput] = useState(''); const [input, setInput] = useState('');
const [parsedJson, setParsedJson] = useState(null); const [parsedJson, setParsedJson] = useState(null);
const [isCopied, setIsCopied] = useState(false);
useEffect(() => { useEffect(() => {
try { try {
...@@ -71,26 +104,49 @@ function JsonFormatter() { ...@@ -71,26 +104,49 @@ function JsonFormatter() {
} }
}, [input]); }, [input]);
const handleCopy = () => {
if (parsedJson) {
const formattedJson = JSON.stringify(parsedJson, null, 2);
navigator.clipboard.writeText(formattedJson).then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
});
}
};
return ( return (
<Wrapper>
<Title>{t('tools.jsonFormatter.title')}</Title>
<Container> <Container>
<Title>JSON格式化工具</Title> <InputText
<FlexContainer> placeholder={t('tools.jsonFormatter.inputPlaceholder')}
<div style={{ flex: 1 }}>
<TextArea
value={input} value={input}
onChange={(e) => setInput(e.target.value)} onChange={(e) => setInput(e.target.value)}
placeholder="输入JSON数据"
/> />
</div> <RelativePreviewContainer>
<JsonContainer>
{parsedJson ? ( {parsedJson ? (
<>
<Preview>
<JsonView data={parsedJson} /> <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>
) : (
<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>
)}
</CopyButton>
</>
) : ( ) : (
<pre>Invalid JSON</pre> <Preview>{t('tools.jsonFormatter.invalidJson')}</Preview>
)} )}
</JsonContainer> </RelativePreviewContainer>
</FlexContainer>
</Container> </Container>
</Wrapper>
); );
} }
...@@ -106,11 +162,14 @@ function JsonView({ data }) { ...@@ -106,11 +162,14 @@ function JsonView({ data }) {
{!isExpanded && <span>Array</span>} {!isExpanded && <span>Array</span>}
{isExpanded && ( {isExpanded && (
<JsonList> <JsonList>
[
{data.map((item, index) => ( {data.map((item, index) => (
<li key={index}> <li key={index}>
<JsonView data={item} /> <JsonView data={item} />
{index < data.length - 1 && ','}
</li> </li>
))} ))}
]
</JsonList> </JsonList>
)} )}
</div> </div>
...@@ -124,11 +183,14 @@ function JsonView({ data }) { ...@@ -124,11 +183,14 @@ function JsonView({ data }) {
{!isExpanded && <span>Object</span>} {!isExpanded && <span>Object</span>}
{isExpanded && ( {isExpanded && (
<JsonList> <JsonList>
{Object.entries(data).map(([key, value]) => ( {'{'}
{Object.entries(data).map(([key, value], index, array) => (
<li key={key}> <li key={key}>
<Key>"{key}"</Key>: <JsonView data={value} /> <Key>"{key}"</Key>: <JsonView data={value} />
{index < array.length - 1 && ','}
</li> </li>
))} ))}
{'}'}
</JsonList> </JsonList>
)} )}
</div> </div>
......
...@@ -13,6 +13,8 @@ function LanguageSelector() { ...@@ -13,6 +13,8 @@ function LanguageSelector() {
<select id="lang-select" value={lang} onChange={handleLanguageChange}> <select id="lang-select" value={lang} onChange={handleLanguageChange}>
<option value="zh">中文</option> <option value="zh">中文</option>
<option value="en">English</option> <option value="en">English</option>
<option value="ja">日本語</option>
<option value="ko">한국어</option>
</select> </select>
</div> </div>
); );
......
import styled from 'styled-components';
export const Title = styled.h1`
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
font-size: 28px;
font-weight: 500;
color: #1a73e8; // Google Blue
text-align: center;
margin-bottom: 24px;
letter-spacing: -0.5px;
position: relative;
padding-bottom: 12px;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 3px;
background: linear-gradient(90deg, #4285f4, #34a853, #fbbc05, #ea4335); // Google colors
border-radius: 2px;
}
`;
export const Wrapper = styled.div`
width: 100%;
max-width: 2000px;
margin: 10px auto;
`;
export const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin: 10px auto;
@media (min-width: 768px) {
flex-direction: row;
height: 70vh;
}
`;
export const InputText = styled.textarea`
width: 100%;
height: 200px;
font-size: 14px;
padding: 10px;
border: none;
border-bottom: 1px solid #e0e0e0;
box-sizing: border-box;
outline: none;
resize: none;
@media (min-width: 768px) {
width: 50%;
height: 100%;
border-bottom: none;
border-right: 1px solid #e0e0e0;
}
`;
export const PreviewContainer = styled.div`
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px;
box-sizing: border-box;
@media (min-width: 768px) {
width: 50%;
height: 100%;
}
`;
export const Preview = styled.div`
word-wrap: break-word;
white-space: pre-wrap;
max-width: 100%;
text-align: left;
overflow-y: auto;
flex-grow: 1;
padding-right: 10px;
font-size: 14px;
max-height: 200px;
@media (min-width: 768px) {
max-height: none;
}
h1, h2, h3, h4 {
color: #2c3e50;
margin-top: 0.5em;
margin-bottom: 0.3em;
line-height: 1.2;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: #555;
}
`;
\ No newline at end of file
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Title, Wrapper, Container, InputText, PreviewContainer, Preview } from './SharedStyles';
const Wrapper = styled.div` import { useTranslation } from '../js/i18n';
width: 100%;
max-width: 1000px;
margin: 10px auto;
`;
const Title = styled.h1`
font-size: 20px;
color: #333;
margin-bottom: 10px;
text-align: center;
`;
const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin: 10px auto;
@media (min-width: 768px) {
flex-direction: row;
height: 70vh;
}
`;
const InputText = styled.textarea`
width: 100%;
height: 200px;
font-size: 14px;
padding: 10px;
border: none;
border-bottom: 1px solid #e0e0e0;
box-sizing: border-box;
outline: none;
resize: none;
@media (min-width: 768px) {
width: 50%;
height: 100%;
border-bottom: none;
border-right: 1px solid #e0e0e0;
}
`;
const PreviewContainer = styled.div`
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px;
box-sizing: border-box;
@media (min-width: 768px) {
width: 50%;
height: 100%;
}
`;
const Preview = styled.div`
word-wrap: break-word;
white-space: pre-wrap;
max-width: 100%;
text-align: left;
overflow-y: auto;
flex-grow: 1;
padding-right: 10px;
font-size: 14px;
max-height: 200px;
@media (min-width: 768px) {
max-height: none;
}
h1, h2, h3 {
color: #2c3e50;
margin-top: 0;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: #555;
}
`;
const DownloadButton = styled.button` const DownloadButton = styled.button`
padding: 8px 16px; padding: 8px 16px;
...@@ -119,15 +21,17 @@ const DownloadButton = styled.button` ...@@ -119,15 +21,17 @@ const DownloadButton = styled.button`
`; `;
function TextToImage() { function TextToImage() {
const { t } = useTranslation();
const [text, setText] = useState(''); const [text, setText] = useState('');
const previewRef = useRef(null); const previewRef = useRef(null);
const formatText = (text) => { const formatText = (text) => {
return text return text
.replace(/^### (.*$)/gim, '<h3>$1</h3>') .replace(/^### (.*$)/gim, '<h4>$1</h4>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>') .replace(/^## (.*$)/gim, '<h3>$1</h3>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>') .replace(/^# (.*$)/gim, '<h2>$1</h2>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\n{2,}/g, '<br/><br/>')
.replace(/\n/g, '<br/>'); .replace(/\n/g, '<br/>');
}; };
...@@ -165,10 +69,10 @@ function TextToImage() { ...@@ -165,10 +69,10 @@ function TextToImage() {
return ( return (
<Wrapper> <Wrapper>
<Title>文字卡片生成器</Title> <Title>{t('tools.text2image.title')}</Title>
<Container> <Container>
<InputText <InputText
placeholder="输入文本(可包含标题,如# 标题1)" placeholder={t('tools.text2image.inputPlaceholder')}
value={text} value={text}
onChange={(e) => setText(e.target.value)} onChange={(e) => setText(e.target.value)}
/> />
...@@ -177,7 +81,9 @@ function TextToImage() { ...@@ -177,7 +81,9 @@ function TextToImage() {
ref={previewRef} ref={previewRef}
dangerouslySetInnerHTML={{ __html: formatText(text) }} dangerouslySetInnerHTML={{ __html: formatText(text) }}
/> />
<DownloadButton onClick={handleDownload}>导出为图片</DownloadButton> <DownloadButton onClick={handleDownload}>
{t('tools.text2image.downloadButton')}
</DownloadButton>
</PreviewContainer> </PreviewContainer>
</Container> </Container>
</Wrapper> </Wrapper>
......
import React, { useState, useCallback } from 'react';
import { Title, Wrapper, Container, InputText, Preview } from './SharedStyles';
import styled from 'styled-components';
import { useTranslation } from '../js/i18n';
const DecoderContainer = styled(Container)`
flex-direction: column;
`;
const StyledInputText = styled(InputText)`
height: 100px;
margin-bottom: 20px;
@media (min-width: 768px) {
height: 100px;
width: 100%;
}
`;
const PreviewWrapper = styled.div`
width: 100%;
`;
const Label = styled.label`
font-weight: 500;
font-size: 14px;
color: #5f6368;
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 ResultContainer = styled.div`
position: relative;
width: 100%;
`;
const CopyButton = styled.button`
position: absolute;
top: 8px;
right: 8px;
background-color: transparent;
border: none;
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.3s, color 0.3s;
&:hover {
opacity: 1;
}
svg {
width: 16px;
height: 16px;
}
&.copied {
color: #34a853; // Google green color for success feedback
}
`;
function UrlDecoder() {
const { t } = useTranslation();
const [input, setInput] = useState('');
const [decodedText, setDecodedText] = useState('');
const [isCopied, setIsCopied] = useState(false);
const handleInputChange = (e) => {
const inputValue = e.target.value;
setInput(inputValue);
try {
const decoded = decodeURIComponent(inputValue);
setDecodedText(decoded);
} catch (error) {
setDecodedText('Invalid URL encoding');
}
};
const handleCopy = useCallback(() => {
navigator.clipboard.writeText(decodedText).then(() => {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
});
}, [decodedText]);
return (
<Wrapper>
<Title>{t('tools.urlDecode.title')}</Title>
<DecoderContainer>
<StyledInputText
id="urlInput"
placeholder={t('tools.urlDecode.inputLabel')}
value={input}
onChange={handleInputChange}
/>
<PreviewWrapper>
<Label>{t('tools.urlDecode.resultLabel')}</Label>
<ResultContainer>
<StyledPreview>{decodedText}</StyledPreview>
<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>
) : (
<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>
)}
</CopyButton>
</ResultContainer>
</PreviewWrapper>
</DecoderContainer>
</Wrapper>
);
}
export default UrlDecoder;
\ No newline at end of file
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
const i18n = { const i18n = {
en: {
title: 'AI Toolbox',
slogan: 'Your collection of intelligent assistants, solving various AI needs in one place.',
tools: {
text2image: {
title: 'Text to Image Card',
description: 'Convert text to image card',
inputPlaceholder: 'Enter text (can include titles, e.g. # Title 1)',
downloadButton: 'Export as Image'
},
jsonFormatter: {
title: 'JSON Formatter',
description: 'Beautify and validate JSON data',
inputPlaceholder: 'Enter JSON data',
invalidJson: 'Invalid JSON',
copyButton: 'Copy',
copiedMessage: 'Copied'
},
urlDecode: {
title: 'URL Decoder',
description: 'Decode URL-encoded strings',
inputLabel: 'Enter URL to decode',
resultLabel: 'Decoded result',
copyButton: 'Copy',
copiedMessage: 'Copied'
},
},
},
zh: { zh: {
title: 'AI 工具箱', title: 'AI 工具箱',
slogan: '您的智能助手集合,一站式解决各种 AI 需求。', slogan: '您的智能助手集合,一站式解决各种 AI 需求。',
tools: { tools: {
text2image: { text2image: {
title: '文字卡片', title: '文字卡片',
description: '将文字转换为图片卡' description: '将文字转换为图片卡',
inputPlaceholder: '输入文本(可包含标题,如# 标题1)',
downloadButton: '导出为图片'
}, },
jsonFormatter: { jsonFormatter: {
title: 'JSON 格式化', title: 'JSON 格式化',
description: '美化和验证 JSON 数据' description: '美化和验证 JSON 数据',
inputPlaceholder: '输入 JSON 数据',
invalidJson: '无效的 JSON',
copyButton: '复制',
copiedMessage: '已复制'
}, },
// 添加更多工具... urlDecode: {
title: 'URL 解码器',
description: '解码 URL 编码的字符串',
inputLabel: '输入需要解码的 URL',
resultLabel: '解码结果',
copyButton: '复制',
copiedMessage: '已复制'
}, },
// 添加更多翻译...
}, },
en: { },
title: 'AI Toolbox', ja: {
slogan: 'Your collection of intelligent assistants, solving various AI needs in one place.', title: 'AIツールボックス',
slogan: 'あなたのインテリジェントアシスタントコレクション、様々なAIニーズを一箇所で解決します。',
tools: { tools: {
text2image: { text2image: {
title: 'Text to Image Card', title: 'テキストから画像',
description: 'Convert text to image card' description: 'テキストを画像カードに変換',
inputPlaceholder: 'テキストを入力(タイトルを含めることができます、例:# タイトル1)',
downloadButton: '画像としてエクスポート'
}, },
jsonFormatter: { jsonFormatter: {
title: 'JSON Formatter', title: 'JSONフォーマッター',
description: 'Beautify and validate JSON data' description: 'JSONデータを整形し検証する',
inputPlaceholder: 'JSONデータを入力',
invalidJson: '無効なJSON',
copyButton: 'コピー',
copiedMessage: 'コピーしました'
},
urlDecode: {
title: 'URLデコーダー',
description: 'URLエンコードされた文字列をデコード',
inputLabel: 'デコードするURLを入力',
resultLabel: 'デコード結果',
copyButton: 'コピー',
copiedMessage: 'コピーしました'
},
},
},
ko: {
title: 'AI 도구 상자',
slogan: '당신의 지능형 어시스턴트 컬렉션, 다양한 AI 요구 사항을 한 곳에서 해결합니다.',
tools: {
text2image: {
title: '텍스트를 이미지로',
description: '텍스트를 이미지 카드로 변환',
inputPlaceholder: '텍스트 입력 (제목 포함 가능, 예: # 제목 1)',
downloadButton: '이미지로 내보내기'
},
jsonFormatter: {
title: 'JSON 포맷터',
description: 'JSON 데이터 정리 및 검증',
inputPlaceholder: 'JSON 데이터 입력',
invalidJson: '유효하지 않은 JSON',
copyButton: '복사',
copiedMessage: '복사됨'
},
urlDecode: {
title: 'URL 디코더',
description: 'URL 인코딩된 문자열 디코딩',
inputLabel: '디코딩할 URL 입력',
resultLabel: '디코딩 결과',
copyButton: '복사',
copiedMessage: '복사됨'
}, },
// 添加更多工具...
}, },
// 添加更多翻译...
}, },
// 添加更多语言...
}; };
let currentLanguage = localStorage.getItem('language') || 'zh'; // 从本地存储获取语言设置,默认为中文 let currentLanguage = localStorage.getItem('language') || 'en';
let listeners = []; let listeners = [];
export function setLanguage(lang) { export function setLanguage(lang) {
if (i18n[lang]) { if (i18n[lang]) {
currentLanguage = lang; currentLanguage = lang;
localStorage.setItem('language', lang); // 将语言设置保存到本地存储 localStorage.setItem('language', lang);
listeners.forEach(listener => listener(currentLanguage)); listeners.forEach(listener => listener(currentLanguage));
} }
} }
...@@ -56,7 +135,7 @@ export function t(key) { ...@@ -56,7 +135,7 @@ export function t(key) {
let value = i18n[currentLanguage]; let value = i18n[currentLanguage];
for (const k of keys) { for (const k of keys) {
if (value[k] === undefined) { if (value[k] === undefined) {
return key; // 如果翻译不存在,返回原始 key return key;
} }
value = value[k]; value = value[k];
} }
......
...@@ -6,7 +6,7 @@ import { useTranslation } from '../js/i18n'; ...@@ -6,7 +6,7 @@ import { useTranslation } from '../js/i18n';
const tools = [ const tools = [
{ id: 'text2image', icon: 'fa-image', path: '/text2image' }, { id: 'text2image', icon: 'fa-image', path: '/text2image' },
{ id: 'jsonFormatter', icon: 'fa-code', path: '/json-formatter' }, { id: 'jsonFormatter', icon: 'fa-code', path: '/json-formatter' },
{ id: 'textTranslation', icon: 'fa-language', path: '/text-translation' }, { id: 'urlDecode', icon: 'fa-decode', path: '/url-decode' },
]; ];
function Home() { function Home() {
......
...@@ -26,8 +26,8 @@ body { ...@@ -26,8 +26,8 @@ body {
.content-wrapper { .content-wrapper {
flex: 1; flex: 1;
padding-top: 2px; padding-top: 10px;
padding-bottom: 30px; padding-bottom: 20px;
} }
header { header {
...@@ -56,7 +56,7 @@ main { ...@@ -56,7 +56,7 @@ main {
max-width: 1200px; max-width: 1200px;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 6rem 1rem 4rem; padding: 3rem 1rem 1rem;
} }
.hero { .hero {
...@@ -88,35 +88,6 @@ h1 { ...@@ -88,35 +88,6 @@ h1 {
margin: 0 auto; margin: 0 auto;
} }
#tool-search {
width: 100%;
padding: 1.2rem 1.5rem;
font-size: 1.2rem;
border: none;
border-radius: 12px;
background-color: rgba(255, 255, 255, 0.2);
color: white;
transition: all 0.3s ease;
}
#tool-search::placeholder {
color: rgba(255, 255, 255, 0.7);
}
#tool-search:focus {
outline: none;
background-color: rgba(255, 255, 255, 0.3);
}
.search-container i {
position: absolute;
right: 1.5rem;
top: 50%;
transform: translateY(-50%);
color: rgba(255, 255, 255, 0.7);
font-size: 1.2rem;
}
.tools-section h2 { .tools-section h2 {
text-align: center; text-align: center;
font-size: 3rem; font-size: 3rem;
...@@ -167,7 +138,6 @@ footer { ...@@ -167,7 +138,6 @@ footer {
background-color: var(--card-background); background-color: var(--card-background);
color: #86868b; color: #86868b;
text-align: center; text-align: center;
padding: 1.5rem;
font-size: 0.9rem; font-size: 0.9rem;
} }
...@@ -184,3 +154,4 @@ footer { ...@@ -184,3 +154,4 @@ footer {
font-size: 1.5rem; font-size: 1.5rem;
} }
} }
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