feat(all): first init
This commit is contained in:
commit
67c6e47f58
34 changed files with 2155 additions and 0 deletions
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# jetbrains setting folder
|
||||||
|
.idea/
|
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["astro-build.astro-vscode"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "./node_modules/.bin/astro dev",
|
||||||
|
"name": "Development server",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"conventionalCommits.scopes": ["workflow", "index", "package"]
|
||||||
|
}
|
22
astro.config.ts
Normal file
22
astro.config.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { defineConfig } from "astro/config";
|
||||||
|
import tailwind from "@astrojs/tailwind";
|
||||||
|
import remarkCallout from "@r4ai/remark-callout";
|
||||||
|
import sitemap from "@astrojs/sitemap";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: "https://blog.z0x.ca",
|
||||||
|
integrations: [
|
||||||
|
tailwind({
|
||||||
|
nesting: true,
|
||||||
|
}),
|
||||||
|
sitemap(),
|
||||||
|
],
|
||||||
|
markdown: {
|
||||||
|
shikiConfig: {
|
||||||
|
theme: "css-variables",
|
||||||
|
},
|
||||||
|
remarkPlugins: [
|
||||||
|
remarkCallout,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
});
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
29
package.json
Normal file
29
package.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "blog.z0x.ca",
|
||||||
|
"type": "module",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"start": "astro dev",
|
||||||
|
"build": "astro check && astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/check": "^0.9.4",
|
||||||
|
"@astrojs/rss": "^4.0.11",
|
||||||
|
"@astrojs/sitemap": "^3.2.1",
|
||||||
|
"@astrojs/tailwind": "^5.1.4",
|
||||||
|
"@fontsource/geist-mono": "^5.1.1",
|
||||||
|
"@fontsource/geist-sans": "^5.1.0",
|
||||||
|
"@r4ai/remark-callout": "^0.6.2",
|
||||||
|
"astro": "^5.1.3",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"tailwind-merge": "^2.6.0",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.16"
|
||||||
|
}
|
||||||
|
}
|
55
public/favicon.svg
Normal file
55
public/favicon.svg
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
id="svg3"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs3"><linearGradient
|
||||||
|
id="linearGradient26"><stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:0.30000001;"
|
||||||
|
offset="0"
|
||||||
|
id="stop26" /><stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:0;"
|
||||||
|
offset="0.9638136"
|
||||||
|
id="stop27" /></linearGradient><radialGradient
|
||||||
|
xlink:href="#linearGradient26"
|
||||||
|
id="radialGradient27"
|
||||||
|
cx="250"
|
||||||
|
cy="250"
|
||||||
|
fx="250"
|
||||||
|
fy="250"
|
||||||
|
r="250"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.6,0,0,0.6,100.00001,100.00002)" /><filter
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter1"
|
||||||
|
x="-0.243"
|
||||||
|
y="-0.243"
|
||||||
|
width="1.486"
|
||||||
|
height="1.486"><feGaussianBlur
|
||||||
|
stdDeviation="30.375"
|
||||||
|
id="feGaussianBlur1" /></filter></defs>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
style="clip-rule:evenodd;fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
id="bg"
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
ry="16.384001" /><circle
|
||||||
|
style="display:inline;fill:url(#radialGradient27);fill-opacity:1;stroke-width:0.300003;filter:url(#filter1)"
|
||||||
|
id="backlight"
|
||||||
|
cx="250"
|
||||||
|
cy="250"
|
||||||
|
r="150"
|
||||||
|
transform="matrix(0.256,0,0,0.256,2.2888184e-6,2.2888184e-6)" /><path
|
||||||
|
d="M 64.00001,6.4 68.608,38.656 c 0.79735,4.95473 3.36351,9.8663 6.912,13.41429 3.54847,3.5487 8.86942,6.52423 13.824,7.32171 L 121.6,64 89.344,68.608 C 84.38942,69.40548 79.06847,72.38102 75.52,75.92971 71.97151,79.4777 69.40535,84.38927 68.608,89.344 L 64.00001,121.6 59.392,89.344 C 58.59465,84.38927 56.02847,79.4777 52.48,75.92971 48.93151,72.38102 43.61058,69.40548 38.656,68.608 L 6.4,64 38.656,59.392 C 43.61058,58.59452 48.93151,55.61899 52.48,52.07029 56.02847,48.5223 58.59465,43.61073 59.392,38.656 Z"
|
||||||
|
style="clip-rule:evenodd;display:inline;fill:#ffffff;fill-rule:evenodd;stroke-width:0.0707615;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
id="star" /></svg>
|
After Width: | Height: | Size: 2.3 KiB |
26
src/components/ArrowCard.astro
Normal file
26
src/components/ArrowCard.astro
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
entry: CollectionEntry<"blog">;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { entry } = Astro.props as {
|
||||||
|
entry: CollectionEntry<"blog">;
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<a href={`/${entry.id}`} class="not-prose group relative flex flex-nowrap rounded-lg border border-black/15 px-4 py-3 pr-10 transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white">
|
||||||
|
<div class="flex flex-1 flex-col truncate">
|
||||||
|
<div class="font-semibold">
|
||||||
|
{entry.data.title}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
{entry.data.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="absolute right-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2">
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"></line>
|
||||||
|
<polyline points="12 5 19 12 12 19" class="-translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0"></polyline>
|
||||||
|
</svg>
|
||||||
|
</a>
|
27
src/components/BackToTop.astro
Normal file
27
src/components/BackToTop.astro
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="back-to-top"
|
||||||
|
class="group relative flex w-fit flex-nowrap rounded border border-black/15 py-1.5 pl-8 pr-3 transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
class="absolute left-2 top-1/2 size-4 -translate-y-1/2 rotate-90 fill-none stroke-current stroke-2"
|
||||||
|
>
|
||||||
|
<line
|
||||||
|
x1="5"
|
||||||
|
y1="12"
|
||||||
|
x2="19"
|
||||||
|
y2="12"
|
||||||
|
class="translate-x-2 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
|
||||||
|
></line>
|
||||||
|
<polyline
|
||||||
|
points="12 5 5 12 12 19"
|
||||||
|
class="translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0"
|
||||||
|
></polyline>
|
||||||
|
</svg>
|
||||||
|
<div class="text-sm">Back to top</div>
|
||||||
|
</button>
|
44
src/components/Callout.astro
Normal file
44
src/components/Callout.astro
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
type: "default" | "info" | "warning" | "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type = "default" } = Astro.props;
|
||||||
|
|
||||||
|
let emoji = "💡";
|
||||||
|
|
||||||
|
if (type === "info") {
|
||||||
|
emoji = "ℹ️";
|
||||||
|
} else if (type === "warning") {
|
||||||
|
emoji = "⚠️";
|
||||||
|
} else if (type === "error") {
|
||||||
|
emoji = "🚨";
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`not-prose callout callout-${type}`}>
|
||||||
|
<span class="emoji pointer-events-none select-none">{emoji}</span>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.callout {
|
||||||
|
@apply relative my-4 flex rounded border border-orange-800 bg-orange-100 p-3 text-orange-950 dark:border-orange-200/20 dark:bg-orange-950/20 dark:text-orange-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
@apply pr-3 text-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-info {
|
||||||
|
@apply border-blue-800 bg-blue-100 text-blue-950 dark:border-blue-200/20 dark:bg-blue-950/20 dark:text-blue-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-warning {
|
||||||
|
@apply border-yellow-800 bg-yellow-100 text-yellow-950 dark:border-yellow-200/20 dark:bg-yellow-950/20 dark:text-yellow-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout-error {
|
||||||
|
@apply border-red-800 bg-red-100 text-red-950 dark:border-red-200/20 dark:bg-red-950/20 dark:text-red-200;
|
||||||
|
}
|
||||||
|
</style>
|
5
src/components/Container.astro
Normal file
5
src/components/Container.astro
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-screen-sm px-3"><slot /></div>
|
52
src/components/Footer.astro
Normal file
52
src/components/Footer.astro
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
import Container from "@components/Container.astro";
|
||||||
|
import BackToTop from "@components/BackToTop.astro";
|
||||||
|
import { SITE } from "@consts";
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer class="animate">
|
||||||
|
<Container>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute -top-12 right-0">
|
||||||
|
<BackToTop />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>© {new Date().getFullYear()} • {SITE.TITLE}</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-1.5">
|
||||||
|
<a aria-label="RSS" href="/rss.xml" target="_blank" class="group flex size-9 items-center justify-center rounded border border-black/15 hover:bg-black/5 focus-visible:bg-black/5 dark:border-white/20 dark:hover:bg-white/5 dark:focus-visible:bg-white/5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="transition-colors duration-300 ease-in-out group-hover:animate-pulse group-hover:stroke-black group-focus-visible:animate-pulse group-focus-visible:stroke-black group-hover:dark:stroke-white dark:group-focus-visible:stroke-white">
|
||||||
|
<path d="M5 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path>
|
||||||
|
<path d="M4 4a16 16 0 0 1 16 16"></path>
|
||||||
|
<path d="M4 11a9 9 0 0 1 9 9"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<button id="light-theme-button" aria-label="Light theme" class="group flex size-9 items-center justify-center rounded border border-black/15 hover:bg-black/5 focus-visible:bg-black/5 dark:border-white/20 dark:hover:bg-white/5 dark:focus-visible:bg-white/5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="transition-colors duration-300 ease-in-out group-hover:animate-pulse group-hover:stroke-black group-focus-visible:animate-pulse group-focus-visible:stroke-black group-hover:dark:stroke-white dark:group-focus-visible:stroke-white">
|
||||||
|
<circle cx="12" cy="12" r="5"></circle>
|
||||||
|
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||||
|
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||||
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||||
|
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||||
|
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||||
|
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||||
|
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||||
|
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button id="dark-theme-button" aria-label="Dark theme" class="group flex size-9 items-center justify-center rounded border border-black/15 hover:bg-black/5 focus-visible:bg-black/5 dark:border-white/20 dark:hover:bg-white/5 dark:focus-visible:bg-white/5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="transition-colors duration-300 ease-in-out group-hover:animate-pulse group-hover:stroke-black group-focus-visible:animate-pulse group-focus-visible:stroke-black group-hover:dark:stroke-white dark:group-focus-visible:stroke-white">
|
||||||
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button id="system-theme-button" aria-label="System theme" class="group flex size-9 items-center justify-center rounded border border-black/15 hover:bg-black/5 focus-visible:bg-black/5 dark:border-white/20 dark:hover:bg-white/5 dark:focus-visible:bg-white/5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="transition-colors duration-300 ease-in-out group-hover:animate-pulse group-hover:stroke-black group-focus-visible:animate-pulse group-focus-visible:stroke-black group-hover:dark:stroke-white dark:group-focus-visible:stroke-white">
|
||||||
|
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||||
|
<line x1="8" y1="21" x2="16" y2="21"></line>
|
||||||
|
<line x1="12" y1="17" x2="12" y2="21"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</footer>
|
17
src/components/FormattedDate.astro
Normal file
17
src/components/FormattedDate.astro
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
date: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { date } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<time datetime={date.toISOString()}>
|
||||||
|
{
|
||||||
|
date.toLocaleDateString("en-SE", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</time>
|
251
src/components/Head.astro
Normal file
251
src/components/Head.astro
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
---
|
||||||
|
import "../styles/global.css";
|
||||||
|
import "../styles/callout.css";
|
||||||
|
|
||||||
|
import { ClientRouter } from "astro:transitions";
|
||||||
|
|
||||||
|
import "@fontsource/geist-sans/100.css";
|
||||||
|
import "@fontsource/geist-sans/200.css";
|
||||||
|
import "@fontsource/geist-sans/300.css";
|
||||||
|
import "@fontsource/geist-sans/400.css";
|
||||||
|
import "@fontsource/geist-sans/500.css";
|
||||||
|
import "@fontsource/geist-sans/600.css";
|
||||||
|
import "@fontsource/geist-sans/700.css";
|
||||||
|
import "@fontsource/geist-sans/800.css";
|
||||||
|
import "@fontsource/geist-sans/900.css";
|
||||||
|
import "@fontsource/geist-mono/100.css";
|
||||||
|
import "@fontsource/geist-mono/200.css";
|
||||||
|
import "@fontsource/geist-mono/300.css";
|
||||||
|
import "@fontsource/geist-mono/400.css";
|
||||||
|
import "@fontsource/geist-mono/500.css";
|
||||||
|
import "@fontsource/geist-mono/600.css";
|
||||||
|
import "@fontsource/geist-mono/700.css";
|
||||||
|
import "@fontsource/geist-mono/800.css";
|
||||||
|
import "@fontsource/geist-mono/900.css";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, description, image = "/blog-placeholder-1.jpg" } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22></text></svg>" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
|
<title>{title}</title>
|
||||||
|
<meta name="title" content={title} />
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content={Astro.url} />
|
||||||
|
<meta property="og:title" content={title} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:image" content={new URL(image, Astro.url)} />
|
||||||
|
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:url" content={Astro.url} />
|
||||||
|
<meta property="twitter:title" content={title} />
|
||||||
|
<meta property="twitter:description" content={description} />
|
||||||
|
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
||||||
|
|
||||||
|
<ClientRouter />
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
function init() {
|
||||||
|
preloadTheme();
|
||||||
|
onScroll();
|
||||||
|
animate();
|
||||||
|
updateThemeButtons();
|
||||||
|
addCopyCodeButtons();
|
||||||
|
setGiscusTheme();
|
||||||
|
|
||||||
|
const backToTop = document.getElementById("back-to-top");
|
||||||
|
backToTop?.addEventListener("click", (event) => scrollToTop(event));
|
||||||
|
|
||||||
|
const backToPrev = document.getElementById("back-to-prev");
|
||||||
|
backToPrev?.addEventListener("click", () => window.history.back());
|
||||||
|
|
||||||
|
const lightThemeButton = document.getElementById("light-theme-button");
|
||||||
|
lightThemeButton?.addEventListener("click", () => {
|
||||||
|
localStorage.setItem("theme", "light");
|
||||||
|
toggleTheme(false);
|
||||||
|
updateThemeButtons();
|
||||||
|
});
|
||||||
|
|
||||||
|
const darkThemeButton = document.getElementById("dark-theme-button");
|
||||||
|
darkThemeButton?.addEventListener("click", () => {
|
||||||
|
localStorage.setItem("theme", "dark");
|
||||||
|
toggleTheme(true);
|
||||||
|
updateThemeButtons();
|
||||||
|
});
|
||||||
|
|
||||||
|
const systemThemeButton = document.getElementById("system-theme-button");
|
||||||
|
systemThemeButton?.addEventListener("click", () => {
|
||||||
|
localStorage.setItem("theme", "system");
|
||||||
|
toggleTheme(window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||||
|
updateThemeButtons();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (event) => {
|
||||||
|
if (localStorage.theme === "system") {
|
||||||
|
toggleTheme(event.matches);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("scroll", onScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateThemeButtons() {
|
||||||
|
const theme = localStorage.getItem("theme");
|
||||||
|
const lightThemeButton = document.getElementById("light-theme-button");
|
||||||
|
const darkThemeButton = document.getElementById("dark-theme-button");
|
||||||
|
const systemThemeButton = document.getElementById("system-theme-button");
|
||||||
|
|
||||||
|
function removeActiveButtonTheme(button) {
|
||||||
|
button?.classList.remove("bg-black/5");
|
||||||
|
button?.classList.remove("dark:bg-white/5");
|
||||||
|
}
|
||||||
|
|
||||||
|
function addActiveButtonTheme(button) {
|
||||||
|
button?.classList.add("bg-black/5");
|
||||||
|
button?.classList.add("dark:bg-white/5");
|
||||||
|
}
|
||||||
|
|
||||||
|
removeActiveButtonTheme(lightThemeButton);
|
||||||
|
removeActiveButtonTheme(darkThemeButton);
|
||||||
|
removeActiveButtonTheme(systemThemeButton);
|
||||||
|
|
||||||
|
if (theme === "light") {
|
||||||
|
addActiveButtonTheme(lightThemeButton);
|
||||||
|
} else if (theme === "dark") {
|
||||||
|
addActiveButtonTheme(darkThemeButton);
|
||||||
|
} else {
|
||||||
|
addActiveButtonTheme(systemThemeButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
const animateElements = document.querySelectorAll(".animate");
|
||||||
|
|
||||||
|
animateElements.forEach((element, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.add("show");
|
||||||
|
}, index * 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScroll() {
|
||||||
|
if (window.scrollY > 0) {
|
||||||
|
document.documentElement.classList.add("scrolled");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("scrolled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToTop(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleTheme(dark) {
|
||||||
|
const css = document.createElement("style");
|
||||||
|
|
||||||
|
css.appendChild(
|
||||||
|
document.createTextNode(
|
||||||
|
`* {
|
||||||
|
-webkit-transition: none !important;
|
||||||
|
-moz-transition: none !important;
|
||||||
|
-o-transition: none !important;
|
||||||
|
-ms-transition: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
document.head.appendChild(css);
|
||||||
|
|
||||||
|
if (dark) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
|
||||||
|
window.getComputedStyle(css).opacity;
|
||||||
|
document.head.removeChild(css);
|
||||||
|
|
||||||
|
setGiscusTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
function preloadTheme() {
|
||||||
|
const userTheme = localStorage.theme;
|
||||||
|
|
||||||
|
if (userTheme === "light" || userTheme === "dark") {
|
||||||
|
toggleTheme(userTheme === "dark");
|
||||||
|
} else {
|
||||||
|
toggleTheme(window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCopyCodeButtons() {
|
||||||
|
let copyButtonLabel = "📋";
|
||||||
|
let codeBlocks = Array.from(document.querySelectorAll("pre"));
|
||||||
|
|
||||||
|
async function copyCode(codeBlock, copyButton) {
|
||||||
|
const codeText = codeBlock.innerText;
|
||||||
|
const buttonText = copyButton.innerText;
|
||||||
|
const textToCopy = codeText.replace(buttonText, "");
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(textToCopy);
|
||||||
|
copyButton.innerText = "✅";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyButton.innerText = copyButtonLabel;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let codeBlock of codeBlocks) {
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
wrapper.style.position = "relative";
|
||||||
|
|
||||||
|
const copyButton = document.createElement("button");
|
||||||
|
copyButton.innerText = copyButtonLabel;
|
||||||
|
copyButton.classList = "copy-code";
|
||||||
|
|
||||||
|
codeBlock.setAttribute("tabindex", "0");
|
||||||
|
codeBlock.appendChild(copyButton);
|
||||||
|
|
||||||
|
codeBlock.parentNode.insertBefore(wrapper, codeBlock);
|
||||||
|
wrapper.appendChild(codeBlock);
|
||||||
|
|
||||||
|
copyButton?.addEventListener("click", async () => {
|
||||||
|
await copyCode(codeBlock, copyButton);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setGiscusTheme = () => {
|
||||||
|
const giscus = document.querySelector(".giscus-frame");
|
||||||
|
|
||||||
|
const isDark = document.documentElement.classList.contains("dark");
|
||||||
|
|
||||||
|
if (giscus) {
|
||||||
|
const url = new URL(giscus.src);
|
||||||
|
url.searchParams.set("theme", isDark ? "dark" : "light");
|
||||||
|
giscus.src = url.toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => init());
|
||||||
|
document.addEventListener("astro:after-swap", () => init());
|
||||||
|
preloadTheme();
|
||||||
|
</script>
|
31
src/components/Link.astro
Normal file
31
src/components/Link.astro
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
import { cn } from "@lib/utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
href: string;
|
||||||
|
external?: boolean;
|
||||||
|
underline?: boolean;
|
||||||
|
group?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
href,
|
||||||
|
external,
|
||||||
|
underline = true,
|
||||||
|
group = false,
|
||||||
|
...rest
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target={external ? "_blank" : "_self"}
|
||||||
|
class={cn(
|
||||||
|
"inline-block decoration-black/30 dark:decoration-white/30 hover:decoration-black/50 focus-visible:decoration-black/50 dark:hover:decoration-white/50 dark:focus-visible:decoration-white/50 text-current hover:text-black focus-visible:text-black dark:hover:text-white dark:focus-visible:text-white transition-colors duration-300 ease-in-out",
|
||||||
|
underline && "underline underline-offset-[3px]",
|
||||||
|
group && "group"
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</a>
|
33
src/components/PostNavigation.astro
Normal file
33
src/components/PostNavigation.astro
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
const { prevPost, nextPost } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-1.5 sm:gap-3">
|
||||||
|
{
|
||||||
|
prevPost?.id ? (
|
||||||
|
<a href={`/${prevPost?.id}`} class="group relative flex flex-nowrap rounded-lg border border-black/15 px-4 py-3 pl-10 no-underline transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="absolute left-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2">
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100" />
|
||||||
|
<polyline points="12 5 5 12 12 19" class="translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0" />
|
||||||
|
</svg>
|
||||||
|
<div class="flex items-center text-sm">{prevPost?.data.title}</div>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div class="invisible" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
nextPost?.id ? (
|
||||||
|
<a href={`/${nextPost?.id}`} class="group relative flex flex-grow flex-row-reverse flex-nowrap rounded-lg border border-black/15 px-4 py-4 pr-10 no-underline transition-colors duration-300 ease-in-out hover:bg-black/5 hover:text-black focus-visible:bg-black/5 focus-visible:text-black dark:border-white/20 dark:hover:bg-white/5 dark:hover:text-white dark:focus-visible:bg-white/5 dark:focus-visible:text-white">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="absolute right-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2">
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100 group-focus-visible:translate-x-0 group-focus-visible:scale-x-100" />
|
||||||
|
<polyline points="12 5 19 12 12 19" class="-translate-x-1 transition-transform duration-300 ease-in-out group-hover:translate-x-0 group-focus-visible:translate-x-0" />
|
||||||
|
</svg>
|
||||||
|
<div class="flex items-center text-sm">{nextPost?.data.title}</div>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div class="invisible" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
50
src/components/TableOfContents.astro
Normal file
50
src/components/TableOfContents.astro
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
import TableOfContentsHeading from "./TableOfContentsHeading.astro";
|
||||||
|
|
||||||
|
const { headings } = Astro.props;
|
||||||
|
const toc = buildToc(headings);
|
||||||
|
|
||||||
|
export interface Heading {
|
||||||
|
depth: number;
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildToc(headings: Heading[]) {
|
||||||
|
const toc: Heading[] = [];
|
||||||
|
const parentHeadings = new Map();
|
||||||
|
headings.forEach((h) => {
|
||||||
|
const heading = { ...h, subheadings: [] };
|
||||||
|
parentHeadings.set(heading.depth, heading);
|
||||||
|
if (heading.depth === 2) {
|
||||||
|
toc.push(heading);
|
||||||
|
} else {
|
||||||
|
parentHeadings.get(heading.depth - 1).subheadings.push(heading);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return toc;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<details open class="animate rounded-lg border border-black/15 dark:border-white/20">
|
||||||
|
<summary>Table of Contents</summary>
|
||||||
|
<nav class="">
|
||||||
|
<ul class="py-3">
|
||||||
|
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
summary {
|
||||||
|
@apply cursor-pointer rounded-t-lg px-3 py-1.5 font-medium transition-colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary:hover {
|
||||||
|
@apply bg-black/5 dark:bg-white/5;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] summary {
|
||||||
|
@apply bg-black/5 dark:bg-white/5;
|
||||||
|
}
|
||||||
|
</style>
|
21
src/components/TableOfContentsHeading.astro
Normal file
21
src/components/TableOfContentsHeading.astro
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
import type { Heading } from "./TableOfContents.astro";
|
||||||
|
import Link from "./Link.astro";
|
||||||
|
|
||||||
|
const { heading } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<li class="list-inside list-disc px-6 py-1.5 text-sm">
|
||||||
|
<Link href={"#" + heading.id} underline>
|
||||||
|
{heading.text}
|
||||||
|
</Link>
|
||||||
|
{
|
||||||
|
heading.subheadings.length > 0 && (
|
||||||
|
<ul class="translate-x-3">
|
||||||
|
{heading.subheadings.map((subheading: Heading) => (
|
||||||
|
<Astro.self heading={subheading} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</li>
|
9
src/consts.ts
Normal file
9
src/consts.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import type { Metadata, Site } from "@types";
|
||||||
|
export const SITE: Site = {
|
||||||
|
TITLE: "z0x",
|
||||||
|
DESCRIPTION: "z0x's blog",
|
||||||
|
};
|
||||||
|
export const HOME: Metadata = {
|
||||||
|
TITLE: "blog",
|
||||||
|
};
|
||||||
|
|
14
src/content.config.ts
Normal file
14
src/content.config.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { defineCollection, z } from "astro:content";
|
||||||
|
import { glob } from 'astro/loaders';
|
||||||
|
|
||||||
|
const blog = defineCollection({
|
||||||
|
loader: glob({ pattern: '**/*.{md,mdx}', base: "./src/content" }),
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
date: z.coerce.date(),
|
||||||
|
draft: z.boolean().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { blog };
|
644
src/content/artix-install-guide/index.md
Normal file
644
src/content/artix-install-guide/index.md
Normal file
|
@ -0,0 +1,644 @@
|
||||||
|
---
|
||||||
|
title: "Artix Linux install guide"
|
||||||
|
description: "Guide to installing Artix Linux with OpenRC and full disk encryption for UEFI and BIOS systems."
|
||||||
|
date: "2025-01-07"
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The goal of this guide is to set up a minimal installation of **Artix Linux** with **OpenRC** as an init system and **full disk encryption** on an **UEFI** or **BIOS** system. This guide is meant to be read alongside the wiki's.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acquire an installation image
|
||||||
|
|
||||||
|
1. Go to the downloads page https://artixlinux.org/download.php
|
||||||
|
2. Scroll down to the **Official ISO images** section.
|
||||||
|
3. Under the **base** section, download the file starting with `artix-base-openrc` and ending with `.iso`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prepare an installation medium
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Use [Rufus](https://rufus.ie/en), here is a [guide](https://www.wikihow.com/Use-Rufus) if you need it.
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
1. Insert a USB flash drive into your PC with at least 2 GB of space available on it.
|
||||||
|
2. Find the corresponding block device for the flash drive in `/dev` folder. Usually it is `/dev/sdb`.
|
||||||
|
3. Burn the image to the flash drive (assuming your flash drive is /dev/sdb and that your terminal is opened in the directory of the image)
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo dd bs=4M if=./artix-base-openrc-YYYY.MM.DD-x86_64.iso of=/dev/sdb conv=fsync oflag=direct status=progress
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Boot the live environment
|
||||||
|
|
||||||
|
> [!Info]
|
||||||
|
>Artix Linux installation images do not support Secure Boot. You will need to disable Secure Boot to boot the installation medium.
|
||||||
|
|
||||||
|
1. Power off your PC.
|
||||||
|
2. Insert the flash drive into the computer on which you are installing Artix Linux.
|
||||||
|
3. Power on your PC and press _boot menu_ key.
|
||||||
|
4. Boot the installation medium.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enter the live environment
|
||||||
|
1. Login with the default credentials.
|
||||||
|
* Username: `artix`
|
||||||
|
* Password: `artix`
|
||||||
|
|
||||||
|
2. Switch to the root user
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>When encountering a code block as below throughout this guide, execute the commands within it directly in the terminal.
|
||||||
|
|
||||||
|
> [!Info]
|
||||||
|
>When prompted for a password, enter `artix`
|
||||||
|
|
||||||
|
```
|
||||||
|
su -
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connect to the internet
|
||||||
|
|
||||||
|
### Via Ethernet
|
||||||
|
|
||||||
|
Connect the computer via an Ethernet cable
|
||||||
|
|
||||||
|
### Via WiFi
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo rfkill unblock wifi
|
||||||
|
sudo ip link set wlan0 up
|
||||||
|
connmanctl
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!Tip]
|
||||||
|
>Network names can be tab-completed.
|
||||||
|
|
||||||
|
```
|
||||||
|
agent on
|
||||||
|
scan wifi
|
||||||
|
services
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!example]
|
||||||
|
>connect wifi_dc85de828967_38303944616e69656c73_managed_psk
|
||||||
|
|
||||||
|
```
|
||||||
|
connect {WiFi name}
|
||||||
|
quit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify internet connectivity
|
||||||
|
|
||||||
|
Check for internet
|
||||||
|
```
|
||||||
|
ping artixlinux.org
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update the system clock
|
||||||
|
|
||||||
|
Activate the NTP daemon to synchronize the computer's real-time clock:
|
||||||
|
```
|
||||||
|
rc-service ntpd start
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partition the disk
|
||||||
|
|
||||||
|
1. Install `gdisk`.
|
||||||
|
```
|
||||||
|
pacman -Sy gdisk
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Partition your drive. You can find your drive name using the `lsblk` command.
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
> I will be using `nvme0n1` as my drive throughout this guide, please adapt it to your disk name.
|
||||||
|
> If you have an hdd, your drive name may ressemble something like `sda`.
|
||||||
|
|
||||||
|
```
|
||||||
|
gdisk /dev/nvme0n1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Delete any existing partitions
|
||||||
|
```
|
||||||
|
Command (m for help): d
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create a boot partition
|
||||||
|
```
|
||||||
|
Command (m for help): n
|
||||||
|
Partition number (1-128, default 1):
|
||||||
|
First sector (...):
|
||||||
|
Last sector (...): +512M
|
||||||
|
Hex code or GUID (...): ef00
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create a root partition
|
||||||
|
```
|
||||||
|
Command (m for help): n
|
||||||
|
Partition number (2-128, default 1):
|
||||||
|
First sector (...):
|
||||||
|
Last sector (...):
|
||||||
|
Hex code or GUID (...): 8300
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Save the changes
|
||||||
|
```
|
||||||
|
Command (m for help): w
|
||||||
|
Do you want to proceed? (Y/N): y
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Verify partitioning
|
||||||
|
```
|
||||||
|
lsblk
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>It should look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
|
||||||
|
nvme0n1 259:0 0 465,8G 0 disk
|
||||||
|
├─nvme0n1p1 259:1 0 512M 0 part
|
||||||
|
└─nvme0n1p2 259:2 0 465,3G 0 part
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Encrypt root partition
|
||||||
|
|
||||||
|
1. Encrypt your root partition.
|
||||||
|
|
||||||
|
> [!Tip]
|
||||||
|
>Make sure to to enter a secure passphrase and to write it down in a secure place as you will not be able to change it later
|
||||||
|
|
||||||
|
```
|
||||||
|
cryptsetup luksFormat /dev/nvme0n1p2
|
||||||
|
Are you sure (Type `yes` in capital letters): YES
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open the encrypted partition
|
||||||
|
```
|
||||||
|
cryptsetup open /dev/nvme0n1p2 root
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Create file systems
|
||||||
|
|
||||||
|
1. Create the boot file system
|
||||||
|
```
|
||||||
|
mkfs.fat -F32 /dev/nvme0n1p1
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Create the root file system
|
||||||
|
```
|
||||||
|
mkfs.ext4 /dev/mapper/root
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mount file systems
|
||||||
|
|
||||||
|
1. Mount the root file system
|
||||||
|
```
|
||||||
|
mount /dev/mapper/root /mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Mount the boot file system
|
||||||
|
```
|
||||||
|
mount -m /dev/nvme0n1p1 /mnt/boot
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Verify mounting
|
||||||
|
```
|
||||||
|
lsblk
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>It should look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
|
||||||
|
nvme0n1 259:0 0 465,8G 0 disk
|
||||||
|
├─nvme0n1p1 259:1 0 512M 0 part /mnt/boot
|
||||||
|
└─nvme0n1p2 259:2 0 465,3G 0 part
|
||||||
|
└─root 254:0 0 465,2G 0 crypt /mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Install Essentials
|
||||||
|
|
||||||
|
Install the base system, kernel, init system and other essential packages.
|
||||||
|
|
||||||
|
```
|
||||||
|
basestrap /mnt base linux linux-firmware openrc elogind-openrc cryptsetup cryptsetup-openrc efibootmgr doas nano
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
> Install AMD or Intel microcode, depending on your system's CPU
|
||||||
|
|
||||||
|
### AMD CPU
|
||||||
|
|
||||||
|
Install AMD CPU microcode updates
|
||||||
|
|
||||||
|
```
|
||||||
|
basestrap /mnt amd-ucode
|
||||||
|
```
|
||||||
|
|
||||||
|
### Intel CPU
|
||||||
|
|
||||||
|
Install Intel CPU microcode updates
|
||||||
|
|
||||||
|
```
|
||||||
|
basestrap /mnt intel-ucode
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network stack
|
||||||
|
|
||||||
|
```
|
||||||
|
basestrap /mnt wpa_supplicant networkmanager networkmanager-openrc iwd iwd-openrc
|
||||||
|
rc-update add NetworkManager
|
||||||
|
rc-update add iwd
|
||||||
|
|
||||||
|
cat << EOF >> /etc/NetworkManager/conf.d/wifi_backend.conf
|
||||||
|
[device]
|
||||||
|
wifi.backend=iwd
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
#### MAC randomization
|
||||||
|
|
||||||
|
> [!Info]
|
||||||
|
>MAC randomization can be used for increased privacy by not disclosing your real MAC address to the WiFi network.
|
||||||
|
|
||||||
|
```
|
||||||
|
cat << EOF >> /etc/NetworkManager/conf.d/00-macrandomize.conf
|
||||||
|
[device-mac-randomization]
|
||||||
|
wifi.scan-rand-mac-address=yes
|
||||||
|
|
||||||
|
[connection-mac-randomization]
|
||||||
|
ethernet.cloned-mac-address=random
|
||||||
|
wifi.cloned-mac-address=random
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Generate File System Table
|
||||||
|
|
||||||
|
```
|
||||||
|
fstabgen -U /mnt >> /mnt/etc/fstab
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Switch to New Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
artix-chroot /mnt bash
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Localization
|
||||||
|
|
||||||
|
### Set the locale
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>Feel free to change `en_DK.UTF-8` to your preferred locale such as `en_US.UTF-8` or `en_GB.UTF-8`
|
||||||
|
|
||||||
|
1. Un-comment `en_DK.UTF-8`
|
||||||
|
```
|
||||||
|
nano /etc/locale.gen
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Generate locales.
|
||||||
|
```
|
||||||
|
locale-gen
|
||||||
|
echo 'LANG=en_DK.UTF-8' > /etc/locale.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Set the time zone
|
||||||
|
|
||||||
|
> [!example]
|
||||||
|
>`ln -sf /usr/share/zoneinfo/America/Toronto /etc/localtime`
|
||||||
|
|
||||||
|
```
|
||||||
|
ln -sf /usr/share/zoneinfo/Region/City /etc/localtime
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Set hardware clock from system clock
|
||||||
|
|
||||||
|
```
|
||||||
|
hwclock --systohc
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hostname and Host
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>Change `artix` to your desired hostname in all of the following commands
|
||||||
|
|
||||||
|
```
|
||||||
|
echo 'artix' > /etc/hostname
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Edit `/etc/conf.d/hostname`
|
||||||
|
```
|
||||||
|
nano /etc/conf.d/hostname
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Replace `hostname="localhost"` with `hostname="artix"`
|
||||||
|
|
||||||
|
3. Edit `/etc/hosts`
|
||||||
|
```
|
||||||
|
nano /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Add the following:
|
||||||
|
```
|
||||||
|
127.0.0.1 localhost
|
||||||
|
::1 localhost
|
||||||
|
127.0.1.1 artix.localdomain artix
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Initramfs
|
||||||
|
|
||||||
|
1. Edit `/etc/mkinitcpio.conf`
|
||||||
|
```
|
||||||
|
nano /etc/mkinitcpio.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
2. In the `HOOKS` array, add `encrypt` between `block` and `filesystems`
|
||||||
|
```
|
||||||
|
mkinitcpio -P
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>It should look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
HOOKS=(base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt filesystems fsck)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Add a user
|
||||||
|
|
||||||
|
1. Set the root password.
|
||||||
|
```
|
||||||
|
passwd
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a user and set his password.
|
||||||
|
|
||||||
|
> [!Tip]
|
||||||
|
>Change `artix` to your desired username
|
||||||
|
|
||||||
|
```
|
||||||
|
useradd -m artix
|
||||||
|
passwd artix
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configure doas
|
||||||
|
|
||||||
|
1. Create the config file.
|
||||||
|
```
|
||||||
|
touch /etc/doas.conf
|
||||||
|
chown -c root:root /etc/doas.conf
|
||||||
|
chmod -c 0400 /etc/doas.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `/etc/doas.conf`
|
||||||
|
```
|
||||||
|
nano /etc/doas.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add the following:
|
||||||
|
```
|
||||||
|
permit artix as root
|
||||||
|
permit nopass artix as root cmd pacman
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Boot Loader
|
||||||
|
### Check for UEFI support
|
||||||
|
|
||||||
|
> [!Tip]
|
||||||
|
>If you see a bunch of files listed, use EFISTUB.
|
||||||
|
>If you do not see a bunch of files listed, your system does not support UEFI and you should use GRUB.
|
||||||
|
|
||||||
|
```
|
||||||
|
ls /sys/firmware/efi/efivars
|
||||||
|
```
|
||||||
|
|
||||||
|
### EFISTUB
|
||||||
|
|
||||||
|
1. Get the UUID of your root partition.
|
||||||
|
```
|
||||||
|
blkid -s UUID -o value /dev/nvme0n1p2
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a boot entry
|
||||||
|
|
||||||
|
> [!Tip]
|
||||||
|
>Replace xxxx with the UUID that you obtained earlier.
|
||||||
|
>Replace `intel-ucode.img` with `amd-ucode.img` if you have an AMD CPU
|
||||||
|
|
||||||
|
```
|
||||||
|
efibootmgr -c -d /dev/nvme0n1 -p 1 -l /vmlinuz-linux -L "Artix" -u "cryptdevice=UUID=xxxx:root root=/dev/mapper/root rw initrd=\intel-ucode.img initrd=\initramfs-linux.img loglevel=3 quiet"
|
||||||
|
```
|
||||||
|
|
||||||
|
### GRUB
|
||||||
|
|
||||||
|
1. Install grub on your boot partition
|
||||||
|
```
|
||||||
|
pacman -S grub
|
||||||
|
grub-install /dev/sda
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Get the UUID of your root partition.
|
||||||
|
```
|
||||||
|
blkid -s UUID -o value /dev/nvme0n1p2
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Edit `/etc/default/grub`
|
||||||
|
```
|
||||||
|
nano /etc/default/grub
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Add the following to the `GRUB_CMDLINE_LINUX` line, where xxxx is the UUID that you obtained earlier.
|
||||||
|
```
|
||||||
|
cryptdevice=UUID=xxxx:root root=/dev/mapper/root
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>It should look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
GRUB_CMDLINE_LINUX="cryptdevice=UUID=550e8400-e29b-41d4-a716-446655440000:root root=/dev/mapper/root"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Un-comment `#GRUB_ENABLE_CRYPTODISK=y`
|
||||||
|
|
||||||
|
6. Generate the config file.
|
||||||
|
```
|
||||||
|
grub-mkconfig -o /boot/grub/grub.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reboot
|
||||||
|
|
||||||
|
1. You can reboot and enter into your new installation.
|
||||||
|
```
|
||||||
|
exit
|
||||||
|
umount -R /mnt
|
||||||
|
reboot now
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!Note]
|
||||||
|
>Unplug your flash drive after the screen turns black.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post install
|
||||||
|
|
||||||
|
You will now be greeted with a similar screen as when you first booted from the flash drive.
|
||||||
|
Login using the credentials that you set, if you followed the example your username would be `artix`.
|
||||||
|
|
||||||
|
### Add arch repositories and sort for fastest mirrors
|
||||||
|
#### Add arch extra repository
|
||||||
|
|
||||||
|
1. Install packages
|
||||||
|
```
|
||||||
|
doas pacman -Syu artix-archlinux-support curl
|
||||||
|
doas pacman-key --populate archlinux
|
||||||
|
doas sh -c "curl https://archlinux.org/mirrorlist/all -o /etc/pacman.d/mirrorlist-arch"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `/etc/pacman.d/mirrorlist-arch`
|
||||||
|
```
|
||||||
|
doas nano /etc/pacman.d/mirrorlist-arch
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Un-comment the first server entries under the worldwide section
|
||||||
|
|
||||||
|
4. Edit `/etc/pacman.conf`
|
||||||
|
```
|
||||||
|
doas nano /etc/pacman.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Add the following to the bottom of the file
|
||||||
|
```
|
||||||
|
##Arch
|
||||||
|
[extra]
|
||||||
|
Include = /etc/pacman.d/mirrorlist-arch
|
||||||
|
|
||||||
|
##[multilib]
|
||||||
|
##Include = /etc/pacman.d/mirrorlist-arch
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sort for fastest mirrors
|
||||||
|
|
||||||
|
```
|
||||||
|
doas pacman -Syu reflector pacman-contrib
|
||||||
|
doas reflector --verbose -p https -l 30 -f 5 --sort rate --save /etc/pacman.d/mirrorlist-arch
|
||||||
|
doas sh -c "curl https://gitea.artixlinux.org/packages/artix-mirrorlist/raw/branch/master/mirrorlist -o /etc/pacman.d/mirrorlist.bak"
|
||||||
|
doas sh -c "rankmirrors -v -n 5 /etc/pacman.d/mirrorlist.bak > /etc/pacman.d/mirrorlist"
|
||||||
|
```
|
||||||
|
|
||||||
|
### AUR
|
||||||
|
#### Install Paru
|
||||||
|
|
||||||
|
```
|
||||||
|
doas pacman -S --needed base-devel
|
||||||
|
git clone https://aur.archlinux.org/paru.git
|
||||||
|
cd paru
|
||||||
|
makepkg -si
|
||||||
|
cd ..
|
||||||
|
rm -rf paru
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Replace sudo with doas
|
||||||
|
|
||||||
|
```
|
||||||
|
doas pacman -Rdd sudo
|
||||||
|
doas ln -s /usr/bin/doas /usr/bin/sudo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Laptop power profiles
|
||||||
|
|
||||||
|
Install and enable the powerprofiles daemon
|
||||||
|
|
||||||
|
```
|
||||||
|
doas pacman -S power-profiles-daemon power-profiles-daemon-openrc
|
||||||
|
doas rc-update add power-profiles-daemon
|
||||||
|
doas rc-service power-profiles-daemon start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add swap
|
||||||
|
|
||||||
|
```
|
||||||
|
doas fallocate -l 4G /swapfile
|
||||||
|
doas chmod 600 /swapfile
|
||||||
|
daos mkswap /swapfile
|
||||||
|
doas swapon /swapfile
|
||||||
|
doas cp /etc/fstab /etc/fstab.bak
|
||||||
|
echo '/swapfile none swap sw 0 0' | doas tee -a /etc/fstab
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-mount an external LUKS encrypted drive
|
||||||
|
|
||||||
|
```
|
||||||
|
doas fdisk /dev/sdb
|
||||||
|
>g, n, w
|
||||||
|
|
||||||
|
doas cryptsetup luksFormat /dev/sdb1
|
||||||
|
doas cryptsetup luksOpen /dev/sdb1 hdd1
|
||||||
|
doas mkfs.ext4 /dev/mapper/hdd1
|
||||||
|
doas mkdir /mnt/hdd1
|
||||||
|
doas mount /dev/mapper/hdd1 /mnt/hdd1
|
||||||
|
doas chown vega:vega /mnt/hdd1
|
||||||
|
doas dd if=/dev/urandom of=/root/keyfile_hdd1 bs=512 count=4
|
||||||
|
doas chmod 0400 /root/keyfile_hdd1
|
||||||
|
doas cryptsetup luksAddKey /dev/sdb1 /root/keyfile_hdd1
|
||||||
|
UUID=$(doas blkid -s UUID -o value /dev/sdb1)
|
||||||
|
|
||||||
|
doas sh -c "cat << EOF >> /etc/conf.d/dmcrypt
|
||||||
|
target=hdd1
|
||||||
|
source=UUID='$UUID'
|
||||||
|
key=/root/keyfile_hdd1
|
||||||
|
wait=2
|
||||||
|
EOF"
|
||||||
|
|
||||||
|
doas rc-update add dmcrypt boot
|
||||||
|
doas reboot
|
||||||
|
```
|
55
src/favicon.svg
Normal file
55
src/favicon.svg
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
id="svg3"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs3"><linearGradient
|
||||||
|
id="linearGradient26"><stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:0.30000001;"
|
||||||
|
offset="0"
|
||||||
|
id="stop26" /><stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:0;"
|
||||||
|
offset="0.9638136"
|
||||||
|
id="stop27" /></linearGradient><radialGradient
|
||||||
|
xlink:href="#linearGradient26"
|
||||||
|
id="radialGradient27"
|
||||||
|
cx="250"
|
||||||
|
cy="250"
|
||||||
|
fx="250"
|
||||||
|
fy="250"
|
||||||
|
r="250"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.6,0,0,0.6,100.00001,100.00002)" /><filter
|
||||||
|
style="color-interpolation-filters:sRGB"
|
||||||
|
id="filter1"
|
||||||
|
x="-0.243"
|
||||||
|
y="-0.243"
|
||||||
|
width="1.486"
|
||||||
|
height="1.486"><feGaussianBlur
|
||||||
|
stdDeviation="30.375"
|
||||||
|
id="feGaussianBlur1" /></filter></defs>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
style="clip-rule:evenodd;fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
id="bg"
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
ry="16.384001" /><circle
|
||||||
|
style="display:inline;fill:url(#radialGradient27);fill-opacity:1;stroke-width:0.300003;filter:url(#filter1)"
|
||||||
|
id="backlight"
|
||||||
|
cx="250"
|
||||||
|
cy="250"
|
||||||
|
r="150"
|
||||||
|
transform="matrix(0.256,0,0,0.256,2.2888184e-6,2.2888184e-6)" /><path
|
||||||
|
d="M 64.00001,6.4 68.608,38.656 c 0.79735,4.95473 3.36351,9.8663 6.912,13.41429 3.54847,3.5487 8.86942,6.52423 13.824,7.32171 L 121.6,64 89.344,68.608 C 84.38942,69.40548 79.06847,72.38102 75.52,75.92971 71.97151,79.4777 69.40535,84.38927 68.608,89.344 L 64.00001,121.6 59.392,89.344 C 58.59465,84.38927 56.02847,79.4777 52.48,75.92971 48.93151,72.38102 43.61058,69.40548 38.656,68.608 L 6.4,64 38.656,59.392 C 43.61058,58.59452 48.93151,55.61899 52.48,52.07029 56.02847,48.5223 58.59465,43.61073 59.392,38.656 Z"
|
||||||
|
style="clip-rule:evenodd;display:inline;fill:#ffffff;fill-rule:evenodd;stroke-width:0.0707615;stroke-linejoin:round;stroke-miterlimit:2"
|
||||||
|
id="star" /></svg>
|
After Width: | Height: | Size: 2.3 KiB |
24
src/layouts/Layout.astro
Normal file
24
src/layouts/Layout.astro
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
import Head from "@components/Head.astro";
|
||||||
|
import Footer from "@components/Footer.astro";
|
||||||
|
import { SITE } from "@consts";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { title, description } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<Head title={`${title} | ${SITE.TITLE}`} description={description} />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
13
src/lib/utils.ts
Normal file
13
src/lib/utils.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readingTime(html: string) {
|
||||||
|
const textOnly = html.replace(/<[^>]+>/g, "");
|
||||||
|
const wordCount = textOnly.split(/\s+/).length;
|
||||||
|
const readingTimeMinutes = (wordCount / 200 + 1).toFixed();
|
||||||
|
return `${readingTimeMinutes} min read`;
|
||||||
|
}
|
13
src/pages/404.astro
Normal file
13
src/pages/404.astro
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
import Layout from "@layouts/Layout.astro";
|
||||||
|
import Container from "@components/Container.astro";
|
||||||
|
import { SITE } from "@consts";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="404" description={SITE.DESCRIPTION}>
|
||||||
|
<Container>
|
||||||
|
<div class="text-center">
|
||||||
|
<h4 class="animate text-2xl font-semibold text-black dark:text-white">404: Page not found</h4>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
68
src/pages/[...id].astro
Normal file
68
src/pages/[...id].astro
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
import { type CollectionEntry, getCollection, render } from "astro:content";
|
||||||
|
import Layout from "@layouts/Layout.astro";
|
||||||
|
import Container from "@components/Container.astro";
|
||||||
|
import FormattedDate from "@components/FormattedDate.astro";
|
||||||
|
import { readingTime } from "@lib/utils";
|
||||||
|
import PostNavigation from "@components/PostNavigation.astro";
|
||||||
|
import TableOfContents from "@components/TableOfContents.astro";
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = (await getCollection("blog")).filter((post) => !post.data.draft).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||||
|
return posts.map((post) => ({
|
||||||
|
params: { id: post.id },
|
||||||
|
props: post,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
type Props = CollectionEntry<"blog">;
|
||||||
|
|
||||||
|
const posts = (await getCollection("blog")).filter((post) => !post.data.draft).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||||
|
|
||||||
|
function getNextPost() {
|
||||||
|
let postIndex;
|
||||||
|
for (const post of posts) {
|
||||||
|
if (post.id === Astro.params.id) {
|
||||||
|
postIndex = posts.indexOf(post);
|
||||||
|
return posts[postIndex + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrevPost() {
|
||||||
|
let postIndex;
|
||||||
|
for (const post of posts) {
|
||||||
|
if (post.id === Astro.params.id) {
|
||||||
|
postIndex = posts.indexOf(post);
|
||||||
|
return posts[postIndex - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPost = getNextPost();
|
||||||
|
const prevPost = getPrevPost();
|
||||||
|
|
||||||
|
const post = Astro.props;
|
||||||
|
const { Content, headings } = await render(post);
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={post.data.title} description={post.data.description}>
|
||||||
|
<Container>
|
||||||
|
<div class="my-10 space-y-1">
|
||||||
|
<div class="animate flex items-center gap-1.5">
|
||||||
|
<div class="font-base text-sm">
|
||||||
|
<FormattedDate date={post.data.date} />
|
||||||
|
</div>
|
||||||
|
•
|
||||||
|
{post.body && <div class="font-base text-sm">{readingTime(post.body)}</div>}
|
||||||
|
</div>
|
||||||
|
<h1 class="animate text-3xl font-semibold text-black dark:text-white">
|
||||||
|
{post.data.title}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{headings.length > 0 && <TableOfContents headings={headings} />}
|
||||||
|
<article class="animate">
|
||||||
|
<Content />
|
||||||
|
<PostNavigation prevPost={prevPost} nextPost={nextPost} />
|
||||||
|
</article>
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
52
src/pages/index.astro
Normal file
52
src/pages/index.astro
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
import { type CollectionEntry, getCollection } from "astro:content";
|
||||||
|
import Layout from "@layouts/Layout.astro";
|
||||||
|
import Container from "@components/Container.astro";
|
||||||
|
import ArrowCard from "@components/ArrowCard.astro";
|
||||||
|
import { SITE } from "@consts";
|
||||||
|
import { HOME } from "@consts";
|
||||||
|
|
||||||
|
const data = (await getCollection("blog")).filter((post) => !post.data.draft).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
|
||||||
|
|
||||||
|
type Acc = {
|
||||||
|
[year: string]: CollectionEntry<"blog">[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const posts = data.reduce((acc: Acc, post) => {
|
||||||
|
const year = post.data.date.getFullYear().toString();
|
||||||
|
if (!acc[year]) {
|
||||||
|
acc[year] = [];
|
||||||
|
}
|
||||||
|
acc[year].push(post);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a));
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={HOME.TITLE} description={SITE.DESCRIPTION}>
|
||||||
|
<Container>
|
||||||
|
<aside>
|
||||||
|
<div class="space-y-10">
|
||||||
|
<div class="space-y-4">
|
||||||
|
{
|
||||||
|
years.map((year) => (
|
||||||
|
<section class="animate space-y-4">
|
||||||
|
<div class="font-semibold text-black dark:text-white">{year}</div>
|
||||||
|
<div>
|
||||||
|
<ul class="not-prose flex flex-col gap-4">
|
||||||
|
{posts[year].map((post) => (
|
||||||
|
<li>
|
||||||
|
<ArrowCard entry={post} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
24
src/pages/rss.xml.js
Normal file
24
src/pages/rss.xml.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import rss from "@astrojs/rss";
|
||||||
|
import { SITE } from "@consts";
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
|
||||||
|
export async function GET(context) {
|
||||||
|
const blog = (await getCollection("blog")).filter((post) => !post.data.draft);
|
||||||
|
|
||||||
|
|
||||||
|
const items = [...blog].sort(
|
||||||
|
(a, b) => new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return rss({
|
||||||
|
title: SITE.TITLE,
|
||||||
|
description: SITE.DESCRIPTION,
|
||||||
|
site: context.site,
|
||||||
|
items: items.map((item) => ({
|
||||||
|
title: item.data.title,
|
||||||
|
description: item.data.description,
|
||||||
|
pubDate: item.data.date,
|
||||||
|
link: `/${item.id}/`,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
384
src/styles/callout.css
Normal file
384
src/styles/callout.css
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
[data-callout] {
|
||||||
|
& {
|
||||||
|
@apply my-6 space-y-2 rounded-lg border border-blue-600/20 bg-blue-400/20 p-4 pb-5 dark:border-blue-800/20 dark:bg-blue-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply flex flex-row items-start gap-2 p-0 font-bold text-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not:only-child {
|
||||||
|
@apply mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:empty::after {
|
||||||
|
content: "Note";
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
@apply mt-1 block h-5 w-5 bg-current content-[""];
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: cover;
|
||||||
|
|
||||||
|
/* lucide-pencil */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTE3IDNhMi44NSAyLjgzIDAgMSAxIDQgNEw3LjUgMjAuNUwyIDIybDEuNS01LjVabS0yIDJsNCA0Ii8+PC9zdmc+");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-body] {
|
||||||
|
& {
|
||||||
|
@apply space-y-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
@apply m-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
details[data-callout] > summary[data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
@apply w-full bg-right bg-no-repeat;
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
/* lucide:chevron-right */
|
||||||
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im05IDE4bDYtNmwtNi02Ii8+PC9zdmc+");
|
||||||
|
background-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:empty)::after {
|
||||||
|
@apply my-auto ml-auto h-6 w-6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
details[data-callout][open] > summary[data-callout-title]::after {
|
||||||
|
/* lucide:chevron-down */
|
||||||
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im02IDlsNiA2bDYtNiIvPjwvc3ZnPg==");
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="info"] {
|
||||||
|
& {
|
||||||
|
@apply border-blue-600/20 bg-blue-400/20 dark:border-blue-800/20 dark:bg-blue-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:empty::after {
|
||||||
|
content: "Info";
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:info */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBhdGggZD0iTTEyIDE2di00bTAtNGguMDEiLz48L2c+PC9zdmc+");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="todo"] {
|
||||||
|
& {
|
||||||
|
@apply border-blue-600/20 bg-blue-400/20 dark:border-blue-800/20 dark:bg-blue-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:empty::after {
|
||||||
|
content: "ToDo";
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:circle-check-big */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Ik0yMiAxMS4wOFYxMmExMCAxMCAwIDEgMS01LjkzLTkuMTQiLz48cGF0aCBkPSJtOSAxMWwzIDNMMjIgNCIvPjwvZz48L3N2Zz4=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="abstract"],
|
||||||
|
[data-callout][data-callout-type="summary"],
|
||||||
|
[data-callout][data-callout-type="tldr"] {
|
||||||
|
& {
|
||||||
|
@apply border-cyan-600/20 bg-cyan-400/20 dark:border-cyan-800/20 dark:bg-cyan-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-cyan-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:clipboard-list */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIi8+PHBhdGggZD0iTTE2IDRoMmEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJINmEyIDIgMCAwIDEtMi0yVjZhMiAyIDAgMCAxIDItMmgybTQgN2g0bS00IDVoNG0tOC01aC4wMU04IDE2aC4wMSIvPjwvZz48L3N2Zz4=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="abstract"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Abstract";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="summary"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Summary";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="tldr"] > [data-callout-title]:empty::after {
|
||||||
|
content: "TL;DR";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="tip"],
|
||||||
|
[data-callout][data-callout-type="hint"],
|
||||||
|
[data-callout][data-callout-type="important"] {
|
||||||
|
& {
|
||||||
|
@apply border-cyan-600/20 bg-cyan-400/20 dark:border-cyan-800/20 dark:bg-cyan-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-cyan-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:flame */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTguNSAxNC41QTIuNSAyLjUgMCAwIDAgMTEgMTJjMC0xLjM4LS41LTItMS0zYy0xLjA3Mi0yLjE0My0uMjI0LTQuMDU0IDItNmMuNSAyLjUgMiA0LjkgNCA2LjVjMiAxLjYgMyAzLjUgMyA1LjVhNyA3IDAgMSAxLTE0IDBjMC0xLjE1My40MzMtMi4yOTQgMS0zYTIuNSAyLjUgMCAwIDAgMi41IDIuNSIvPjwvc3ZnPg==");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="tip"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Tip";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="hint"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Hint";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="important"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Important";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="success"],
|
||||||
|
[data-callout][data-callout-type="check"],
|
||||||
|
[data-callout][data-callout-type="done"] {
|
||||||
|
& {
|
||||||
|
@apply border-green-600/20 bg-green-400/20 dark:border-green-800/20 dark:bg-green-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:check */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIwIDZMOSAxN2wtNS01Ii8+PC9zdmc+");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="success"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Success";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="check"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Check";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="done"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Done";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="question"],
|
||||||
|
[data-callout][data-callout-type="help"],
|
||||||
|
[data-callout][data-callout-type="faq"] {
|
||||||
|
& {
|
||||||
|
@apply border-orange-600/20 bg-orange-400/20 dark:border-orange-800/20 dark:bg-orange-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-orange-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:circle-help */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiLz48cGF0aCBkPSJNOS4wOSA5YTMgMyAwIDAgMSA1LjgzIDFjMCAyLTMgMy0zIDNtLjA4IDRoLjAxIi8+PC9nPjwvc3ZnPg==");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="question"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Question";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="help"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Help";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="faq"] > [data-callout-title]:empty::after {
|
||||||
|
content: "FAQ";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="warning"],
|
||||||
|
[data-callout][data-callout-type="caution"],
|
||||||
|
[data-callout][data-callout-type="attention"] {
|
||||||
|
& {
|
||||||
|
@apply border-orange-600/20 bg-orange-400/20 dark:border-orange-800/20 dark:bg-orange-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-orange-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:triangle-alert */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0ibTIxLjczIDE4bC04LTE0YTIgMiAwIDAgMC0zLjQ4IDBsLTggMTRBMiAyIDAgMCAwIDQgMjFoMTZhMiAyIDAgMCAwIDEuNzMtM00xMiA5djRtMCA0aC4wMSIvPjwvc3ZnPg==");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="warning"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Warning";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="caution"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Caution";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="attention"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Attention";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="failure"],
|
||||||
|
[data-callout][data-callout-type="fail"],
|
||||||
|
[data-callout][data-callout-type="missing"] {
|
||||||
|
& {
|
||||||
|
@apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:check */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIwIDZMOSAxN2wtNS01Ii8+PC9zdmc+");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="failure"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Failure";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="fail"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Fail";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="missing"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Missing";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="danger"],
|
||||||
|
[data-callout][data-callout-type="error"] {
|
||||||
|
& {
|
||||||
|
@apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:zap */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXphcCI+PHBhdGggZD0iTTQgMTRhMSAxIDAgMCAxLS43OC0xLjYzbDkuOS0xMC4yYS41LjUgMCAwIDEgLjg2LjQ2bC0xLjkyIDYuMDJBMSAxIDAgMCAwIDEzIDEwaDdhMSAxIDAgMCAxIC43OCAxLjYzbC05LjkgMTAuMmEuNS41IDAgMCAxLS44Ni0uNDZsMS45Mi02LjAyQTEgMSAwIDAgMCAxMSAxNHoiLz48L3N2Zz4=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="danger"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Danger";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="error"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="bug"] {
|
||||||
|
& {
|
||||||
|
@apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:bug */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PHBhdGggZD0ibTggMmwxLjg4IDEuODhtNC4yNCAwTDE2IDJNOSA3LjEzdi0xYTMuMDAzIDMuMDAzIDAgMSAxIDYgMHYxIi8+PHBhdGggZD0iTTEyIDIwYy0zLjMgMC02LTIuNy02LTZ2LTNhNCA0IDAgMCAxIDQtNGg0YTQgNCAwIDAgMSA0IDR2M2MwIDMuMy0yLjcgNi02IDZtMCAwdi05Ii8+PHBhdGggZD0iTTYuNTMgOUM0LjYgOC44IDMgNy4xIDMgNW0zIDhIMm0xIDhjMC0yLjEgMS43LTMuOSAzLjgtNE0yMC45NyA1YzAgMi4xLTEuNiAzLjgtMy41IDRNMjIgMTNoLTRtLS44IDRjMi4xLjEgMy44IDEuOSAzLjggNCIvPjwvZz48L3N2Zz4=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="bug"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Bug";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="example"] {
|
||||||
|
& {
|
||||||
|
@apply border-purple-600/20 bg-purple-400/20 dark:border-purple-800/20 dark:bg-purple-600/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-purple-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:list */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTggNmgxM004IDEyaDEzTTggMThoMTNNMyA2aC4wMU0zIDEyaC4wMU0zIDE4aC4wMSIvPjwvc3ZnPg==");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="example"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Example";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="quote"],
|
||||||
|
[data-callout][data-callout-type="cite"] {
|
||||||
|
& {
|
||||||
|
@apply border-zinc-600/20 bg-zinc-400/20 dark:border-zinc-800/20 dark:bg-zinc-600/15;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > [data-callout-title] {
|
||||||
|
& {
|
||||||
|
@apply text-zinc-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* lucide:quote */
|
||||||
|
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTMgMjFjMyAwIDctMSA3LThWNWMwLTEuMjUtLjc1Ni0yLjAxNy0yLTJINGMtMS4yNSAwLTIgLjc1LTIgMS45NzJWMTFjMCAxLjI1Ljc1IDIgMiAyYzEgMCAxIDAgMSAxdjFjMCAxLTEgMi0yIDJzLTEgLjAwOC0xIDEuMDMxVjIwYzAgMSAwIDEgMSAxbTEyIDBjMyAwIDctMSA3LThWNWMwLTEuMjUtLjc1Ny0yLjAxNy0yLTJoLTRjLTEuMjUgMC0yIC43NS0yIDEuOTcyVjExYzAgMS4yNS43NSAyIDIgMmguNzVjMCAyLjI1LjI1IDQtMi43NSA0djNjMCAxIDAgMSAxIDEiLz48L3N2Zz4=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="quote"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Quote";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-callout][data-callout-type="cite"] > [data-callout-title]:empty::after {
|
||||||
|
content: "Cite";
|
||||||
|
}
|
116
src/styles/global.css
Normal file
116
src/styles/global.css
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html {
|
||||||
|
overflow-y: auto;
|
||||||
|
color-scheme: light;
|
||||||
|
scroll-padding-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
@apply size-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply font-sans antialiased;
|
||||||
|
@apply flex flex-col;
|
||||||
|
@apply bg-neutral-100 dark:bg-neutral-900;
|
||||||
|
@apply text-black/75 dark:text-white/75;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
@apply fixed left-0 right-0 top-0 z-50 py-6;
|
||||||
|
@apply bg-neutral-100/75 dark:bg-neutral-900/75;
|
||||||
|
@apply saturate-200 backdrop-blur-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
@apply flex-1 py-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
@apply py-6 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
@apply prose prose-neutral max-w-full dark:prose-invert prose-img:mx-auto prose-img:my-auto;
|
||||||
|
@apply prose-headings:font-semibold;
|
||||||
|
@apply prose-headings:text-black prose-headings:dark:text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
article a {
|
||||||
|
@apply font-sans text-current underline underline-offset-[3px];
|
||||||
|
@apply decoration-black/30 dark:decoration-white/30;
|
||||||
|
@apply transition-colors duration-300 ease-in-out;
|
||||||
|
}
|
||||||
|
article a:hover {
|
||||||
|
@apply text-black dark:text-white;
|
||||||
|
@apply decoration-black/50 dark:decoration-white/50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate {
|
||||||
|
@apply -translate-y-3 opacity-0;
|
||||||
|
@apply transition-all duration-300 ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate.show {
|
||||||
|
@apply translate-y-0 opacity-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
html #back-to-top {
|
||||||
|
@apply pointer-events-none opacity-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.scrolled #back-to-top {
|
||||||
|
@apply pointer-events-auto opacity-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
@apply border border-black/15 py-5 dark:border-white/20;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--astro-code-foreground: #09090b;
|
||||||
|
--astro-code-background: #fafafa;
|
||||||
|
--astro-code-token-comment: #a19595;
|
||||||
|
--astro-code-token-keyword: #f47067;
|
||||||
|
--astro-code-token-string: #00a99a;
|
||||||
|
--astro-code-token-function: #429996;
|
||||||
|
--astro-code-token-constant: #2b70c5;
|
||||||
|
--astro-code-token-parameter: #4e8fdf;
|
||||||
|
--astro-code-token-string-expression: #ae42a0;
|
||||||
|
--astro-code-token-punctuation: #8996a3;
|
||||||
|
--astro-code-token-link: #8d85ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--astro-code-foreground: #fafafa;
|
||||||
|
--astro-code-background: #09090b;
|
||||||
|
--astro-code-token-comment: #a19595;
|
||||||
|
--astro-code-token-keyword: #f47067;
|
||||||
|
--astro-code-token-string: #00a99a;
|
||||||
|
--astro-code-token-function: #6eafad;
|
||||||
|
--astro-code-token-constant: #b3cceb;
|
||||||
|
--astro-code-token-parameter: #4e8fdf;
|
||||||
|
--astro-code-token-string-expression: #bf7db6;
|
||||||
|
--astro-code-token-punctuation: #8996a3;
|
||||||
|
--astro-code-token-link: #8d85ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-code {
|
||||||
|
@apply absolute right-3 top-3 grid size-9 place-content-center rounded border border-black/15 bg-neutral-100 text-center duration-300 ease-in-out dark:border-white/20 dark:bg-neutral-900;
|
||||||
|
}
|
||||||
|
.copy-code:hover {
|
||||||
|
@apply bg-[#E9E9E9] transition-colors dark:bg-[#232323];
|
||||||
|
}
|
||||||
|
.copy-code:active {
|
||||||
|
@apply scale-90 transition-transform;
|
||||||
|
}
|
7
src/types.ts
Normal file
7
src/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export type Site = {
|
||||||
|
TITLE: string;
|
||||||
|
DESCRIPTION: string;
|
||||||
|
};
|
||||||
|
export type Metadata = {
|
||||||
|
TITLE: string;
|
||||||
|
};
|
15
tailwind.config.mjs
Normal file
15
tailwind.config.mjs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import defaultTheme from "tailwindcss/defaultTheme";
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
darkMode: "class",
|
||||||
|
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Geist Sans", ...defaultTheme.fontFamily.sans],
|
||||||
|
mono: ["Geist Mono", ...defaultTheme.fontFamily.mono],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("@tailwindcss/typography")],
|
||||||
|
};
|
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strictest",
|
||||||
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
|
"exclude": ["dist"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue