chore: layout

This commit is contained in:
enscribe 2024-09-10 12:51:03 -07:00
parent b9561ad2d0
commit 230dca64ca
No known key found for this signature in database
GPG key ID: 9BBD5C4114E25322
27 changed files with 446 additions and 339 deletions

View file

@ -8,10 +8,19 @@ import remarkEmoji from 'remark-emoji'
import remarkMath from 'remark-math'
import remarkToc from 'remark-toc'
import react from '@astrojs/react'
// https://astro.build/config
export default defineConfig({
site: 'https://astro-micro-academic.vercel.app',
integrations: [tailwind(), sitemap(), mdx()],
integrations: [
tailwind({
applyBaseStyles: false,
}),
sitemap(),
mdx(),
react(),
],
markdown: {
shikiConfig: {
theme: 'css-variables',
@ -19,5 +28,11 @@ export default defineConfig({
rehypePlugins: [rehypeHeadingIds, rehypeKatex],
remarkPlugins: [remarkToc, remarkMath, remarkEmoji],
},
server: { port: 1234, host: true },
server: {
port: 1234,
host: true,
},
devToolbar: {
enabled: false,
},
})

20
components.json Normal file
View file

@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/styles/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

208
package-lock.json generated
View file

@ -11,20 +11,29 @@
"@astrojs/check": "^0.7.0",
"@astrojs/markdown-remark": "^5.2.0",
"@astrojs/mdx": "^3.1.2",
"@astrojs/react": "^3.6.2",
"@astrojs/rss": "^4.0.7",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.0",
"@fontsource/geist-mono": "^5.0.3",
"@fontsource/geist-sans": "^5.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"astro": "^4.11.3",
"bootstrap-icons": "^1.11.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.439.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rehype-katex": "^7.0.0",
"remark-emoji": "^5.0.1",
"remark-math": "^6.0.0",
"remark-toc": "^9.0.0",
"tailwind-merge": "^2.3.0",
"tailwind-merge": "^2.5.2",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.4.5"
},
"devDependencies": {
@ -222,6 +231,24 @@
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"
}
},
"node_modules/@astrojs/react": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/@astrojs/react/-/react-3.6.2.tgz",
"integrity": "sha512-fK29lYI7zK/KG4ZBy956x4dmauZcZ18osFkuyGa8r3gmmCQa2NZ9XNu9WaVYEUm0j89f4Gii4tbxLoyM8nk2MA==",
"dependencies": {
"@vitejs/plugin-react": "^4.3.1",
"ultrahtml": "^1.5.3"
},
"engines": {
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"
},
"peerDependencies": {
"@types/react": "^17.0.50 || ^18.0.21",
"@types/react-dom": "^17.0.17 || ^18.0.6",
"react": "^17.0.2 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0-beta"
}
},
"node_modules/@astrojs/rss": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.7.tgz",
@ -593,6 +620,34 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz",
"integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz",
"integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/template": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
@ -1705,6 +1760,14 @@
"node": ">=14"
}
},
"node_modules/@radix-ui/react-icons": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz",
"integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==",
"peerDependencies": {
"react": "^16.x || ^17.x || ^18.x"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
@ -2082,6 +2145,28 @@
"undici-types": "~5.26.4"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/react": {
"version": "18.3.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz",
"integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/sax": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
@ -2108,6 +2193,24 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"license": "ISC"
},
"node_modules/@vitejs/plugin-react": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz",
"integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==",
"dependencies": {
"@babel/core": "^7.24.5",
"@babel/plugin-transform-react-jsx-self": "^7.24.5",
"@babel/plugin-transform-react-jsx-source": "^7.24.1",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.14.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"vite": "^4.2.0 || ^5.0.0"
}
},
"node_modules/@volar/kit": {
"version": "2.4.0-alpha.15",
"resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.0-alpha.15.tgz",
@ -2832,6 +2935,25 @@
"node": ">=8"
}
},
"node_modules/class-variance-authority": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz",
"integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==",
"dependencies": {
"clsx": "2.0.0"
},
"funding": {
"url": "https://joebell.co.uk"
}
},
"node_modules/class-variance-authority/node_modules/clsx": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
"engines": {
"node": ">=6"
}
},
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
@ -2980,7 +3102,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
@ -3121,6 +3242,11 @@
"node": ">=4"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/debug": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
@ -4619,6 +4745,17 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -4628,6 +4765,14 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.439.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.439.0.tgz",
"integrity": "sha512-PafSWvDTpxdtNEndS2HIHxcNAbd54OaqSYJO90/b63rab2HWYqDbH194j0i82ZFdWOAcf0AHinRykXRRK2PJbw==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
"node_modules/magic-string": {
"version": "0.30.10",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
@ -6693,6 +6838,37 @@
],
"license": "MIT"
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -7161,6 +7337,14 @@
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@ -7586,10 +7770,9 @@
}
},
"node_modules/tailwind-merge": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz",
"integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==",
"license": "MIT",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz",
"integrity": "sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
@ -7632,6 +7815,14 @@
"node": ">=14.0.0"
}
},
"node_modules/tailwindcss-animate": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tailwindcss/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -7801,6 +7992,11 @@
"semver": "^7.3.8"
}
},
"node_modules/ultrahtml": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.3.tgz",
"integrity": "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg=="
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",

