Compare commits

...

15 Commits

Author SHA1 Message Date
4b56a90c14 Update README.md
Some checks failed
Build and Update Flux / build-push-update (push) Has been cancelled
2026-05-28 08:20:26 +00:00
Raul Costa
15db73e7bc RC: (update) workflow file to create timestamp for tag
All checks were successful
Build and Update Flux / build-push-update (push) Successful in 17s
2026-04-03 19:58:31 +01:00
Raul Costa
bedbd016a8 RC: (update) workflow file commit reason
All checks were successful
Build and Update Flux / build-push-update (push) Successful in 19s
2026-04-03 01:21:14 +01:00
Raul Costa
6d73b544c6 RC: (update) workflow file to manual clone repo
All checks were successful
Build and Update Flux / build-push-update (push) Successful in 17s
2026-04-02 01:27:53 +01:00
Raul Costa
1ac70a0b1a RC: (debug) workflow not pushing to chart
Some checks failed
Build and Update Flux / build-push-update (push) Failing after 51s
2026-04-02 01:25:35 +01:00
Raul Costa
2d644a1ec3 RC: (debug) workflow not pushing to chart
Some checks failed
Build and Update Flux / build-push-update (push) Failing after 43s
2026-04-02 01:18:16 +01:00
Raul Costa
ada3e2c1b7 RC: (update) astro latest official integrations
Some checks failed
Build and Update Flux / build-push-update (push) Failing after 51s
2026-04-02 01:03:59 +01:00
Raul Costa
2b25b18fc8 RC: (update) dockerfile npm run command
Some checks failed
Build and Update Flux / build-push-update (push) Failing after 36s
2026-04-02 00:58:55 +01:00
Raul Costa
6d44c47bfe RC: (update) workflow file
Some checks failed
Build and Update Flux / build-push-update (push) Failing after 40s
2026-04-02 00:55:08 +01:00
Raul Costa
d6fe3f0dc0 RC: (update) layout of blog list page
Some checks failed
Build and Update Flux / build-push-update (push) Failing after 2m14s
2026-04-02 00:02:07 +01:00
Raul Costa
edc3e97d3b RC: (add) filter by blog tag
Some checks failed
Build and Update Flux / build-push-update (push) Has been cancelled
2026-04-02 00:01:07 +01:00
Raul Costa
bbea284c49 RC: (update) workflow file 2026-04-01 23:59:54 +01:00
Raul Costa
815912d20e RC: (update) workflow file 2026-04-01 23:59:41 +01:00
Raul Costa
94837f5493 RC: (update) workflow file
Some checks failed
Build and Update Flux / build-push-update (push) Has been cancelled
2026-04-01 00:41:23 +01:00
Raul Costa
94338d556e RC: (update) .gitignore file
Some checks failed
Build and Update Flux / build-push-update (push) Has been cancelled
2026-04-01 00:39:42 +01:00
11 changed files with 341 additions and 81 deletions

View File

@@ -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
View File

@@ -23,3 +23,4 @@ pnpm-debug.log*
# jetbrains setting folder # jetbrains setting folder
.idea/ .idea/
.vscode/ .vscode/

View File

@@ -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

View File

@@ -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
View File

@@ -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",

View File

@@ -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": {

View File

@@ -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; }

View File

@@ -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; }

View File

@@ -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 &rarr;</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
View 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>

View File

@@ -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;
} }