new site style (#1)

* initial phase

* remove pagefind

* phase 2

* restyle blog post pages, markdown rendering, pagination, and social link components.

* use dark-plus theme for markdown code

* update base layout and header components, and update the remote deployment directory

* use expressive code for code styling

* adjust inline code style

* format code

* re-add pagefind

* add sidebar with dev qotd

* add sidebar component with dynamic quote fetching and caching

* add Docker setup with Dockerfile, docker-compose, and dockerignore for the Astro site

* integrate Docker Compose with Traefik proxy and remove the legacy PowerShell deployment script
This commit is contained in:
fiatcode 2026-02-17 18:25:45 +07:00
parent bfb12ded11
commit 8d615bd421
37 changed files with 522 additions and 240 deletions

11
.dockerignore Normal file
View file

@ -0,0 +1,11 @@
node_modules
dist
.git
.vscode
.idea
.astro
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

18
Dockerfile Normal file
View file

@ -0,0 +1,18 @@
# Stage 1: Build
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Serve
FROM joseluisq/static-web-server:2-alpine
ENV SERVER_ROOT=/public
ENV SERVER_PORT=80
COPY --from=build /app/dist /public

View file

@ -1,13 +1,14 @@
// @ts-check // @ts-check
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "astro/config"; import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite"; import expressiveCode from "astro-expressive-code";
import pagefind from "astro-pagefind"; import pagefind from "astro-pagefind";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
integrations: [pagefind()],
vite: { vite: {
plugins: [tailwindcss()], plugins: [tailwindcss()],
}, },
integrations: [expressiveCode(), pagefind()],
}); });

10
compose.yml Normal file
View file

@ -0,0 +1,10 @@
services:
site-server:
build: .
restart: unless-stopped
networks:
- traefik-proxy
networks:
traefik-proxy:
external: true

View file

@ -1,33 +0,0 @@
# Variables
$remoteUser = "dhemas"
$remoteHost = "dhemasnurjaya.com"
$remoteDir = "/home/dhemas/apps/blog/public/"
$localDir = "dist/"
$keyPath = "D:\Secrets\giocloud-default.ppk"
$winscpExecutable = "C:\Users\dhemas\AppData\Local\Programs\WinSCP\WinSCP.com" # Update this if needed
# Build astro site
npm run build
if ($LASTEXITCODE -ne 0) {
Write-Host "Build failed!" -ForegroundColor Red
exit 1
}
# Generate WinSCP sync commands
$scriptContent = @"
open sftp://$remoteUser@$remoteHost -privatekey=$keyPath
synchronize remote -delete -criteria=checksum $localDir $remoteDir
exit
"@
# Save the script to a temporary file
$tempScript = [System.IO.Path]::GetTempFileName()
Set-Content -Path $tempScript -Value $scriptContent
# Execute the WinSCP command
& $winscpExecutable /script=$tempScript
# Clean up
Remove-Item $tempScript
Write-Host "Deployed to $remoteHost!"

11
ec.config.mjs Normal file
View file

@ -0,0 +1,11 @@
import { defineEcConfig } from "astro-expressive-code";
export default defineEcConfig({
themes: ["dark-plus"],
styleOverrides: {
borderRadius: "0rem",
frames: {
shadowColor: "transparent",
},
},
});

237
package-lock.json generated
View file