View file

@ -15,20 +15,29 @@
"@astrojs/check": "^0.7.0",
"@astrojs/markdown-remark": "^5.2.0",
"@astrojs/mdx": "^3.1.2",
"@astrojs/react": "^3.6.2",
"@astrojs/rss": "^4.0.7",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.0",
"@fontsource/geist-mono": "^5.0.3",
"@fontsource/geist-sans": "^5.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"astro": "^4.11.3",
"bootstrap-icons": "^1.11.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.439.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rehype-katex": "^7.0.0",
"remark-emoji": "^5.0.1",
"remark-math": "^6.0.0",
"remark-toc": "^9.0.0",
"tailwind-merge": "^2.3.0",
"tailwind-merge": "^2.5.2",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.4.5"
},
"devDependencies": {

View file

@ -2,4 +2,4 @@
---
<div class="mx-auto max-w-screen-md px-3"><slot /></div>
<div class="mx-auto max-w-screen-md"><slot /></div>

View file

@ -5,7 +5,7 @@ import BackToTop from '@components/BackToTop.astro'
import SocialIcons from './SocialIcons.astro'
---
<footer class="animate">
<footer>
<Container>
<div class="my-2 flex justify-between">
<SocialIcons icon_size={'text-xl'} />
@ -16,78 +16,6 @@ import SocialIcons from './SocialIcons.astro'
&copy; {new Date().getFullYear()} • {SITE.TITLE} 👀<br />
Built with Astro
</div>
<div class="flex flex-wrap items-center gap-1.5">
<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>

View file

@ -1,6 +1,5 @@
---
import '../styles/global.css'
import { ViewTransitions } from 'astro:transitions'
import '@fontsource/geist-sans'
import '@fontsource/geist-mono'
@ -16,46 +15,31 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site)
const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props
---
<!-- Global Metadata -->
<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} />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<!-- Open Graph / Facebook -->
<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)} />
<!-- Twitter -->
<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)} />
<ViewTransitions />
<script is:inline>
function init() {
preloadTheme()
onScroll()
animate()
updateThemeButtons()
addCopyCodeButtons()
const backToTop = document.getElementById('back-to-top')
@ -64,77 +48,9 @@ const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props
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')
@ -151,44 +67,6 @@ const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props
})
}
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)
}
function preloadTheme() {
const userTheme = localStorage.theme
if (userTheme === 'light' || userTheme === 'dark') {
toggleTheme(true) // set default to dark theme
} else {
toggleTheme(window.matchMedia('(prefers-color-scheme: dark)').matches)
}
}
function addCopyCodeButtons() {
let copyButtonLabel = '📋'
let codeBlocks = Array.from(document.querySelectorAll('pre'))
@ -228,5 +106,4 @@ const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props
document.addEventListener('DOMContentLoaded', () => init())
document.addEventListener('astro:after-swap', () => init())
preloadTheme()
</script>

View file

@ -2,33 +2,30 @@
import Container from '@components/Container.astro'
import Link from '@components/Link.astro'
import { SITE } from '@consts'
const items = [
{ href: '/blog', label: 'blog' },
{ href: '/authors', label: 'authors' },
{ href: '/about', label: 'about' },
{ href: '/tags', label: 'tags' },
]
---
<header transition:persist>
<header class="sticky top-0 z-10" transition:persist>
<Container>
<div class="flex flex-wrap justify-between gap-y-2">
<div class="flex items-center justify-between py-4">
<Link href="/" underline={false}>
<div class="font-semibold">
{SITE.TITLE}&nbsp;🍄
</div>
{SITE.TITLE}<span class="ml-1">🍄</span>
</Link>
<nav class="flex items-center gap-1 text-sm">
<Link href="/blog">blog</Link>
<span>
{`/`}
</span>
<Link href="/authors">authors</Link>
<span>
{`/`}
</span>
<Link href="/about">about</Link>
<span>
{`/`}
</span>
<Link href="/tags">tags</Link>
<span>
{`/`}
</span>
<nav class="flex items-center space-x-1 text-sm">
{
items.map((item, index) => (
<>
<Link href={item.href}>{item.label}</Link>
{index < items.length - 1 && <span class="text-gray-400">/</span>}
</>
))
}
</nav>
</div>
</Container>

View file

@ -1,17 +1,17 @@
---
import type { CollectionEntry } from "astro:content";
import { Image } from "astro:assets";
import type { CollectionEntry } from 'astro:content'
import { Image } from 'astro:assets'
type Props = {
member: CollectionEntry<"authors">;
};
member: CollectionEntry<'authors'>
}
const { member } = Astro.props;
const { name, avatar, bio } = member.data;
const { member } = Astro.props
const { name, avatar, bio } = member.data
---
<div
class="animate not-prose flex flex-col sm:flex-row size-full sm:items-center gap-4 overflow-hidden rounded-xl border border-foreground bg-background p-6 hover:bg-secondary"
class="not-prose flex size-full flex-col gap-4 overflow-hidden rounded-xl border border-foreground bg-background p-6 hover:bg-secondary sm:flex-row sm:items-center"
>
<Image
src={avatar}

