Compare commits
4 Commits
dev
...
bf76ca99d6
| Author | SHA1 | Date | |
|---|---|---|---|
| bf76ca99d6 | |||
| 6338bc3aae | |||
| f2b47f38b4 | |||
| 8e19c2ee20 |
@@ -51,7 +51,14 @@ async function createOptimizedBuild() {
|
|||||||
'video-container',
|
'video-container',
|
||||||
'photo-gallery',
|
'photo-gallery',
|
||||||
'photo-item',
|
'photo-item',
|
||||||
'dark'
|
'dark',
|
||||||
|
// Responsive navigation classes
|
||||||
|
'desktop-nav',
|
||||||
|
'mobile-nav',
|
||||||
|
'dropdown',
|
||||||
|
// Button classes
|
||||||
|
'secondary',
|
||||||
|
'outline'
|
||||||
],
|
],
|
||||||
deep: [
|
deep: [
|
||||||
/^--pico-/,
|
/^--pico-/,
|
||||||
@@ -60,7 +67,12 @@ async function createOptimizedBuild() {
|
|||||||
/:hover/,
|
/:hover/,
|
||||||
/:focus/,
|
/:focus/,
|
||||||
/:active/,
|
/:active/,
|
||||||
/:visited/
|
/:visited/,
|
||||||
|
// Dropdown and interactive states
|
||||||
|
/\[open\]/,
|
||||||
|
/\[role=/,
|
||||||
|
/details/,
|
||||||
|
/summary/
|
||||||
],
|
],
|
||||||
// Keep base typography selectors that are essential for proper font rendering
|
// Keep base typography selectors that are essential for proper font rendering
|
||||||
greedy: [
|
greedy: [
|
||||||
@@ -78,7 +90,14 @@ async function createOptimizedBuild() {
|
|||||||
/^a$/,
|
/^a$/,
|
||||||
/^a:/, // Add anchor pseudo-classes like a:hover, a:focus
|
/^a:/, // Add anchor pseudo-classes like a:hover, a:focus
|
||||||
/^a\./, // Add anchor with classes like a.secondary
|
/^a\./, // Add anchor with classes like a.secondary
|
||||||
/^a\[/ // Add anchor with attributes like a[role=button]
|
/^a\[/, // Add anchor with attributes like a[role=button]
|
||||||
|
// Dropdown and button elements
|
||||||
|
/^details$/,
|
||||||
|
/^summary$/,
|
||||||
|
/^button$/,
|
||||||
|
/^button:/,
|
||||||
|
/^button\./,
|
||||||
|
/^button\[/
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
variables: true,
|
variables: true,
|
||||||
@@ -109,20 +128,21 @@ async function createOptimizedBuild() {
|
|||||||
})
|
})
|
||||||
]).process(combinedCSS, { from: undefined });
|
]).process(combinedCSS, { from: undefined });
|
||||||
|
|
||||||
await fs.writeFile('dist/css/styles.css', minifiedCSS.css);
|
// Combine with custom site styles
|
||||||
|
|
||||||
// Also minify custom site styles
|
|
||||||
const customCSS = await fs.readFile('public/site-styles/style.css', 'utf8');
|
const customCSS = await fs.readFile('public/site-styles/style.css', 'utf8');
|
||||||
const minifiedCustomCSS = await postcss([
|
const combinedWithCustomCSS = minifiedCSS.css + '\n' + customCSS;
|
||||||
|
|
||||||
|
// Minify the combined CSS bundle
|
||||||
|
const finalMinifiedCSS = await postcss([
|
||||||
cssnano({
|
cssnano({
|
||||||
preset: ['default', {
|
preset: ['default', {
|
||||||
discardComments: { removeAll: true },
|
discardComments: { removeAll: true },
|
||||||
normalizeWhitespace: true
|
normalizeWhitespace: true
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
]).process(customCSS, { from: undefined });
|
]).process(combinedWithCustomCSS, { from: undefined });
|
||||||
|
|
||||||
await fs.writeFile('dist/site-styles/style.css', minifiedCustomCSS.css);
|
await fs.writeFile('dist/css/bundle.css', finalMinifiedCSS.css);
|
||||||
|
|
||||||
// Step 3: JavaScript Bundling and Minification
|
// Step 3: JavaScript Bundling and Minification
|
||||||
console.log('📦 Step 3: Bundling and minifying JavaScript...');
|
console.log('📦 Step 3: Bundling and minifying JavaScript...');
|
||||||
@@ -148,9 +168,9 @@ async function createOptimizedBuild() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate compression results
|
// Calculate compression results
|
||||||
const originalCSSSize = (await fs.stat('public/css/pico.green.min.css')).size +
|
const originalCSSSize = (await fs.stat('public/css/pico.jade.min.css')).size +
|
||||||
(await fs.stat('public/css/pico.colors.min.css')).size;
|
(await fs.stat('public/css/pico.min.css')).size;
|
||||||
const optimizedCSSSize = (await fs.stat('dist/css/styles.css')).size;
|
const optimizedCSSSize = (await fs.stat('dist/css/bundle.css')).size;
|
||||||
const cssReduction = ((originalCSSSize - optimizedCSSSize) / originalCSSSize * 100).toFixed(1);
|
const cssReduction = ((originalCSSSize - optimizedCSSSize) / originalCSSSize * 100).toFixed(1);
|
||||||
|
|
||||||
const originalHTMLSize = (await fs.stat('public/index.html')).size +
|
const originalHTMLSize = (await fs.stat('public/index.html')).size +
|
||||||
@@ -238,7 +258,7 @@ async function bundleAndMinifyJS() {
|
|||||||
hoist_vars: false,
|
hoist_vars: false,
|
||||||
if_return: true,
|
if_return: true,
|
||||||
join_vars: true,
|
join_vars: true,
|
||||||
cascade: true,
|
|
||||||
side_effects: true
|
side_effects: true
|
||||||
},
|
},
|
||||||
mangle: {
|
mangle: {
|
||||||
@@ -271,10 +291,9 @@ async function minifyHTMLFile(inputPath, outputPath) {
|
|||||||
|
|
||||||
// Update CSS references for production and add script reference
|
// Update CSS references for production and add script reference
|
||||||
let updatedHTML = html
|
let updatedHTML = html
|
||||||
.replace(/<link rel="stylesheet" href="css\/pico\.green\.min\.css">/g, '')
|
.replace(/<link rel="stylesheet" href="css\/pico\.jade\.min\.css">/g, '')
|
||||||
.replace(/<link rel="stylesheet" href="css\/pico\.colors\.min\.css">/g, '')
|
|
||||||
.replace(/<link rel="stylesheet" href="site-styles\/style\.css">/g,
|
.replace(/<link rel="stylesheet" href="site-styles\/style\.css">/g,
|
||||||
'<link rel="stylesheet" href="css/styles.css"><link rel="stylesheet" href="site-styles/style.css">')
|
'<link rel="stylesheet" href="css/bundle.css">')
|
||||||
.replace(/<\/body>/g, '<script src="scripts/bundle.js"></script></body>');
|
.replace(/<\/body>/g, '<script src="scripts/bundle.js"></script></body>');
|
||||||
|
|
||||||
const minified = await htmlMinify(updatedHTML, {
|
const minified = await htmlMinify(updatedHTML, {
|
||||||
|
|||||||
15
nginx.conf
15
nginx.conf
@@ -40,14 +40,21 @@ http {
|
|||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# Temporary: Disable caching during development
|
||||||
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
expires 1w;
|
# expires 1w;
|
||||||
add_header Cache-Control "public, immutable";
|
# add_header Cache-Control "public, immutable";
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
add_header Pragma "no-cache";
|
||||||
|
add_header Expires "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~* \.html$ {
|
location ~* \.html$ {
|
||||||
expires 1h;
|
# expires 1h;
|
||||||
add_header Cache-Control "public";
|
# add_header Cache-Control "public";
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
add_header Pragma "no-cache";
|
||||||
|
add_header Expires "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /health {
|
location /health {
|
||||||
|
|||||||
@@ -4,22 +4,39 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<link rel="stylesheet" href="css/pico.green.min.css">
|
<link rel="stylesheet" href="css/pico.jade.min.css">
|
||||||
<link rel="stylesheet" href="css/pico.colors.min.css">
|
|
||||||
<link rel="stylesheet" href="site-styles/style.css">
|
<link rel="stylesheet" href="site-styles/style.css">
|
||||||
<title>Keyboard Vagabond - About</title>
|
<title>Keyboard Vagabond - About</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="container">
|
<header class="container">
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<!-- Desktop Navigation -->
|
||||||
<li><a target="_blank"href="https://mastodon.keyboardvagabond.com/public">Mastodon</a></li>
|
<ul class="desktop-nav">
|
||||||
|
<li><a target="_blank" href="https://mastodon.keyboardvagabond.com/public">Mastodon</a></li>
|
||||||
<li><a target="_blank" href="https://piefed.keyboardvagabond.com">Piefed</a></li>
|
<li><a target="_blank" href="https://piefed.keyboardvagabond.com">Piefed</a></li>
|
||||||
<li><a target="_blank" href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></li>
|
<li><a target="_blank" href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></li>
|
||||||
<li><a target="_blank" href="https://bookwyrm.keyboardvagabond.com">Bookwyrm</a></li>
|
<li><a target="_blank" href="https://bookwyrm.keyboardvagabond.com">Bookwyrm</a></li>
|
||||||
<li><a target="_blank" href="https://blog.keyboardvagabond.com">Write Freely</a></li>
|
<li><a target="_blank" href="https://blog.keyboardvagabond.com">Write Freely</a></li>
|
||||||
<li><a target="_blank" href="https://picsur.keyboardvagabond.com">Picsur</a></li>
|
<li><a target="_blank" href="https://picsur.keyboardvagabond.com">Picsur</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Mobile Navigation -->
|
||||||
|
<ul class="mobile-nav">
|
||||||
|
<li>
|
||||||
|
<details class="dropdown">
|
||||||
|
<summary role="button" class="secondary">Sites</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a target="_blank" href="https://mastodon.keyboardvagabond.com/public">Mastodon</a></li>
|
||||||
|
<li><a target="_blank" href="https://piefed.keyboardvagabond.com">Piefed</a></li>
|
||||||
|
<li><a target="_blank" href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></li>
|
||||||
|
<li><a target="_blank" href="https://bookwyrm.keyboardvagabond.com">Bookwyrm</a></li>
|
||||||
|
<li><a target="_blank" href="https://blog.keyboardvagabond.com">Write Freely</a></li>
|
||||||
|
<li><a target="_blank" href="https://picsur.keyboardvagabond.com">Picsur</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="index.html">Home</a></li>
|
<li><a href="index.html">Home</a></li>
|
||||||
<li>
|
<li>
|
||||||
@@ -51,31 +68,54 @@
|
|||||||
There will be a 3 month announcement in advance, in accordance with the <a href="https://joinmastodon.org/covenant">Mastodon Server Covenant</a>.</p>
|
There will be a 3 month announcement in advance, in accordance with the <a href="https://joinmastodon.org/covenant">Mastodon Server Covenant</a>.</p>
|
||||||
<p><strong>Funding</strong> -
|
<p><strong>Funding</strong> -
|
||||||
Keyboard Mastodon is currently funded by the admin, for a cost of ~$40 - $45 per month. Donations may be opened in the future, but have not been set up at this time.</p>
|
Keyboard Mastodon is currently funded by the admin, for a cost of ~$40 - $45 per month. Donations may be opened in the future, but have not been set up at this time.</p>
|
||||||
|
|
||||||
|
<section>
|
||||||
<h1>The Dirty Technicals</h1>
|
<h1>The Dirty Technicals</h1>
|
||||||
<p>If you're not a mega-nerd, turn back now.</p>
|
<p>If you're not a mega-nerd, turn back now.</p>
|
||||||
<p>I warned you.</p>
|
<p>I warned you.</p>
|
||||||
<p>Keyboard Vagabond is run on a 3 node Kubernetes cluster running on 3x Arm VPSs hosted by NetCup in Amsterdam. I chose Amsterdam because I thought that Europe would be more centrally located for people who are traveling the world.</p>
|
<p>Keyboard Vagabond is run on a 3 node Kubernetes cluster running on 3x Arm VPSs hosted by NetCup in Amsterdam. I chose Amsterdam because I thought that Europe would be more centrally located for people who are traveling the world.</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
<h4>The Specs</h4>
|
<h4>The Specs</h4>
|
||||||
<p><strong>Servers</strong> - 3x 10 ARM vCPUs, 16GB Ram, 500GB (~50GB for Talos and the rest for Longhorn) storage running <a href="https://www.talos.dev">Talos</a> and Kubernetes.
|
<dl>
|
||||||
<p><strong>Storage</strong> - Longhorn ensures that there are at least 2 copies across the nodes.</p>
|
<dt><strong>Servers</strong></dt>
|
||||||
<p><strong>Backups and Content</strong> - Backups and content are stored in S3 storage hosted by BackBlaze with CloudFlare providing CDN. I've already run through disaster recovery and restored database backups from S3.</p>
|
<dd>3x 10 ARM vCPUs, 16GB Ram, 500GB (~50GB for Talos and the rest for Longhorn) storage running <a href="https://www.talos.dev">Talos</a> and Kubernetespan.</dd>
|
||||||
<p><strong>CDN</strong> - CloudFlare provides CDN and special rules have been set up to be sure that as much as possible is cached.</p>
|
<dt><strong>Storage</strong></dt>
|
||||||
<p><strong>Security</strong> - ports are closed off to the world and secured with CloudFlare tunnels and TailScale as the only means of access outside of website access.</p>
|
<dd>Longhorn ensures that there are at least 2 copies across the nodes.</dd>
|
||||||
<p><strong>Observability and Logging</strong> - OpenObserve dashboards and log aggregation.</p>
|
<dt><strong>Backups and Content</strong></dt>
|
||||||
<p><strong>Domain</strong> - domain is provided by CloudFlare</p>
|
<dd>Stored in S3 storage hosted by BackBlaze with CloudFlare providing CDN. I've already run through disaster recovery and restored database backups from S3.</dd>
|
||||||
<p><strong>Services</strong> - Typical arrangement for services is that web services get 2 instances and workers get 1 instance with autoscaling. Web pods scale horizontally and workers scale vertically, then horizontally.</p>
|
<dt><strong>CDN</strong></dt>
|
||||||
<p><strong>Source Code</strong> - If I get the source code to where I'm comfortable sharing, I'll post a link here. And if you're experienced in k8s, I'd always appreciate a review. :)</p>
|
<dd>CloudFlare provides CDN and special rules have been set up to be sure that as much as possible is cached.</dd>
|
||||||
<p><strong>Costs</strong><br />
|
<dt><strong>Security</strong></dt>
|
||||||
|
<dd>Ports are closed off to the world and secured with CloudFlare tunnels and TailScale as the only means of access outside of website access.</dd>
|
||||||
|
<dt><strong>Observability and Logging</strong></dt>
|
||||||
|
<dd>OpenObserve dashboards and log aggregation.</dd>
|
||||||
|
<dt><strong>Domain</strong></dt>
|
||||||
|
<dd>Domain is provided by CloudFlare</dd>
|
||||||
|
<dt><strong>Services</strong></dt>
|
||||||
|
<dd>Typical arrangement for services is that web services get 2 instances and workers get 1 instance with autoscaling. Web pods scale horizontally and workers scale vertically, then horizontally.</dd>
|
||||||
|
<dt><strong>Source Code</strong></dt>
|
||||||
|
<dd>If I get the source code to where I'm comfortable sharing, I'll post a link here. And if you're experienced in k8s, I'd always appreciate a review. :)</dd>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<span><strong>Costs</strong></span><br />
|
||||||
|
|
||||||
|
<span>
|
||||||
VPS servers - 3x ~$13 / mth = $40/mth<br />
|
VPS servers - 3x ~$13 / mth = $40/mth<br />
|
||||||
Domain name - $12/year<br />
|
Domain name - $12/year<br />
|
||||||
Backblaze - $6/TB/mth = ~$2/mth<br />
|
Backblaze - $6/TB/mth = ~$2/mth<br />
|
||||||
Total: ~$45/mth</p>
|
Total: ~$45/mth
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="container">
|
<footer class="container">
|
||||||
<p>Contact: <a href="mailto:admin@keyboardvagabond.com">admin@keyboardvagabond.com</a>, any of the @sysadmin accounts on the instances</p>
|
<p>
|
||||||
<p></p>
|
<span>Contact: <a href="mailto:admin@keyboardvagabond.com">admin@keyboardvagabond.com</a>, any of the @sysadmin accounts on the instances</span></br>
|
||||||
<p>Copyright 2025 Keyboard Vagabond</p>
|
<span>Copyright 2025 Keyboard Vagabond</span>
|
||||||
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="scripts/bundle.js"></script>
|
<script src="scripts/bundle.js"></script>
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<link rel="stylesheet" href="css/pico.green.min.css">
|
<link rel="stylesheet" href="css/pico.jade.min.css">
|
||||||
<link rel="stylesheet" href="css/pico.colors.min.css">
|
|
||||||
<link rel="stylesheet" href="site-styles/style.css">
|
<link rel="stylesheet" href="site-styles/style.css">
|
||||||
<title>Keyboard Vagabond</title>
|
<title>Keyboard Vagabond</title>
|
||||||
</head>
|
</head>
|
||||||
@@ -15,14 +14,33 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="container">
|
<header class="container">
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<!-- Desktop Navigation -->
|
||||||
<li><a target="_blank"href="https://mastodon.keyboardvagabond.com/public">Mastodon</a></li>
|
<ul class="desktop-nav">
|
||||||
|
<li><a target="_blank" href="https://mastodon.keyboardvagabond.com/public">Mastodon</a></li>
|
||||||
<li><a target="_blank" href="https://piefed.keyboardvagabond.com">Piefed</a></li>
|
<li><a target="_blank" href="https://piefed.keyboardvagabond.com">Piefed</a></li>
|
||||||
<li><a target="_blank" href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></li>
|
<li><a target="_blank" href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></li>
|
||||||
<li><a target="_blank" href="https://bookwyrm.keyboardvagabond.com">Bookwyrm</a></li>
|
<li><a target="_blank" href="https://bookwyrm.keyboardvagabond.com">Bookwyrm</a></li>
|
||||||
<li><a target="_blank" href="https://blog.keyboardvagabond.com">Write Freely</a></li>
|
<li><a target="_blank" href="https://blog.keyboardvagabond.com">Write Freely</a></li>
|
||||||
<li><a target="_blank" href="https://picsur.keyboardvagabond.com">Picsur</a></li>
|
<li><a target="_blank" href="https://picsur.keyboardvagabond.com">Picsur</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Mobile Navigation -->
|
||||||
|
<ul class="mobile-nav">
|
||||||
|
<li>
|
||||||
|
<details class="dropdown">
|
||||||
|
<summary role="button" class="secondary">Sites</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a target="_blank" href="https://mastodon.keyboardvagabond.com/public">Mastodon</a></li>
|
||||||
|
<li><a target="_blank" href="https://piefed.keyboardvagabond.com">Piefed</a></li>
|
||||||
|
<li><a target="_blank" href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></li>
|
||||||
|
<li><a target="_blank" href="https://bookwyrm.keyboardvagabond.com">Bookwyrm</a></li>
|
||||||
|
<li><a target="_blank" href="https://blog.keyboardvagabond.com">Write Freely</a></li>
|
||||||
|
<li><a target="_blank" href="https://picsur.keyboardvagabond.com">Picsur</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="about.html">About</a></li>
|
<li><a href="about.html">About</a></li>
|
||||||
<li>
|
<li>
|
||||||
@@ -58,8 +76,7 @@
|
|||||||
with <a href="https://join-lemmy.org">Lemmy</a>, but with extra features for topics and communities.
|
with <a href="https://join-lemmy.org">Lemmy</a>, but with extra features for topics and communities.
|
||||||
Both are an alternative to Reddit. See more at <a href="https://join-lemmy.org">join-lemmy.org</a> and
|
Both are an alternative to Reddit. See more at <a href="https://join-lemmy.org">join-lemmy.org</a> and
|
||||||
<a href="https://join.piefed.social/try/">join.piefed.social</a>.</li>
|
<a href="https://join.piefed.social/try/">join.piefed.social</a>.</li>
|
||||||
<li><strong><a href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></strong> - An alternative to
|
<li><strong><a href="https://pixelfed.keyboardvagabond.com">Pixelfed</a></strong> - Like old instagram, where you can share your photos, albums, and create stories. See more at <a
|
||||||
instagram, where you can share your photos, albums, and create stories. See more at <a
|
|
||||||
href="https://pixelfed.org/servers">pixelfed.org</a>.</li>
|
href="https://pixelfed.org/servers">pixelfed.org</a>.</li>
|
||||||
<li><strong><a href="https://mastodon.keyboardvagabond.com">Mastodon</a></strong> - An alternative to
|
<li><strong><a href="https://mastodon.keyboardvagabond.com">Mastodon</a></strong> - An alternative to
|
||||||
twitter. Create your timeline by following users and hashtags. See more at <a
|
twitter. Create your timeline by following users and hashtags. See more at <a
|
||||||
|
|||||||
@@ -245,3 +245,86 @@
|
|||||||
display: block;
|
display: block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive Navigation Styles */
|
||||||
|
/* Hide mobile nav by default (desktop first) */
|
||||||
|
.mobile-nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show desktop nav by default */
|
||||||
|
.desktop-nav {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile breakpoint - hide desktop nav and show mobile nav */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.desktop-nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the dropdown for mobile */
|
||||||
|
.mobile-nav .dropdown {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown summary {
|
||||||
|
margin: 0;
|
||||||
|
padding: calc(var(--pico-spacing) * 0.5) var(--pico-spacing);
|
||||||
|
border-radius: var(--pico-border-radius);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown[open] summary {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown ul {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--pico-background-color);
|
||||||
|
border: 1px solid var(--pico-muted-border-color);
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 var(--pico-border-radius) var(--pico-border-radius);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown ul li {
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid var(--pico-muted-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown ul li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown ul li a {
|
||||||
|
display: block;
|
||||||
|
padding: calc(var(--pico-spacing) * 0.75) var(--pico-spacing);
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--pico-color);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown ul li a:hover {
|
||||||
|
background-color: var(--pico-muted-color);
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure proper positioning context */
|
||||||
|
.mobile-nav li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user