@ -8,12 +8,11 @@
"name": "site-astro", "name": "site-astro",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@fontsource-variable/noto-sans": "^5.2.10",
"@fontsource/jetbrains-mono": "^5.2.8", "@fontsource/jetbrains-mono": "^5.2.8",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"astro": "^5.16.10", "astro": "^5.16.10",
"astro-expressive-code": "^0.41.6",
"astro-pagefind": "^1.8.5", "astro-pagefind": "^1.8.5",
"lucide-astro": "^0.556.0",
"tailwindcss": "^4.1.18" "tailwindcss": "^4.1.18"
}, },
"devDependencies": { "devDependencies": {
@ -152,6 +151,15 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@ctrl/tinycolor": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
"integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
@ -578,13 +586,49 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@fontsource-variable/noto-sans": { "node_modules/@expressive-code/core": {
"version": "5.2.10", "version": "0.41.6",
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans/-/noto-sans-5.2.10.tgz", "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.6.tgz",
"integrity": "sha512-wyFgKkFu7jki5kEL8qv7avjQ8rxHX0J/nhLWvbR9T0hOH1HRKZEvb9EW9lMjZfWHHfEzKkYf5J+NadwgCS7TXA==", "integrity": "sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g==",
"license": "OFL-1.1", "license": "MIT",
"funding": { "dependencies": {
"url": "https://github.com/sponsors/ayuhito" "@ctrl/tinycolor": "^4.0.4",
"hast-util-select": "^6.0.2",
"hast-util-to-html": "^9.0.1",
"hast-util-to-text": "^4.0.1",
"hastscript": "^9.0.0",
"postcss": "^8.4.38",
"postcss-nested": "^6.0.1",
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1"
}
},
"node_modules/@expressive-code/plugin-frames": {
"version": "0.41.6",
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.6.tgz",
"integrity": "sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg==",
"license": "MIT",
"dependencies": {
"@expressive-code/core": "^0.41.6"
}
},
"node_modules/@expressive-code/plugin-shiki": {
"version": "0.41.6",
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.6.tgz",
"integrity": "sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng==",
"license": "MIT",
"dependencies": {
"@expressive-code/core": "^0.41.6",
"shiki": "^3.2.2"
}
},
"node_modules/@expressive-code/plugin-text-markers": {
"version": "0.41.6",
"resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.6.tgz",
"integrity": "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q==",
"license": "MIT",
"dependencies": {
"@expressive-code/core": "^0.41.6"
} }
}, },
"node_modules/@fontsource/jetbrains-mono": { "node_modules/@fontsource/jetbrains-mono": {
@ -2176,6 +2220,18 @@
"sharp": "^0.34.0" "sharp": "^0.34.0"
} }
}, },
"node_modules/astro-expressive-code": {
"version": "0.41.6",
"resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.6.tgz",
"integrity": "sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw==",
"license": "MIT",
"dependencies": {
"rehype-expressive-code": "^0.41.6"
},
"peerDependencies": {
"astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta"
}
},
"node_modules/astro-pagefind": { "node_modules/astro-pagefind": {
"version": "1.8.5", "version": "1.8.5",
"resolved": "https://registry.npmjs.org/astro-pagefind/-/astro-pagefind-1.8.5.tgz", "resolved": "https://registry.npmjs.org/astro-pagefind/-/astro-pagefind-1.8.5.tgz",
@ -2215,6 +2271,16 @@
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bcp-47-match": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
"integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/boolbase": { "node_modules/boolbase": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@ -2427,6 +2493,22 @@
"url": "https://github.com/sponsors/fb55" "url": "https://github.com/sponsors/fb55"
} }
}, },
"node_modules/css-selector-parser": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz",
"integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/mdevils"
},
{
"type": "patreon",
"url": "https://patreon.com/mdevils"
}
],
"license": "MIT"
},
"node_modules/css-tree": { "node_modules/css-tree": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
@ -2597,6 +2679,19 @@
"node": ">=0.3.1" "node": ">=0.3.1"
} }
}, },
"node_modules/direction": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
"integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==",
"license": "MIT",
"bin": {
"direction": "cli.js"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/dlv": { "node_modules/dlv": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@ -2784,6 +2879,18 @@
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/expressive-code": {
"version": "0.41.6",
"resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.6.tgz",
"integrity": "sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA==",
"license": "MIT",
"dependencies": {
"@expressive-code/core": "^0.41.6",
"@expressive-code/plugin-frames": "^0.41.6",
"@expressive-code/plugin-shiki": "^0.41.6",
"@expressive-code/plugin-text-markers": "^0.41.6"
}
},
"node_modules/extend": { "node_modules/extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -2930,6 +3037,19 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/hast-util-has-property": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz",
"integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-is-element": { "node_modules/hast-util-is-element": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
@ -2981,6 +3101,33 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/hast-util-select": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz",
"integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
"bcp-47-match": "^2.0.0",
"comma-separated-tokens": "^2.0.0",
"css-selector-parser": "^3.0.0",
"devlop": "^1.0.0",
"direction": "^2.0.0",
"hast-util-has-property": "^3.0.0",
"hast-util-to-string": "^3.0.0",
"hast-util-whitespace": "^3.0.0",
"nth-check": "^2.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"unist-util-visit": "^5.0.0",
"zwitch": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-html": { "node_modules/hast-util-to-html": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@ -3023,6 +3170,19 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/hast-util-to-string": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
"integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-text": { "node_modules/hast-util-to-text": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
@ -3477,16 +3637,6 @@
"node": "20 || >=22" "node": "20 || >=22"
} }
}, },
"node_modules/lucide-astro": {
"version": "0.556.0",
"resolved": "https://registry.npmjs.org/lucide-astro/-/lucide-astro-0.556.0.tgz",
"integrity": "sha512-ugMjPb45AMfkLCaduNSbyy5NQEKvB1TxVVMmUS4S6L807PMESnX0Qp+DIKHjbyjJmPXOyLRbrzvR3YikTK7brg==",
"deprecated": "Deprecated: Use `@lucide/astro`",
"license": "MIT",
"peerDependencies": {
"astro": ">=2.7.1"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@ -4572,6 +4722,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@ -4581,6 +4732,44 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-nested": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "^6.1.1"
},
"engines": {
"node": ">=12.0"
},
"peerDependencies": {
"postcss": "^8.2.14"
}
},
"node_modules/postcss-nested/node_modules/postcss-selector-parser": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-selector-parser": { "node_modules/postcss-selector-parser": {
"version": "6.0.10", "version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
@ -4735,6 +4924,15 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/rehype-expressive-code": {
"version": "0.41.6",
"resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.6.tgz",
"integrity": "sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g==",
"license": "MIT",
"dependencies": {
"expressive-code": "^0.41.6"
}
},
"node_modules/rehype-parse": { "node_modules/rehype-parse": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz",
@ -5602,7 +5800,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/vfile": { "node_modules/vfile": {

View file

@ -10,12 +10,11 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/noto-sans": "^5.2.10",
"@fontsource/jetbrains-mono": "^5.2.8", "@fontsource/jetbrains-mono": "^5.2.8",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"astro": "^5.16.10", "astro": "^5.16.10",
"astro-expressive-code": "^0.41.6",
"astro-pagefind": "^1.8.5", "astro-pagefind": "^1.8.5",
"lucide-astro": "^0.556.0",
"tailwindcss": "^4.1.18" "tailwindcss": "^4.1.18"
}, },
"devDependencies": { "devDependencies": {

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(92.2% 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-at-sign"><circle cx="12" cy="12" r="4"></circle><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"></path></svg>

After

Width:  |  Height:  |  Size: 326 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(92.2% 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-facebook"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg>

After

Width:  |  Height:  |  Size: 307 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(92.2% 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>

After

Width:  |  Height:  |  Size: 531 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(92.2% 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(92.2% 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-twitter"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>

After

Width:  |  Height:  |  Size: 412 B

View file

@ -1,51 +0,0 @@
---
import Tag from "@/components/Tag.astro";
interface Props {
title: string;
description: string;
date: Date;
tags?: string[];
}
const { title, description, date, tags } = Astro.props;
---
<article data-pagefind-body>
<header class="mb-12">
<h1 class="text-4xl font-bold mb-2">{title}</h1>
<h2 class="text-lg mb-2">{description}</h2>
<div class="flex gap-4 text-gray-600 text-sm">
<time datetime={new Date(date).toISOString()}>
{
date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})
}
</time>
</div>
</header>
<div
class="max-w-3xl mb-8
prose prose-invert
prose-h1:border-b prose-h1:pb-4
prose-a:underline prose-a:underline-offset-4
prose-p:text-zinc-300 prose-p:text-justify prose-img:rounded-xl
prose-headings:text-zinc-300"
>
<slot />
</div>
<ul class="flex flex-wrap gap-2">
{
tags?.map((tag) => (
<li>
<Tag name={tag} variant="small" />
</li>
))
}
</ul>
</article>

View file

@ -1,26 +0,0 @@
---
interface Props {
id: string;
date: Date;
title: string;
description: string;
}
const { id, date, title, description } = Astro.props;
---
<a href={`/posts/${id}`}>
<div class="rounded-lg p-4 bg-zinc-800">
<div class="flex items-center text-zinc-400 text-sm mb-2">
{
date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})
}
</div>
<h2 class="text-2xl font-semibold mb-1">{title}</h2>
<p class="text-base text-zinc-400">{description}</p>
</div>
</a>

View file

@ -0,0 +1,28 @@
---
import Link from "@/components/Link.astro";
interface Props {
id: string;
date: Date;
title: string;
description: string;
}
const { id, date, title, description } = Astro.props;
---
<div>
<Link href={`/posts/${id}`}>
<h2 class="text-lg font-semibold">{title}</h2>
</Link>
<div class="flex items-center text-neutral-500 text-sm">
{
date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})
}
</div>
<p class="text-base text-neutral-400">{description}</p>
</div>

View file

@ -6,4 +6,4 @@ interface Props {
const { message } = Astro.props; const { message } = Astro.props;
--- ---
<p class="text-lg text-zinc-400">{message}</p> <p class="text-lg text-neutral-400">{message}</p>

View file

@ -1,5 +1,6 @@
<footer class="w-full text-center"> <footer class="w-full p-6 text-sm text-neutral-400">
<p class="text-xs text-zinc-400"> <p>
&copy; {new Date().getFullYear()} Dhemas Nurjaya. All rights reserved. &copy; {new Date().getFullYear()} Dhemas Nurjaya.
</p> </p>
<p>All rights reserved.</p>
</footer> </footer>

View file

@ -1,19 +1,27 @@
--- ---
import NavLink from "@/components/NavLink.astro"; import Link from "@/components/Link.astro";
--- ---
<header class="w-full"> <header class="w-full p-6">
<nav class="flex flex-row justify-between" aria-label="Main navigation"> <nav aria-label="Main navigation">
<div class="flex gap-2 items-center"> <ul class="flex flex-wrap gap-4 items-center">
<a href="/" aria-label="Home"> <li>
<p class="text-2xl font-semibold">#!</p> <a
</a> href="/"
{/* <ThemeToggle /> */} aria-label="Home"
</div> class="flex before:content-['['] after:content-[']_#'] p-1 bg-green-600 text-neutral-800 hover:bg-green-500 transition-colors"
<div class="flex gap-6 items-center justify-end"> >dhemasnurjaya</a
<NavLink href="/posts" ariaLabel="Posts">Posts</NavLink> >
<NavLink href="/tags" ariaLabel="Tags">Tags</NavLink> </li>
<NavLink href="/search" ariaLabel="Search">Search</NavLink> <li class="flex before:content-['./'] before:text-neutral-500">
</div> <Link href="/posts" ariaLabel="Posts">posts</Link>
</li>
<li class="flex before:content-['./'] before:text-neutral-500">
<Link href="/tags" ariaLabel="Tags">tags</Link>
</li>
<li class="flex before:content-['./'] before:text-neutral-500">
<Link href="/search" ariaLabel="Search">search</Link>
</li>
</ul>
</nav> </nav>
</header> </header>

View file

@ -1,25 +1,39 @@
--- ---
import { Image } from "astro:assets"; import PageTitle from "@/components/PageTitle.astro";
import profileImage from "@/assets/images/profile.webp";
import { Linkedin, Github, Twitter, Facebook, Mail } from "lucide-astro";
import SocialLink from "@/components/SocialLink.astro"; import SocialLink from "@/components/SocialLink.astro";
import Linkedin from "@/assets/images/linkedin.svg";
import Github from "@/assets/images/github.svg";
import Twitter from "@/assets/images/twitter.svg";
import Facebook from "@/assets/images/facebook.svg";
import Mail from "@/assets/images/at-sign.svg";
--- ---
<div class="flex flex-col min-h-full items-center justify-center"> <div class="flex flex-col">
<Image <PageTitle title="Hi, I'm Dhemas 👋" />
src={profileImage} <p class="mb-4">
loading="eager" Cross-platform developer. Linux fan stuck on Windows. Craftsman at heart.
fetchpriority="high" </p>
alt="Dhemas Nurjaya" <p class="mb-4">
class="size-36 sm:size-48 rounded-2xl" I write about building apps, learning new tech, and the occasional
/> existential crisis that comes with debugging production code. This is where
<h1 class="text-3xl sm:text-5xl font-bold mt-4 text-center"> I share what I'm working on, what I'm learning, and thoughts that don't fit
Dhemas Nurjaya in a commit message.
</h1> </p>
<h2 class="text-lg sm:text-xl mt-2 text-center"> <p class="mb-4">
Passionate Software Engineer Currently working at a local startup where "full-stack" means Flutter 📱,
</h2> Spring ☕, and React Router 🌐—sometimes all in the same day. I follow The
<div class="flex flex-row gap-8 mt-8" aria-label="social links"> Craftsman's Way: quality is not negotiable. Clean Architecture, TDD, and DDD
aren't just buzzwords here; they're how I try to keep my sanity intact.
</p>
<p class="mb-4">
Also, I built this blog with Astro ⚡ because life's too short for slow
websites.
</p>
<p class="mb-8">
Stick around if you're into cross-platform dev, software craftsmanship, or
just want to see someone figure things out in public. 🚀
</p>
<div class="flex gap-6 items-center" aria-label="social links">
<SocialLink <SocialLink
href="https://www.linkedin.com/in/dhemas-nurjaya-030890bb" href="https://www.linkedin.com/in/dhemas-nurjaya-030890bb"
label="Linkedin" label="Linkedin"
@ -42,5 +56,4 @@ import SocialLink from "@/components/SocialLink.astro";
icon={Mail} icon={Mail}
/> />
</div> </div>
<div class="mt-24"></div>
</div> </div>

17
src/components/Link.astro Normal file
View file

@ -0,0 +1,17 @@
---
interface Props {
href: string;
ariaLabel?: string;
class?: string;
}
const { href, ariaLabel, class: className } = Astro.props;
---
<a
href={href}
class={`text-emerald-300 hover:text-emerald-500 transition-colors underline underline-offset-4 ${className ?? ""}`}
aria-label={ariaLabel}
>
<slot />
</a>

View file

@ -0,0 +1,14 @@
<div
class="max-w-5xl mb-8
prose prose-invert
prose-p:text-neutral-300
prose-headings:pb-4 prose-headings:text-neutral-300 prose-headings:before:mr-2 prose-headings:before:text-neutral-500
prose-h1:border-b prose-h1:border-neutral-500 prose-h1:text-xl prose-h1:before:content-['#']
prose-h2:font-bold prose-h2:text-lg prose-h2:before:content-['##']
prose-h3:font-bold prose-h3:text-base prose-h3:before:content-['###']
prose-h4:font-semibold prose-h4:text-base prose-h4:before:content-['####']
prose-a:underline prose-a:underline-offset-4 prose-a:text-emerald-300 prose-a:hover:text-emerald-500 prose-a:transition-colors
prose-code:before:content-none prose-code:after:content-none prose-code:bg-neutral-900 prose-code:px-1 prose-code:py-0.5 prose-code:font-normal"
>
<slot />
</div>

View file

@ -1,17 +0,0 @@
---
interface Props {
href: string;
ariaLabel?: string;
class?: string;
}
const { href, ariaLabel, class: className } = Astro.props;
---
<a
href={href}
class={`hover:underline underline-offset-4 decoration-transparent hover:decoration-current transition-colors duration-300 aria-[current]:underline aria-[current]:decoration-current aria-[current]:font-semibold ${className ?? ""}`}
aria-label={ariaLabel}
>
<slot />
</a>

View file

@ -6,4 +6,4 @@ interface Props {
const { title } = Astro.props; const { title } = Astro.props;
--- ---
<h1 class="text-4xl font-bold mb-8">{title}</h1> <h1 class="text-2xl font-bold mb-4">{title}</h1>

View file

@ -1,5 +1,5 @@
--- ---
import NavLink from "./NavLink.astro"; import Link from "./Link.astro";
interface Props { interface Props {
currentPage: number; currentPage: number;
@ -13,14 +13,14 @@ const { currentPage, lastPage, getPageUrl } = Astro.props;
<nav aria-label="Pagination Navigation" class="flex py-6"> <nav aria-label="Pagination Navigation" class="flex py-6">
{ {
currentPage > 1 && ( currentPage > 1 && (
<NavLink href={getPageUrl(currentPage - 1)}>« Previous</NavLink> <Link href={getPageUrl(currentPage - 1)}>« Previous</Link>
) )
} }
{ {
currentPage < lastPage && ( currentPage < lastPage && (
<NavLink href={getPageUrl(currentPage + 1)} class="ml-auto"> <Link href={getPageUrl(currentPage + 1)} class="ml-auto">
Next » Next »
</NavLink> </Link>
) )
} }
</nav> </nav>

View file

@ -0,0 +1,12 @@
---
import { getQuote } from "@/lib/quote";
const { quote, author } = await getQuote();
---
<div class="flex flex-col sticky top-4">
<blockquote class="border-l border-neutral-500 pl-4">
<p class="mb-2">{quote}</p>
<footer class="text-neutral-400 text-sm">{author}</footer>
</blockquote>
<span class="text-neutral-500 py-4">---</span>
</div>

View file

@ -11,7 +11,7 @@ const { href, label, icon: Icon } = Astro.props;
<a <a
href={href} href={href}
aria-label={label} aria-label={label}
class="text-zinc-300 hover:text-zinc-100 transition-colors" class="text-neutral-300 hover:text-neutral-100 transition-colors"
> >
<Icon class="size-6" /> <Icon class="size-5" />
</a> </a>

View file

@ -14,7 +14,7 @@ const textSize = isSmall ? "text-sm" : "text-base";
<a href={linkHref}> <a href={linkHref}>
<span <span
class={`${textSize} text-zinc-400 font-semibold rounded-md bg-zinc-800 px-2 py-2 hover:bg-zinc-700`} class={`${textSize} text-neutral-400 font-semibold bg-neutral-900 px-2 py-2 hover:bg-neutral-700`}
> >
#{name}{count !== undefined && <sup>{count}</sup>} #{name}{count !== undefined && <sup>{count}</sup>}
</span> </span>

View file

@ -2,6 +2,7 @@
import "@/styles/global.css"; import "@/styles/global.css";
import Footer from "@/components/Footer.astro"; import Footer from "@/components/Footer.astro";
import Header from "@/components/Header.astro"; import Header from "@/components/Header.astro";
import Sidebar from "@/components/Sidebar.astro";
interface Props { interface Props {
title: string; title: string;
@ -21,10 +22,15 @@ const { title, description } = Astro.props;
<meta name="description" content={description} /> <meta name="description" content={description} />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
</head> </head>
<body class="h-screen max-w-5xl mx-auto flex flex-col font-sans p-4"> <body class="max-w-5xl mx-auto flex flex-col font-default">
<Header /> <Header />
<main class="w-full max-w-3xl mx-auto flex-1 py-12"> <main class="flex flex-col md:flex-row">
<slot /> <section class="w-full md:flex-3 p-4 bg-neutral-800">
<slot />
</section>
<aside class="w-full md:flex-1 p-4">
<Sidebar />
</aside>
</main> </main>
<Footer /> <Footer />
</body> </body>

17
src/lib/quote.ts Normal file
View file

@ -0,0 +1,17 @@
let cache: { quote: string; author: string } | null = null;
export async function getQuote() {
if (cache) return cache;
console.log("\nFetching fresh quote...");
const response = await fetch("https://quotes-github-readme.vercel.app/api");
const svgText = await response.text();
cache = {
quote: svgText.match(/<h3>([\s\S]*?)<\/h3>/)?.[1]?.trim() ?? "",
author: svgText.match(/<p>([\s\S]*?)<\/p>/)?.[1]?.trim() ?? "",
};
console.log("Quote fetched: ", cache);
return cache;
}

View file

@ -2,7 +2,7 @@
import type { PaginateFunction, Page } from "astro"; import type { PaginateFunction, Page } from "astro";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import Layout from "@/layouts/Layout.astro"; import Layout from "@/layouts/Layout.astro";
import BlogPostCard from "@/components/BlogPostCard.astro"; import BlogPostEntry from "@/components/BlogPostEntry.astro";
import PageTitle from "@/components/PageTitle.astro"; import PageTitle from "@/components/PageTitle.astro";
import type { CollectionEntry } from "astro:content"; import type { CollectionEntry } from "astro:content";
import Pagination from "@/components/Pagination.astro"; import Pagination from "@/components/Pagination.astro";
@ -30,11 +30,11 @@ const { page } = Astro.props;
description="Read the latest blog posts by Dhemas Nurjaya" description="Read the latest blog posts by Dhemas Nurjaya"
> >
<PageTitle title="Posts" /> <PageTitle title="Posts" />
<ul class="space-y-4"> <ul class="space-y-6">
{ {
page.data.map((post) => ( page.data.map((post) => (
<li> <li>
<BlogPostCard <BlogPostEntry
id={post.id} id={post.id}
title={post.data.title} title={post.data.title}
date={post.data.date} date={post.data.date}

View file

@ -6,7 +6,8 @@ import {
type RenderResult, type RenderResult,
} from "astro:content"; } from "astro:content";
import Layout from "@/layouts/Layout.astro"; import Layout from "@/layouts/Layout.astro";
import BlogPost from "@/components/BlogPost.astro"; import Tag from "@/components/Tag.astro";
import Markdown from "@/components/Markdown.astro";
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await getCollection("blog"); const posts = await getCollection("blog");
@ -36,12 +37,35 @@ const { post, content } = Astro.props;
title={`${post.data.title} - Dhemas Nurjaya`} title={`${post.data.title} - Dhemas Nurjaya`}
description={post.data.description} description={post.data.description}
> >
<BlogPost <article data-pagefind-body>
title={post.data.title} <header class="mb-12">
description={post.data.description} <h1 class="text-2xl font-bold mb-2">{post.data.title}</h1>
date={post.data.date} <h2 class="text-base mb-2">{post.data.description}</h2>
tags={post.data.tags} <div class="flex gap-4 text-neutral-500 text-sm">
> <time datetime={new Date(post.data.date).toISOString()}>
<content.Content /> {
</BlogPost> post.data.date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})
}
</time>
</div>
</header>
<Markdown>
<content.Content />
</Markdown>
<ul class="flex flex-wrap gap-2">
{
post.data.tags?.map((tag) => (
<li>
<Tag name={tag} variant="small" />
</li>
))
}
</ul>
</article>
</Layout> </Layout>

View file

@ -4,7 +4,10 @@ import Layout from "@/layouts/Layout.astro";
import Search from "astro-pagefind/components/Search"; import Search from "astro-pagefind/components/Search";
--- ---
<Layout title="Dhemas Nurjaya" description="Welcome to my personal website"> <Layout
title="Search - Dhemas Nurjaya"
description="Search posts on Dhemas Nurjaya's website"
>
<PageTitle title="Search" /> <PageTitle title="Search" />
<Search <Search
id="search" id="search"
@ -20,37 +23,40 @@ import Search from "astro-pagefind/components/Search";
<style is:global> <style is:global>
.pagefind-ui { .pagefind-ui {
--pagefind-ui-scale: 1; --pagefind-ui-scale: 0.9;
--pagefind-ui-primary: #a1a1aa; /* zinc-400 */ --pagefind-ui-primary: #a3a3a3; /* neutral-400 */
--pagefind-ui-text: #d4d4d8; /* zinc-300 */ --pagefind-ui-text: #d4d4d8; /* neutral-300 */
--pagefind-ui-background: #18181b; /* zinc-900 */ --pagefind-ui-background: #171717; /* neutral-900 */
--pagefind-ui-border: #3f3f46; /* zinc-700 */ --pagefind-ui-border: #404040; /* neutral-700 */
--pagefind-ui-tag: #27272a; /* zinc-800 */ --pagefind-ui-tag: #262626; /* neutral-800 */
--pagefind-ui-border-width: 1px; --pagefind-ui-border-width: 1px;
--pagefind-ui-border-radius: 0.5rem; --pagefind-ui-border-radius: 0rem;
--pagefind-ui-font: "Noto Sans Variable", sans-serif; --pagefind-ui-font: "JetBrains Mono", monospace;
} }
.pagefind-ui__search-input { .pagefind-ui__search-input {
background: #27272a; /* zinc-800 */ background: #262626; /* neutral-800 */
color: #d4d4d8; /* zinc-300 */ color: #d4d4d8; /* neutral-300 */
border: 1px solid #404040; /* neutral-700 */
} }
.pagefind-ui__search-input::placeholder { .pagefind-ui__search-input::placeholder {
color: #71717a; /* zinc-500 */ color: #737373; /* neutral-500 */
} }
.pagefind-ui__result { .pagefind-ui__result {
border-radius: 0.5rem !important; border-bottom: 1px solid #404040 !important; /* neutral-700 */
padding: 1rem !important; padding: 1rem 0 !important;
background: #27272a !important; /* zinc-800 */ background: transparent !important;
border: none !important; border-top: none !important;
margin-bottom: 1rem !important; border-left: none !important;
border-right: none !important;
margin-bottom: 0 !important;
position: relative !important; position: relative !important;
} }
.pagefind-ui__result:last-child { .pagefind-ui__result:last-child {
margin-bottom: 0 !important; border-bottom: none !important;
} }
.pagefind-ui__result-inner { .pagefind-ui__result-inner {
@ -60,8 +66,14 @@ import Search from "astro-pagefind/components/Search";
} }
.pagefind-ui__result-link { .pagefind-ui__result-link {
color: #d4d4d8; /* zinc-300 */ color: #d4d4d8; /* neutral-300 */
text-decoration: none !important; text-decoration: none !important;
font-weight: 500;
}
.pagefind-ui__result-link:hover {
text-decoration: underline !important;
color: #fafafa; /* neutral-50 */
} }
.pagefind-ui__result-link::after { .pagefind-ui__result-link::after {
@ -75,15 +87,22 @@ import Search from "astro-pagefind/components/Search";
} }
.pagefind-ui__result-title { .pagefind-ui__result-title {
color: #fafafa; /* zinc-50 */ color: #fafafa; /* neutral-50 */
} }
.pagefind-ui__result-excerpt { .pagefind-ui__result-excerpt {
color: #a1a1aa; /* zinc-400 */ color: #a3a3a3; /* neutral-400 */
margin-top: 0.5rem;
} }
.pagefind-ui__message { .pagefind-ui__message {
color: #a1a1aa; /* zinc-400 */ color: #a3a3a3; /* neutral-400 */
padding: 1rem 0;
}
/* Remove default image styling if present, though showImages: false handles most */
.pagefind-ui__result-thumb {
display: none;
} }
</style> </style>

View file

@ -1,6 +1,6 @@
--- ---
import { getCollection, type CollectionEntry } from "astro:content"; import { getCollection, type CollectionEntry } from "astro:content";
import BlogPostCard from "@/components/BlogPostCard.astro"; import BlogPostEntry from "@/components/BlogPostEntry.astro";
import Layout from "@/layouts/Layout.astro"; import Layout from "@/layouts/Layout.astro";
import PageTitle from "@/components/PageTitle.astro"; import PageTitle from "@/components/PageTitle.astro";
import EmptyState from "@/components/EmptyState.astro"; import EmptyState from "@/components/EmptyState.astro";
@ -44,7 +44,7 @@ const { filteredPosts } = Astro.props;
<ul class="space-y-4"> <ul class="space-y-4">
{filteredPosts.map((post) => ( {filteredPosts.map((post) => (
<li> <li>
<BlogPostCard <BlogPostEntry
id={post.id} id={post.id}
title={post.data.title} title={post.data.title}
date={post.data.date} date={post.data.date}

View file

@ -20,7 +20,7 @@ const tags = Array.from(tagsMap.entries()).sort((a, b) =>
<Layout title="Tags - Dhemas Nurjaya" description="Browse blog posts by tags"> <Layout title="Tags - Dhemas Nurjaya" description="Browse blog posts by tags">
<PageTitle title="Tags" /> <PageTitle title="Tags" />
<ul class="flex flex-wrap gap-4"> <ul class="flex flex-wrap gap-4 mt-8">
{ {
tags?.map(([tag, count]) => ( tags?.map(([tag, count]) => (
<li class="mb-4"> <li class="mb-4">

View file

@ -1,15 +1,13 @@
@import "@fontsource-variable/noto-sans";
@import "@fontsource/jetbrains-mono"; @import "@fontsource/jetbrains-mono";
@import "tailwindcss"; @import "tailwindcss";
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
@theme { @theme {
--font-sans: "Noto Sans Variable", sans-serif; --font-default: "JetBrains Mono", monospace;
--font-mono: "JetBrains Mono", monospace;
} }
@layer base { @layer base {
body { body {
@apply bg-zinc-900 text-zinc-300; @apply bg-neutral-900 text-neutral-300;
} }
} }