View file

@ -27,10 +27,7 @@ function buildToc(headings: Heading[]) {
}
---
<details
open
class="animate rounded-lg border border-black/15 dark:border-white/20"
>
<details open class="rounded-lg border border-black/15 dark:border-white/20">
<summary>Table of Contents</summary>
<nav class="">
<ul class="py-3">

View file

@ -1,8 +1,8 @@
---
name: "enscribe"
avatar: "https://gravatar.com/avatar/9bfdc4ec972793cf05cb91efce5f4aaaec2a0da1bf4ec34dad0913f1d845faf6.webp?size=256"
bio: "d(-_-)b"
website: "https://enscribe.dev"
twitter: "enscry"
github: "jktrn"
---
name: 'enscribe'
avatar: 'https://gravatar.com/avatar/9bfdc4ec972793cf05cb91efce5f4aaaec2a0da1bf4ec34dad0913f1d845faf6.webp?size=256'
bio: 'd(-_-)b'
website: 'https://enscribe.dev'
twitter: 'enscry'
github: 'jktrn'
---

View file

@ -62,7 +62,7 @@ When developing you can continue to use `npm run dev` and Pagefind will use the
## UI enhancements 🎨
- Elements are styled and animate on focus
- Elements are styled and on focus
- Increased contrast in light mode
- Active theme is indicated by theme buttons
- Separate syntax highlight themes for light and dark mode

View file

