Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e013842ce |
@@ -1,3 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
# 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
|
||||
|
||||
# Direnv junk
|
||||
.direnv/*
|
||||
*/.direnv/*
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
FROM node:lts AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
EXPOSE 4321
|
||||
CMD node ./dist/server/entry.mjs
|
||||
@@ -1,43 +0,0 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
pnpm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `pnpm install` | Installs dependencies |
|
||||
| `pnpm dev` | Starts local dev server at `localhost:4321` |
|
||||
| `pnpm build` | Build your production site to `./dist/` |
|
||||
| `pnpm preview` | Preview your build locally, before deploying |
|
||||
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `pnpm astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
@@ -1,20 +0,0 @@
|
||||
// @ts-check
|
||||
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()],
|
||||
},
|
||||
});
|
||||
Generated
-61
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767364772,
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
description = "Lifetracker";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs =
|
||||
{ nixpkgs, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
|
||||
in
|
||||
with pkgs;
|
||||
{
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
bashInteractive
|
||||
glibcLocales
|
||||
git
|
||||
nodejs
|
||||
pnpm
|
||||
postgresql
|
||||
inotify-tools
|
||||
claude-code
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "ryanpandya-blog",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.13",
|
||||
"@astrojs/node": "^9.5.1",
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
Generated
-4277
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 749 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,27 +0,0 @@
|
||||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
|
||||
type Props = {
|
||||
entry: CollectionEntry<"blog"> | CollectionEntry<"projects">;
|
||||
}
|
||||
|
||||
const { entry } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={`/${entry.collection}/${entry.slug}`} class="relative group flex flex-nowrap py-3 px-4 pr-10 rounded-lg border border-black/15 dark:border-white/20 hover:bg-black/5 dark:hover:bg-white/5 hover:text-black dark:hover:text-white transition-colors duration-300 ease-in-out">
|
||||
<div class="flex flex-col flex-1 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 top-1/2 right-2 -translate-y-1/2 size-5 stroke-2 fill-none stroke-current">
|
||||
<line x1="5" y1="12" x2="19" y2="12" class="translate-x-3 group-hover:translate-x-0 scale-x-0 group-hover:scale-x-100 transition-transform duration-300 ease-in-out" />
|
||||
<polyline points="12 5 19 12 12 19" class="-translate-x-1 group-hover:translate-x-0 transition-transform duration-300 ease-in-out" />
|
||||
</svg>
|
||||
</a>
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
type Props = {
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href } = Astro.props;
|
||||
---
|
||||
|
||||
<a href={href} class="relative group w-fit flex pl-7 pr-3 py-1.5 flex-nowrap rounded border border-black/15 dark:border-white/20 hover:bg-black/5 dark:hover:bg-white/5 hover:text-black dark:hover:text-white transition-colors duration-300 ease-in-out">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute top-1/2 left-2 -translate-y-1/2 size-4 stroke-2 fill-none stroke-current">
|
||||
<line x1="5" y1="12" x2="19" y2="12" class="translate-x-2 group-hover:translate-x-0 scale-x-0 group-hover:scale-x-100 transition-transform duration-300 ease-in-out" />
|
||||
<polyline points="12 5 5 12 12 19" class="translate-x-1 group-hover:translate-x-0 transition-transform duration-300 ease-in-out" />
|
||||
</svg>
|
||||
<div class="text-sm">
|
||||
<slot/>
|
||||
</div>
|
||||
</a>
|
||||
@@ -1,12 +0,0 @@
|
||||
<button id="back-to-top" class="relative group w-fit flex pl-8 pr-3 py-1.5 flex-nowrap rounded border border-black/15 dark:border-white/20 hover:bg-black/5 dark:hover:bg-white/5 hover:text-black dark:hover:text-white transition-colors duration-300 ease-in-out">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
class="absolute top-1/2 left-2 -translate-y-1/2 size-4 stroke-2 fill-none stroke-current rotate-90">
|
||||
<line x1="5" y1="12" x2="19" y2="12" class="translate-x-2 group-hover:translate-x-0 scale-x-0 group-hover:scale-x-100 transition-transform duration-300 ease-in-out" />
|
||||
<polyline points="12 5 5 12 12 19" class="translate-x-1 group-hover:translate-x-0 transition-transform duration-300 ease-in-out" />
|
||||
</svg>
|
||||
<div class="text-sm">
|
||||
Back to top
|
||||
</div>
|
||||
</button>
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="mx-auto max-w-screen-sm px-5">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
import Container from "@components/Container.astro";
|
||||
import { SITE } from "@consts";
|
||||
import BackToTop from "@components/BackToTop.astro";
|
||||
---
|
||||
|
||||
<footer class="animate">
|
||||
<Container>
|
||||
<div class="relative">
|
||||
<div class="absolute right-0 -top-20">
|
||||
<BackToTop />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
© {new Date().getFullYear()} ·
|
||||
{SITE.NAME}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1 items-center">
|
||||
<button
|
||||
id="light-theme-button"
|
||||
aria-label="Light theme"
|
||||
class="group size-8 flex items-center justify-center rounded-full"
|
||||
>
|
||||
<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="group-hover:stroke-black group-hover:dark:stroke-white transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
<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 size-8 flex items-center justify-center rounded-full"
|
||||
>
|
||||
<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="group-hover:stroke-black group-hover:dark:stroke-white transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
<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 size-8 flex items-center justify-center rounded-full"
|
||||
>
|
||||
<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="group-hover:stroke-black group-hover:dark:stroke-white transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
<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>
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
const { date } = Astro.props;
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()}>
|
||||
{
|
||||
date.toLocaleDateString("en-us", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})
|
||||
}
|
||||
</time>
|
||||
@@ -1,198 +0,0 @@
|
||||
---
|
||||
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;
|
||||
---
|
||||
|
||||
<!-- 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-dark.svg"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/svg+xml"
|
||||
href="/favicon-light.svg"
|
||||
media="(prefers-color-scheme: light)"
|
||||
/>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon-light.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- Font preloads -->
|
||||
<!-- <link rel="preload" href={martel400} as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href={martel600} as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href={plex400} as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href={plex600} as="font" type="font/woff2" crossorigin /> -->
|
||||
|
||||
<!-- 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)} />
|
||||
|
||||
<ClientRouter />
|
||||
|
||||
<!-- RSS Link -->
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title={SITE.NAME}
|
||||
href={new URL("rss.xml", Astro.site)}
|
||||
/>
|
||||
|
||||
<script>
|
||||
import type { TransitionBeforeSwapEvent } from "astro:transitions/client";
|
||||
document.addEventListener("astro:before-swap", (e) =>
|
||||
[
|
||||
...(e as TransitionBeforeSwapEvent).newDocument.head.querySelectorAll(
|
||||
'link[as="font"]'
|
||||
),
|
||||
].forEach((link) => link.remove())
|
||||
);
|
||||
</script>
|
||||
|
||||
<script is:inline>
|
||||
function init() {
|
||||
preloadTheme();
|
||||
onScroll();
|
||||
animate();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
const darkThemeButton = document.getElementById("dark-theme-button");
|
||||
darkThemeButton?.addEventListener("click", () => {
|
||||
localStorage.setItem("theme", "dark");
|
||||
toggleTheme(true);
|
||||
});
|
||||
|
||||
const systemThemeButton = document.getElementById("system-theme-button");
|
||||
systemThemeButton?.addEventListener("click", () => {
|
||||
localStorage.setItem("theme", "system");
|
||||
toggleTheme(window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
});
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (event) => {
|
||||
if (localStorage.theme === "system" || !localStorage.theme) {
|
||||
toggleTheme(event.matches);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("scroll", onScroll);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
const animateElements = document.querySelectorAll(".animate");
|
||||
|
||||
animateElements.forEach((element, index) => {
|
||||
setTimeout(() => {
|
||||
element.classList.add("show");
|
||||
}, index * 150);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function preloadTheme() {
|
||||
const userTheme = localStorage.theme;
|
||||
|
||||
if (userTheme === "light" || userTheme === "dark") {
|
||||
toggleTheme(userTheme === "dark");
|
||||
} else {
|
||||
toggleTheme(window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => init());
|
||||
document.addEventListener("astro:after-swap", () => init());
|
||||
preloadTheme();
|
||||
</script>
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
import Container from "@components/Container.astro";
|
||||
import Link from "@components/Link.astro";
|
||||
import { SITE } from "@consts";
|
||||
---
|
||||
|
||||
<header>
|
||||
<Container>
|
||||
<div class="flex flex-wrap gap-y-2 justify-between">
|
||||
<Link href="/" underline={false}>
|
||||
<div class="font-semibold">
|
||||
{SITE.NAME}
|
||||
</div>
|
||||
</Link>
|
||||
<nav class="flex gap-1">
|
||||
<Link href="/blog">
|
||||
blog
|
||||
</Link>
|
||||
<span>
|
||||
{`/`}
|
||||
</span>
|
||||
<Link href="/work">
|
||||
work
|
||||
</Link>
|
||||
<span>
|
||||
{`/`}
|
||||
</span>
|
||||
<Link href="/projects">
|
||||
projects
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
import { cn } from "@lib/utils";
|
||||
|
||||
type Props = {
|
||||
href: string;
|
||||
external?: boolean;
|
||||
underline?: boolean;
|
||||
}
|
||||
|
||||
const { href, external, underline = true, ...rest } = Astro.props;
|
||||
---
|
||||
|
||||
<a
|
||||
href={href}
|
||||
target={ external ? "_blank" : "_self" }
|
||||
class={cn("inline-block decoration-black/15 dark:decoration-white/30 hover:decoration-black/25 hover:dark:decoration-white/50 text-current hover:text-black hover:dark:text-white transition-colors duration-300 ease-in-out", underline && "underline underline-offset-2")}
|
||||
{...rest}>
|
||||
<slot/>
|
||||
</a>
|
||||
@@ -1,44 +0,0 @@
|
||||
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",
|
||||
}
|
||||
];
|
||||
@@ -1,13 +0,0 @@
|
||||
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 };
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
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;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<Head title={`${title} | ${SITE.NAME}`} description={description} />
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,40 +0,0 @@
|
||||
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}`;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
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));
|
||||
---
|
||||
|
||||
<PageLayout title={BLOG.TITLE} description={BLOG.DESCRIPTION}>
|
||||
<Container>
|
||||
<div class="space-y-10">
|
||||
<div class="animate font-semibold text-black dark:text-white">Blog</div>
|
||||
<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="flex flex-col gap-4">
|
||||
{posts[year].map((post) => (
|
||||
<li>
|
||||
<ArrowCard entry={post} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</PageLayout>
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Ryan Pandya</h1>
|
||||
<h4>More to come.</h4>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,72 +0,0 @@
|
||||
@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;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
}[];
|
||||
@@ -1,20 +0,0 @@
|
||||
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")
|
||||
],
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user