From a5c1eaedc7a2176d905b3a62e6a991c5f67b3a7b Mon Sep 17 00:00:00 2001
From: ryan
Date: Mon, 5 Jan 2026 13:21:19 +1100
Subject: [PATCH] Set up tailwind and nano theme
---
astro.config.mjs | 9 +-
package.json | 10 +-
pnpm-lock.yaml | 458 ++++++++++++++++++++++++++++-
src/components/ArrowCard.astro | 27 ++
src/components/BackToPrev.astro | 20 ++
src/components/BackToTop.astro | 12 +
src/components/Container.astro | 7 +
src/components/Footer.astro | 93 ++++++
src/components/FormattedDate.astro | 17 ++
src/components/Head.astro | 198 +++++++++++++
src/components/Header.astro | 34 +++
src/components/Link.astro | 19 ++
src/consts.ts | 44 +++
src/content/config.ts | 13 +
src/layouts/PageLayout.astro | 27 ++
src/lib/utils.ts | 40 +++
src/pages/blog/index.astro | 52 ++++
src/styles/global.css | 72 +++++
src/types.ts | 17 ++
tailwind.config.mjs | 20 ++
tsconfig.json | 11 +-
21 files changed, 1180 insertions(+), 20 deletions(-)
create mode 100644 src/components/ArrowCard.astro
create mode 100644 src/components/BackToPrev.astro
create mode 100644 src/components/BackToTop.astro
create mode 100644 src/components/Container.astro
create mode 100644 src/components/Footer.astro
create mode 100644 src/components/FormattedDate.astro
create mode 100644 src/components/Head.astro
create mode 100644 src/components/Header.astro
create mode 100644 src/components/Link.astro
create mode 100644 src/consts.ts
create mode 100644 src/content/config.ts
create mode 100644 src/layouts/PageLayout.astro
create mode 100644 src/lib/utils.ts
create mode 100644 src/pages/blog/index.astro
create mode 100644 src/styles/global.css
create mode 100644 src/types.ts
create mode 100644 tailwind.config.mjs
diff --git a/astro.config.mjs b/astro.config.mjs
index adf62d1..00ab446 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -2,14 +2,19 @@
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
-
import node from '@astrojs/node';
+import tailwindcss from '@tailwindcss/vite';
// https://astro.build/config
export default defineConfig({
+ site: 'https://ryanpandya.com',
integrations: [mdx()],
adapter: node({
mode: 'standalone'
- })
+ }),
+
+ vite: {
+ plugins: [tailwindcss()],
+ },
});
\ No newline at end of file
diff --git a/package.json b/package.json
index e0e56cb..e053bd1 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,14 @@
"dependencies": {
"@astrojs/mdx": "^4.3.13",
"@astrojs/node": "^9.5.1",
- "astro": "^5.16.6"
+ "@fontsource-variable/ibm-plex-sans": "^5.2.8",
+ "@fontsource/martel": "^5.2.8",
+ "@tailwindcss/postcss": "^4.1.18",
+ "@tailwindcss/typography": "^0.5.19",
+ "@tailwindcss/vite": "^4.1.18",
+ "astro": "^5.16.6",
+ "clsx": "^2.1.1",
+ "tailwind-merge": "^3.4.0",
+ "tailwindcss": "^4.1.18"
}
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5372c10..447de37 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10,16 +10,44 @@ importers:
dependencies:
'@astrojs/mdx':
specifier: ^4.3.13
- version: 4.3.13(astro@5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3))
+ version: 4.3.13(astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2))
'@astrojs/node':
specifier: ^9.5.1
- version: 9.5.1(astro@5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3))
+ version: 9.5.1(astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2))
+ '@fontsource-variable/ibm-plex-sans':
+ specifier: ^5.2.8
+ version: 5.2.8
+ '@fontsource/martel':
+ specifier: ^5.2.8
+ version: 5.2.8
+ '@tailwindcss/postcss':
+ specifier: ^4.1.18
+ version: 4.1.18
+ '@tailwindcss/typography':
+ specifier: ^0.5.19
+ version: 0.5.19(tailwindcss@4.1.18)
+ '@tailwindcss/vite':
+ specifier: ^4.1.18
+ version: 4.1.18(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))
astro:
specifier: ^5.16.6
- version: 5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3)
+ version: 5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2)
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ tailwind-merge:
+ specifier: ^3.4.0
+ version: 3.4.0
+ tailwindcss:
+ specifier: ^4.1.18
+ version: 4.1.18
packages:
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
'@astrojs/compiler@2.13.0':
resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==}
@@ -228,6 +256,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@fontsource-variable/ibm-plex-sans@5.2.8':
+ resolution: {integrity: sha512-n5PF2iFa0CZT0QYTPzxvZ39opC9LnU0zdoRccoADbs+Dtsd+lbXOZF7RNuIPHcQX1dKjF63sxnRImQIB5eD0Ag==}
+
+ '@fontsource/martel@5.2.8':
+ resolution: {integrity: sha512-5leWG3FVh2BS+4TfVYoaUoDGizhjmR98e0j2UpalfYu008Oe5s5XgIqhXCDCqaWBtXmZOb2I1Q9S47YLiXGEiQ==}
+
'@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
engines: {node: '>=18'}
@@ -365,9 +399,22 @@ packages:
cpu: [x64]
os: [win32]
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
'@mdx-js/mdx@3.1.1':
resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==}
@@ -517,6 +564,104 @@ packages:
'@swc/helpers@0.5.18':
resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==}
+ '@tailwindcss/node@4.1.18':
+ resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.18':
+ resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.18':
+ resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==}
+
+ '@tailwindcss/typography@0.5.19':
+ resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+
+ '@tailwindcss/vite@4.1.18':
+ resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7
+
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -797,6 +942,10 @@ packages:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
+ enhanced-resolve@5.18.4:
+ resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
+ engines: {node: '>=10.13.0'}
+
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@@ -898,6 +1047,9 @@ packages:
github-slugger@2.0.0:
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
h3@1.15.4:
resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==}
@@ -996,6 +1148,10 @@ packages:
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
engines: {node: '>=16'}
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
@@ -1004,6 +1160,76 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -1282,6 +1508,10 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
+ postcss-selector-parser@6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
@@ -1462,6 +1692,16 @@ packages:
engines: {node: '>=16'}
hasBin: true
+ tailwind-merge@3.4.0:
+ resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
+
+ tailwindcss@4.1.18:
+ resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
@@ -1621,6 +1861,9 @@ packages:
uploadthing:
optional: true
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
vfile-location@5.0.3:
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
@@ -1696,6 +1939,11 @@ packages:
xxhash-wasm@1.1.0:
resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==}
+ yaml@2.8.2:
+ resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -1731,6 +1979,8 @@ packages:
snapshots:
+ '@alloc/quick-lru@5.2.0': {}
+
'@astrojs/compiler@2.13.0': {}
'@astrojs/internal-helpers@0.7.5': {}
@@ -1761,12 +2011,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@astrojs/mdx@4.3.13(astro@5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3))':
+ '@astrojs/mdx@4.3.13(astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2))':
dependencies:
'@astrojs/markdown-remark': 6.3.10
'@mdx-js/mdx': 3.1.1
acorn: 8.15.0
- astro: 5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3)
+ astro: 5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2)
es-module-lexer: 1.7.0
estree-util-visit: 2.0.0
hast-util-to-html: 9.0.5
@@ -1780,10 +2030,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@astrojs/node@9.5.1(astro@5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3))':
+ '@astrojs/node@9.5.1(astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2))':
dependencies:
'@astrojs/internal-helpers': 0.7.5
- astro: 5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3)
+ astro: 5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2)
send: 1.2.1
server-destroy: 1.0.1
transitivePeerDependencies:
@@ -1905,6 +2155,10 @@ snapshots:
'@esbuild/win32-x64@0.25.12':
optional: true
+ '@fontsource-variable/ibm-plex-sans@5.2.8': {}
+
+ '@fontsource/martel@5.2.8': {}
+
'@img/colour@1.0.0':
optional: true
@@ -2002,8 +2256,25 @@ snapshots:
'@img/sharp-win32-x64@0.34.5':
optional: true
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
'@jridgewell/sourcemap-codec@1.5.5': {}
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
'@mdx-js/mdx@3.1.1':
dependencies:
'@types/estree': 1.0.8
@@ -2147,6 +2418,87 @@ snapshots:
dependencies:
tslib: 2.8.1
+ '@tailwindcss/node@4.1.18':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.4
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.18':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-x64': 4.1.18
+ '@tailwindcss/oxide-freebsd-x64': 4.1.18
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.18
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.18
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.18
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+
+ '@tailwindcss/postcss@4.1.18':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.18
+ '@tailwindcss/oxide': 4.1.18
+ postcss: 8.5.6
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/typography@0.5.19(tailwindcss@4.1.18)':
+ dependencies:
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/vite@4.1.18(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))':
+ dependencies:
+ '@tailwindcss/node': 4.1.18
+ '@tailwindcss/oxide': 4.1.18
+ tailwindcss: 4.1.18
+ vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)
+
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@@ -2216,7 +2568,7 @@ snapshots:
astring@1.9.0: {}
- astro@5.16.6(@types/node@25.0.3)(rollup@4.54.0)(typescript@5.9.3):
+ astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2):
dependencies:
'@astrojs/compiler': 2.13.0
'@astrojs/internal-helpers': 0.7.5
@@ -2273,8 +2625,8 @@ snapshots:
unist-util-visit: 5.0.0
unstorage: 1.17.3
vfile: 6.0.3
- vite: 6.4.1(@types/node@25.0.3)
- vitefu: 1.1.1(vite@6.4.1(@types/node@25.0.3))
+ vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)
+ vitefu: 1.1.1(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))
xxhash-wasm: 1.1.0
yargs-parser: 21.1.1
yocto-spinner: 0.2.3
@@ -2427,8 +2779,7 @@ snapshots:
destr@2.0.5: {}
- detect-libc@2.1.2:
- optional: true
+ detect-libc@2.1.2: {}
deterministic-object-hash@2.0.2:
dependencies:
@@ -2474,6 +2825,11 @@ snapshots:
encodeurl@2.0.0: {}
+ enhanced-resolve@5.18.4:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
entities@4.5.0: {}
entities@6.0.1: {}
@@ -2602,6 +2958,8 @@ snapshots:
github-slugger@2.0.0: {}
+ graceful-fs@4.2.11: {}
+
h3@1.15.4:
dependencies:
cookie-es: 1.2.2
@@ -2789,12 +3147,63 @@ snapshots:
dependencies:
is-inside-container: 1.0.0
+ jiti@2.6.1: {}
+
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
kleur@3.0.3: {}
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
longest-streak@3.1.0: {}
lru-cache@10.4.3: {}
@@ -3344,6 +3753,11 @@ snapshots:
picomatch@4.0.3: {}
+ postcss-selector-parser@6.0.10:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
@@ -3662,6 +4076,12 @@ snapshots:
picocolors: 1.1.1
sax: 1.4.3
+ tailwind-merge@3.4.0: {}
+
+ tailwindcss@4.1.18: {}
+
+ tapable@2.3.0: {}
+
tiny-inflate@1.0.3: {}
tinyexec@1.0.2: {}
@@ -3778,6 +4198,8 @@ snapshots:
ofetch: 1.5.1
ufo: 1.6.1
+ util-deprecate@1.0.2: {}
+
vfile-location@5.0.3:
dependencies:
'@types/unist': 3.0.3
@@ -3793,7 +4215,7 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
- vite@6.4.1(@types/node@25.0.3):
+ vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
@@ -3804,10 +4226,13 @@ snapshots:
optionalDependencies:
'@types/node': 25.0.3
fsevents: 2.3.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ yaml: 2.8.2
- vitefu@1.1.1(vite@6.4.1(@types/node@25.0.3)):
+ vitefu@1.1.1(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)):
optionalDependencies:
- vite: 6.4.1(@types/node@25.0.3)
+ vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)
web-namespaces@2.0.1: {}
@@ -3825,6 +4250,9 @@ snapshots:
xxhash-wasm@1.1.0: {}
+ yaml@2.8.2:
+ optional: true
+
yargs-parser@21.1.1: {}
yocto-queue@1.2.2: {}
diff --git a/src/components/ArrowCard.astro b/src/components/ArrowCard.astro
new file mode 100644
index 0000000..b7087cd
--- /dev/null
+++ b/src/components/ArrowCard.astro
@@ -0,0 +1,27 @@
+---
+import type { CollectionEntry } from "astro:content";
+
+type Props = {
+ entry: CollectionEntry<"blog"> | CollectionEntry<"projects">;
+}
+
+const { entry } = Astro.props;
+---
+
+
+
+
+ {entry.data.title}
+
+
+ {entry.data.description}
+
+
+
+
diff --git a/src/components/BackToPrev.astro b/src/components/BackToPrev.astro
new file mode 100644
index 0000000..5cce7d2
--- /dev/null
+++ b/src/components/BackToPrev.astro
@@ -0,0 +1,20 @@
+---
+type Props = {
+ href: string;
+}
+
+const { href } = Astro.props;
+---
+
+
+
+
+
+
+
diff --git a/src/components/BackToTop.astro b/src/components/BackToTop.astro
new file mode 100644
index 0000000..c11d0c7
--- /dev/null
+++ b/src/components/BackToTop.astro
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/components/Container.astro b/src/components/Container.astro
new file mode 100644
index 0000000..328975a
--- /dev/null
+++ b/src/components/Container.astro
@@ -0,0 +1,7 @@
+---
+
+---
+
+
+
+
diff --git a/src/components/Footer.astro b/src/components/Footer.astro
new file mode 100644
index 0000000..e697571
--- /dev/null
+++ b/src/components/Footer.astro
@@ -0,0 +1,93 @@
+---
+import Container from "@components/Container.astro";
+import { SITE } from "@consts";
+import BackToTop from "@components/BackToTop.astro";
+---
+
+
diff --git a/src/components/FormattedDate.astro b/src/components/FormattedDate.astro
new file mode 100644
index 0000000..d923f5d
--- /dev/null
+++ b/src/components/FormattedDate.astro
@@ -0,0 +1,17 @@
+---
+interface Props {
+ date: Date;
+}
+
+const { date } = Astro.props;
+---
+
+
diff --git a/src/components/Head.astro b/src/components/Head.astro
new file mode 100644
index 0000000..c56b687
--- /dev/null
+++ b/src/components/Head.astro
@@ -0,0 +1,198 @@
+---
+import "../styles/global.css";
+import "@fontsource/martel/latin-400.css";
+import "@fontsource/martel/latin-600.css";
+import "@fontsource-variable/ibm-plex-sans";
+// import martel400 from "@fontsource/martel/files/martel-latin-400-normal.woff2";
+// import martel600 from "@fontsource/martel/files/martel-latin-600-normal.woff2";
+// import plex from "@fontsource/ibm-plex-sans/files/ibm-plex-sans-latin-400-normal.woff2";
+
+import { ClientRouter } from "astro:transitions";
+import { SITE } from "@consts";
+
+interface Props {
+ title: string;
+ description: string;
+ image?: string;
+}
+const canonicalURL = new URL(Astro.url.pathname, Astro.site);
+
+const { title, description, image = "/nano.png" } = Astro.props;
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Header.astro b/src/components/Header.astro
new file mode 100644
index 0000000..62d195f
--- /dev/null
+++ b/src/components/Header.astro
@@ -0,0 +1,34 @@
+---
+import Container from "@components/Container.astro";
+import Link from "@components/Link.astro";
+import { SITE } from "@consts";
+---
+
+
+
+
+
+
+ {SITE.NAME}
+
+
+
+
+
+
diff --git a/src/components/Link.astro b/src/components/Link.astro
new file mode 100644
index 0000000..2014ca2
--- /dev/null
+++ b/src/components/Link.astro
@@ -0,0 +1,19 @@
+---
+import { cn } from "@lib/utils";
+
+type Props = {
+ href: string;
+ external?: boolean;
+ underline?: boolean;
+}
+
+const { href, external, underline = true, ...rest } = Astro.props;
+---
+
+
+
+
diff --git a/src/consts.ts b/src/consts.ts
new file mode 100644
index 0000000..78d6565
--- /dev/null
+++ b/src/consts.ts
@@ -0,0 +1,44 @@
+import type { Site, Metadata, Socials } from "@types";
+
+export const SITE: Site = {
+ NAME: "Ryan Pandya",
+ EMAIL: "ryan@ryanpandya.com",
+ NUM_POSTS_ON_HOMEPAGE: 3,
+ NUM_WORKS_ON_HOMEPAGE: 2,
+ NUM_PROJECTS_ON_HOMEPAGE: 3,
+};
+
+export const HOME: Metadata = {
+ TITLE: "Home",
+ DESCRIPTION: "Astro Nano is a minimal and lightweight blog and portfolio.",
+};
+
+export const BLOG: Metadata = {
+ TITLE: "Blog",
+ DESCRIPTION: "A collection of articles on topics I am passionate about.",
+};
+
+export const WORK: Metadata = {
+ TITLE: "Work",
+ DESCRIPTION: "Where I have worked and what I have done.",
+};
+
+export const PROJECTS: Metadata = {
+ TITLE: "Projects",
+ DESCRIPTION: "A collection of my projects, with links to repositories and demos.",
+};
+
+export const SOCIALS: Socials = [
+ {
+ NAME: "twitter-x",
+ HREF: "https://twitter.com/markhorn_dev",
+ },
+ {
+ NAME: "github",
+ HREF: "https://github.com/markhorn-dev"
+ },
+ {
+ NAME: "linkedin",
+ HREF: "https://www.linkedin.com/in/markhorn-dev",
+ }
+];
\ No newline at end of file
diff --git a/src/content/config.ts b/src/content/config.ts
new file mode 100644
index 0000000..860201c
--- /dev/null
+++ b/src/content/config.ts
@@ -0,0 +1,13 @@
+import { defineCollection, z } from "astro:content";
+
+const blog = defineCollection({
+ type: "content",
+ schema: z.object({
+ title: z.string(),
+ description: z.string(),
+ date: z.coerce.date(),
+ draft: z.boolean().optional(),
+ }),
+});
+
+export const collections = { blog };
\ No newline at end of file
diff --git a/src/layouts/PageLayout.astro b/src/layouts/PageLayout.astro
new file mode 100644
index 0000000..c5d2156
--- /dev/null
+++ b/src/layouts/PageLayout.astro
@@ -0,0 +1,27 @@
+---
+import Head from "@components/Head.astro";
+import Header from "@components/Header.astro";
+import Footer from "@components/Footer.astro";
+import { SITE } from "@consts";
+
+type Props = {
+ title: string;
+ description: string;
+};
+
+const { title, description } = Astro.props;
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..03b4e2f
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,40 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+export function formatDate(date: Date) {
+ return Intl.DateTimeFormat("en-US", {
+ month: "short",
+ day: "2-digit",
+ year: "numeric"
+ }).format(date);
+}
+
+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`;
+}
+
+export function dateRange(startDate: Date, endDate?: Date | string): string {
+ const startMonth = startDate.toLocaleString("default", { month: "short" });
+ const startYear = startDate.getFullYear().toString();
+ let endMonth;
+ let endYear;
+
+ if (endDate) {
+ if (typeof endDate === "string") {
+ endMonth = "";
+ endYear = endDate;
+ } else {
+ endMonth = endDate.toLocaleString("default", { month: "short" });
+ endYear = endDate.getFullYear().toString();
+ }
+ }
+
+ return `${startMonth}${startYear} - ${endMonth}${endYear}`;
+}
\ No newline at end of file
diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro
new file mode 100644
index 0000000..9f45a27
--- /dev/null
+++ b/src/pages/blog/index.astro
@@ -0,0 +1,52 @@
+---
+import { type CollectionEntry, getCollection } from "astro:content";
+import PageLayout from "@layouts/PageLayout.astro";
+import Container from "@components/Container.astro";
+import ArrowCard from "@components/ArrowCard.astro";
+import { BLOG } 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));
+---
+
+
+
+
+
Blog
+
+ {
+ years.map((year) => (
+
+ {year}
+
+
+ {posts[year].map((post) => (
+ -
+
+
+ ))}
+
+
+
+ ))
+ }
+
+
+
+
diff --git a/src/styles/global.css b/src/styles/global.css
new file mode 100644
index 0000000..6c2900d
--- /dev/null
+++ b/src/styles/global.css
@@ -0,0 +1,72 @@
+@import "tailwindcss";
+@plugin "@tailwindcss/typography";
+
+html {
+ overflow-y: scroll;
+ color-scheme: light;
+}
+
+html.dark {
+ color-scheme: dark;
+}
+
+html,
+body {
+ @apply size-full;
+}
+
+body {
+ @apply font-sans antialiased;
+ @apply flex flex-col;
+ @apply bg-stone-100 dark:bg-stone-900;
+ @apply text-black/50 dark:text-white/75;
+}
+
+header {
+ @apply fixed top-0 left-0 right-0 z-50 py-5;
+ @apply bg-stone-100/75 dark:bg-stone-900/25;
+ @apply backdrop-blur-sm saturate-200;
+}
+
+main {
+ @apply flex-1 py-32;
+}
+
+footer {
+ @apply py-5 text-sm;
+}
+
+article {
+ @apply max-w-full prose dark:prose-invert prose-img:mx-auto prose-img:my-auto;
+ @apply prose-headings:font-semibold prose-p:font-serif;
+ @apply prose-headings:text-black prose-headings:dark:text-white;
+}
+
+@layer utilities {
+ article a {
+ @apply font-sans text-current underline underline-offset-2;
+ @apply decoration-black/15 dark:decoration-white/30;
+ @apply transition-colors duration-300 ease-in-out;
+ }
+ article a:hover {
+ @apply text-black dark:text-white;
+ @apply decoration-black/25 dark:decoration-white/50;
+ }
+}
+
+.animate {
+ @apply opacity-0 translate-y-3;
+ @apply transition-all duration-700 ease-out;
+}
+
+.animate.show {
+ @apply opacity-100 translate-y-0;
+}
+
+html #back-to-top {
+ @apply opacity-0 pointer-events-none;
+}
+
+html.scrolled #back-to-top {
+ @apply opacity-100 pointer-events-auto;
+}
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..6b7be52
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,17 @@
+export type Site = {
+ NAME: string;
+ EMAIL: string;
+ NUM_POSTS_ON_HOMEPAGE: number;
+ NUM_WORKS_ON_HOMEPAGE: number;
+ NUM_PROJECTS_ON_HOMEPAGE: number;
+};
+
+export type Metadata = {
+ TITLE: string;
+ DESCRIPTION: string;
+};
+
+export type Socials = {
+ NAME: string;
+ HREF: string;
+}[];
\ No newline at end of file
diff --git a/tailwind.config.mjs b/tailwind.config.mjs
new file mode 100644
index 0000000..d48452b
--- /dev/null
+++ b/tailwind.config.mjs
@@ -0,0 +1,20 @@
+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: ["IBM Plex Sans", ...defaultTheme.fontFamily.sans],
+ serif: ["Martel", ...defaultTheme.fontFamily.serif],
+ },
+ },
+ },
+ plugins: [require("@tailwindcss/typography"),
+ require("@tailwindcss/postcss")
+ ],
+};
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 8bf91d3..aa4bd2c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,12 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
- "exclude": ["dist"]
-}
+ "exclude": ["dist"],
+ "compilerOptions": {
+ "strictNullChecks": true,
+ "baseUrl": ".",
+ "paths": {
+ "@*": ["./src/*"]
+ }
+ }
+}
\ No newline at end of file