@ -1,7 +1,7 @@
import { defineCollection, reference, z } from "astro:content";
import { defineCollection, reference, z } from 'astro:content'
const blog = defineCollection({
type: "content",
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
@ -9,12 +9,12 @@ const blog = defineCollection({
draft: z.boolean().optional(),
tags: z.array(z.string()).optional(),
author: z.union([reference("authors"), z.string()]).optional(),
author: z.union([reference('authors'), z.string()]).optional(),
}),
});
})
const authors = defineCollection({
type: "content",
type: 'content',
schema: z.object({
name: z.string(),
avatar: z.string().url(),
@ -26,6 +26,6 @@ const authors = defineCollection({
mail: z.string().email().optional(),
discord: z.string().optional(),
}),
});
})
export const collections = { blog, authors };
export const collections = { blog, authors }

View file

@ -4,6 +4,7 @@ import Header from '@components/Header.astro'
import Footer from '@components/Footer.astro'
import { SITE } from '@consts'
import '../styles/katex.css'
import { ViewTransitions } from 'astro:transitions'
type Props = {
title: string
@ -17,10 +18,13 @@ const { title, description } = Astro.props
<html lang="en">
<head>
<Head title={`${title} | ${SITE.TITLE}`} description={description} />
<ViewTransitions />
</head>
<body>
<body
class="box-border flex h-fit min-h-screen flex-col px-4 font-sans antialiased"
>
<Header />
<main>
<main class="flex-grow">
<slot />
</main>
<Footer />

View file

@ -9,10 +9,10 @@ import { SITE } from '@consts'
<Layout title="404" description={SITE.DESCRIPTION}>
<Container>
<div class="mt-16 grid place-items-center gap-3">
<h4 class="animate text-2xl font-semibold text-black dark:text-white">
<h4 class="text-2xl font-semibold text-black dark:text-white">
404: Page not found
</h4>
<span class="animate">
<span>
<BackToPrevious href="/">Go to home page</BackToPrevious>
</span>
</div>

View file

@ -8,8 +8,8 @@ import { Image } from 'astro:assets'
<Layout title="About" description="About">
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">About</div>
<section class="animate not-prose flex flex-col gap-4 text-justify">
<div class="font-semibold text-black dark:text-white">About</div>
<section class="not-prose flex flex-col gap-4 text-justify">
<p class="text-justify">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores porro
hic minima incidunt explicabo obcaecati consectetur consequuntur at

View file

@ -1,33 +1,33 @@
---
import { type CollectionEntry, getCollection, getEntry } from "astro:content";
import Layout from "@layouts/Layout.astro";
import MemberCard from "@components/MemberCard.astro";
import { type CollectionEntry, getCollection, getEntry } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import MemberCard from '@components/MemberCard.astro'
export async function getStaticPaths() {
const authors = await getCollection("authors");
const authors = await getCollection('authors')
return authors.map((member) => ({
params: { slug: member.slug },
props: { member },
}));
}))
}
type Props = {
member: CollectionEntry<"authors">;
};
member: CollectionEntry<'authors'>
}
const { member } = Astro.props;
const { member } = Astro.props
const allPosts = await getCollection("blog");
const allPosts = await getCollection('blog')
const memberPosts = allPosts
.filter((post) => {
if (typeof post.data.author === 'string') {
return post.data.author === member.data.name && !post.data.draft;
return post.data.author === member.data.name && !post.data.draft
} else if (post.data.author && 'slug' in post.data.author) {
return post.data.author.slug === member.slug && !post.data.draft;
return post.data.author.slug === member.slug && !post.data.draft
}
return false;
return false
})
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
---
<Layout

View file

@ -1,16 +1,14 @@
---
import { getCollection } from "astro:content";
import Layout from "@layouts/Layout.astro";
import MemberCard from "@components/MemberCard.astro";
import { getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import MemberCard from '@components/MemberCard.astro'
const authors = await getCollection("authors");
const authors = await getCollection('authors')
---
<Layout title="authors" description="authors">
<section>
<ul
class="animate not-prose grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3"
>
<ul class="not-prose grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3">
{
authors.map((member) => (
<li>

View file

@ -53,7 +53,7 @@ const { Content, headings } = await post.render()
<BackToPrevious href="/blog">Back to blog</BackToPrevious>
</div>
<div class="my-10 space-y-4">
<div class="animate flex items-center gap-1.5">
<div class="flex items-center gap-1.5">
<div class="font-base text-sm">
<FormattedDate date={post.data.date} />
</div>
@ -62,7 +62,7 @@ const { Content, headings } = await post.render()
{readingTime(post.body)}
</div>
</div>
<h1 class="animate text-4xl font-semibold text-black dark:text-white">
<h1 class="text-4xl font-semibold text-black dark:text-white">
{post.data.title}
</h1>
<div class="font-base text-sm">

View file

@ -30,7 +30,7 @@ const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a))
<div class="space-y-4">
{
years.map((year) => (
<section class="animate space-y-4">
<section class="space-y-4">
<div class="font-semibold text-black dark:text-white">{year}</div>
<div>
<ul class="not-prose flex flex-col gap-4">

View file

@ -16,7 +16,7 @@ const blog = (await getCollection('blog'))
<Layout title="Home" description="Home">
<Container>
<section class="animate space-y-6">
<section class="space-y-6">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<h2 class="font-semibold text-black dark:text-white">Latest posts</h2>
<Link href="/blog"> See all posts </Link>

View file

@ -1,23 +1,23 @@
import rss from '@astrojs/rss'
import { SITE } from '@consts'
import { getCollection } from 'astro:content'
import type { APIContext } from 'astro'
import { getCollection } from 'astro:content'
export async function GET(context: APIContext) {
try {
const blog = (await getCollection('blog')).filter(
(post) => !post.data.draft
(post) => !post.data.draft,
)
// Filter posts by tag 'rss-feed'
const filteredBlogs = blog.filter(
(post) => post.data.tags && post.data.tags.includes('rss-feed')
(post) => post.data.tags && post.data.tags.includes('rss-feed'),
)
// Sort posts by date
const items = [...filteredBlogs].sort(
(a, b) =>
new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf()
new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf(),
)
// Return RSS feed

View file

@ -36,7 +36,7 @@ export async function getStaticPaths() {
>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
<div class="font-semibold text-black dark:text-white">
Tag: <span
class="mx-2 rounded-full bg-orange-300 px-3 py-2 transition-colors duration-300 ease-in-out hover:bg-cyan-200 dark:bg-orange-500 dark:hover:bg-cyan-500"
>#{tag}</span
@ -45,7 +45,7 @@ export async function getStaticPaths() {
<div class="space-y-4">
{
posts.map((post) => (
<section class="animate space-y-4">
<section class="space-y-4">
<div>
<ul class="not-prose flex flex-col gap-4">
<li>

View file

@ -14,7 +14,7 @@ const tags = blog
<Layout title="Tags" description="Tags">
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">Tags</div>
<div class="font-semibold text-black dark:text-white">Tags</div>
<ul class="flex flex-wrap">
{
tags.map((tag) => (

View file

@ -2,42 +2,6 @@
@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-300 dark:bg-slate-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-32;
}
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;
@ -61,15 +25,6 @@ article {
scrollbar-color: #1e293b #0f172a;
}
.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;
}
@ -123,3 +78,65 @@ pre {
.copy-code:active {
@apply scale-90 transition-transform;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
}

View file

@ -2,7 +2,7 @@ import type { Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
const config: Config = {
darkMode: 'class',
darkMode: ['class', 'class'],
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
@ -10,9 +10,56 @@ const config: Config = {
sans: ['Geist Sans', ...defaultTheme.fontFamily.sans],
mono: ['Geist Mono', ...defaultTheme.fontFamily.mono],
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))',
},
},
},
},
plugins: [require('@tailwindcss/typography')],
plugins: [require('@tailwindcss/typography'), require('tailwindcss-animate')],
}
export default config

View file

@ -5,6 +5,8 @@
"baseUrl": ".",
"paths": {
"@*": ["./src/*"]
}
},
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}