+3
.gitignore
+3
.gitignore
+10
-4
astro.config.mjs
+10
-4
astro.config.mjs
···
3
3
import icon from "astro-icon";
4
4
import mdx from "@astrojs/mdx";
5
5
import svelte from "@astrojs/svelte";
6
-
import tailwind from "@astrojs/tailwind";
6
+
import tailwindcss from "@tailwindcss/vite";
7
7
8
8
import netlify from "@astrojs/netlify";
9
+
10
+
import og from "astro-og";
9
11
10
12
// https://astro.build/config
11
13
export default defineConfig({
12
-
integrations: [tailwind(), svelte(), mdx(), icon()],
13
-
output: "server",
14
+
integrations: [svelte(), mdx(), icon(), og()],
15
+
output: "static",
14
16
adapter: netlify(),
15
-
});
17
+
vite: {
18
+
plugins: [tailwindcss()],
19
+
},
20
+
site: "https://zeu.dev"
21
+
});
bun.lockb
bun.lockb
This is a binary file and will not be displayed.
+15
-13
package.json
+15
-13
package.json
···
10
10
"astro": "astro"
11
11
},
12
12
"dependencies": {
13
-
"@astrojs/check": "^0.9.4",
14
-
"@astrojs/mdx": "^3.1.9",
15
-
"@astrojs/netlify": "^5.5.4",
16
-
"@astrojs/svelte": "^5.7.3",
17
-
"@astrojs/tailwind": "^5.1.2",
18
-
"@iconify-json/tabler": "^1.2.7",
19
-
"@sveltejs/vite-plugin-svelte": "^4.0.0",
20
-
"astro": "^4.16.11",
21
-
"astro-icon": "^1.1.2",
22
-
"svelte": "^5.1.16",
23
-
"tailwindcss": "^3.4.14",
24
-
"typescript": "^5.6.3"
13
+
"@astrojs/check": "0.9.6",
14
+
"@astrojs/mdx": "4.3.12",
15
+
"@astrojs/netlify": "6.6.3",
16
+
"@astrojs/svelte": "7.2.2",
17
+
"@iconify-json/tabler": "^1.2.23",
18
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
19
+
"@tailwindcss/vite": "^4.1.17",
20
+
"astro": "5.16.4",
21
+
"astro-icon": "^1.1.5",
22
+
"astro-og": "^0.3.1",
23
+
"bits-ui": "^2.14.4",
24
+
"svelte": "^5.45.5",
25
+
"tailwindcss": "^4.1.17",
26
+
"typescript": "^5.9.3"
25
27
},
26
28
"devDependencies": {
27
-
"@tailwindcss/typography": "^0.5.15"
29
+
"@tailwindcss/typography": "^0.5.19"
28
30
}
29
31
}
public/opensauced.png
public/opensauced.png
This is a binary file and will not be displayed.
public/pomerium.png
public/pomerium.png
This is a binary file and will not be displayed.
-1
public/src/env.d.ts
-1
public/src/env.d.ts
···
1
-
/// <reference path="../.astro/types.d.ts" />
public/zuplo.png
public/zuplo.png
This is a binary file and will not be displayed.
+1
-3
src/components/BlogCard.astro
+1
-3
src/components/BlogCard.astro
···
6
6
}
7
7
8
8
const { blog } = Astro.props;
9
-
10
-
const directory = blog.collection + "/";
11
9
---
12
10
13
-
<a href={directory + blog.slug}>
11
+
<a href={`/blog/${blog.slug}`}>
14
12
<article
15
13
class="flex flex-row justify-between items-center w-full h-fit px-8 py-4 border-2 text-white rounded-md">
16
14
<div class="flex flex-col gap-2 !!no-underline ">
+40
-23
src/components/SiteLayout.astro
+40
-23
src/components/SiteLayout.astro
···
1
1
---
2
-
import { ViewTransitions } from "astro:transitions";
2
+
import NavigationDialog from "./NavigationDialog.svelte";
3
3
const { title } = Astro.props;
4
4
---
5
5
···
7
7
<head>
8
8
<meta charset="utf-8" />
9
9
<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>">
10
+
<script
11
+
defer
12
+
is:inline
13
+
src="https://assets.onedollarstats.com/tracker.js"
14
+
id="stonks"
15
+
></script>
10
16
<meta name="viewport" content="width=device-width" />
11
17
<meta name="generator" content={Astro.generator} />
18
+
<meta property="og:type" content="website" />
19
+
<meta property="og:url" content="https://www.zeu.dev" />
20
+
<meta property="og:title" content={title ?? "zeu.dev"} />
21
+
<meta property="og:description" content="Personal site of Zeu, a software engineer." />
22
+
<meta property="twitter:card" content="/site_screenshot.png">
23
+
<meta property="og:image" content="/site_screenshot.png">
24
+
<meta property="og:image:alt" content="zeu in lower case ASCII">
12
25
<title>{ title }</title>
13
-
<ViewTransitions />
14
26
</head>
15
27
<body class="relative font-syne bg-neutral-900 text-yellow-500 grid grid-cols-1 md:grid-cols-5 w-full h-full min-w-screen min-h-screen">
16
-
<aside class="sticky top-0 max-h-screen flex items-center border-yellow-500 justify-between md:border-r-2 w-full md:w-fit md:justify-start md:items-start h-fit md:h-full md:flex-col gap-4 font-medium p-4">
28
+
<aside class="sticky top-0 z-50 bg-neutral-900 max-h-screen flex items-center border-yellow-500 justify-between md:border-r-2 w-full md:w-fit md:justify-start md:items-start h-fit md:h-full md:flex-col gap-4 font-medium p-4">
17
29
<div class="flex flex-col gap-2 text-xl">
18
30
<h1>
19
31
<a href="/" class="after:content-['_->']">zeu.dev</a>
···
21
33
<h2 class="hidden md:flex">all things me!</h2>
22
34
</div>
23
35
24
-
<button id="menu_button" class="px-2 py-1 border border-yellow-500 self-end w-fit h-fit md:hidden">
25
-
Menu
26
-
</button>
36
+
<NavigationDialog client:load />
27
37
28
38
<nav id="navigation" class="hidden md:flex flex-col gap-4 w-full md:col-span-1" transition:persist>
29
39
<hr class="border-yellow-500 " />
30
40
31
41
<section class="flex flex-col gap-2 text-white">
32
-
<h3 class="text-yellow-500">socials</h3>
33
-
<a href="https://twitter.com/zeu_dev" target="_blank" class="after:content-['_โ']">twitter</a>
34
-
<a href="https://github.com/zeucapua" target="_blank" class="after:content-['_โ']">github</a>
35
-
<a href="https://twitch.tv/zeu_dev" target="_blank" class="after:content-['_โ']">twitch</a>
42
+
<h3 class="text-yellow-500">
43
+
<a href="/blog" class="after:content-['_->'] before:content-['๐_']">blog</a>
44
+
</h3>
36
45
</section>
37
46
38
47
<hr class="border-yellow-500 " />
39
48
40
49
<section class="flex flex-col gap-2 text-white">
41
50
<h3 class="text-yellow-500">
42
-
<a href="/blog" class="after:content-['_->']">blog</a>
51
+
<a href="/resume" class="after:content-['_->'] before:content-['๐ผ_']">resume</a>
52
+
</h3>
53
+
<h3 class="text-yellow-500">
54
+
<a href="/open-source" class="after:content-['_->'] before:content-['๐งฎ_']">open source</a>
55
+
</h3>
56
+
<h3 class="text-yellow-500">
57
+
<a href="/as-seen-on" class="after:content-['_->'] before:content-['๐ฅ_']">as seen on</a>
43
58
</h3>
44
59
</section>
45
60
46
61
<hr class="border-yellow-500 " />
47
62
48
63
<section class="flex flex-col gap-2 text-white">
49
-
<h3 class="text-yellow-500">
50
-
<a href="/resume" class="after:content-['_->']">resume</a>
51
-
</h3>
52
-
<h3 class="text-yellow-500">
53
-
<a href="/open-source" class="after:content-['_->']">open source</a>
54
-
</h3>
55
-
<a href="https://app.opensauced.pizza" target="_blank" class="after:content-['_โ']">OpenSauced</a>
56
-
<a href="https://easytodo.link" target="_blank" class="after:content-['_โ']">easytodo.link</a>
64
+
<h3 class="text-yellow-500">socials</h3>
65
+
<a href="https://bsky.app/profile/zeu.dev" target="_blank" class="after:content-['_โ'] before:content-['๐ฆ_']">bluesky</a>
66
+
<a href="https://github.com/zeucapua" target="_blank" class="after:content-['_โ'] before:content-['๐ป_']">github</a>
67
+
<a href="https://tangled.sh/zeu.dev" target="_blank" class="after:content-['_โ'] before:content-['๐งถ_']">tangled</a>
68
+
<a href="https://twitch.tv/zeu_dev" target="_blank" class="after:content-['_โ'] before:content-['๐พ_']">twitch</a>
69
+
<a href="https://stream.place/zeu.dev" target="_blank" class="after:content-['_โ'] before:content-['๐ช_']">streamplace</a>
57
70
</section>
71
+
58
72
</nav>
59
73
</aside>
60
74
···
64
78
</body>
65
79
</html>
66
80
67
-
<script>
68
-
const menuButton = document.querySelector("#menu_button");
69
-
const navSection = document.querySelector("#navigation");
70
-
</script>
71
81
72
82
<style is:global>
83
+
@import "tailwindcss";
84
+
@plugin "@tailwindcss/typography";
85
+
86
+
@theme {
87
+
--font-syne: "SyneMono";
88
+
}
89
+
73
90
@font-face {
74
91
font-family: "SyneMono";
75
92
src: url("/SyneMono-Regular.ttf");
+25
src/content/about/as-seen-on.mdx
+25
src/content/about/as-seen-on.mdx
···
1
+
---
2
+
updated_at: 12-5-2025
3
+
draft: false
4
+
---
5
+
6
+
import { Icon } from "astro-icon/components";
7
+
8
+
I've participated in a few community events and video. I stream on [stream.place](https://stream.place/zeu.dev)
9
+
and [Twitch](https://twitch.tv/zeu_dev), and post videos sometimes on my [youtube channel](https://www.youtube.com/@zeucapua851).
10
+
11
+
## Web Dev Challenge: Build a multiplayer web app using PartyKit [<Icon name="tabler:brand-youtube" class="inline-block" />](https://youtu.be/DbaBeLDU-oY?si=Y074Vjt9-7BX2NnI)
12
+
13
+
- 24k+ views on YouTube
14
+
- 18 stars on the typer99 GitHub repository
15
+
- Built a type racing game with powerups and obstacles, implementing PartyKit into a SvelteKit application
16
+
- Participated alongside Jason Lengstorf (host), Nick Taylor (prev OpenSauced), and Steve Ruiz (tldraw.com)
17
+
18
+
## Web Dev Challenge: What can you build in 4 hours with Google Gemini? [<Icon name="tabler:brand-youtube" class="inline-block" />](https://www.youtube.com/watch?v=pn5Jju4FNG4)
19
+
20
+
- 97k+ views on YouTube
21
+
- 8 stars on the thankadev GitHub repository
22
+
- Built an AI generated thank you note that can be shared on X (formerly Twitter) for each library listed in a given repository's SBOM (software bill of materials)
23
+
- Sponsored by Google Gemini AI and is integrated into the project, all of which was built in 4 hours
24
+
- Participated alongside Jason Lengstorf (host), Brian Holt (Neon DB), and Sarah aka badcop_ (prev Boot.dev)
25
+
- Recorded live in studio by a professional camera and sound crew throughout a 12 hour production day
+21
-4
src/content/about/open-source.mdx
+21
-4
src/content/about/open-source.mdx
···
1
1
---
2
-
updated_at: 10-21-2024
2
+
updated_at: 01-08-2025
3
3
draft: false
4
4
---
5
5
6
6
import { Icon } from "astro-icon/components";
7
7
8
+
<hr />
9
+
10
+
# Applications
11
+
## Myb [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/zeucapua/myb)
12
+
- A custom third party Bluesky client with extra features like bookmarks and drafts
13
+
- Implemented with SvelteKit, using libraries such as the AT Protocol SDK and TanStack Query
14
+
15
+
## Easytodo [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/zeucapua/easytodo.link)
16
+
- An easy and local first todo list app, with per task stopwatch and other planned features
17
+
- Implemented with SvelteKit, using a custom persistent local storage reactive function closure
18
+
19
+
## Gust [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/zeucapua/gust)
20
+
- A time tracker productivity tool with authentication, cloud storage, and full CRUD capabilities
21
+
- Implemented features using Astro Partials and Client Islands (Svelte components) that are inline and rendered on demand using HTMX library, a HTML-centered AJAX API
22
+
23
+
<hr />
24
+
25
+
# Explorations
8
26
## Skogz [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/zeucapua/skogz)
9
27
- An experimental Svelte 5 SSR (server side rendered) router, built on top of the Vite Extra template
10
28
- Implemented custom middleware that can handle loader functions and load the correct components based on a routes configuration object
11
29
12
-
## Gust [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/zeucapua/gust)
13
-
- A time tracker productivity tool with authentication, cloud storage, and full CRUD capabilities
14
-
- Implemented features using Astro Partials and Client Islands (Svelte components) that are inline and rendered on demand using HTMX library, a HTML-centered AJAX API
30
+
<hr />
15
31
32
+
# Packages
16
33
## Typew [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/zeucapua/typew)
17
34
- An animated text component that mimics the typewriter, writing letter by letter
18
35
- Implemented as a no dependency needed Svelte library
+47
-21
src/content/about/resume.mdx
+47
-21
src/content/about/resume.mdx
···
5
5
6
6
import { Icon } from "astro-icon/components";
7
7
8
-
A generalist Software Engineer, specialized in solutions and product engineering, utilizing technologies across the stack and languages like Javascript (Next.js), Python (Django), and GoLang (Cobra). Based in San Bernardino, California USA with 8 months of remote and open source experience.
8
+
A Software Engineer making products and solutions, particular in frontend development,
9
+
10
+
<hr />
9
11
10
12
# Experience
13
+
## Software Engineer, Pomerium [<Icon name="tabler:external-link" class="inline-block" />](https://pomerium.com)
14
+
_April 2025 - October 2025_
15
+
- Implemented new features and bug fixes on the Layer 7 proxy security dashboards
16
+
- Worked on two product offerings for general users and enterprise customers, each with their own monorepo
17
+
- Coordinated with backend engineers and management to ensure continuity between UX for more technical users
18
+
with the CLI and the frontend web interface
19
+
- Technology Stack: React Router, React, Redux, Material UI, PostHog, GoLang
20
+
<a href="https://pomerium.com" class="flex gap-2 items-center -my-8 hover:underline" target="_blank">
21
+
<img src="/pomerium.png" alt="Pomerium Logo" class="size-8 rounded" />
22
+
pomerium.com
23
+
<Icon name="tabler:external-link" class="inline-block -top-2" />
24
+
</a>
25
+
11
26
## Software Engineer, OpenSauced [<Icon name="tabler:external-link" class="inline-block" />](https://opensauced.pizza)
12
-
_February 2024 - Present_
27
+
_February 2024 - October 2024_
13
28
- Worked with different technology stacks and products throughout the company
14
29
- Coordinated and collaborated with every department, including marketing, product, and engineering
15
30
- Helped market the brand and features on X (formerly Twitter), resulting in thousands of impressions
···
17
32
- Reviewed external contributors' issues and pull requests
18
33
19
34
### The OpenSauced App [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/open-sauced/app)
20
-
- Tech Stack: Next.js, React, TailwindCSS, PostHog, Playwright, Supabase, Storybook, SWR and more
35
+
- Technology Stack: Next.js, React, TailwindCSS, PostHog, Playwright, Supabase, Storybook, SWR and more
21
36
- 88 pull requests in the last 6 months, about 20% of all contributions on the application product
22
37
23
38
#### Repo Pages
···
38
53
- Implemented major features for the CODEOWNERS centered CLI program built on Golang, Cobra CLI framework, and the BubbleTea terminal framework
39
54
- `pizza generate config`: an automatic or interactive terminal command that creates a global available YAML file that keeps track Git attributions
40
55
- `pizza offboard`: removes Git attributions by YAML configuration and CODEOWNERS files
41
-
- Tech Stack: GoLang, Cobra CLI, BubbleTea
56
+
- Technology Stack: GoLang, Cobra CLI, BubbleTea
42
57
43
58
### Landing Page & Blog [<Icon name="tabler:brand-github-filled" class="inline-block" />](https://github.com/open-sauced/landing-page)
44
59
- Implemented and managed the Sanity CMS for viewing on the company blog
45
60
- Styled with TailwindCSS and Framer Motion based on Figma designs by the Product Manager
46
61
- Created and updated multiple landing pages for product (the app and Pizza CLI) and various use cases (Teams, Maintainers, Contributors, and Students)
47
-
- Tech Stack: Next.js, React, TailwindCSS, PostHog and more
62
+
- Technology Stack: Next.js, React, TailwindCSS, PostHog and more
63
+
64
+
<a href="https://github.com/open-sauced" class="flex gap-2 items-center -my-8 hover:underline" target="_blank">
65
+
<img src="/opensauced.png" alt="OpenSauced Logo" class="size-8 rounded" />
66
+
github.com/open-sauced
67
+
<Icon name="tabler:external-link" class="inline-block -top-2" />
68
+
</a>
69
+
70
+
<hr />
71
+
72
+
# Contract Work
73
+
74
+
## Consultant Software Engineer, Zuplo [<Icon name="tabler:external-link" class="inline-block" />](https://zuplo.com)
75
+
_November 2025 - December 2025_
76
+
- Implemented a proof of concept for `zudoku schema generate example` CLI command, generates examples for response and request body schemas
77
+
without explicit examples given an OpenAPI JSON file
78
+
- Technology Stack: Yargs, @clack/prompts, Typescript, Nx
79
+
<a href="https://zuplo.com" class="flex gap-2 items-center -my-8 hover:underline" target="_blank">
80
+
<img src="/zuplo.png" alt="Zuplo Logo" class="size-8 rounded" />
81
+
zuplo.com
82
+
<Icon name="tabler:external-link" class="inline-block -top-2" />
83
+
</a>
48
84
49
85
## Full Stack Developer, Buttondown [<Icon name="tabler:external-link" class="inline-block" />](https://buttondown.email)
50
86
_November 2023 - February 2024_
51
87
- Implemented features and bug fixes to the email platform, supporting thousands of newsletters
52
88
- Managed and updated copy and designs on the platformโs marketing website
53
-
- Tech Stack: Django, Vue, Next.js, TailwindCSS and more
89
+
- Technology Stack: Django, Vue, Next.js, TailwindCSS and more
54
90
55
91
## Web Developer, SmartWiz [<Icon name="tabler:external-link" class="inline-block" />](https://smartwiz.io)
56
-
_September 2023 - November 2024_
92
+
_September 2023 - November 2023_
57
93
- Refactored existing Stripe integrations and implemented designs from Figma
58
-
- Tech Stack: Remix, React, TailwindCSS, Stripe, and more
94
+
- Technology Stack: Remix, React, TailwindCSS, Stripe, and more
95
+
96
+
<hr />
59
97
60
98
## Volunteer Web Developer, Progressive Victory [<Icon name="tabler:external-link" class="inline-block" />](https://progressivevictory.win)
61
99
_August 2022 - November 2022_
62
100
- Led the frontend development of the initial organization website, implementing designs in coordination with organizers and backend engineers
63
101
- Implemented Airtable as a CMS to display information about volunteer efforts and political candidatesโ campaigns throughout the United States
64
-
- Tech Stack: React, Next.js, Airtable, Storybook, Vercel and more
65
-
66
-
# Public Speaking and Community Participation
67
-
## Web Dev Challenge: Build a multiplayer web app using PartyKit
68
-
- 24k+ views on YouTube
69
-
- 18 stars on the typer99 GitHub repository
70
-
- Built a type racing game with powerups and obstacles, implementing PartyKit into a SvelteKit application
71
-
72
-
## Upcoming Web Dev Challenge (Set to release late November as a Thanksgiving episode)
73
-
- Built an AI generated thank you note that can be shared on X (formerly Twitter) for each library listed in a given repository's SBOM (software bill of materials)
74
-
- Sponsored by Google Gemini AI and is integrated into the project, all of which was built in 4 hours
75
-
- Participated alongside Jason Lengstorf (host), Brian Holt (Neon DB), and Sarah aka badcop_ (prev Boot.dev)
76
-
- Recorded live in studio by a professional camera and sound crew throughout a 12 hour production day
102
+
- Technology Stack: React, Next.js, Airtable, Storybook, Vercel and more
-22
src/content/blog/atproto-oauth.md
-22
src/content/blog/atproto-oauth.md
···
1
-
---
2
-
title: AT Protocol OAuth Basics Tutorial
3
-
description: Implement your own Bluesky client using the `@atproto/oauth-client-node` package
4
-
date: "2024-11-24"
5
-
draft: true
6
-
---
7
-
8
-
# Initial Project Setup
9
-
10
-
# Database and Auth Stores
11
-
12
-
# Implement AT Protocol Client
13
-
14
-
# Encryption Practices feat. Pilcrow
15
-
16
-
# Locals Variables and Login Flow
17
-
18
-
# Implement ATProto OAuth Callback Endpoint
19
-
20
-
# Hooks and Middleware for User and Agent
21
-
22
-
# Logout Flow
-92
src/content/blog/indexed-1.mdx
-92
src/content/blog/indexed-1.mdx
···
1
-
---
2
-
title: "This is HTML: Indexed - Web Development Explained"
3
-
description: An overview on what HTML is and how to use them while making websites. Part 1 of the Indexed - Web Development Explained series.
4
-
date: 01-06-2024
5
-
draft: false
6
-
---
7
-
import CodePreview from "../../components/CodePreview.astro";
8
-
9
-
## Elements, Tags, and the Properties Within
10
-
11
-
HTML is the programming language used to layout the content inside a webpage. Writing in HTML involves using
12
-
elements, also known as tags (throughout the series, I will be using these interchangeably), and putting them in
13
-
order for your design.
14
-
15
-
Most tags are used in twos, both with angle brackets with the type inside, with the ending tag's type starting
16
-
with a slash. Between the two is where we'd put content, which can also include more elements. Some elements,
17
-
like `<img />`, are what's called "self-closing tags", denoted with the slash at the end.
18
-
19
-
<CodePreview>
20
-
<div slot="preview">
21
-
<h2>An error has occurred??</h2>
22
-
<img src="https://media.giphy.com/media/ltx2rcXk8sE4OCHnFB/giphy.gif" />
23
-
</div>
24
-
<div slot="code">
25
-
```html
26
-
<div>
27
-
<h2>An error has occurred??</h2>
28
-
<img src="https://media.giphy.com/media/ltx2rcXk8sE4OCHnFB/giphy.gif" />
29
-
</div>
30
-
```
31
-
</div>
32
-
</CodePreview>
33
-
34
-
We can
35
-
configure tags like these with its properties, or "props" for short. For example, to have this image tag to show a
36
-
picture, we'll set its `src` property a link, as well as state an alternate text to make it accessible to those
37
-
hard of seeing.
38
-
39
-
## So Common You Should Memorize Them
40
-
41
-
Mozilla Developer Network (MDN), a definitive web development resource online, has a list of all HTML tags. All in
42
-
all, there are 142 elements, 115 of which are functional today with the rest being deprecated. Now you
43
-
aren't expected to learn every single tag, but when you make enough websites there will be elements that
44
-
comes up often.
45
-
46
-
Here's my list of tags I use everyday:
47
-
48
-
- `<div>`, `<section>`, `<main>`: containers to label off parts of the page. I use `<main>` for the page's
49
-
content, `<section>` when there's a defined part (like the code preview above), and `<div>` when I need
50
-
to group elements, but don't need to label them, usually for layout and styling reasons.
51
-
- `<h1>`->`<h6>`, `<p>`: headers and paragraph text elements. These come with default styles,
52
-
but since I use TailwindCSS, they all look the same. No matter how they look, they do help me
53
-
distinguish between a wall of text and something to highlight.
54
-
- `<img>`: images (and gifs)! Make sure to use the `alt` prop to describe the image to those who might
55
-
need to use screen users. Accessibility is great!
56
-
- `<input>`, `<button>`: interactive elements, connect these with a `<form>` or script function to
57
-
allow people to use your websites.
58
-
- `<a>`: the link tag. Go to another page (either on the same website or another on the internet) by
59
-
adding the link as its `href` prop.
60
-
61
-
There are plenty more that I'm missing as I rattle these off at the top of my head. I memorize these since
62
-
I use these everyday no matter the kind of website I'm creating, but if there's anything I need that I
63
-
don't remember (for example `<video>` or `<track>`), MDN and Google are always there for me.
64
-
65
-
66
-
## Just the beginning
67
-
68
-
HTML can only get you so far. Over the years, a lot of functionality has been baked into these elements.
69
-
A recent example can be seen with the `<dialog>` tag that makes modals native to the browser, a feature
70
-
that's been in use for decades before and practically only available in Javascript packages and frameworks.
71
-
72
-
With that in mind, dialogs and buttons can't work without some scripting, so in the next part of the
73
-
Indexed series, we'll start to take a look at the basics of Javascript ("JS") and how to use them
74
-
with HTML. First up is using vanilla JS, before moving onto JS UI frameworks that are popular today,
75
-
like React and Svelte.
76
-
77
-
## Thanks for reading
78
-
79
-
Indexed is a series to write down all I've learned about web development. Think of this as my notebook
80
-
scribbled with tips and lessons about how I think about websites. We'll see if I can make one of these
81
-
blogs every week.
82
-
83
-
Thatโs in the future though! For now, Iโd like to thank you for reading this blog.
84
-
I very much appreciate it, and if you can do me a favor, take a look at the links down below.
85
-
Catch you in the next one!
86
-
87
-
## Shameless Plugs
88
-
89
-
- If you'd like to look at any of my code, they are all open sourced on my Github [here](https://github.com/zeucapua).
90
-
- Most of my projects are made live on my Twitch stream! Code new projects with me weekly on [twitch.tv/zeu_dev](https://twitch.tv/zeu_dev).
91
-
- Any comments or questions can reach me on Twitter. Follow me at [twitter.com/zeu_dev](https://twitter.com/zeu_dev).
92
-
- Interested on other stuff? Visit my personal website at [zeu.dev](https://zeu.dev) and my other blogs here on [thoughts.zeu.dev](https://thoughts.zeu.dev).
-100
src/content/blog/rash-stack.md
-100
src/content/blog/rash-stack.md
···
1
-
---
2
-
title: "The RASH Stack: React, Astro, Svelte, HTMX"
3
-
description: Strap a horse on a rocket and use Astro Partials to stream UI components via HTMX.
4
-
date: "2024-09-10"
5
-
draft: true
6
-
link: https://github.com/zeucapua/
7
-
---
8
-
9
-
## The Project
10
-
11
-
I'm building a Notion-like note taking system that'll (probably) make me more productive with as
12
-
little fluff and features as possible. Each block can be added, edited, and deleted freely, with
13
-
the full page data is saved locally.
14
-
15
-
For the features to demo the RASH stack, we are making:
16
-
- Plain text (Astro + HTMX)
17
-
- Checkbox (Svelte)
18
-
- Whiteboard (React via tldraw library)
19
-
20
-
## What is HTMX?
21
-
22
-
The basic idea behind HTMX is that APIs should return HTML that shows the updated part of the page
23
-
after a request. For example, let's make a button that adds our feature, simple plain text input:
24
-
25
-
```html
26
-
<h1>Hodgepodge App</h1>
27
-
<ul id="blocks">
28
-
29
-
</ul>
30
-
31
-
<button
32
-
hx-post="/addTextBlock"
33
-
hx-target="#blocks"
34
-
hx-swap="beforeend"
35
-
>
36
-
Text
37
-
</button>
38
-
```
39
-
40
-
The button has a few new attributes to use HTMX: `hx-post` specifies a `POST` API request
41
-
at the endpoint `/addTextBlock`; `hx-target` is to determine what element we want to update;
42
-
`hx-swap` tells us how we should handle the HTML after the response, in this case adding the
43
-
response before the button. Now the endpoint can return HTML like so...
44
-
45
-
```html
46
-
<li><textarea /></li>
47
-
```
48
-
49
-
And on the button press, the initial page will now be:
50
-
51
-
```html
52
-
<h1>Hodgepodge App</h1>
53
-
<ul id="blocks">
54
-
<li><textarea /></li>
55
-
</ul>
56
-
57
-
<button
58
-
hx-post="/addTextBlock"
59
-
hx-target="#blocks"
60
-
hx-swap="beforeend"
61
-
>
62
-
Text
63
-
</button>
64
-
```
65
-
66
-
While we will be using more attributes during the rest of the blog, there are plenty more
67
-
I can't possibly go over. To find more information on the list of attributes and HTMX in
68
-
general, go to [htmx.org](https://htmx.org) or read their book
69
-
[Hypermedia Systems](https://hypermedia.systems) which is free online, but you can be cool
70
-
like me and get the physical book:
71
-
72
-
# TODO: ADD PHOTO OF HYPERMEDIA SYSTEMS
73
-
74
-
## Astro Partials
75
-
76
-
Since we are using Astro as our meta framework, we can use its feature **Astro Partials** in
77
-
conjunction with HTMX for a smooth developer experience. It allows Astro components inside the
78
-
`/src/pages` directory to be easily called via its endpoint and returned as HTML.
79
-
80
-
For the example above, the only thing needed is for the Lemon to be in the
81
-
`/src/pages/addTextBlock.astro` file with one extra line in the *Component Script* (the code
82
-
inside the frontmatter like section ran on the server):
83
-
84
-
```astro
85
-
---
86
-
export const partial = true;
87
-
---
88
-
<li><textarea /></li>
89
-
```
90
-
91
-
Now we can use this page via HTMX under the `/revealSecret` endpoint, like the example above!
92
-
93
-
## Forms and Values via Astro Partials
94
-
95
-
Swapping HTML is all fine and dandy, but if we want to do more, we can leverage existing
96
-
hypermedia controls to extend functionality like the `<form>` and `<input>` elements.
97
-
98
-
## Streaming in Client Components as Astro Islands
99
-
100
-
## Using the HTMX Javascript API
+21
src/pages/as-seen-on.astro
+21
src/pages/as-seen-on.astro
···
1
+
---
2
+
import { render } from "astro:content";
3
+
import SiteLayout from "../components/SiteLayout.astro";
4
+
import { getEntry } from "astro:content";
5
+
6
+
const entry = await getEntry("about", "as-seen-on");
7
+
if (!entry) {
8
+
throw new Error("Could not find ../content/about/as-seen-on.mdx");
9
+
}
10
+
const { Content } = await render(entry);
11
+
---
12
+
13
+
<SiteLayout title="zeu.dev">
14
+
<main class="flex flex-col gap-4 px-4 py-8 text-white text-lg">
15
+
<h1 class="text-5xl text-yellow-500 font-bold">As seen on</h1>
16
+
17
+
<span class="prose prose-invert prose-yellow lg:prose-xl prose-headings:text-yellow-500">
18
+
<Content />
19
+
</span>
20
+
</main>
21
+
</SiteLayout>
+1
-1
src/pages/blog/[...slug].astro
+1
-1
src/pages/blog/[...slug].astro
···
21
21
const { Content } = await blog.render();
22
22
---
23
23
24
-
<SiteLayout>
24
+
<SiteLayout title={blog.data.title}>
25
25
<main class="flex flex-col gap-4 w-full py-8 px-4">
26
26
<article class="prose prose-invert w-full max-w-6xl prose-code:text-purple-400">
27
27
<h1 class="text-yellow-500">{blog.data.title}</h1>
+1
-1
src/pages/blog/index.astro
+1
-1
src/pages/blog/index.astro
···
11
11
12
12
<SiteLayout title="Blogs | zeu.dev">
13
13
<main class="flex flex-col gap-8 py-8 max-w-3xl">
14
-
<h1 class="text-5xl text-white font-bold">Blog Posts</h1>
14
+
<h1 class="text-5xl text-yellow-500 font-bold">Blog Posts</h1>
15
15
16
16
{ // @ts-ignore
17
17
blogs.map((blog: any) => <BlogCard {blog} />)
+1
-14
src/pages/index.astro
+1
-14
src/pages/index.astro
···
23
23
</pre>
24
24
<div class="flex flex-col gap-4">
25
25
<h1 class="text-5xl font-bold text-yellow-500">hey hi hello!</h1>
26
-
<h2 class="text-yellow-500 font-medium text-2xl">welcome to my site!</h2>
27
-
28
-
<p>
29
-
I have plenty of ideas and projects I want to talk to you guys about
30
-
and so I made this site to showcase all I've been doing.
31
-
</p>
32
-
33
-
<p>
34
-
updates and showcases will be hosted here, alongside my own thoughts.
35
-
</p>
36
-
37
-
38
-
<p class="text-yellow-500">- @zeu_dev</p>
39
26
</div>
40
27
41
28
<section class="flex flex-col lg:flex-row gap-8 my-8">
···
46
33
47
34
<a href="/resume" class="border-2 rounded-lg w-fit p-4">
48
35
<h3 class="text-xl text-yellow-500 font-bold">Resume</h3>
49
-
<p>Notable professional works</p>
36
+
<p>Career and professional work</p>
50
37
</a>
51
38
</section>
52
39
</main>
+4
-1
src/pages/open-source.astro
+4
-1
src/pages/open-source.astro
···
1
1
---
2
2
import SiteLayout from "../components/SiteLayout.astro";
3
3
import { getEntry } from "astro:content";
4
+
4
5
const entry = await getEntry("about", "open-source");
5
-
6
+
if (!entry) {
7
+
throw new Error("Could not find ../content/about/open-source.mdx");
8
+
}
6
9
const { Content } = await entry.render();
7
10
---
8
11
+4
-1
src/pages/resume.astro
+4
-1
src/pages/resume.astro
···
1
1
---
2
2
import SiteLayout from "../components/SiteLayout.astro";
3
3
import { getEntry } from "astro:content";
4
+
4
5
const entry = await getEntry("about", "resume");
5
-
6
+
if (!entry) {
7
+
throw new Error("Could not find ../content/about/resume.mdx");
8
+
}
6
9
const { Content } = await entry.render();
7
10
---
8
11
-12
tailwind.config.mjs
-12
tailwind.config.mjs