Compare commits
7 commits
1cf002b8b8
...
350f6f3865
Author | SHA1 | Date | |
---|---|---|---|
|
350f6f3865 | ||
|
c6db8689b4 | ||
|
1da8ae56c4 | ||
|
d5f81c0f81 | ||
|
7d3987f124 | ||
|
afd5f125a7 | ||
|
ed0caf5727 |
8 changed files with 291 additions and 46 deletions
|
@ -1,16 +1,24 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
|
||||
import mdx from '@astrojs/mdx';
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://terminal-blog.example.com',
|
||||
base: '/',
|
||||
trailingSlash: 'ignore',
|
||||
|
||||
build: {
|
||||
format: 'directory'
|
||||
},
|
||||
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: 'nord',
|
||||
wrap: true
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
integrations: [sitemap(), mdx()]
|
||||
});
|
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.2.6",
|
||||
"@astrojs/rss": "^4.0.1",
|
||||
"@astrojs/sitemap": "^3.3.1",
|
||||
"astro": "^5.2.5",
|
||||
"fuse.js": "^7.0.0",
|
||||
"ultrahtml": "^1.6.0"
|
||||
|
|
51
pnpm-lock.yaml
generated
51
pnpm-lock.yaml
generated
|
@ -14,6 +14,9 @@ importers:
|
|||
'@astrojs/rss':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.11
|
||||
'@astrojs/sitemap':
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
astro:
|
||||
specifier: ^5.2.5
|
||||
version: 5.7.10(@types/node@22.15.3)(rollup@4.40.1)(typescript@5.8.3)
|
||||
|
@ -52,6 +55,9 @@ packages:
|
|||
'@astrojs/rss@4.0.11':
|
||||
resolution: {integrity: sha512-3e3H8i6kc97KGnn9iaZBJpIkdoQi8MmR5zH5R+dWsfCM44lLTszOqy1OBfGGxDt56mpQkYVtZJWoxMyWuUZBfw==}
|
||||
|
||||
'@astrojs/sitemap@3.3.1':
|
||||
resolution: {integrity: sha512-GRnDUCTviBSNfXJ0Jmur+1/C+z3g36jy79VyYggfe1uNyEYSTcmAfTTCmbytrRvJRNyJJnSfB/77Gnm9PiXRRg==}
|
||||
|
||||
'@astrojs/telemetry@3.2.1':
|
||||
resolution: {integrity: sha512-SSVM820Jqc6wjsn7qYfV9qfeQvePtVc1nSofhyap7l0/iakUKywj3hfy3UJAOV4sGV4Q/u450RD4AaCaFvNPlg==}
|
||||
engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0}
|
||||
|
@ -500,9 +506,15 @@ packages:
|
|||
'@types/nlcst@2.0.3':
|
||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||
|
||||
'@types/node@17.0.45':
|
||||
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
||||
|
||||
'@types/node@22.15.3':
|
||||
resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==}
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
|
||||
|
||||
'@types/unist@2.0.11':
|
||||
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
||||
|
||||
|
@ -541,6 +553,9 @@ packages:
|
|||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
|
@ -1289,6 +1304,9 @@ packages:
|
|||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
sax@1.4.1:
|
||||
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
|
||||
|
||||
semver@7.7.1:
|
||||
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -1307,6 +1325,11 @@ packages:
|
|||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
sitemap@8.0.0:
|
||||
resolution: {integrity: sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==}
|
||||
engines: {node: '>=14.0.0', npm: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
smol-toml@1.3.4:
|
||||
resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==}
|
||||
engines: {node: '>= 18'}
|
||||
|
@ -1322,6 +1345,9 @@ packages:
|
|||
space-separated-tokens@2.0.2:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
stream-replace-string@2.0.0:
|
||||
resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -1678,6 +1704,12 @@ snapshots:
|
|||
fast-xml-parser: 4.5.3
|
||||
kleur: 4.1.5
|
||||
|
||||
'@astrojs/sitemap@3.3.1':
|
||||
dependencies:
|
||||
sitemap: 8.0.0
|
||||
stream-replace-string: 2.0.0
|
||||
zod: 3.24.3
|
||||
|
||||
'@astrojs/telemetry@3.2.1':
|
||||
dependencies:
|
||||
ci-info: 4.2.0
|
||||
|
@ -2031,10 +2063,16 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@17.0.45': {}
|
||||
|
||||
'@types/node@22.15.3':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
dependencies:
|
||||
'@types/node': 22.15.3
|
||||
|
||||
'@types/unist@2.0.11': {}
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
@ -2062,6 +2100,8 @@ snapshots:
|
|||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-query@5.3.2: {}
|
||||
|
@ -3343,6 +3383,8 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc': 4.40.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
sax@1.4.1: {}
|
||||
|
||||
semver@7.7.1: {}
|
||||
|
||||
sharp@0.33.5:
|
||||
|
@ -3390,6 +3432,13 @@ snapshots:
|
|||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
sitemap@8.0.0:
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
'@types/sax': 1.2.7
|
||||
arg: 5.0.2
|
||||
sax: 1.4.1
|
||||
|
||||
smol-toml@1.3.4: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
@ -3398,6 +3447,8 @@ snapshots:
|
|||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
stream-replace-string@2.0.0: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
|
|
27
src/components/BackToTop.astro
Normal file
27
src/components/BackToTop.astro
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
|
||||
---
|
||||
<button id="toTopBtn" title="Go to top">Top</button>
|
||||
<script>
|
||||
// Get the button
|
||||
let toTopButton = document.getElementById("toTopBtn");
|
||||
|
||||
// When the user scrolls down from the top of the document, show the button
|
||||
window.addEventListener("scroll", ()=> {
|
||||
if (window.scrollY < document.documentElement.clientHeight) {
|
||||
toTopButton.classList.remove('fade-in');
|
||||
toTopButton.classList.add('fade-out');
|
||||
toTopButton.style.display = 'block';
|
||||
} else {
|
||||
toTopButton.classList.remove('fade-out');
|
||||
toTopButton.classList.remove('fade-in');
|
||||
}
|
||||
})
|
||||
|
||||
// When the user clicks on the button, scroll to the top of the document
|
||||
toTopButton.addEventListener("click", toTop);
|
||||
function toTop() {
|
||||
document.body.scrollTop = 0;
|
||||
document.documentElement.scrollTop = 0;
|
||||
}
|
||||
</script>
|
|
@ -1,22 +1,119 @@
|
|||
---
|
||||
---
|
||||
<button class="theme-switcher" id="theme-switcher">
|
||||
Toggle Theme
|
||||
</button>
|
||||
<div class="theme-dropdown" id="theme-dropdown">
|
||||
<button class="theme-switcher" id="theme-switcher">Theme</button>
|
||||
<div class="menu-body" id="menu-body">
|
||||
<div class="dropdown-item" data-theme="auto">System</div>
|
||||
<div class="dropdown-item" data-theme="dark">Dark</div>
|
||||
<div class="dropdown-item" data-theme="light">Light</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.theme-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.menu-body {
|
||||
display: none;
|
||||
position: fixed;
|
||||
right: 45px;
|
||||
bottom: 70px;
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
min-width: 140px;
|
||||
z-index: 20;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
|
||||
.dropdown-item.active {
|
||||
color: var(--accent-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.theme-dropdown:hover .menu-body {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const themeSwitcher = document.getElementById('theme-switcher');
|
||||
// Constants for theme options
|
||||
const THEME_AUTO = 'auto';
|
||||
const THEME_DARK = 'dark';
|
||||
const THEME_LIGHT = 'light';
|
||||
|
||||
// DOM elements
|
||||
const themeDropdown = document.getElementById('theme-dropdown');
|
||||
const menuBody = document.getElementById('menu-body');
|
||||
const dropdownItems = document.querySelectorAll('.dropdown-item');
|
||||
const root = document.documentElement;
|
||||
|
||||
// Check for saved theme preference or default to dark
|
||||
const savedTheme = localStorage.getItem('theme') || 'dark';
|
||||
root.setAttribute('data-theme', savedTheme);
|
||||
|
||||
themeSwitcher?.addEventListener('click', () => {
|
||||
const currentTheme = root.getAttribute('data-theme');
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
|
||||
root.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
|
||||
// Get system preference
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const systemTheme = prefersDark ? THEME_DARK : THEME_LIGHT;
|
||||
|
||||
// Get saved theme from localStorage or default to auto
|
||||
const savedThemeMode = localStorage.getItem('themeMode') || THEME_AUTO;
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
|
||||
// Function to update active state in dropdown menu
|
||||
function updateActiveState(activeTheme) {
|
||||
dropdownItems.forEach(item => {
|
||||
if (item.getAttribute('data-theme') === activeTheme) {
|
||||
item.classList.add('active');
|
||||
} else {
|
||||
item.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to apply theme
|
||||
function applyTheme(themeMode) {
|
||||
// Update active state in dropdown
|
||||
updateActiveState(themeMode);
|
||||
|
||||
// Save theme mode preference
|
||||
localStorage.setItem('themeMode', themeMode);
|
||||
|
||||
// Apply the appropriate theme
|
||||
if (themeMode === THEME_AUTO) {
|
||||
// Follow system
|
||||
localStorage.removeItem('theme');
|
||||
root.removeAttribute('data-theme');
|
||||
} else {
|
||||
// Set to specific theme
|
||||
root.setAttribute('data-theme', themeMode);
|
||||
localStorage.setItem('theme', themeMode);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme
|
||||
applyTheme(savedThemeMode);
|
||||
|
||||
// Add click handlers to dropdown items
|
||||
dropdownItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const selectedTheme = item.getAttribute('data-theme');
|
||||
applyTheme(selectedTheme);
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for system preference changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
if (savedThemeMode === THEME_AUTO) {
|
||||
// Only update if we're in auto mode
|
||||
root.removeAttribute('data-theme');
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -2,6 +2,7 @@
|
|||
import '../styles/global.css';
|
||||
import Search from '../components/Search.astro';
|
||||
import ThemeSwitcher from '../components/ThemeSwitcher.astro';
|
||||
import BackToTop from "../components/BackToTop.astro";
|
||||
import { siteConfig } from "../config";
|
||||
|
||||
interface Props {
|
||||
|
@ -26,7 +27,6 @@ const { title, path = formattedPath } = Astro.props;
|
|||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<ThemeSwitcher />
|
||||
<main>
|
||||
<div class="container">
|
||||
<div class="terminal-path">
|
||||
|
@ -47,6 +47,10 @@ const { title, path = formattedPath } = Astro.props;
|
|||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="floating">
|
||||
<BackToTop/>
|
||||
<ThemeSwitcher/>
|
||||
</div>
|
||||
<div class="container">
|
||||
Powered by <a href="https://git.gb0.dev/gb/mercury"><img src="/mercury.svg" width="16px" alt="mercury logo" /> mercury</a>
|
||||
</div>
|
||||
|
|
|
@ -25,16 +25,7 @@ posts.sort((a, b) => new Date(b.data.pubDate).getTime() - new Date(a.data.pubDat
|
|||
{posts.length === 0 && (
|
||||
<>
|
||||
<p>
|
||||
<span style="color: var(--terminal-yellow);">2025-06-08</span>
|
||||
<a href="/blog/terminal-setup">My Terminal Setup</a>
|
||||
</p>
|
||||
<p>
|
||||
<span style="color: var(--terminal-yellow);">2025-06-05</span>
|
||||
<a href="/blog/minimalism">The Art of Minimalism</a>
|
||||
</p>
|
||||
<p>
|
||||
<span style="color: var(--terminal-yellow);">2025-06-01</span>
|
||||
<a href="/blog/first-post">First Post</a>
|
||||
<span style="color: var(--terminal-yellow);">No posts here yet</span>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -11,7 +11,30 @@
|
|||
--terminal-red: #ef4444;
|
||||
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* Light theme */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--bg-color: #f3f4f6;
|
||||
--text-color: #374151;
|
||||
--accent-color: #3b82f6;
|
||||
--border-color: #d1d5db;
|
||||
--header-color: #1f2937;
|
||||
--terminal-green: #059669;
|
||||
--terminal-yellow: #d97706;
|
||||
--terminal-red: #dc2626;
|
||||
}
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg-color: #1f2937;
|
||||
--text-color: #a5b4cf;
|
||||
--accent-color: #64a0ff;
|
||||
--border-color: #3b4351;
|
||||
--header-color: #83a2ce;
|
||||
--terminal-green: #4ade80;
|
||||
--terminal-yellow: #fbbf24;
|
||||
--terminal-red: #ef4444;
|
||||
}
|
||||
}
|
||||
/* Light theme override (for switch) */
|
||||
:root[data-theme="light"] {
|
||||
--bg-color: #f3f4f6;
|
||||
--text-color: #374151;
|
||||
|
@ -116,6 +139,55 @@ main {
|
|||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.footer img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.footer .floating {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 30px;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
|
||||
#toTopBtn {
|
||||
display: none;
|
||||
background: var(--border-color);
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem 1rem;
|
||||
font-family: var(--font-mono);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#toTopBtn:hover {
|
||||
background: var(--accent-color);
|
||||
color: var(--bg-color);
|
||||
}
|
||||
/* Theme Switcher */
|
||||
.theme-switcher {
|
||||
background: var(--border-color);
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem 1rem;
|
||||
font-family: var(--font-mono);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.theme-switcher:hover {
|
||||
background: var(--accent-color);
|
||||
color: var(--bg-color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Post styles */
|
||||
.post-title {
|
||||
color: var(--header-color);
|
||||
|
@ -146,24 +218,18 @@ main {
|
|||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Theme Switcher */
|
||||
.theme-switcher {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: var(--border-color);
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
font-family: var(--font-mono);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
/* helper Class */
|
||||
.fade-in {
|
||||
opacity: 1;
|
||||
transition-property: opacity;
|
||||
transition-duration: .7s;
|
||||
transition-timing-function: cubic-bezier(.4,0,1,1);
|
||||
}
|
||||
|
||||
.theme-switcher:hover {
|
||||
background: var(--accent-color);
|
||||
color: var(--bg-color);
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
transition-property: opacity;
|
||||
transition-duration: .7s;
|
||||
transition-timing-function: cubic-bezier(.4,0,1,1);
|
||||
}
|
||||
|
||||
/* Media Queries */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue