Compare commits
15 Commits
ecb6fc0832
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b56a90c14 | |||
|
|
15db73e7bc | ||
|
|
bedbd016a8 | ||
|
|
6d73b544c6 | ||
|
|
1ac70a0b1a | ||
|
|
2d644a1ec3 | ||
|
|
ada3e2c1b7 | ||
|
|
2b25b18fc8 | ||
|
|
6d44c47bfe | ||
|
|
d6fe3f0dc0 | ||
|
|
edc3e97d3b | ||
|
|
bbea284c49 | ||
|
|
815912d20e | ||
|
|
94837f5493 | ||
|
|
94338d556e |
@@ -12,21 +12,26 @@ on:
|
|||||||
env:
|
env:
|
||||||
REGISTRY: git.h0melab.uk
|
REGISTRY: git.h0melab.uk
|
||||||
IMAGE_NAME: git.h0melab.uk/rgcosta/slashroot-cc
|
IMAGE_NAME: git.h0melab.uk/rgcosta/slashroot-cc
|
||||||
|
# Change this to your actual Gitea username / infra repo name
|
||||||
|
INFRA_REPO: h0melab/infra-cluster-fluxcd
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-push-update:
|
build-push-update:
|
||||||
runs-on: gitea-runner-docker
|
runs-on: docker
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Generate Timestamp Tag
|
||||||
|
run: echo "BUILD_TIME=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Log in to Gitea Container Registry
|
- name: Log in to Gitea Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ gitea.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Build and Push Docker Image
|
- name: Build and Push Docker Image
|
||||||
@@ -35,19 +40,45 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.IMAGE_NAME }}:${{ gitea.sha }}
|
${{ env.IMAGE_NAME }}:${{ env.BUILD_TIME }}
|
||||||
${{ env.IMAGE_NAME }}:latest
|
${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- name: Checkout Infra Repository Manually
|
||||||
|
run: |
|
||||||
|
echo "🧹 Cleaning up old workspace..."
|
||||||
|
rm -rf infra-workspace
|
||||||
|
|
||||||
|
echo "🚀 Attempting to clone the infrastructure repository..."
|
||||||
|
# Using the token directly in the URL to bypass the API and clone over HTTPS
|
||||||
|
git clone https://gitea_bot:${{ secrets.INFRA_REPO_TOKEN }}@git.h0melab.uk/${{ env.INFRA_REPO }}.git infra-workspace
|
||||||
|
|
||||||
|
echo "✅ Clone successful! Contents:"
|
||||||
|
ls -la infra-workspace
|
||||||
|
|
||||||
- name: Update Helm values.yaml for Flux
|
- name: Update Helm values.yaml for Flux
|
||||||
run: |
|
run: |
|
||||||
# 1. Update the image tag in your local Helm chart using sed
|
cd infra-workspace
|
||||||
sed -i "s/tag: .*/tag: ${{ gitea.sha }}/g" charts/slashroot/values.yaml
|
|
||||||
|
|
||||||
# 2. Configure the Gitea Bot to commit the change
|
echo "--- TARGET FILE BEFORE ---"
|
||||||
|
cat charts/slashroot/values.yaml
|
||||||
|
|
||||||
|
# The sed command (Make sure the path matches perfectly!)
|
||||||
|
sed -i 's/tag: .*/tag: "${{ env.BUILD_TIME }}"/g' charts/slashroot/values.yaml
|
||||||
|
|
||||||
|
echo "--- TARGET FILE AFTER ---"
|
||||||
|
cat charts/slashroot/values.yaml
|
||||||
|
|
||||||
|
# Set up Git
|
||||||
git config user.name "Gitea Actions Bot"
|
git config user.name "Gitea Actions Bot"
|
||||||
git config user.email "actions@gitea.local"
|
git config user.email "actions@gitea.local"
|
||||||
|
|
||||||
# 3. Commit and push the updated values.yaml back to the main branch
|
|
||||||
git add charts/slashroot/values.yaml
|
git add charts/slashroot/values.yaml
|
||||||
git commit -m "chore: update slashroot image tag to ${{ gitea.sha }} [skip ci]"
|
|
||||||
git push
|
# Check if there are actually changes to commit
|
||||||
|
if git diff --staged --quiet; then
|
||||||
|
echo "❌ ERROR: No changes were made! Check if 'tag: ' actually exists in your values.yaml"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "✅ Changes detected! Committing and pushing..."
|
||||||
|
git commit -m "BOT: (deploy) slashroot-cc update ${{ env.BUILD_TIME }}"
|
||||||
|
git push origin main
|
||||||
|
fi
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ pnpm-debug.log*
|
|||||||
# jetbrains setting folder
|
# jetbrains setting folder
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm install
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ npm run build # outputs to dist/
|
|||||||
# Deploy dist/ to any static host: Cloudflare Pages, Netlify, Vercel, nginx
|
# Deploy dist/ to any static host: Cloudflare Pages, Netlify, Vercel, nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
For Cloudflare Pages (recommended for rcosta.uk):
|
For Cloudflare Pages:
|
||||||
|
|
||||||
- Build command: `npm run build`
|
- Build command: `npm run build`
|
||||||
- Output directory: `dist`
|
- Output directory: `dist`
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -9,8 +9,8 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^5.0.3",
|
"@astrojs/mdx": "^5.0.3",
|
||||||
"@astrojs/sitemap": "^3.1.0",
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"astro": "^6.1.2",
|
"astro": "^6.1.3",
|
||||||
"gsap": "^3.12.5"
|
"gsap": "^3.12.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1780,9 +1780,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/astro": {
|
"node_modules/astro": {
|
||||||
"version": "6.1.2",
|
"version": "6.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/astro/-/astro-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/astro/-/astro-6.1.3.tgz",
|
||||||
"integrity": "sha512-r3iIvmB6JvQxsdJLvapybKKq7Bojd1iQK6CCx5P55eRnXJIyUpHx/1UB/GdMm+em/lwaCUasxHCmIO0lCLV2uA==",
|
"integrity": "sha512-FUKbBYOdYYrRNZwDd9I5CVSfR6Nj9aZeNzcjcvh1FgHwR0uXawkYFR3HiGxmdmAB2m8fs0iIkDdsiUfwGeO8qA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^3.0.1",
|
"@astrojs/compiler": "^3.0.1",
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^4.15.0",
|
"@astrojs/mdx": "^5.0.3",
|
||||||
"@astrojs/mdx": "^3.1.0",
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"@astrojs/sitemap": "^3.1.0",
|
"astro": "^6.1.3",
|
||||||
"gsap": "^3.12.5"
|
"gsap": "^3.12.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const posts = (await getCollection('blog'))
|
|||||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
|
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
|
||||||
.slice(0, 3);
|
.slice(0, 3);
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="section recent-posts" id="recent-posts">
|
<section class="section recent-posts" id="recent-posts">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -31,7 +32,7 @@ const posts = (await getCollection('blog'))
|
|||||||
</time>
|
</time>
|
||||||
<div class="post-card-tags">
|
<div class="post-card-tags">
|
||||||
{(post.data.tags ?? []).slice(0,3).map((t: string) => (
|
{(post.data.tags ?? []).slice(0,3).map((t: string) => (
|
||||||
<span class="tag">{t}</span>
|
<a href={`/tags/${t}`} class="tag" onclick="event.stopPropagation()">{t}</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,9 +52,7 @@ const posts = (await getCollection('blog'))
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.recent-posts {
|
.recent-posts { padding-bottom: 2rem; }
|
||||||
padding-bottom: 1rem;
|
|
||||||
padding-top: 15rem; }
|
|
||||||
|
|
||||||
.section-header { margin-bottom: 3rem; }
|
.section-header { margin-bottom: 3rem; }
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const formatted = pubDate.toLocaleDateString('en-GB', {
|
|||||||
<div class="post-meta">
|
<div class="post-meta">
|
||||||
<time class="font-mono text-muted" datetime={pubDate.toISOString()}>{formatted}</time>
|
<time class="font-mono text-muted" datetime={pubDate.toISOString()}>{formatted}</time>
|
||||||
<div class="post-tags">
|
<div class="post-tags">
|
||||||
{tags.map(tag => <span class="tag">{tag}</span>)}
|
{tags.map(tag => <a href={`/tags/${tag}`} class="tag">{tag}</a>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +51,7 @@ const formatted = pubDate.toLocaleDateString('en-GB', {
|
|||||||
<div class="card sidebar-card" style="margin-top:1rem">
|
<div class="card sidebar-card" style="margin-top:1rem">
|
||||||
<p class="font-display" style="font-size:0.8rem;letter-spacing:0.1em;text-transform:uppercase;color:var(--text-muted);margin-bottom:0.75rem">Tags</p>
|
<p class="font-display" style="font-size:0.8rem;letter-spacing:0.1em;text-transform:uppercase;color:var(--text-muted);margin-bottom:0.75rem">Tags</p>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:0.4rem">
|
<div style="display:flex;flex-wrap:wrap;gap:0.4rem">
|
||||||
{tags.map(tag => <span class="tag">{tag}</span>)}
|
{tags.map(tag => <a href={`/tags/${tag}`} class="tag">{tag}</a>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -84,9 +84,13 @@ const formatted = pubDate.toLocaleDateString('en-GB', {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.post-wrapper { padding-top: 8rem; padding-bottom: 4rem; }
|
.post-wrapper { padding-top: 8rem; padding-bottom: 2rem; }
|
||||||
|
|
||||||
.post-hero { max-width: 900px; }
|
/* Hero is explicitly left-aligned — Rajdhani can inherit centre from somewhere */
|
||||||
|
.post-hero {
|
||||||
|
max-width: 800px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.day-badge {
|
.day-badge {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
@@ -95,12 +99,16 @@ const formatted = pubDate.toLocaleDateString('en-GB', {
|
|||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-title { margin-bottom: 1rem; }
|
.post-title {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.post-description {
|
.post-description {
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
max-width: 65ch;
|
max-width: 65ch;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-meta {
|
.post-meta {
|
||||||
@@ -119,8 +127,28 @@ const formatted = pubDate.toLocaleDateString('en-GB', {
|
|||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sidebar card — sticky */
|
||||||
.sidebar-card { position: sticky; top: 6rem; }
|
.sidebar-card { position: sticky; top: 6rem; }
|
||||||
|
|
||||||
|
/* TOC heading labels — was almost same colour as bg */
|
||||||
|
.sidebar-card p[style] {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOC links — clearly visible */
|
||||||
|
#toc-placeholder a {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
#toc-placeholder a:hover {
|
||||||
|
color: var(--petronas-teal) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar section labels */
|
||||||
|
.sidebar-card .font-display {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.post-body { grid-template-columns: 1fr; }
|
.post-body { grid-template-columns: 1fr; }
|
||||||
.post-sidebar { display: none; }
|
.post-sidebar { display: none; }
|
||||||
|
|||||||
@@ -20,29 +20,39 @@ const allTags = [...new Set(allPosts.flatMap(p => p.data.tags ?? []))].sort();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blog-layout container">
|
<div class="blog-layout container">
|
||||||
<!-- Post list -->
|
|
||||||
<div class="post-list">
|
<div class="post-list">
|
||||||
{allPosts.map((post, i) => (
|
{allPosts.map((post, i) => (
|
||||||
<article class="post-row card">
|
<article class="post-row card">
|
||||||
<a href={`/blog/${post.id}`} class="post-row-link">
|
<a href={`/blog/${post.id}`} class="post-row-link">
|
||||||
|
|
||||||
|
{/* Top row: entry badge + title stacked over description */}
|
||||||
|
<div class="post-row-top">
|
||||||
<div class="post-row-left">
|
<div class="post-row-left">
|
||||||
{post.data.day && (
|
{post.data.day && (
|
||||||
<div class="font-mono" style="font-size:0.7rem;color:var(--petronas-teal);letter-spacing:0.1em;margin-bottom:0.3rem">
|
<div class="post-row-day font-mono">
|
||||||
entry_{String(post.data.day).padStart(3,'0')}
|
entry_{String(post.data.day).padStart(3,'0')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<h2 class="post-row-title">{post.data.title}</h2>
|
<h2 class="post-row-title">{post.data.title}</h2>
|
||||||
|
</div>
|
||||||
<p class="text-muted post-row-desc">{post.data.description}</p>
|
<p class="text-muted post-row-desc">{post.data.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom row: date + tags inline + MORE button */}
|
||||||
<div class="post-row-meta">
|
<div class="post-row-meta">
|
||||||
<time class="font-mono text-muted" style="font-size:0.72rem">
|
<time class="font-mono text-muted post-row-date">
|
||||||
{post.data.pubDate.toLocaleDateString('en-GB', {day:'numeric',month:'short',year:'numeric'})}
|
{post.data.pubDate.toLocaleDateString('en-GB', {day:'numeric', month:'short', year:'numeric'})}
|
||||||
</time>
|
</time>
|
||||||
<div style="display:flex;gap:0.35rem;flex-wrap:wrap">
|
<div class="post-row-tags">
|
||||||
{(post.data.tags ?? []).map((t: string) => <span class="tag">{t}</span>)}
|
{(post.data.tags ?? []).map((t: string) => (
|
||||||
|
<a href={`/tags/${t}`} class="tag" onclick="event.stopPropagation()">{t}</a>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<a href={`/blog/${post.id}`} >
|
||||||
|
<div class="post-row-more font-mono">MORE →</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="post-row-arrow text-accent font-display">→</div>
|
|
||||||
</a>
|
</a>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
@@ -51,17 +61,23 @@ const allTags = [...new Set(allPosts.flatMap(p => p.data.tags ?? []))].sort();
|
|||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="blog-sidebar">
|
<aside class="blog-sidebar">
|
||||||
<div class="card" style="position:sticky;top:5.5rem">
|
<div class="card" style="position:sticky;top:5.5rem">
|
||||||
<p class="font-display" style="font-size:0.75rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--text-muted);margin-bottom:1rem">Tags</p>
|
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:0.4rem">
|
|
||||||
{allTags.map(t => <span class="tag">{t}</span>)}
|
|
||||||
</div>
|
|
||||||
<hr class="glow-divider" style="margin:1.5rem 0" />
|
|
||||||
<p class="font-display" style="font-size:0.75rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--text-muted);margin-bottom:0.75rem">Navigate</p>
|
<p class="font-display" style="font-size:0.75rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--text-muted);margin-bottom:0.75rem">Navigate</p>
|
||||||
<div style="display:flex;flex-direction:column;gap:0.35rem">
|
<div style="display:flex;flex-direction:column;gap:0.35rem">
|
||||||
<a href="/" class="text-muted font-mono" style="font-size:0.8rem">← Home</a>
|
<a href="/" class="text-muted font-mono" style="font-size:0.8rem">← Home</a>
|
||||||
<a href="/homelab" class="text-muted font-mono" style="font-size:0.8rem">Homelab status</a>
|
<a href="/homelab" class="text-muted font-mono" style="font-size:0.8rem">Homelab status</a>
|
||||||
<a href="/hardware" class="text-muted font-mono" style="font-size:0.8rem">Hardware list</a>
|
<a href="/hardware" class="text-muted font-mono" style="font-size:0.8rem">Hardware list</a>
|
||||||
</div>
|
</div>
|
||||||
|
<hr class="glow-divider" style="margin:1.5rem 0" />
|
||||||
|
<p class="font-display" style="font-size:0.75rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--text-muted);margin-bottom:1rem">Tags</p>
|
||||||
|
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:0.4rem">
|
||||||
|
{allTags.map(t => <a href={`/tags/${t}`} class="tag">{t}</a>)}
|
||||||
|
</div>
|
||||||
|
<hr class="glow-divider" style="margin:1.5rem 0" />
|
||||||
|
<p class="font-display" style="font-size:0.75rem;letter-spacing:0.12em;text-transform:uppercase;color:var(--text-muted);margin-bottom:1rem">Posts</p>
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:0.4rem">
|
||||||
|
{allPosts.map(t => <a href={`/blog/${t}`} class="blog">{t}</a>)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,46 +102,101 @@ const allTags = [...new Set(allPosts.flatMap(p => p.data.tags ?? []))].sort();
|
|||||||
|
|
||||||
.post-row-link {
|
.post-row-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 1.5rem;
|
gap: 1rem;
|
||||||
padding: 1.5rem;
|
padding: 1.25rem 1.5rem;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
transition: background var(--transition-fast);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-row:hover .post-row-title { color: var(--petronas-teal); }
|
/* * FIXED: Top section now stacks the title and description in 2 rows
|
||||||
|
*/
|
||||||
|
.post-row-top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.post-row-left { flex: 1; }
|
.post-row-left { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||||
|
|
||||||
|
.post-row-day {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
color: var(--petronas-teal);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.post-row-title {
|
.post-row-title {
|
||||||
font-size: 1.05rem;
|
font-size: 1rem;
|
||||||
margin-bottom: 0.4rem;
|
line-height: 1.35;
|
||||||
transition: color var(--transition-fast);
|
transition: color var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
.post-row:hover .post-row-title { color: var(--petronas-teal); }
|
||||||
|
|
||||||
.post-row-desc {
|
.post-row-desc {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bottom section: date + tags + button all on one line */
|
||||||
.post-row-meta {
|
.post-row-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-row-arrow {
|
.post-row-date {
|
||||||
font-size: 1.5rem;
|
font-size: 0.7rem;
|
||||||
opacity: 0.3;
|
white-space: nowrap;
|
||||||
transition: opacity var(--transition-fast), transform var(--transition-fast);
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.post-row:hover .post-row-arrow { opacity: 1; transform: translateX(4px); }
|
|
||||||
|
.post-row-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.35rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * FIXED: Force the tag contents to center perfectly
|
||||||
|
* (You can move this to your global CSS file if `.tag` is shared globally)
|
||||||
|
*/
|
||||||
|
.tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.3rem 0.65rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * FIXED: Replaced arrow with a styled 'MORE' button
|
||||||
|
*/
|
||||||
|
.post-row-more {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
padding: 0.35rem 0.6rem;
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.post-row:hover .post-row-more {
|
||||||
|
border-color: var(--petronas-teal);
|
||||||
|
color: var(--petronas-teal);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.blog-layout { grid-template-columns: 1fr; }
|
.blog-layout { grid-template-columns: 1fr; }
|
||||||
.blog-sidebar { display: none; }
|
.blog-sidebar { display: none; }
|
||||||
|
.post-row-meta { flex-wrap: wrap; }
|
||||||
|
.post-row-more { margin-left: 0; } /* Optional: push button to left on mobile if wrapped */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
109
src/pages/tags/[tag].astro
Normal file
109
src/pages/tags/[tag].astro
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = await getCollection('blog', ({ data }) => !data.draft);
|
||||||
|
const tags = [...new Set(posts.flatMap(p => p.data.tags ?? []))];
|
||||||
|
|
||||||
|
return tags.map(tag => ({
|
||||||
|
params: { tag },
|
||||||
|
props: {
|
||||||
|
tag,
|
||||||
|
posts: posts
|
||||||
|
.filter(p => p.data.tags?.includes(tag))
|
||||||
|
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tag, posts } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={`#${tag}`} description={`All posts tagged ${tag}`}>
|
||||||
|
<div class="tag-page">
|
||||||
|
<div class="container page-hero">
|
||||||
|
<a href="/blog" class="back-link font-mono">← all posts</a>
|
||||||
|
<div class="accent-line" style="margin-top:1rem"></div>
|
||||||
|
<h1>
|
||||||
|
<span class="text-muted" style="font-weight:400">#</span>{tag}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted font-mono" style="font-size:0.85rem;margin-top:0.5rem">
|
||||||
|
{posts.length} {posts.length === 1 ? 'entry' : 'entries'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container post-list">
|
||||||
|
{posts.map(post => (
|
||||||
|
<article class="card post-row">
|
||||||
|
<a href={`/blog/${post.id}`} class="post-row-link">
|
||||||
|
<div class="post-row-left">
|
||||||
|
{post.data.day && (
|
||||||
|
<div class="font-mono" style="font-size:0.7rem;color:var(--petronas-teal);letter-spacing:0.1em;margin-bottom:0.3rem">
|
||||||
|
entry_{String(post.data.day).padStart(3,'0')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h2 class="post-row-title">{post.data.title}</h2>
|
||||||
|
<p class="text-muted post-row-desc">{post.data.description}</p>
|
||||||
|
<div class="post-row-meta">
|
||||||
|
<time class="font-mono text-muted" style="font-size:0.72rem">
|
||||||
|
{post.data.pubDate.toLocaleDateString('en-GB', { day:'numeric', month:'short', year:'numeric' })}
|
||||||
|
</time>
|
||||||
|
<div style="display:flex;gap:0.35rem;flex-wrap:wrap">
|
||||||
|
{(post.data.tags ?? []).map((t: string) => (
|
||||||
|
<a href={`/tags/${t}`} class="tag">{t}</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="post-row-arrow text-accent font-display">→</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tag-page { padding-top: 8rem; padding-bottom: 4rem; }
|
||||||
|
.page-hero { margin-bottom: 3rem; }
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
transition: color 150ms;
|
||||||
|
}
|
||||||
|
.back-link:hover { color: var(--petronas-teal); }
|
||||||
|
|
||||||
|
.post-list { display: flex; flex-direction: column; gap: 1rem; }
|
||||||
|
.post-row { padding: 0; }
|
||||||
|
|
||||||
|
.post-row-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-row-left { flex: 1; }
|
||||||
|
.post-row-title { font-size: 1.05rem; margin-bottom: 0.4rem; transition: color 150ms; }
|
||||||
|
.post-row:hover .post-row-title { color: var(--petronas-teal); }
|
||||||
|
.post-row-desc { font-size: 0.85rem; line-height: 1.55; margin-bottom: 0.75rem; }
|
||||||
|
|
||||||
|
.post-row-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-row-arrow {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: opacity 150ms, transform 150ms;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.post-row:hover .post-row-arrow { opacity: 1; transform: translateX(4px); }
|
||||||
|
</style>
|
||||||
@@ -115,13 +115,23 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
background: var(--bg-surface);
|
background: #0d1918;
|
||||||
border: 1px solid var(--border-subtle);
|
border: 1px solid rgba(0, 210, 190, 0.12);
|
||||||
border-left: 3px solid var(--petronas-teal);
|
border-left: 3px solid var(--petronas-teal);
|
||||||
padding: 1.25rem 1.5rem;
|
padding: 1.25rem 1.5rem;
|
||||||
border-radius: 0 8px 8px 0;
|
border-radius: 0 8px 8px 0;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
|
color: #a8c8c6; /* single readable text colour — no syntax highlighting */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kill any syntax highlighter span colours — keep it monochrome */
|
||||||
|
pre span,
|
||||||
|
pre code span {
|
||||||
|
color: inherit !important;
|
||||||
|
background: transparent !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-weight: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Layout ────────────────────────────────────────────────── */
|
/* ── Layout ────────────────────────────────────────────────── */
|
||||||
@@ -193,14 +203,24 @@ pre {
|
|||||||
/* ── Tag / Badge ───────────────────────────────────────────── */
|
/* ── Tag / Badge ───────────────────────────────────────────── */
|
||||||
.tag {
|
.tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.2rem 0.65rem;
|
padding: 0.15rem 0.55rem;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 0.72rem;
|
font-size: 0.68rem;
|
||||||
color: var(--petronas-teal);
|
color: var(--petronas-teal);
|
||||||
background: var(--petronas-glow);
|
background: var(--petronas-glow);
|
||||||
border: 1px solid rgba(0, 210, 190, 0.2);
|
border: 1px solid rgba(0, 210, 190, 0.2);
|
||||||
border-radius: 3px;
|
border-radius: 20px;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition:
|
||||||
|
background var(--transition-fast),
|
||||||
|
border-color var(--transition-fast),
|
||||||
|
color var(--transition-fast);
|
||||||
|
}
|
||||||
|
a.tag:hover {
|
||||||
|
background: rgba(0, 210, 190, 0.2);
|
||||||
|
border-color: rgba(0, 210, 190, 0.5);
|
||||||
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Glow Divider ──────────────────────────────────────────── */
|
/* ── Glow Divider ──────────────────────────────────────────── */
|
||||||
@@ -215,7 +235,6 @@ pre {
|
|||||||
);
|
);
|
||||||
margin: 4rem 0;
|
margin: 4rem 0;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
margin: 4rem auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Noise texture overlay ─────────────────────────────────── */
|
/* ── Noise texture overlay ─────────────────────────────────── */
|
||||||
@@ -246,7 +265,7 @@ body::after {
|
|||||||
/* ── Prose (blog posts) ────────────────────────────────────── */
|
/* ── Prose (blog posts) ────────────────────────────────────── */
|
||||||
.prose {
|
.prose {
|
||||||
max-width: 72ch;
|
max-width: 72ch;
|
||||||
color: var(--text-secondary);
|
color: #9bbfbd; /* brighter than text-secondary for long-form readability */
|
||||||
}
|
}
|
||||||
.prose h2,
|
.prose h2,
|
||||||
.prose h3 {
|
.prose h3 {
|
||||||
@@ -255,6 +274,7 @@ body::after {
|
|||||||
}
|
}
|
||||||
.prose p {
|
.prose p {
|
||||||
margin-bottom: 1.4rem;
|
margin-bottom: 1.4rem;
|
||||||
|
line-height: 1.8;
|
||||||
}
|
}
|
||||||
.prose ul,
|
.prose ul,
|
||||||
.prose ol {
|
.prose ol {
|
||||||
@@ -285,8 +305,9 @@ body::after {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.prose code {
|
.prose code {
|
||||||
background: var(--bg-surface);
|
background: #0d1918;
|
||||||
padding: 0.15em 0.4em;
|
padding: 0.15em 0.45em;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: var(--petronas-teal);
|
color: #7dcfca; /* slightly muted teal — readable but distinct from body text */
|
||||||
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user