Repository: alexcarpenter/alexcarpenter.me
Branch: main
Commit: 837e3b238481
Files: 228
Total size: 33.2 MB
Directory structure:
gitextract_15dbuwo8/
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc.json
├── .vscode/
│ └── settings.json
├── Makefile
├── README.md
├── astro.config.mjs
├── bin/
│ └── chezmoi
├── package.json
├── public/
│ ├── humans.txt
│ └── robots.txt
├── src/
│ ├── components/
│ │ ├── demo.astro
│ │ └── external-link.astro
│ ├── consts.ts
│ ├── content/
│ │ ├── gear/
│ │ │ ├── aer-day-sling-3.md
│ │ │ ├── anker-laptop-charger.md
│ │ │ ├── apple-studio-display.md
│ │ │ ├── aqara-presence-sensor-fp2.md
│ │ │ ├── aqara-smart-home-hub-m3.md
│ │ │ ├── aqara-smart-lock-u100.md
│ │ │ ├── aqara-water-leak-sensor.md
│ │ │ ├── aqara-wireless-mini-switch.md
│ │ │ ├── benchmade-mini-bugout.md
│ │ │ ├── breville-barista-express-espresso-machine.md
│ │ │ ├── caldigit-ts4-thunderbolt-4-dock.md
│ │ │ ├── carepod-mini-ultrasonic-cool-mist-humidifier.md
│ │ │ ├── cerave-hydrating-facial-cleanser.md
│ │ │ ├── chris-reeve-small-sebenza-31.md
│ │ │ ├── coway-airmega-air-purifier.md
│ │ │ ├── coway-airmega-hepa-purifier.md
│ │ │ ├── ecobee-smart-thermostat.md
│ │ │ ├── eltamd-uv-daily-face-sunscreen.md
│ │ │ ├── emr-tek-inferno-red-light.md
│ │ │ ├── eufy-robot-lawn-mower-e15.md
│ │ │ ├── eufy-security-camera-floodlight-camera-e340.md
│ │ │ ├── eufy-security-homebase-s380.md
│ │ │ ├── eufy-video-doorbell-e340.md
│ │ │ ├── evergoods-civic-access-pouch-2l.md
│ │ │ ├── evergoods-civic-half-zip.md
│ │ │ ├── evergoods-civic-panel-loader-16l.md
│ │ │ ├── fellow-atmos-vacuum-coffee-canister.md
│ │ │ ├── focusrite-scarlett-audio-interface.md
│ │ │ ├── foursevens-mini-turbo-mk-iii.md
│ │ │ ├── global-6-inch-chef-s-knife.md
│ │ │ ├── goruck-bullet-15l.md
│ │ │ ├── hds-systems-edc-tactical.md
│ │ │ ├── herman-miller-sayl-chair.md
│ │ │ ├── hurom-h400-cold-press-juicer.md
│ │ │ ├── ironmaster-super-bench-pro-v2.md
│ │ │ ├── kalita-wave.md
│ │ │ ├── kinesis-advantage-360-keyboard.md
│ │ │ ├── knipex-cobra-water-pump-pliers.md
│ │ │ ├── knipex-pliers-wrench.md
│ │ │ ├── la-roche-posay-lipikar-ap-triple-repair-moisturizing-cream.md
│ │ │ ├── lutron-aurora-smart-bulb-dimmer-switch.md
│ │ │ ├── m4-mac-mini.md
│ │ │ ├── maestri-house-rechargeable-milk-frother-with-stand.md
│ │ │ ├── neutrogena-hydro-boost-water-cream.md
│ │ │ ├── nitecore-nb-air.md
│ │ │ ├── peak-design-packing-cube.md
│ │ │ ├── pica-dry-longlife-automatic-pencil.md
│ │ │ ├── powerblock-elite-exp-adjustable-dumbbells.md
│ │ │ ├── prometheus-beta-qrv2-titanium.md
│ │ │ ├── rocket-espresso-appartamento-tca-espresso-machine.md
│ │ │ ├── rode-podmic-microphone.md
│ │ │ ├── roka-oslo-2-0-wind-down.md
│ │ │ ├── roost-laptop-stand.md
│ │ │ ├── snow-peak-ti-double-h200-stacking-mug.md
│ │ │ ├── sony-wh-1000xm4-headphones.md
│ │ │ ├── spyderco-dragonfly-2.md
│ │ │ ├── subminimal-flick-wdt-espresso-distribution-tool.md
│ │ │ ├── synology-ds224.md
│ │ │ ├── tactile-turn-apollo-flashlight.md
│ │ │ ├── tactile-turn-rockwall-thumbstud.md
│ │ │ ├── technivorm-moccamaster.md
│ │ │ ├── the-james-brand-midland.md
│ │ │ ├── timemore-sculptor-064s-coffee-grinder.md
│ │ │ ├── tom-bihn-synik-22.md
│ │ │ ├── tom-bihn-truck.md
│ │ │ ├── toto-washlet-s5-electronic-bidet.md
│ │ │ ├── tp-link-16-port-gigabit-poe-switch.md
│ │ │ ├── tp-link-6e-mesh-system.md
│ │ │ ├── unifi-dream-router-7.md
│ │ │ ├── wera-bitholding-screwdriver.md
│ │ │ ├── wera-tools-hex-set.md
│ │ │ ├── yubikey-5c-nfc.md
│ │ │ └── zebralight-sc5c-ii-le.md
│ │ ├── jobs/
│ │ │ ├── clerk.md
│ │ │ ├── hashicorp.md
│ │ │ ├── masuga-design.md
│ │ │ ├── mighty-in-the-midwest.md
│ │ │ ├── nationbuilder.md
│ │ │ └── watershed.md
│ │ ├── notes/
│ │ │ ├── 2024-12-05-1.md
│ │ │ ├── 2025-02-17-1.md
│ │ │ ├── 2025-02-19-2.md
│ │ │ ├── 2025-02-25-1.md
│ │ │ ├── 2025-03-22-1.md
│ │ │ ├── 2025-04-13-1.md
│ │ │ ├── 2025-04-14-1.md
│ │ │ ├── 2025-04-14-2.md
│ │ │ ├── 2025-04-15-1.md
│ │ │ ├── 2025-04-16-1.md
│ │ │ ├── 2025-04-17-1.md
│ │ │ ├── 2025-04-18-1.md
│ │ │ ├── 2025-04-18-2.md
│ │ │ ├── 2025-04-19-1.md
│ │ │ ├── 2025-04-19-2.md
│ │ │ ├── 2025-04-30-1.md
│ │ │ ├── 2025-05-01-1.md
│ │ │ ├── 2025-05-02-1.md
│ │ │ ├── 2025-05-14-1.md
│ │ │ ├── 2025-05-26-1.md
│ │ │ ├── 2025-06-18-1.md
│ │ │ ├── 2025-06-18-2.md
│ │ │ ├── 2025-06-18-3.md
│ │ │ ├── 2025-06-18-4.md
│ │ │ ├── 2025-06-21-1.md
│ │ │ ├── 2025-06-24-1.md
│ │ │ ├── 2025-06-27-1.md
│ │ │ ├── 2025-07-01-1.md
│ │ │ ├── 2025-07-02-1.md
│ │ │ ├── 2025-07-08-1.md
│ │ │ ├── 2025-07-11-1.md
│ │ │ ├── 2025-07-15-1.md
│ │ │ ├── 2025-07-17-1.md
│ │ │ ├── 2025-07-17-2.md
│ │ │ ├── 2025-07-18-1.md
│ │ │ ├── 2025-07-18-2.md
│ │ │ ├── 2025-07-21-1.md
│ │ │ ├── 2025-07-23-1.md
│ │ │ ├── 2025-07-29-1.md
│ │ │ ├── 2025-08-05-1.md
│ │ │ ├── 2025-08-13-1.md
│ │ │ ├── 2025-08-13-2.md
│ │ │ ├── 2025-08-13-3.md
│ │ │ ├── 2025-08-14-1.md
│ │ │ ├── 2025-08-18-1.md
│ │ │ ├── 2025-08-18-2.md
│ │ │ ├── 2025-08-23-1.md
│ │ │ ├── 2025-08-25-1.md
│ │ │ ├── 2025-08-26-1.md
│ │ │ ├── 2025-08-26-2.md
│ │ │ ├── 2025-09-05-1.md
│ │ │ ├── 2025-09-05-2.md
│ │ │ ├── 2025-09-06-1.md
│ │ │ ├── 2025-09-12-1.md
│ │ │ ├── 2025-09-19-1.md
│ │ │ ├── 2025-09-20-1.md
│ │ │ ├── 2025-09-28-1.md
│ │ │ ├── 2025-10-23-1.md
│ │ │ ├── 2025-11-05-1.md
│ │ │ ├── 2025-11-05-2.md
│ │ │ ├── 2025-11-18-1.md
│ │ │ ├── 2025-12-03-1.md
│ │ │ ├── 2025-12-15-1.md
│ │ │ ├── 2026-01-06-1.md
│ │ │ ├── 2026-01-17-01.md
│ │ │ ├── 2026-01-19-1.md
│ │ │ ├── 2026-01-22-1.md
│ │ │ ├── 2026-01-26-1.md
│ │ │ ├── 2026-01-28-1.md
│ │ │ ├── 2026-01-29-1.md
│ │ │ ├── 2026-02-03-1.md
│ │ │ ├── 2026-02-03-2.md
│ │ │ ├── 2026-02-06-1.md
│ │ │ ├── 2026-02-06-2.md
│ │ │ ├── 2026-02-09-1.md
│ │ │ ├── 2026-02-11-1.md
│ │ │ ├── 2026-02-11-2.md
│ │ │ ├── 2026-02-12-1.md
│ │ │ ├── 2026-02-13-1.md
│ │ │ ├── 2026-02-14-1.md
│ │ │ ├── 2026-02-15-1.md
│ │ │ ├── 2026-02-15-2.md
│ │ │ ├── 2026-02-16-1.md
│ │ │ ├── 2026-02-16-2.md
│ │ │ ├── 2026-02-24-1.md
│ │ │ ├── 2026-03-01-1.md
│ │ │ ├── 2026-03-01-2.md
│ │ │ ├── 2026-03-04-1.md
│ │ │ ├── 2026-03-05-1.md
│ │ │ ├── 2026-03-06-1.md
│ │ │ ├── 2026-03-06-2.md
│ │ │ ├── 2026-03-08-1.md
│ │ │ ├── 2026-03-11-1.md
│ │ │ ├── 2026-03-15-1.md
│ │ │ ├── 2026-03-17-1.md
│ │ │ ├── 2026-03-20-1.md
│ │ │ ├── 2026-03-21-1.md
│ │ │ ├── 2026-03-21-2.md
│ │ │ ├── 2026-03-27-1.md
│ │ │ ├── 2026-04-04-1.md
│ │ │ └── 2026-04-26-1.md
│ │ ├── oss-contributions.json
│ │ └── recommendations/
│ │ ├── amy-stuart.md
│ │ ├── andrew-possehl.md
│ │ ├── benjamin-kohl.md
│ │ ├── danielle-dunn.md
│ │ ├── david-mosher.md
│ │ ├── jimmy-merritello.md
│ │ ├── kyle-luck.md
│ │ └── melissa-taylor.md
│ ├── content.config.ts
│ ├── env.d.ts
│ ├── layouts/
│ │ ├── base-layout.astro
│ │ └── demo-layout.astro
│ ├── lib/
│ │ ├── markdown.js
│ │ └── utils.ts
│ ├── pages/
│ │ ├── 404.astro
│ │ ├── demos/
│ │ │ ├── bg-repeat-round.astro
│ │ │ ├── box-decoration-break-clone.astro
│ │ │ ├── button-disabled-color-mix.astro
│ │ │ ├── button-tint.astro
│ │ │ ├── hanging-punctuation.astro
│ │ │ ├── inline-trailing-icon.astro
│ │ │ ├── list-format-to-parts.astro
│ │ │ ├── list-item-icon-baseline.astro
│ │ │ └── modal-aspect-ratio.astro
│ │ ├── index.astro
│ │ ├── notes/
│ │ │ ├── [...page].astro
│ │ │ ├── [...slug].astro
│ │ │ └── rss.xml.js
│ │ └── uses/
│ │ └── index.astro
│ └── styles/
│ ├── demos.css
│ └── global.css
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# 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
AGENT.md
================================================
FILE: .npmrc
================================================
min-release-age=7
================================================
FILE: .prettierignore
================================================
node_modules/**
package.json
pnpm-lock.yaml
================================================
FILE: .prettierrc.json
================================================
{
"plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"prettier.documentSelectors": ["**/*.astro"],
"[astro]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
================================================
FILE: Makefile
================================================
checkpoint:
@git add .
@git commit -m "checkpoint at $$(date '+%Y-%m-%dT%H:%M:%S%z')"
@git push
@echo Checkpoint created and pushed to remote
================================================
FILE: README.md
================================================
# alexcarpenter.me
- Framework: Astro
- Deployment: Vercel
- Styling: Tailwind CSS
## Running locally
```bash
git clone https://github.com/alexcarpenter/alexcarpenter.me.git
cd alexcarpenter.me
pnpm install
pnpm dev
```
================================================
FILE: astro.config.mjs
================================================
import sitemap from "@astrojs/sitemap";
import {
transformerMetaHighlight,
transformerMetaWordHighlight,
transformerNotationDiff,
} from "@shikijs/transformers";
import tailwindcss from "@tailwindcss/vite";
import icon from "astro-icon";
import { defineConfig } from "astro/config";
import rehypeExternalLinks from "rehype-external-links";
export default defineConfig({
site: "https://alexcarpenter.me",
vite: {
plugins: [tailwindcss()],
define: {
"import.meta.env.COMMIT_SHA": JSON.stringify(
process.env.VERCEL_GIT_COMMIT_SHA ||
process.env.COMMIT_SHA ||
"56720e6",
),
},
},
integrations: [sitemap(), icon()],
markdown: {
rehypePlugins: [[rehypeExternalLinks, { target: "_blank" }]],
shikiConfig: {
themes: {
light: "github-light",
dark: "github-dark",
},
transformers: [
transformerNotationDiff(),
transformerMetaHighlight(),
transformerMetaWordHighlight(),
],
cache: true,
},
},
});
================================================
FILE: bin/chezmoi
================================================
[File too large to display: 33.1 MB]
================================================
FILE: package.json
================================================
{
"name": "alexcarpenter-me",
"type": "module",
"version": "7.0.0",
"engines": {
"node": ">=22"
},
"pnpm": {
"overrides": {
"lodash": "4.17.23"
}
},
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro",
"format": "prettier --write .",
"check": "astro check"
},
"dependencies": {
"@astrojs/check": "0.9.9",
"@astrojs/rss": "4.0.18",
"@astrojs/sitemap": "3.7.2",
"@shikijs/transformers": "^4.1.0",
"@sindresorhus/slugify": "^3.0.0",
"@tailwindcss/vite": "^4.3.0",
"astro": "6.3.6",
"astro-icon": "^1.1.5",
"markdown-it": "^14.1.1",
"rehype-external-links": "^3.0.0",
"sanitize-html": "^2.17.4",
"sharp": "^0.34.5",
"tailwindcss": "4.2.4",
"typescript": "^6.0.3"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2",
"@types/sanitize-html": "^2.16.1",
"prettier": "^3.8.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.7.4"
},
"packageManager": "pnpm@10.33.0"
}
================================================
FILE: public/humans.txt
================================================
TECHNOLOGIES
- Astro: https://astro.build
- Tailwind CSS: https://tailwindcss.com
- Geist Font: https://vercel.com/font
================================================
FILE: public/robots.txt
================================================
Sitemap: https://alexcarpenter.me/sitemap-index.xml
================================================
FILE: src/components/demo.astro
================================================
---
import { Icon } from "astro-icon/components";
type Props = {
src: string;
title?: string;
class?: string;
aspectRatio?: "16:9" | "4:3" | "1:1";
};
const { src, title, class: className, aspectRatio = "16:9" } = Astro.props;
---
<div
class:list={[
"border-separator bg-grid group/demo relative overflow-hidden rounded-md border",
aspectRatio === "16:9" && "aspect-video",
aspectRatio === "4:3" && "aspect-4/3",
aspectRatio === "1:1" && "aspect-square",
className,
]}
>
<a
href={src}
target="_blank"
class:list={[
"text-muted-foreground absolute top-0 right-0 grid size-8 place-content-center",
"hover:text-foreground opacity-0 transition-[opacity,color] group-hover/demo:opacity-100 focus:opacity-100",
]}
>
<span class="sr-only">Open demo in a new tab</span>
<Icon name="expand" class="size-3" aria-hidden="true" />
</a>
<div class="h-full max-w-full min-w-80 resize-x overflow-x-auto">
<iframe
src={src}
class="h-full w-full"
title={title || "Demo"}
loading="lazy"></iframe>
</div>
</div>
================================================
FILE: src/components/external-link.astro
================================================
---
import { Icon } from "astro-icon/components";
const { href, text, class: className } = Astro.props;
---
{/* prettier-ignore */}
<a
href={href}
target="_blank"
rel="noopener noreferrer"
class:list={["relative inline-block pr-3.5", className]}
>{text}<Icon
name="arrow-up-right"
aria-hidden="true"
class="absolute ml-1 inline h-5 w-2.5 flex-none"
/></a
>
================================================
FILE: src/consts.ts
================================================
export const GEAR_CATEGORIES = [
"bag",
"coffee",
"edc",
"gym",
"home",
"wellness",
"kitchen",
"knife",
"flashlight",
"misc",
"tools",
"travel",
"workspace",
] as const;
type GearCategory = (typeof GEAR_CATEGORIES)[number];
export const GEAR_CATEGORY_MAP: Record<
GearCategory,
{
title: string;
description: string;
slug: string;
}
> = {
bag: {
title: "Bags",
description: "My current rotation of bags.",
slug: "bags",
},
coffee: {
title: "Coffee",
description: "My favorite coffee gear and accessories.",
slug: "coffee",
},
edc: {
title: "Everyday Carry",
description: "Tools I carry daily that fit into my pockets.",
slug: "everyday-carry",
},
flashlight: {
title: "Flashlights",
description: "My favorite flashlights and lighting gear.",
slug: "flashlights",
},
gym: {
title: "Home Gym",
description: "My home gym equipment and setup.",
slug: "home-gym",
},
wellness: {
title: "Health & Wellness",
description: "",
slug: "health-and-wellness",
},
home: {
title: "Home",
description: "My home automation setup.",
slug: "home",
},
kitchen: {
title: "Kitchen",
description: "My favorite kitchen gadgets and tools.",
slug: "kitchen",
},
knife: {
title: "Knives",
description: "Pocket knives and multi-tools.",
slug: "knives",
},
tools: {
title: "Tools",
description: "Homeowner tools and equipment.",
slug: "tools",
},
travel: {
title: "Travel",
description: "Gear I use while traveling and out and about.",
slug: "travel",
},
workspace: {
title: "Workspace",
description: "The tools I use for work.",
slug: "workspace",
},
misc: {
title: "Miscellaneous",
description: "Other gear that doesn't fit into a specific category.",
slug: "miscellaneous",
},
};
================================================
FILE: src/content/gear/aer-day-sling-3.md
================================================
---
eyebrow: "Diaper bag"
name: Aer Day Sling 3
category: bag
link: https://aersf.com/collections/slings/products/day-sling-3
---
================================================
FILE: src/content/gear/anker-laptop-charger.md
================================================
---
eyebrow: Laptop charger
name: Anker Laptop Charger
category: travel
link: https://amzn.to/3JbHQdS
---
================================================
FILE: src/content/gear/apple-studio-display.md
================================================
---
eyebrow: Display
name: Apple Studio Display
category: workspace
link: https://www.apple.com/studio-display/
---
================================================
FILE: src/content/gear/aqara-presence-sensor-fp2.md
================================================
---
eyebrow: Presence Sensor
name: Aqara Presence Sensor FP2
category: home
link: https://amzn.to/4me46ma
---
================================================
FILE: src/content/gear/aqara-smart-home-hub-m3.md
================================================
---
eyebrow: Hub
name: Aqara Smart Home Hub M3
category: home
link: https://amzn.to/4nJzDNP
---
================================================
FILE: src/content/gear/aqara-smart-lock-u100.md
================================================
---
eyebrow: Lock
name: Aqara Smart Lock U100
category: home
link: https://amzn.to/3ImFIzM
status: retired
---
================================================
FILE: src/content/gear/aqara-water-leak-sensor.md
================================================
---
eyebrow: Water sensor
name: Aqara Water Leak Sensor
category: home
link: https://amzn.to/3IxiqHj
---
================================================
FILE: src/content/gear/aqara-wireless-mini-switch.md
================================================
---
eyebrow: Switch
name: Aqara Wireless Mini Switch
category: home
link: https://amzn.to/4lSIE5r
---
================================================
FILE: src/content/gear/benchmade-mini-bugout.md
================================================
---
eyebrow: Lightweight
name: Benchmade mini bugout
category: knife
link: https://benchmade.com/products/533bk-1-mini-bugout
---
================================================
FILE: src/content/gear/breville-barista-express-espresso-machine.md
================================================
---
status: retired
eyebrow: Espresso machine
name: Breville Barista Express Espresso Machine
category: coffee
link: https://amzn.to/4lpyCbt
---
================================================
FILE: src/content/gear/caldigit-ts4-thunderbolt-4-dock.md
================================================
---
eyebrow: Thunderbolt Doc
name: CalDigit TS4 Thunderbolt 4 Dock
category: workspace
link: https://amzn.to/4p7vy73
---
================================================
FILE: src/content/gear/carepod-mini-ultrasonic-cool-mist-humidifier.md
================================================
---
eyebrow: Humidifier
name: Carepod Mini Ultrasonic Cool Mist Humidifier
category: wellness
link: https://amzn.to/4p7vy73
---
================================================
FILE: src/content/gear/cerave-hydrating-facial-cleanser.md
================================================
---
eyebrow: Face Cleanser
name: CeraVe Hydrating Facial Cleanser
category: wellness
link: https://amzn.to/4rf3wH9
---
================================================
FILE: src/content/gear/chris-reeve-small-sebenza-31.md
================================================
---
eyebrow: Titanium
name: Chris Reeve Small Sebenza 31
category: knife
link: https://chrisreeve.com/collections/sebenza-31
favorite: true
---
================================================
FILE: src/content/gear/coway-airmega-air-purifier.md
================================================
---
eyebrow: Air purifier
name: Coway Airmega Air Purifier
category: home
link: https://amzn.to/4lJGy7I
---
================================================
FILE: src/content/gear/coway-airmega-hepa-purifier.md
================================================
---
eyebrow: Air purifier
name: Coway Airmega AP-1512HH(W) True HEPA Purifier
category: wellness
link: https://amzn.to/4gVxZWt
---
================================================
FILE: src/content/gear/ecobee-smart-thermostat.md
================================================
---
eyebrow: Thermostat
name: ecobee Smart Thermostat
category: home
link: https://amzn.to/40hxmiV
status: retired
---
================================================
FILE: src/content/gear/eltamd-uv-daily-face-sunscreen.md
================================================
---
eyebrow: Sunscreen
name: EltaMD UV Daily Face Sunscreen
category: wellness
link: https://amzn.to/4aCSO6n
---
================================================
FILE: src/content/gear/emr-tek-inferno-red-light.md
================================================
---
eyebrow: Red light therapy
name: EMR-TEK Inferno Red Light Panel
category: wellness
link: https://www.emr-tek.com/alexcarpenter
---
================================================
FILE: src/content/gear/eufy-robot-lawn-mower-e15.md
================================================
---
eyebrow: Lawnmower
name: eufy Robot Lawn Mower E15
category: home
link: https://eufy.com/products/t28801a1
---
================================================
FILE: src/content/gear/eufy-security-camera-floodlight-camera-e340.md
================================================
---
eyebrow: Security Camera
name: eufy Security Camera Floodlight Camera E340
category: home
link: https://amzn.to/451OziV
status: retired
---
================================================
FILE: src/content/gear/eufy-security-homebase-s380.md
================================================
---
eyebrow: Camera storage
name: eufy Security HomeBase S380
category: home
link: https://amzn.to/44Mcfqb
---
================================================
FILE: src/content/gear/eufy-video-doorbell-e340.md
================================================
---
eyebrow: Video doorbell
name: eufy Video Doorbell E340
category: home
link: https://eufy.com/products/t8214111
---
================================================
FILE: src/content/gear/evergoods-civic-access-pouch-2l.md
================================================
---
eyebrow: Tech pouch
name: EVERGOODS CIVIC Access Pouch 2L
category: bag
link: https://evergoods.us/collections/pouches/products/civic-access-pouch-2l
---
================================================
FILE: src/content/gear/evergoods-civic-half-zip.md
================================================
---
eyebrow: Everday bag
name: EVERGOODS CIVIC Half Zip 22L
category: bag
link: https://evergoods.us/collections/backpacks/products/civic-half-zip
---
================================================
FILE: src/content/gear/evergoods-civic-panel-loader-16l.md
================================================
---
eyebrow: "Tech bag"
name: EVERGOODS CIVIC Panel Loader 16L
category: bag
link: https://evergoods.us/collections/backpacks/products/civic-panel-loader?variant=42250173153414
---
================================================
FILE: src/content/gear/fellow-atmos-vacuum-coffee-canister.md
================================================
---
eyebrow: "Coffee Storage"
name: Fellow Atmos Vacuum Coffee Canister
category: coffee
link: https://amzn.to/4qIHQ5c
---
================================================
FILE: src/content/gear/focusrite-scarlett-audio-interface.md
================================================
---
eyebrow: Audio Interface
name: Focusrite Scarlett Audio Interface
category: workspace
link: https://amzn.to/3HW1OsZ
---
================================================
FILE: src/content/gear/foursevens-mini-turbo-mk-iii.md
================================================
---
eyebrow: Mini
name: Foursevens Mini Turbo Mk. III
category: flashlight
link: https://darksucks.com/products/mini-turbo-mkiii-ti-two-tone
---
================================================
FILE: src/content/gear/global-6-inch-chef-s-knife.md
================================================
---
eyebrow: Chefs knife
name: Global 6 inch Chef's Knife
category: kitchen
link: https://amzn.to/46GLh5z
favorite: true
---
================================================
FILE: src/content/gear/goruck-bullet-15l.md
================================================
---
eyebrow: Ruck
name: Goruck Bullet 15L
category: bag
link: https://www.goruck.com/products/bullet-ruck-15l
---
================================================
FILE: src/content/gear/hds-systems-edc-tactical.md
================================================
---
eyebrow: Rotary UI
name: HDS Systems EDC Tactical
category: flashlight
link: https://hdssystems.com/Products/Tactical/
favorite: true
---
================================================
FILE: src/content/gear/herman-miller-sayl-chair.md
================================================
---
eyebrow: Chair
name: Herman Miller Sayl chair
category: workspace
link: https://amzn.to/4p6gl5X
---
================================================
FILE: src/content/gear/hurom-h400-cold-press-juicer.md
================================================
---
eyebrow: Juicer
name: Hurom H400 Cold Press Juicer
category: wellness
link: https://amzn.to/4pyDeiQ
---
================================================
FILE: src/content/gear/ironmaster-super-bench-pro-v2.md
================================================
---
eyebrow: Adjustable Bench
name: Ironmaster Super Bench PRO V2
category: gym
link: https://www.ironmaster.com/products/super-bench-pro-v2/
---
================================================
FILE: src/content/gear/kalita-wave.md
================================================
---
eyebrow: Pour over
name: Kalita Wave
category: coffee
link: https://amzn.to/3JdOd0e
---
================================================
FILE: src/content/gear/kinesis-advantage-360-keyboard.md
================================================
---
eyebrow: Keyboard
name: Kinesis Advantage 360 Keyboard
category: workspace
link: https://kinesis-ergo.com/keyboards/advantage360/
---
================================================
FILE: src/content/gear/knipex-cobra-water-pump-pliers.md
================================================
---
eyebrow: Pliers
name: KNIPEX Cobra Pliers Wrench
category: tools
link: https://amzn.to/4scQBq2
---
================================================
FILE: src/content/gear/knipex-pliers-wrench.md
================================================
---
eyebrow: Pliers
name: KNIPEX Pliers Wrench
category: tools
link: https://amzn.to/3MCR3xL
---
================================================
FILE: src/content/gear/la-roche-posay-lipikar-ap-triple-repair-moisturizing-cream.md
================================================
---
eyebrow: Body Moisturizer
name: La Roche-Posay Lipikar AP+ Triple Repair Moisturizing Cream
category: wellness
link: https://amzn.to/4bVp6fc
---
================================================
FILE: src/content/gear/lutron-aurora-smart-bulb-dimmer-switch.md
================================================
---
eyebrow: Dimmer switch
name: Lutron Aurora Smart Bulb Dimmer Switch
category: home
link: https://amzn.to/44wCl1N
status: retired
---
================================================
FILE: src/content/gear/m4-mac-mini.md
================================================
---
eyebrow: Personal computer
name: M4 Mac Mini
category: workspace
link: https://amzn.to/4d1AExL
---
================================================
FILE: src/content/gear/maestri-house-rechargeable-milk-frother-with-stand.md
================================================
---
eyebrow: Frother
name: Maestri House Rechargeable Milk Frother with Stand
category: kitchen
link: https://amzn.to/4p1RnUi
---
================================================
FILE: src/content/gear/neutrogena-hydro-boost-water-cream.md
================================================
---
eyebrow: Face Moisturizer
name: Neutrogena Hydro Boost Water Cream
category: wellness
link: https://amzn.to/4685v7O
---
================================================
FILE: src/content/gear/nitecore-nb-air.md
================================================
---
eyebrow: Power bank
name: Nitecore NB Air
category: travel
link: https://amzn.to/4mf8YYf
---
================================================
FILE: src/content/gear/peak-design-packing-cube.md
================================================
---
eyebrow: Packing cube
name: Peak Design Packing Cube
category: travel
link: https://amzn.to/4mdapq8
---
================================================
FILE: src/content/gear/pica-dry-longlife-automatic-pencil.md
================================================
---
eyebrow: Pencil
name: Pica-Dry Longlife Automatic Pencil
category: tools
link: https://amzn.to/4s37SSq
---
================================================
FILE: src/content/gear/powerblock-elite-exp-adjustable-dumbbells.md
================================================
---
eyebrow: Adjustable Dumbbells
name: PowerBlock Elite EXP Adjustable Dumbbells
category: gym
link: https://amzn.to/4rti9Hr
---
================================================
FILE: src/content/gear/prometheus-beta-qrv2-titanium.md
================================================
---
eyebrow: Flashlight
name: Prometheus Beta QRv2 Titanium
category: edc
link: https://darksucks.com/products/beta-qrv3-titanium-6al-4v
---
================================================
FILE: src/content/gear/rocket-espresso-appartamento-tca-espresso-machine.md
================================================
---
eyebrow: Espresso machine
name: Rocket Espresso Appartamento TCA Espresso Machine
category: coffee
link: https://amzn.to/49Oc3uw
---
================================================
FILE: src/content/gear/rode-podmic-microphone.md
================================================
---
eyebrow: Microphone
name: Rode PodMic Microphone
category: workspace
link: https://amzn.to/42h7wMM
---
================================================
FILE: src/content/gear/roka-oslo-2-0-wind-down.md
================================================
---
eyebrow: Red lens filter glasses
name: ROKA Oslo 2.0 Wind Down
category: wellness
link: https://www.roka.com/products/oslo-2-0-huberman-blue-light-glasses
---
================================================
FILE: src/content/gear/roost-laptop-stand.md
================================================
---
eyebrow: Laptop stand
name: Roost Laptop Stand
category: travel
link: https://amzn.to/4oDH49T
---
================================================
FILE: src/content/gear/snow-peak-ti-double-h200-stacking-mug.md
================================================
---
eyebrow: Mug
name: snow peak Ti-Double H200 Stacking Mug
category: coffee
link: https://snowpeak.com/products/ti-double-h200-stacking-mug-tw-124
favorite: true
---
================================================
FILE: src/content/gear/sony-wh-1000xm4-headphones.md
================================================
---
eyebrow: Noise canceling
name: Sony WH-1000XM4 Headphones
category: travel
link: https://amzn.to/4fIJDmY
---
================================================
FILE: src/content/gear/spyderco-dragonfly-2.md
================================================
---
eyebrow: Knife
name: Spyderco Dragonfly 2
category: edc
link: https://spyderco.com/products/dragonfly%E2%84%A2-2-lightweight
---
================================================
FILE: src/content/gear/subminimal-flick-wdt-espresso-distribution-tool.md
================================================
---
eyebrow: "Distribution Tool"
name: Subminimal Flick WDT Espresso Distribution Tool
category: coffee
link: https://amzn.to/3OioxCm
---
================================================
FILE: src/content/gear/synology-ds224.md
================================================
---
eyebrow: Storage
name: Synology DS224+
category: home
link: https://amzn.to/3MV10GO
---
================================================
FILE: src/content/gear/tactile-turn-apollo-flashlight.md
================================================
---
eyebrow: Titanium
name: Tactile Turn Apollo
category: flashlight
link: https://tactileturn.com/products/apollo?variant=41733727813737
---
================================================
FILE: src/content/gear/tactile-turn-rockwall-thumbstud.md
================================================
---
eyebrow: Titanium
name: Tactile Turn Rockwall Thumbstud
category: knife
link: https://tactileknife.co/products/rockwall-thumbstud
---
================================================
FILE: src/content/gear/technivorm-moccamaster.md
================================================
---
eyebrow: Coffee maker
name: Technivorm Moccamaster
category: coffee
link: https://amzn.to/3HvPY8x
favorite: true
---
================================================
FILE: src/content/gear/the-james-brand-midland.md
================================================
---
eyebrow: Key Management
name: The James Brand Midland
category: edc
link: https://thejamesbrand.com/products/the-midland
---
================================================
FILE: src/content/gear/timemore-sculptor-064s-coffee-grinder.md
================================================
---
eyebrow: Espresso grinder
name: TIMEMORE Sculptor 064S Flat Burr Grinder
category: coffee
link: https://amzn.to/46mbhTa
---
================================================
FILE: src/content/gear/tom-bihn-synik-22.md
================================================
---
eyebrow: "Family bag"
name: TOM BIHN Synik 22
category: bag
link: https://tombihn.com/collections/best-sellers/products/synik-22?variant=44784969253053
favorite: true
---
================================================
FILE: src/content/gear/tom-bihn-truck.md
================================================
---
eyebrow: "Grocery bag"
name: TOM BIHN Truck
category: bag
link: https://tombihn.com/collections/tote-bags/products/the-truck?variant=42944967246013
favorite: true
---
================================================
FILE: src/content/gear/toto-washlet-s5-electronic-bidet.md
================================================
---
eyebrow: Bidet
name: TOTO WASHLET S5 Electronic Bidet
category: wellness
link: https://amzn.to/4pvfagv
---
================================================
FILE: src/content/gear/tp-link-16-port-gigabit-poe-switch.md
================================================
---
eyebrow: Network switch
name: TP-Link 16 Port Gigabit PoE Switch
category: home
link: https://amzn.to/44E00gN
status: retired
---
================================================
FILE: src/content/gear/tp-link-6e-mesh-system.md
================================================
---
eyebrow: Mesh network
name: TP-Link 6E Mesh System
category: home
link: https://amzn.to/4eQdCc1
status: retired
---
================================================
FILE: src/content/gear/unifi-dream-router-7.md
================================================
---
eyebrow: Router
name: Unifi Dream Router 7
category: home
link: https://techspecs.ui.com/unifi/cloud-gateways/udr7
---
================================================
FILE: src/content/gear/wera-bitholding-screwdriver.md
================================================
---
eyebrow: Screwdriver
name: Wera 7-In-1 Bitholding Screwdriver
category: tools
link: https://amzn.to/49uQRcD
---
================================================
FILE: src/content/gear/wera-tools-hex-set.md
================================================
---
eyebrow: Hex Set
name: Wera Tools Hex Set
category: tools
link: https://amzn.to/4pHnQQG
---
================================================
FILE: src/content/gear/yubikey-5c-nfc.md
================================================
---
eyebrow: Security key
name: YubiKey 5C NFC
category: edc
link: https://amzn.to/41zE4Bi
---
================================================
FILE: src/content/gear/zebralight-sc5c-ii-le.md
================================================
---
eyebrow: Classic
name: Zebralight SC5c II LE
category: flashlight
link: https://zebralight.com/sc5c-mk-ii-le-aa-flashlight-neutral-white-high-cri-limited-edition_p_246.html
---
================================================
FILE: src/content/jobs/clerk.md
================================================
---
title: Staff UI Engineer
company: Clerk
startDate: 2024-01-04
projects:
- title: Component theme editor
link: https://clerk.com/components/theme-editor
published: 2026-03-06
- title: Improve RTL support within components
link: https://github.com/clerk/javascript/pull/7718
published: 2026-01-30
- title: Add UNSAFE_PortalProvider component
link: https://github.com/clerk/javascript/pull/7310
published: 2026-01-21
- title: Add light-dark theme support
link: https://github.com/clerk/javascript/pull/7560
published: 2026-01-20
- title: Interactive docs inline theme editing
link: https://x.com/alexcarp_me/status/1970189970414399723
published: 2025-11-22
- title: shadcn/ui registry
description: Get started with Clerk authentication in Next.js apps using the shadcn/ui CLI
link: https://clerk.com/changelog/2025-08-13-shadcn-registry
published: 2025-08-13
- title: New simple theme for easier customization
description: A minimal theme with stripped-down styling that provides a clean foundation for custom designs.
link: https://clerk.com/changelog/2025-07-29-theme-simple
published: 2025-07-29
- title: shadcn/ui theme compatibility
description: Introducing a new Clerk theme based on shadcn/ui that styles Clerk's components according to your shadcn/ui theme.
link: https://clerk.com/changelog/2025-07-23-shadcn-theme
published: 2025-07-23
- title: Clerk CSS variables support
description: Clerk now supports theming via Clerk CSS variables.
link: https://clerk.com/changelog/2025-07-15-clerk-css-variables-support
published: 2025-07-15
- title: Clerk appearance object CSS variables support
description: Clerk's appearance system now supports CSS variables for seamless design system integration and dynamic theming.
link: https://clerk.com/changelog/2025-07-08-css-variables-support
published: 2025-07-08
- title: CSS layer name support
description: Introducing the cssLayerName option for compatibility with Tailwind CSS v4, allowing Clerk styles to be wrapped in a dedicated CSS cascade layer.
link: https://clerk.com/changelog/2025-06-17-css-layer-name
published: 2025-06-17
- title: Clerk Billing components
link: https://clerk.com/docs/components/pricing-table
published:
- title: Combined sign-in-or-up
description: Start collecting sign-in and sign-ups within a single flow.
link: https://clerk.com/changelog/2025-01-16-sign-in-or-up
published: 2025-01-16
- title: Clerk Elements
description: Introducing Clerk Elements, a new set of unstyled UI primitives that make it easy to build completely custom user interfaces on top of Clerk's API.
link: "https://clerk.com/changelog/2024-05-02-elements-beta"
published: 2024-05-02
- title: Clerk.com user auth page
link: "https://clerk.com/user-authentication"
tools:
- Next.js
- Tailwind CSS
- React Aria Components
- TypeScript
- Motion
- Floating UI
---
UI Engineer apart of the SDK team, working on UI Components and Dashboard UI.
================================================
FILE: src/content/jobs/hashicorp.md
================================================
---
title: Senior Lead Web Engineer
company: HashiCorp
startDate: 2021-07-01
endDate: 2023-07-01
tools:
- Next.js
- Dato CMS
- Reach UI
- TypeScript
- CSS modules
- Framer Motion
---
Lead Web Engineer on the core web team, helped build and maintain public-facing HashiCorp websites and web applications with Next.js.
================================================
FILE: src/content/jobs/masuga-design.md
================================================
---
title: Front-end Developer
company: Masuga Design
startDate: 2012-02-01
endDate: 2015-08-01
tools:
- ExpressionEngine
- Craft CMS
- SCSS
- jQuery
---
Front-end Developer building user interfaces for clients like A&E Networks, Image Comics, and Fox Networks Info with ExpressionEngine.
================================================
FILE: src/content/jobs/mighty-in-the-midwest.md
================================================
---
title: Senior Web Engineer
company: Mighty in the Midwest
startDate: 2015-08-01
endDate: 2018-11-01
projects:
- title: Consrevation Legacy
link: "https://conservationlegacy.org"
- title: Dake Corp
link: "https://dakecorp.com"
- title: Brunswick Bowling
link: "https://brunswickbowling.com"
tools:
- Craft CMS
- Shopify
- SCSS
- jQuery
---
Senior Developer building and maintaining client websites built with Craft CMS, ExpressionEngine, and Shopify. Lead front-end initiatives to improve CSS and JavaScript architecture and implement an atomic deployment pipeline for our projects.
================================================
FILE: src/content/jobs/nationbuilder.md
================================================
---
title: UI Engineer
company: NationBuilder
startDate: 2018-11-01
endDate: 2021-07-01
tools:
- React
- Rails
- Bootstrap
---
UI Engineer working on the design team, collaborating closely with engineers implementing new features and building out our design system Radius.
================================================
FILE: src/content/jobs/watershed.md
================================================
---
title: Lead Web Engineer
company: Watershed
startDate: 2023-08-01
endDate: 2023-12-01
projects:
- title: Watershed homepage
link: "https://watershed.com"
- title: Watershed customers page
link: "https://watershed.com/customers"
- title: Watershed events page
link: "https://watershed.com/events"
- title: Watershed change campaign page
link: "https://watershed.com/change"
tools:
- Next.js
- Sanity CMS
- CSS Modules
- Framer Motion
---
Web Engineer apart of the marketing department, built and maintained everything across watershed.com.
================================================
FILE: src/content/notes/2024-12-05-1.md
================================================
---
published: 2024-12-05T19:25:15-0400
title: Just use children
link: https://www.sid.st/post/just-use-children/
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-02-17-1.md
================================================
---
published: 2025-02-17T19:21:04-0400
title: The "everything bagel" of components
link: https://dio.la/article/the-everything-bagel-of-components
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-02-19-2.md
================================================
---
published: 2025-02-19T16:37:34-0500
tags:
- tip
---
React Aria exposing state through the className is super handy. Here we're using the placement returned to define a Tailwind CSS variable which we can then use in Motion to define its animation direction. View sandbox example [here](https://codesandbox.io/p/sandbox/zwfndk).
```tsx {6-11,13}
<TooltipTrigger isOpen={open} onOpenChange={setOpen}>
<Button>Trigger</Button>
<AnimatePresence>
{open ? (
<MotionTooltip
className={({ placement }) =>
cx({
"[--y:4px]": placement === "top",
"[--y:-4px]": placement === "bottom",
})
}
offset={6}
initial={{ opacity: 0, y: "var(--y)" }}
animate={{ opacity: 1, y: 0 }}
>
Content
</MotionTooltip>
) : null}
</AnimatePresence>
</TooltipTrigger>
```
================================================
FILE: src/content/notes/2025-02-25-1.md
================================================
---
published: 2025-02-25T19:26:08-0400
title: Sanding UI
link: https://blog.jim-nielsen.com/2024/sanding-ui/
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-03-22-1.md
================================================
---
published: 2025-03-22T19:25:15-0400
title: Naming things in design systems–and why it’s the worst
link: https://pjonori.blog/posts/design-system-naming/
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-04-13-1.md
================================================
---
published: 2025-04-13T20:14:22-0400
title: Write code that is easy to delete, not easy to extend
link: https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-04-14-1.md
================================================
---
published: 2025-04-14T12:41:36-0400
title: Animate height from 0 to auto
tags:
- tip
- css
- animation
---
```css
.element {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.5s ease-in-out;
}
.element.open {
grid-template-rows: 1fr;
}
```
## References:
- [How to animate an element's height with CSS grid](https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/)
- [Animating CSS Grid (How To + Examples) ](https://css-tricks.com/animating-css-grid-how-to-examples/)
================================================
FILE: src/content/notes/2025-04-14-2.md
================================================
---
published: 2025-04-14T20:50:41-0400
title: Create a list of links from an array using Intl.ListFormat with formatToParts
demo: /demos/list-format-to-parts
tags:
- demo
- javascript
- tip
---
```tsx
const tags = ["HTML", "CSS", "JavaScript"];
{
new Intl.ListFormat("en-US").formatToParts(tags).map(({ type, value }) => {
if (type === "element") {
return <a href={`/${slugify(value)}`}>{value}</a>;
}
return value;
});
}
```
which returns the following markup:
```html
<a href="/html">HTML</a>, <a href="/css">CSS</a>, and
<a href="/javascript">JavaScript</a>
```
================================================
FILE: src/content/notes/2025-04-15-1.md
================================================
---
published: 2025-04-15T08:20:41-0400
tags:
- css
---
This was a fun layout challenge for some dashboard UI we've been working on at Clerk. The structure of the layout contains your typical sidebar and content elements that live side by side.
Simplified markup example of what we are working with:
```html
<div class="header"></div>
<div class="nav"></div>
<div class="layout">
<div class="sidebar"></div>
<div class="content">
<div class="data"></div>
</div>
</div>
```
The data element is a large table which i've hard coded to cause an overflow that needs to be able to scroll vertically and horizontally but we wanted to make sure that its scroll bars were always within the viewport.
To accomplish this we can make use of `contain: size;` to ensure the content overflows within its container and not cause the page to need to scroll.
```css {29}
body {
min-height: 100%;
display: flex;
flex-direction: column;
flex: 1;
}
.header {
width: 100%;
height: 56px;
}
.nav {
width: 100%;
height: 56px;
position: sticky;
}
.layout {
flex: 1;
display: grid;
grid-template-columns: 360px 1fr;
}
.sidebar {...}
.content {
overflow: auto;
contain: size;
}
/* This is simply to cause an overflow to demonstrate the table size */
.data {
width: 200vw;
height: 200vh;
}
```
View the demo CodePen [here](https://codepen.io/alexcarpenter/pen/emmOLoN?editors=0100).
================================================
FILE: src/content/notes/2025-04-16-1.md
================================================
---
published: 2025-04-16T08:45:17-0400
tags:
- css
- tip
---
Stop vertically aligning your checkboxes with `center`. Instead use `baseline` to keep it aligned with the first line of the label text.
```scss
label {
display: flex;
align-items: center; // [!code --]
align-items: baseline; // [!code ++]
}
```
================================================
FILE: src/content/notes/2025-04-17-1.md
================================================
---
published: 2025-04-17T08:18:43-0400
tags:
- css
- tip
---
Quick little improvement for elements that render dark even in light mode and have overflow, force the color-scheme to dark to blend the native form controls a bit better.
```css
pre {
color-scheme: dark;
}
```
View demo on [Twitter](https://x.com/alexcarp_me/status/1823406174034649137/video/1).
================================================
FILE: src/content/notes/2025-04-18-1.md
================================================
---
published: 2025-04-18T07:59:33-0400
tags:
- css
- tip
---
Quick little improvement for wrapping highlighted text. Make use of `box-decoration-break: clone;` to ensure elements fragments break across lines.
```css
mark {
box-decoration-break: clone;
}
```
View demo on [Twitter](https://x.com/alexcarp_me/status/1821533842228089056/video/1).
================================================
FILE: src/content/notes/2025-04-18-2.md
================================================
---
published: 2025-04-18T08:02:53-0400
title: "OKLCH in CSS: why we moved from RGB and HSL"
link: https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl
tags:
- css
- bookmark
---
================================================
FILE: src/content/notes/2025-04-19-1.md
================================================
---
published: 2025-04-19T07:24:05-0400
title: The Wet Codebase
link: https://www.deconstructconf.com/2019/dan-abramov-the-wet-codebase
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-04-19-2.md
================================================
---
published: 2025-04-19T07:29:11-0400
title: Adaptive dotted pattern
link: https://codepen.io/myf/pen/poXYLNB?editors=0100
tags:
- css
---
================================================
FILE: src/content/notes/2025-04-30-1.md
================================================
---
published: 2025-04-30T08:04:10-0400
tags:
- tip
---
Not uncommon to see folks add form submit handlers on the submit buttons click event vs on the forms submit handler. The problem is this prevents users from being able to fill out the form and submit it solely from the keyboard.
```jsx
<form>
<button onClick={handleSubmit} />
</form>
```
Instead, apply the `handleSubmit` function to the form `onSubmit` handler. This ensures the form can be submitted when the user hits the <kbd>return</kbd> key.
```jsx
<form onSubmit={handleSubmit}>
<button type="submit" />
</form>
```
If for whatever reason your button needs to live outside of the form element, likely in a dialog situation, you can ensure the same functionality by passing the forms id to the button via the `form` attribute as shown below.
```jsx
<form id="contactForm" onSubmit={handleSubmit}>
</form>
<button form="contactForm" type="submit" />
```
================================================
FILE: src/content/notes/2025-05-01-1.md
================================================
---
published: 2025-05-01T08:19:09-0400
title: useMaskedScroll hook
link: https://github.com/sambecker/exif-photo-blog/blob/main/src/components/useMaskedScroll.ts
tags:
- css
- bookmark
---
Very nice scroll mask implementation using registered @ properties by [Sam Becker](https://x.com/sambecker).
================================================
FILE: src/content/notes/2025-05-02-1.md
================================================
---
published: 2025-05-02T07:32:39-0400
title: CSS Spring Easing Generator
link: https://www.kvin.me/css-springs
tags:
- css
- bookmark
---
Also the companion [tailwindcss-spring](https://tailwindcss-spring.kvin.me/) plugin.
================================================
FILE: src/content/notes/2025-05-14-1.md
================================================
---
published: 2025-05-14T09:10:48-0400
title: never just
link: https://www.neverjust.net/
tags:
- bookmark
---
> it's never just that simple
================================================
FILE: src/content/notes/2025-05-26-1.md
================================================
---
published: 2025-05-26T12:25:57-0400
title: Evil Martions Harmonizer
link: https://harmonizer.evilmartians.com
tags:
- bookmark
---
> Harmonizer is a tool for generating accessible, consistent color palettes for user interfaces. Using the OKLCH color model and APCA contrast formula, Harmonizer helps you create color palettes with consistent chroma and contrast across all levels and hues.
================================================
FILE: src/content/notes/2025-06-18-1.md
================================================
---
published: 2025-06-18T07:43:51-0400
tags:
- css
---
Automatic foreground color contrast based on the provided background color.
```css
button {
--background: black;
--foreground: color(
from var(--background) xyz round(up, min(1, max(0, 0.18 - y)))
round(up, min(1, max(0, 0.18 - y))) round(up, min(1, max(0, 0.18 - y)))
);
background-color: var(--background);
color: var(--foreground);
}
```
via [blog.damato.design](https://blog.damato.design/posts/css-only-contrast/)
================================================
FILE: src/content/notes/2025-06-18-2.md
================================================
---
title: The Prettify Helper
link: https://www.totaltypescript.com/concepts/the-prettify-helper
published: 2025-06-18T07:55:18-0400
tags:
- bookmark
---
> The Prettify helper is a utility type that takes an object type and makes the hover overlay more readable.
```ts
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
```
================================================
FILE: src/content/notes/2025-06-18-3.md
================================================
---
title: Tiny polyfill for CSS scroll driven animations
link: https://x.com/devongovett/status/1932478787507425405
published: 2025-06-18T08:04:20-0400
tags:
- tip
- javascript
---
```js
let animationRange = [0, 62];
if (!CSS.supports("(animation-timeline: scroll())")) {
let [start, end] = animationRange;
let animations = header.getAnimations();
let onScroll = () => {
// Calculate animation time based on percentage of animationRange * duration.
let time =
Math.max(0, Math.min(end, window.scrollY - start) / (end - start)) * 1000;
for (let animation of animations) {
animation.currentTime = time;
}
};
window.addEventListener("scroll", onScroll, { passive: true });
}
```
================================================
FILE: src/content/notes/2025-06-18-4.md
================================================
---
title: Handle all potential cases in a switch statement
link: https://x.com/housecor/status/1927688347754881245
published: 2025-06-18T08:08:25-0400
tags:
- bookmark
---
```ts {12}
type Cat = { kind: 'cat' }
type Dog = { kind: 'dog' }
type Pet = Cat | Dog
function example(pet: Pet) {
switch (pet.kind) {
case: 'cat':
return ...
case: 'dog'
return ...
default:
pet satisfies never
}
}
```
================================================
FILE: src/content/notes/2025-06-21-1.md
================================================
---
published: 2025-06-21T07:59:56-0400
tags:
- css
- work
---
Working on some updates to make it easier to theme Clerk components from your existing CSS variables.
Generating a color palette using relative color syntax and color-mix.
```css
:root {
--brand-color: oklch(49.1% 0.27 292.581);
}
@media (prefers-color-scheme: dark) {
:root {
--brand-color: oklch(54.1% 0.281 293.009);
}
}
```
```tsx
<ClerkProvider
appearance={{
variables: {
colorPrimary: "var(--brand-color)",
},
}}
/>
```
================================================
FILE: src/content/notes/2025-06-24-1.md
================================================
---
title: On moving from implicit to explicit coupling
link: https://ineffable.co/writing/moving-from-implicit-to-explicit-coupling
published: 2025-06-24T08:49:00-0400
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-06-27-1.md
================================================
---
title: keyux
description: JS library to improve keyboard UI of web apps
link: https://github.com/ai/keyux
published: 2025-06-27T07:36:28-0400
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-07-01-1.md
================================================
---
title: apcach
description: JS color calculator for composing colors with consistent APCA contrast ratio.
link: https://github.com/antiflasher/apcach
published: 2025-07-01T07:23:59-0400
tags:
- bookmark
- javascript
- color
---
================================================
FILE: src/content/notes/2025-07-02-1.md
================================================
---
title: That boolean should probably be something else
link: https://ntietz.com/blog/that-boolean-should-probably-be-something-else/
published: 2025-07-02T08:42:00-0400
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-07-08-1.md
================================================
---
title: culori
description: A comprehensive color library for JavaScript.
link: https://github.com/Evercoder/culori
published: 2025-07-08T08:36:45-0400
tags:
- bookmark
- javascript
- color
---
================================================
FILE: src/content/notes/2025-07-11-1.md
================================================
---
title: What's the "Geometry" of Colours?
link: https://www.youtube.com/watch?v=7KYwi2F5Ce4
published: 2025-07-11T10:55:45-0400
tags:
- color
---
================================================
FILE: src/content/notes/2025-07-15-1.md
================================================
---
title: Clerk CSS variables are now available!
link: https://clerk.com/changelog/2025-07-15-clerk-css-variables-support
published: 2025-07-15T15:21:54-0400
tags:
- work
---
Theme your Clerk components directly from your CSS files where your design tokens live – no more CSS-in-JS required.
```css
:root {
--clerk-color-primary: #6d47ff;
}
```
We learned from our own experience: the variables appearance option had limited adoption because it was hard to integrate with existing design systems. Even we had to use workarounds with elements prop + Tailwind classes in our dashboard.
Now you can theme components where your tokens are already defined. Plus we've improved variable naming and added new ones like `colorRing`, `colorMuted`, and `colorShadow` for more flexible theming.
================================================
FILE: src/content/notes/2025-07-17-1.md
================================================
---
title: Agentic Engineering
description: Combining human craftsmanship with AI tools to build better software.
link: https://zed.dev/agentic-engineering
published: 2025-07-17T20:49:48-0400
tags:
- ai
---
> Software development is changing and we find ourselves at a convergence. Between the extremes of technological zealotry ("all code will be AI-generated") and dismissive skepticism ("AI-generated code is garbage") lies a more practical and nuanced approach—one that is ours to discover together.
================================================
FILE: src/content/notes/2025-07-17-2.md
================================================
---
title: Building your ideas with Claude Code and Figma MCP with Dan Hollick
link: https://www.youtube.com/watch?v=7XxhMR6-0BY
published: 2025-07-17T20:57:35-0400
tags:
- ai
---
================================================
FILE: src/content/notes/2025-07-18-1.md
================================================
---
title: Jonas Brinkhoff's personal website
link: https://www.jonasbrinkhoff.com/
published: 2025-07-18T08:16:47-0400
tags:
- bookmark
- inspiration
---
================================================
FILE: src/content/notes/2025-07-18-2.md
================================================
---
title: Chase McCoy's personal website
link: https://chsmc.org/
published: 2025-07-18T08:18:50-0400
tags:
- bookmark
- inspiration
---
================================================
FILE: src/content/notes/2025-07-21-1.md
================================================
---
title: Write like you talk
link: https://paulgraham.com/talk.html
published: 2025-07-21T08:37:45-0400
tags:
- bookmark
---
> Here's a simple trick for getting more people to read what you write: write in spoken language.
================================================
FILE: src/content/notes/2025-07-23-1.md
================================================
---
title: shadcn/ui theme compatibility is now available
link: https://clerk.com/changelog/2025-07-23-shadcn-theme
published: 2025-07-23T11:44:32-0400
tags:
- work
---
We shipped a new Clerk theme based on shadcn/ui that styles Clerk's components according to your shadcn/ui theme.
================================================
FILE: src/content/notes/2025-07-29-1.md
================================================
---
published: 2025-07-29T08:25:58-0400
tags:
- css
- tip
---
Your component library ships bundled CSS via CSS-in-JS. You want folks to opt in to being able to toggle between light/dark mode but you don't know how they are handling toggling between light/dark.
Use CSS var toggle hack?
```css
/* Component library styles */
:root {
--dark-mode: ;
}
button {
padding: 1rem;
background-color: var(--dark-mode, white) black;
color: var(--dark-mode, black) white;
}
/* User styles turn on dark mode */
.dark {
--dark-mode: initial;
}
@media (prefers-color-scheme: dark) {
:root {
--dark-mode: initial;
}
}
```
This gives the user the ability to enable dark mode based on how they have their app configured. @media query, class, data-attr, etc.
They can even be very targeted on which components this is enabled for.
================================================
FILE: src/content/notes/2025-08-05-1.md
================================================
---
published: 2025-08-05T19:26:48-0400
tags:
- work
---
Had a quick chat with Hamed about the recent work we have done to improve the customization of Clerk UI components. Watch it on [YouTube](https://www.youtube.com/watch?v=0SWck1H3XSg).
================================================
FILE: src/content/notes/2025-08-13-1.md
================================================
---
published: 2025-08-13T07:54:31-0400
demo: /demos/bg-repeat-round
tags:
- demo
- css
---
Using `background-repeat: round;` to get that repeated dot background to fit perfectly across differing viewport widths.
```css {5}
div {
background-image: radial-gradient(red 1px, transparent 1.3px);
background-size: 24px 24px;
background-position: 0 0;
background-repeat: round;
}
```
================================================
FILE: src/content/notes/2025-08-13-2.md
================================================
---
published: 2025-08-13T08:10:08-0400
demo: /demos/box-decoration-break-clone
tags:
- demo
- css
---
For when `<mark />` elements wrap to multiple lines, use `box-decoration-break: clone;` to render each fragment with their own specified border, padding, and margin.
```css {2}
mark {
box-decoration-break: clone;
padding-inline: 0.25rem;
}
```
================================================
FILE: src/content/notes/2025-08-13-3.md
================================================
---
published: 2025-08-13T16:34:12-0400
title: We shipped a shadcn/ui registry
link: https://clerk.com/changelog/2025-08-13-shadcn-registry
tags:
- work
---
```bash
npx shadcn@latest add https://clerk.com/r/nextjs-quickstart.json
```
This single command will install:
- App layout with ClerkProvider and theme integration
- Sign-in and sign-up pages with catch-all routes
- Clerk middleware for route protection
- Header component with authentication buttons
- Theme provider for dark/light mode support
================================================
FILE: src/content/notes/2025-08-14-1.md
================================================
---
title: How to properly align icons within list items
published: 2025-08-14T15:49:05-0400
demo: /demos/list-item-icon-baseline
tags:
- demo
- css
---
My current favorite approach to building bullet proof icon alignment within list items. Ensures icons are always vertically aligned to the first line of text, and ensures the icons do not shrink when text wraps to two lines.
```tsx {3-5}
<ul>
<li class="flex gap-2">
<span class="flex h-[1lh] items-center">
<Icon class="size-[1em] flex-none" name="badge-check" />
</span>
List item 2 that is longer than the others and wraps to two lines
</li>
</ul>
```
With this approach, you can apply a font size to the list item, and the icon will scale accordingly.
================================================
FILE: src/content/notes/2025-08-18-1.md
================================================
---
published: 2025-08-18T14:56:44-0400
demo: /demos/inline-trailing-icon
tags:
- demo
- css
---
Ensure the trailing icon never orphans itself on to a new line. Paired with the icon alignment technique I [shared last week](/notes/2025-08-14-1) to vertically center it.
```tsx {3-5}
<p class="relative inline-block pr-[1.25em]">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia, ducimus
<span class="absolute ml-[.25em] inline-flex h-[1lh] items-center">
<Icon name="arrow-up-right" class="size-[1em]" />
</span>
</p>
```
Picked this tip of from [John Phamous](https://x.com/JohnPhamous) some time ago.
================================================
FILE: src/content/notes/2025-08-18-2.md
================================================
---
published: 2025-08-18T17:20:03-0400
title: use-stick-to-bottom
description: A lightweight React Hook intended mainly for AI chat applications, for smoothly sticking to bottom of messages.
link: https://github.com/stackblitz-labs/use-stick-to-bottom
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-08-23-1.md
================================================
---
published: 2025-08-23T13:53:44-0400
title: Silk
description: Native‑like swipeable sheets on the web
link: https://silkhq.com/
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-08-25-1.md
================================================
---
published: 2025-08-25T08:39:17-0400
demo: /demos/hanging-punctuation
tags:
- css
- demo
---
Another use case for absolute positioning elements within inline text to support hanging punctuation.
Supports varying sizes of text, without the need for custom negative text indent values.
```html
<p class="relative inline-block">
<span class="absolute right-full">“</span>Lorem ipsum dolor sit amet
consectetur adipisicing elit. Iste officia quasi fugiat, dolores ab nam
repellendus voluptate”
</p>
```
================================================
FILE: src/content/notes/2025-08-26-1.md
================================================
---
published: 2025-08-26T11:32:27-0400
title: What are OKLCH colors?
link: https://jakub.kr/components/oklch-colors
tags:
- color
---
================================================
FILE: src/content/notes/2025-08-26-2.md
================================================
---
published: 2025-08-26T11:34:53-0400
title: A Clock That Doesn't Snap
link: https://ethanniser.dev/blog/a-clock-that-doesnt-snap
tags:
- javascript
---
================================================
FILE: src/content/notes/2025-09-05-1.md
================================================
---
title: My shadcn/ui registry
link: https://ui.alexcarpenter.me
published: 2025-09-05T08:31:49-0400
tags:
- javascript
- work
---
I've been slowly working on my own shadcn/ui registry while learning more about the whole component distrubution setup at work. So far I have the following components:
- [`<InfoList />`](https://ui.alexcarpenter.me/#info-list)\
A list component with support for items, headers, and icons slots.
- [`<InlineText />`](https://ui.alexcarpenter.me/#inline-text)\
An inline text component with an icon slot that wraps perfectly with the text.
- [`<PricingTable />`](https://ui.alexcarpenter.me/#pricing-table)\
A pricing table component with support for monthly/yearly pricing.
================================================
FILE: src/content/notes/2025-09-05-2.md
================================================
---
published: 2025-09-05T08:58:10-0400
tags:
- inspiration
---
Two fantastic pieces of web inspiration:
- [operate.so](https://operate.so)
- [tempo.xyz](https://tempo.xyz/)
================================================
FILE: src/content/notes/2025-09-06-1.md
================================================
---
published: 2025-09-06T15:33:30-0400
tags:
- work
---
Fixing folks list alignment one PR at a time.
- [midday/pull/606](https://github.com/midday-ai/midday/pull/606)
- [billingsdk/pull/144](https://github.com/dodopayments/billingsdk/pull/144)
================================================
FILE: src/content/notes/2025-09-12-1.md
================================================
---
published: 2025-09-12T18:19:56-0400
tags:
- work
---
I've been fortunate to work with some amazing people over the years and a couple of those folks are open for [new opportunities](/rolodex). Scoop them up.
================================================
FILE: src/content/notes/2025-09-19-1.md
================================================
---
published: 2025-09-19T08:23:59-0400
title: ▲ Vercel Web Interface Guidelines
link: https://vercel.com/design/guidelines
tags:
- bookmark
- inspiration
---
See also Rauno's [Web Interface Guidelines](https://interfaces.rauno.me/).
================================================
FILE: src/content/notes/2025-09-20-1.md
================================================
---
published: 2025-09-20T08:39:55-0400
title: Dither plugin for TailwindCSS
link: https://dither.floriankiem.com/
tags:
- bookmark
- css
---
================================================
FILE: src/content/notes/2025-09-28-1.md
================================================
---
published: 2025-09-28T09:44:13-0400
demo: /demos/button-disabled-color-mix
tags:
- demo
- css
---
It's always felt weird using opacity for disabled buttons for situations where its not placed on a solid background.
In this alternative approach I am using color-mix to create a similar disabled effect without the opacity.
```html
<button
class="bg-[color-mix(in_srgb,var(--color-blue-500)_50%,var(--color-background))] text-[color-mix(in_srgb,var(--color-white)_50%,var(--color-background))]"
disabled
>
Hello world
</button>
```
================================================
FILE: src/content/notes/2025-10-23-1.md
================================================
---
published: 2025-10-23T08:20:33-0400
title: Preserve modal aspect ratio across viewport sizes
demo: /demos/modal-aspect-ratio
tags:
- demo
- css
---
This is one of those that feels like it should be easier than it ends up being, but here is where I landed at to ensure a modal image preserves its 3/2 aspect ratio no mater the viewport width/height.
```css
.modal {
aspect-ratio: 3 / 2;
height: min(calc(100vh - 2rem), calc((100vw - 2rem) * 2 / 3));
margin: auto;
position: relative;
}
```
See it in action on [I Brew My Own Coffee](https://www.ibrewmyown.coffee).
================================================
FILE: src/content/notes/2025-11-05-1.md
================================================
---
title: "RESILIENT—UI interview #001 with Dima Belyaev"
description: Staff front-end engineer based in Amsterdam, working on AI assistants at Shopify.
link: https://www.resilient-ui.com/interviews/dima-belyaev
published: 2025-11-05T07:52:09-0500
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-11-05-2.md
================================================
---
title: The Web Animation Performance Tier List
link: https://motion.dev/blog/web-animation-performance-tier-list
published: 2025-11-05T10:20:46-0500
tags:
- bookmark
- animation
---
================================================
FILE: src/content/notes/2025-11-18-1.md
================================================
---
title: "RESILIENT—UI interview #002 with Hayden Bleasel"
description: Australian Design Engineer who loves working on open source software for the web.
link: https://www.resilient-ui.com/interviews/hayden-bleasel
published: 2025-11-18T08:30:55-0500
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-12-03-1.md
================================================
---
title: "2025 Holiday Coffee Gift Guide"
description: Discover the best coffee gifts for the holidays—espresso machines, drippers, brewers, and bean subscriptions that coffee lovers will actually use every day.
link: https://ibrewmyown.coffee/2025-holiday-gift-guide
published: 2025-12-03T07:49:22-0500
tags:
- bookmark
---
================================================
FILE: src/content/notes/2025-12-15-1.md
================================================
---
title: How to target Safari with a CSS @supports media query
description: Easiest method for targeting Safari with CSS and Tailwind in 2025.
link: https://wojtek.im/journal/targeting-safari-with-css-media-query
published: 2025-12-15T08:03:15-0500
tags:
- bookmark
- css
---
================================================
FILE: src/content/notes/2026-01-06-1.md
================================================
---
published: 2026-01-06T17:02:58-0500
tags:
- css
- demo
---
CSS only scroll fade example that I implemented on [ibrewmyown.coffee](https://github.com/alexcarpenter/ibrewmyown.coffee/commit/b5458aa1d151b14b085394c7fa9f2263320f1384):
```css
@supports (animation-timeline: scroll()) {
@property --fade-left {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --fade-right {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
.scroll-fade {
--fade-distance: 40px;
mask-image: linear-gradient(
to right,
transparent 0,
#000 var(--fade-left),
#000 calc(100% - var(--fade-right)),
transparent 100%
);
mask-size: 100% 100%;
mask-repeat: no-repeat;
animation:
fade-in-left 1 linear both,
fade-out-right 1 linear both;
animation-timeline: scroll(x self), scroll(x self);
animation-range:
0% 12%,
88% 100%;
}
@keyframes fade-in-left {
from {
--fade-left: 0px;
}
to {
--fade-left: var(--fade-distance);
}
}
@keyframes fade-out-right {
from {
--fade-right: var(--fade-distance);
}
to {
--fade-right: 0px;
}
}
}
```
================================================
FILE: src/content/notes/2026-01-17-01.md
================================================
---
title: 21 Lessons From 14 Years at Google
link: https://addyosmani.com/blog/21-lessons/
published: 2026-01-17T08:17:52-0500
tags:
- bookmark
- work
---
================================================
FILE: src/content/notes/2026-01-19-1.md
================================================
---
title: Annotating for agents
published: 2026-01-19T12:28:08-0500
link: https://benji.org/annotating
tags:
- bookmark
- ai
- privacy
---
================================================
FILE: src/content/notes/2026-01-22-1.md
================================================
---
published: 2026-01-22T16:17:27-0500
title: Greppability is an underrated code metric
link: https://morizbuesing.com/blog/greppability-code-metric/
tags:
- bookmark
---
================================================
FILE: src/content/notes/2026-01-26-1.md
================================================
---
published: 2026-01-26T14:34:05-0500
tags:
- til
---
TIL you can slow down animations in Chrome DevTools.
Press <kbd>cmd+shift+p</kbd> (or <kbd>ctrl+shift+p</kbd> on Windows/Linux), then search for "animations". An Animations panel will appear at the bottom of DevTools where you can adjust the playback speed to 100%, 25%, or 10%. All animations on the page will now run at the selected speed.
================================================
FILE: src/content/notes/2026-01-28-1.md
================================================
---
published: 2026-01-28T06:47:36-0500
title: When life gives you lemons, write better error messages
link: https://wix-ux.com/when-life-gives-you-lemons-write-better-error-messages-46c5223e1a2f
tags:
- bookmark
---
1. Say what happened
1. Say why it's a problem
1. Point out how to solve the problem
================================================
FILE: src/content/notes/2026-01-29-1.md
================================================
---
title: Composition is all you need.
link: https://www.youtube.com/watch?v=4KvbVq3Eg5w
published: 2026-01-29T07:21:29-0500
tags:
- bookmark
---
See also `vercel-composition-patterns`
```bash
npx skills add vercel-labs/agent-skills
```
================================================
FILE: src/content/notes/2026-02-03-1.md
================================================
---
published: 2026-02-03T07:06:47-0500
title: before-and-after
link: https://www.jm.sv/before-and-after
tags:
- bookmark
---
================================================
FILE: src/content/notes/2026-02-03-2.md
================================================
---
published: 2026-02-03T08:43:34-0500
title: Alexander Vilinskyy's personal website
link: https://www.vilinskyy.com/
tags:
- bookmark
- inspiration
- website
---
================================================
FILE: src/content/notes/2026-02-06-1.md
================================================
---
title: A React trick to improve exit animations
link: https://barvian.me/react-exit-animations
published: 2026-02-06T07:50:26-0500
tags:
- bookmark
- javascript
---
================================================
FILE: src/content/notes/2026-02-06-2.md
================================================
---
title: Building Bulletproof React Components
link: https://shud.in/thoughts/build-bulletproof-react-components
published: 2026-02-06T07:50:48-0500
tags:
- bookmark
- javascript
---
================================================
FILE: src/content/notes/2026-02-09-1.md
================================================
---
title: RESILIENT-UI SKILLS
link: https://www.resilient-ui.com/skills
published: 2026-02-09T10:49:31-0500
tags:
- javascript
---
```bash
npx skills add alexcarpenter/resilient-ui
```
================================================
FILE: src/content/notes/2026-02-11-1.md
================================================
---
title: Matt Rothenberg's personal website
link: https://mattrothenberg.com/
published: 2026-02-11T07:43:56-0500
tags:
- bookmark
- inspiration
- website
---
================================================
FILE: src/content/notes/2026-02-11-2.md
================================================
---
title: eslint-plugin-react-render-types
link: https://github.com/HorusGoul/eslint-plugin-react-render-types
published: 2026-02-11T07:47:53-0500
tags:
- bookmark
- javascript
---
> ESLint plugin that brings Flow's Render Types to TypeScript via JSDoc. Enforce component composition constraints like `@renders {MenuItem}` at lint time.
================================================
FILE: src/content/notes/2026-02-12-1.md
================================================
---
title: Digital hygiene
link: https://karpathy.bearblog.dev/digital-hygiene/
published: 2026-02-12T07:43:49-0500
tags:
- bookmark
- privacy
---
================================================
FILE: src/content/notes/2026-02-13-1.md
================================================
---
title: How I Use Claude Code
link: https://boristane.com/blog/how-i-use-claude-code/
published: 2026-02-13T08:12:56-0500
tags:
- ai
---
================================================
FILE: src/content/notes/2026-02-14-1.md
================================================
---
title: "ASCII characters are not pixels: a deep dive into ASCII rendering"
link: https://alexharri.com/blog/ascii-rendering
published: 2026-02-14T19:56:51-0500
tags:
- bookmark
---
================================================
FILE: src/content/notes/2026-02-15-1.md
================================================
---
title: react-prehydrate
link: https://github.com/javivelasco/react-prehydrate
published: 2026-02-15T07:59:10-0500
tags:
- bookmark
---
> Eliminate flash-of-incorrect-state for user preferences in React Server Component apps.
================================================
FILE: src/content/notes/2026-02-15-2.md
================================================
---
title: "@nano_kit/store"
link: https://nano_kit.js.org/store/
published: 2026-02-15T08:41:03-0500
tags:
- bookmark
---
> `@nano_kit/store` is a lightweight state management library inspired by Nano Stores and built around a push-pull based reactivity system.
================================================
FILE: src/content/notes/2026-02-16-1.md
================================================
---
title: Building Type-Safe Compound Components
link: https://tkdodo.eu/blog/building-type-safe-compound-components
published: 2026-02-16T08:21:11-0500
tags:
- bookmark
---
================================================
FILE: src/content/notes/2026-02-16-2.md
================================================
---
title: <TextMorph />
link: https://torph.lochie.me/
published: 2026-02-16T11:48:22-0500
tags:
- bookmark
---
> Dependency-free animated text component.
================================================
FILE: src/content/notes/2026-02-24-1.md
================================================
---
title: ESLint complexity rule
link: https://eslint.org/docs/latest/rules/complexity
published: 2026-02-24T15:50:52-0500
---
================================================
FILE: src/content/notes/2026-03-01-1.md
================================================
---
title: Anton Repponen's personal website
link: https://repponen.com
published: 2026-03-01T09:30:41-0500
tags:
- inspiration
- bookmark
---
================================================
FILE: src/content/notes/2026-03-01-2.md
================================================
---
title: Naz Hamid's personal website
link: https://nazhamid.com/
published: 2026-03-01T11:25:34-0500
tags:
- inspiration
- bookmark
---
================================================
FILE: src/content/notes/2026-03-04-1.md
================================================
---
title: How to implement beautiful shadow borders
link: https://x.com/PixelJanitor/status/1623358514440859649
published: 2026-03-04T11:25:34-0500
tags:
- bookmark
---
================================================
FILE: src/content/notes/2026-03-05-1.md
================================================
---
title: History of Software Design
link: https://historyofsoftware.org/
published: 2026-03-05T14:20:10-0500
---
================================================
FILE: src/content/notes/2026-03-06-1.md
================================================
---
title: Smooth Dot Indicators with Embla Carousel and CSS color-mix()
description: How I implemented smooth carousel pagination indicators that respond to scroll progress
published: 2026-03-06T08:43:22-0500
---
The interview gallery on [ibrewmyown.coffee](https://ibrewmyown.coffee/interviews/fatih-arslan) uses image carousels with pagination dots. Standard pagination dots using Embla Carousel toggle between active and inactive, but I wanted them to respond to scroll progress: as you drag, the current indicator fades out while the next one fades in.
## Implementation
[Embla Carousel](https://www.embla-carousel.com/) exposes scroll progress, making this possible:
```typescript
const onScroll = (): void => {
const snapList = emblaApi.scrollSnapList();
const progress = emblaApi.scrollProgress();
if (snapList.length < 2) return;
let lower = snapList.length - 2;
for (let i = 0; i < snapList.length - 1; i++) {
if (progress >= snapList[i] && progress <= snapList[i + 1]) {
lower = i;
break;
}
}
const upper = lower + 1;
const range = snapList[upper] - snapList[lower];
const t = range === 0 ? 0 : (progress - snapList[lower]) / range;
dotNodes.forEach((_, i) => {
if (i === lower) setDotProgress(i, 1 - t);
else if (i === upper) setDotProgress(i, t);
else setDotProgress(i, 0);
});
};
```
As `t` interpolates from 0 to 1, a CSS custom property `--dot-progress` is set for each dot. This single value drives two animations: width expansion and color interpolation.
## Width and Color
The dot width expands as you approach it:
```css
width: calc(var(--spacing) * 2 + var(--spacing) * 3 * var(--dot-progress, 0));
```
And the color blends between two states using `color-mix()`:
```css
background-color: color-mix(
in srgb,
var(--color-neutral-600) calc(var(--dot-progress, 0) * 100%),
var(--color-neutral-300)
);
```
This approach preserves contrast, keeping the active dot darkest and the inactive lightest. The browser handles both interpolations without additional JS overhead.
Hat tip to [Derek Briggs](https://x.com/PixelJanitor/status/2029700875006922763) for the color fade idea: "What if the current active indicator faded out the primary color during the drag and the next active faded in so that the most active item had the most contrast always?"
================================================
FILE: src/content/notes/2026-03-06-2.md
================================================
---
title: React Grab
link: https://www.react-grab.com/
published: 2026-03-06T19:23:27-0500
---
================================================
FILE: src/content/notes/2026-03-08-1.md
================================================
---
title: Agentic Engineering Patterns
link: https://simonwillison.net/guides/agentic-engineering-patterns/
published: 2026-03-08T09:19:33-0400
---
> Patterns for getting the best results out of coding agents like Claude Code and OpenAI Codex.
================================================
FILE: src/content/notes/2026-03-11-1.md
================================================
---
title: The Anatomy of an Agent Harness
link: https://x.com/Vtrivedy10/status/2031408954517971368
published: 2026-03-11T07:18:23-0400
---
================================================
FILE: src/content/notes/2026-03-15-1.md
================================================
---
title: Daniel Griesser's PI Config
link: https://github.com/HazAT/pi-config
published: 2026-03-15T19:53:02-0400
---
================================================
FILE: src/content/notes/2026-03-17-1.md
================================================
---
title: "Managing Context Windows with pi /tree: Branches, Summaries, and Subagent-like Workflows"
link: https://stacktoheap.com/blog/2026/02/26/pi-tree-context-window-management/
published: 2026-03-17T07:19:43-0400
---
================================================
FILE: src/content/notes/2026-03-20-1.md
================================================
---
title: Using Container Queries in a CSS Grid to Detect Truncation
link: https://v0-css-container-query-test.vercel.app/
published: 2026-03-20T08:37:14-0400
---
================================================
FILE: src/content/notes/2026-03-21-1.md
================================================
---
title: William Jansson's personal website
link: https://williamjansson.com/
published: 2026-03-21T08:29:03-0400
---
================================================
FILE: src/content/notes/2026-03-21-2.md
================================================
---
title: Nguyễn Ngọc Ánh's personal website
link: https://anh.ng/
published: 2026-03-21T08:31:11-0400
---
================================================
FILE: src/content/notes/2026-03-27-1.md
================================================
---
title: Nikhil Anand's personal website
link: https://nikhil.io/
published: 2026-03-27T14:49:41-0400
tags:
- website
---
================================================
FILE: src/content/notes/2026-04-04-1.md
================================================
---
title: Tree structure using CSS anchor positioning
link: https://lab.chsmc.org/anchor-positioned-tree
published: 2026-04-04T14:35:11-0400
---
One of the coolest uses of CSS anchor positioning I have seen.
================================================
FILE: src/content/notes/2026-04-26-1.md
================================================
---
published: 2026-04-26T09:58:20-0400
demo: /demos/button-tint
tags:
- demo
- css
---
Tint buttons based on their foreground color for `:hover` and `:active` states.
```css
button {
--bg: transparent;
--fg: currentColor;
--am: 0%;
background-color: color-mix(in srgb, var(--bg), var(--fg) var(--am));
color: var(--fg);
&:hover {
--am: 10%;
}
&:active {
--am: 20%;
}
}
.blue {
--bg: oklch(48.8% 0.243 264.376);
--fg: oklch(1 0 0);
}
.neutral {
--bg: oklch(92.9% 0.013 255.508);
--fg: oklch(27.8% 0.033 256.848);
}
```
================================================
FILE: src/content/oss-contributions.json
================================================
[
{
"date": "2026-03-18",
"link": "https://github.com/developit/kinu/pull/102",
"description": "Allow clicking between radio/checkbox and labels",
"status": "closed"
},
{
"date": "2026-02-17",
"link": "https://github.com/anomalyco/opencode/pull/13987",
"description": "Fix homepage video layout shift",
"status": "merged"
},
{
"date": "2026-02-17",
"link": "https://github.com/benjitaylor/agentation/pull/102",
"description": "Fix keyboard interactions when collapsed, allow toggling via keyboard",
"status": "closed"
},
{
"date": "2026-02-13",
"link": "https://github.com/TanStack/tanstack.com/pull/712",
"description": "Fix docs feature grid alignment and icon sizing",
"status": "merged"
},
{
"date": "2026-02-05",
"link": "https://github.com/cloudflare/kumo/pull/19",
"description": "Wrap pagination counts with tabular-nums to avoid shifts",
"status": "merged"
},
{
"date": "2026-01-29",
"link": "https://github.com/vercel/next.js/pull/89245",
"description": "Improve templates layout flexibility",
"status": "merged"
},
{
"date": "2026-01-05",
"link": "https://github.com/facebook/stylex/pull/1439",
"description": "Fix confirm dialog closing when clicking inside.",
"status": "merged"
},
{
"date": "2026-01-04",
"link": "https://github.com/facebook/stylex/pull/1434",
"description": "Fixes issues in docs codeblock component where the icons were not rendering properly before the title, causing the title to not be properly aligned with the code, but also just not rendering the icon visibly.",
"status": "merged"
},
{
"date": "2026-01-04",
"link": "https://github.com/facebook/stylex/pull/1433",
"description": "Align docs callout component icon to the first line of text.",
"status": "merged"
},
{
"date": "2025-09-30",
"link": "https://github.com/fuma-nama/fumadocs/pull/2416",
"description": "Add missing ring classes for buttonVariants to ensure focus states are visible for buttons.",
"status": "merged"
},
{
"date": "2025-09-29",
"link": "https://github.com/vercel/ai-chatbot/pull/1252",
"description": "Ensure both PureModelSelectorCompact and VisibilitySelector have visible outlines when focus visible.",
"status": "merged"
},
{
"date": "2025-09-29",
"link": "https://github.com/haydenbleasel/kibo/pull/266",
"description": "Fixes icon shrinking and alignment within pricing block when the feature text wraps to two lines.",
"status": "merged"
},
{
"date": "2025-09-24",
"link": "https://github.com/fuma-nama/fumadocs/pull/2399",
"description": "Updates copy button to preserve the active state while in checked state.",
"status": "merged"
},
{
"date": "2025-09-06",
"link": "https://github.com/midday-ai/midday/pull/606",
"description": "Ensure icons are aligned on the first line vs being vertically centered within the whole item.",
"status": "merged"
},
{
"date": "2025-09-06",
"link": "https://github.com/dodopayments/billingsdk/pull/144",
"description": "Fix icon alignment within pricing-table-one",
"status": "merged"
},
{
"date": "2025-07-04",
"link": "https://github.com/shadcn-ui/ui/pull/7944",
"description": "Add support for css imports",
"status": "closed"
},
{
"date": "2023-09-03",
"link": "https://github.com/rauchg/blog/pull/133",
"description": "Defines color scheme when in light/dark modes.",
"status": "merged"
}
]
================================================
FILE: src/content/recommendations/amy-stuart.md
================================================
---
name: Amy Stuart
title: Senior Designer
company: nationbuilder
published: 2021-01-02
---
Alex and I worked together on the design team at NationBuilder. Alex is a natural problem solver, and a talented visual designer. That combination makes him an excellent front-end engineer. We worked together on a new feature with a complex UI and <mark>he was able to interpret the designs with a care and detail that is rare, while also making thoughtful UX suggestions</mark>. Not to mention Alex is just a great person, easy to talk to and lovely to work with.
================================================
FILE: src/content/recommendations/andrew-possehl.md
================================================
---
name: Andrew Possehl
title: Senior Designer
company: nationbuilder
published: 2021-01-01
---
I worked with Alex on a variety of different projects. He is excellent at his craft and a pleasure to collaborate with. <mark>His attention to detail always resulted in an extremely polished final product</mark>.
================================================
FILE: src/content/recommendations/benjamin-kohl.md
================================================
---
name: Benjamin Kohl
title: Back-end Developer
company: masuga-design
published: 2015-01-01
status: hidden
---
Alex is a true autodidact that is dedicated to keeping up with the latest development tools, methods and trends.
================================================
FILE: src/content/recommendations/danielle-dunn.md
================================================
---
name: Danielle Dunn
title: Project Manager
company: mighty-in-the-midwest
published: 2018-01-02
---
Alex is incredibly thorough and thoughtful with his work and is always seeking out improving in his craft. He’s <mark>conscious of meeting deadlines and communicates concerns early</mark> so that project teams can be proactive in problem solving.
================================================
FILE: src/content/recommendations/david-mosher.md
================================================
---
name: David Mosher
title: Staff Software Engineer
company: clerk
published: 2024-10-14
---
If you are seeking someone with <mark>deep expertise in web development, particularly with React, accessibility, animations, and a keen eye for design</mark>, then Alex is the perfect teammate. During my time at Clerk, I had the pleasure of working closely with Alex, and <mark>I can confidently say that he is an exceptional collaborator</mark>. His frontend web UI engineering skills are top-notch, and he is a fantastic human being.
================================================
FILE: src/content/recommendations/jimmy-merritello.md
================================================
---
name: Jimmy Merritello
title: Web Engineer
company: hashicorp
published: 2023-01-01
---
Alex is an excellent teammate and an exceptionally talented web developer. When he joined the team we gained an <mark>incredibly strong collaborator</mark>. His thoughtful approach to both producing work and <mark>actively reviewing code instantly improved the entire teams workflow</mark>. It is clear that Alex is a life long learner and therefore is always sure to bring a new, novel approach to solve a problem.
================================================
FILE: src/content/recommendations/kyle-luck.md
================================================
---
name: Kyle Luck
title: Developer
company: mighty-in-the-midwest
published: 2018-01-03
---
Alex is <mark>incredibly skilled, efficient, and thorough in his work</mark>. Perhaps more than any other co-worker I have had, Alex possesses a deep and wide understanding of modern web technologies, while his steady passion for <mark>producing best-of-class work inspires his peers</mark> to write cleaner, simpler, and more elegant code.
================================================
FILE: src/content/recommendations/melissa-taylor.md
================================================
---
name: Melissa Taylor
title: Director Client Services
company: mighty-in-the-midwest
published: 2018-01-01
---
I would recommend Alex to any forward-thinking web team. <mark>His passion for web standards along with his friendly attitude</mark> made him an invaluable part of our development team. I`m always especially impressed with his commitment to continuous learning and I hope to have the chance to work with him again!
================================================
FILE: src/content.config.ts
================================================
import { defineCollection, reference } from "astro:content";
import { z } from "astro/zod";
import { file, glob } from "astro/loaders";
import { GEAR_CATEGORIES } from "./consts";
const gear = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/gear" }),
schema: z.object({
name: z.string(),
category: z.enum(GEAR_CATEGORIES),
eyebrow: z.string().optional(),
link: z.url().optional(),
favorite: z.boolean().optional().default(false),
status: z.enum(["active", "retired"]).default("active"),
}),
});
const notes = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/notes" }),
schema: z.object({
published: z.coerce.date(),
title: z.string().optional(),
description: z.string().optional(),
link: z.string().optional(),
tags: z.array(z.string()).optional(),
demo: z.string().optional(),
}),
});
const jobs = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/jobs" }),
schema: z.object({
title: z.string(),
company: z.string(),
startDate: z.coerce.date(),
endDate: z.coerce.date().optional(),
tools: z.array(z.string()).optional(),
projects: z
.array(
z.object({
title: z.string(),
description: z.string().optional(),
link: z.string(),
published: z.coerce.date().optional(),
}),
)
.optional(),
}),
});
const recommendations = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/recommendations" }),
schema: z.object({
name: z.string(),
title: z.string(),
company: reference("jobs"),
published: z.coerce.date(),
avatar: z.string().optional(),
status: z.enum(["visible", "hidden"]).default("visible"),
}),
});
const ossContributions = defineCollection({
loader: file("src/content/oss-contributions.json", {
parser: (fileContent) => {
const data = JSON.parse(fileContent);
return data.map((item: Record<string, unknown>, index: number) => ({
id: `contribution-${index + 1}`,
...item,
}));
},
}),
schema: z.object({
link: z.url(),
date: z.coerce.date(),
description: z.string(),
status: z.enum(["open", "merged", "closed"]).default("open"),
}),
});
export const collections = {
gear,
jobs,
notes,
ossContributions,
recommendations,
};
================================================
FILE: src/env.d.ts
================================================
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
================================================
FILE: src/layouts/base-layout.astro
================================================
---
import "@/styles/global.css";
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description } = Astro.props;
const metaTitle = title ? `${title} // Alex Carpenter` : "Alex Carpenter";
const metaDescription = description
? description
: "Staff UI Engineer at Clerk";
import { version } from "../../package.json";
import ExternalLink from "@/components/external-link.astro";
const COMMIT_SHA = import.meta.env.COMMIT_SHA.slice(0, 7);
---
<html lang="en">
<head>
<link
rel="preload"
href="/fonts/GeistMono[wght].woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<!-- Theme Color -->
<meta
name="theme-color"
content="#fafafa"
media="(prefers-color-scheme: light)"
/>
<meta
name="theme-color"
content="#0a0a0a"
media="(prefers-color-scheme: dark)"
/>
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{metaTitle}</title>
<meta name="title" content={metaTitle} />
<meta name="description" content={metaDescription} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={metaTitle} />
<meta property="og:description" content={metaDescription} />
<meta property="og:image" content={new URL("/og.jpg", Astro.site)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={metaTitle} />
<meta property="twitter:description" content={metaDescription} />
<meta property="twitter:image" content={new URL("/og.jpg", Astro.site)} />
<!-- Sitemap -->
<link rel="sitemap" href={new URL("/sitemap-index.xml", Astro.site)} />
<!-- RSS Feed -->
<link
rel="alternate"
type="application/rss+xml"
title="Notes - Alex Carpenter"
href={new URL("/notes/rss.xml", Astro.site)}
/>
</head>
<body class="py-20 font-mono text-xs/5">
<a
href="#main"
class="fixed top-0 left-0 -translate-y-full bg-blue-600 px-3 py-1.5 text-white focus-visible:translate-y-0 dark:bg-blue-500"
>Skip to content</a
>
<header class="px-4">
<div class="mx-auto max-w-prose">
<h1>
<a href="/">Alex Carpenter</a>
</h1>
<p class="text-muted-foreground">Staff UI Engineer at Clerk</p>
<p class="text-muted-foreground">Grand Rapids, MI.</p>
</div>
</header>
<main class="flex-1" id="main">
<slot />
</main>
<footer class="border-separator mt-20 border-t px-4 pt-20 lg:px-20">
<div
class="mx-auto grid max-w-prose grid-cols-1 gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]"
>
<h2>Connect</h2>
<dl>
{
[
{
title: "GitHub",
username: "@alexcarpenter",
link: "https://github.com/alexcarpenter",
},
{
title: "Twitter",
username: "@alexcarp_me",
link: "https://twitter.com/alexcarp_me",
},
{
title: "Bluesky",
username: "@alexcarpenter.me",
link: "https://bsky.app/profile/alexcarpenter.me",
},
{
title: "LinkedIn",
username: "@alexcarpenter",
link: "https://www.linkedin.com/in/imalexcarpenter/",
},
].map((social) => {
return (
<Fragment>
<dt class="text-muted-foreground not-first:mt-5">
{social.title}
</dt>
<dd>
<ExternalLink href={social.link} text={social.username} />
</dd>
</Fragment>
);
})
}
</dl>
<div class="flex flex-col lg:items-end">
<dl class="[&_dt]:text-muted-foreground lg:text-right">
<dt>Version</dt>
<dd>v{version}</dd>
<dt class="mt-5">Last Modified</dt>
<dd>
{
new Date().toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "2-digit",
})
}
</dd>
<dt class="mt-5">Commit</dt>
<dd>
<ExternalLink
href={`https://github.com/alexcarpenter/alexcarpenter.me/commit/${COMMIT_SHA}`}
text={COMMIT_SHA}
/>
</dd>
</dl>
</div>
</div>
</footer>
</body>
</html>
================================================
FILE: src/layouts/demo-layout.astro
================================================
---
import "@/styles/demos.css";
---
<html lang="en" class="h-full">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
</head>
<body class="bg-background text-foreground flex min-h-full flex-col p-4">
<slot />
</body>
</html>
================================================
FILE: src/lib/markdown.js
================================================
import MarkdownIt from "markdown-it";
let markdownParser = null;
export function getMarkdownParser() {
if (!markdownParser) {
markdownParser = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
}
return markdownParser;
}
================================================
FILE: src/lib/utils.ts
================================================
export function enforceExhaustive(
value: never,
message = "Unexpected value",
): never {
throw new Error(`${message} '${value}'`);
}
export function formatLinkHostname(urlString: string): string {
const url = new URL(urlString);
// For Twitter/X links, show x.com/username or twitter.com/username
if (url.hostname.includes("twitter.com") || url.hostname.includes("x.com")) {
const username = url.pathname.split("/")[1];
return username ? `${url.hostname}/${username}` : url.hostname;
}
// For all other links, just show the hostname
return url.hostname;
}
================================================
FILE: src/pages/404.astro
================================================
---
import BaseLayout from "@/layouts/base-layout.astro";
---
<BaseLayout title="404" description="Page not found">
<section class="mt-20 px-4">
<div class="mx-auto max-w-prose">
<h2 class="text-muted-foreground text-balance">
<span class="text-foreground">404</span> Page not found.
</h2>
</div>
</section>
</BaseLayout>
================================================
FILE: src/pages/demos/bg-repeat-round.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
---
<DemoLayout>
<div
class="w-full flex-1 bg-[radial-gradient(var(--color-red-600)_1px,transparent_1.3px)] bg-size-[24px_24px] bg-position-[0_0] bg-repeat-round"
>
</div>
</DemoLayout>
================================================
FILE: src/pages/demos/box-decoration-break-clone.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
---
<DemoLayout>
<p class="my-auto max-w-xl leading-relaxed">
Lorem ipsum dolor sit amet, <mark
class="rounded bg-yellow-500 [box-decoration-break:clone] px-1"
>consectetur adipisicing elit. Deleniti, repellendus aspernatur quisquam
inventore aliquam</mark
> maxime laboriosam maiores provident illum.
</p>
</DemoLayout>
================================================
FILE: src/pages/demos/button-disabled-color-mix.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
---
<DemoLayout>
<div
class="bg-grid absolute inset-0 p-2 [&:not(:has(input:checked))]:[background:transparent]"
>
<label class="flex items-center gap-x-2 font-mono text-xs select-none">
<input type="checkbox" checked />
Toggle background
</label>
</div>
<div
class:list={[
"relative m-auto w-full max-w-lg p-8",
"[--bg:var(--color-blue-500)] [--fg:var(--color-white)]",
"dark:[--bg:var(--color-blue-700)]",
]}
>
<div class="grid grid-cols-2 gap-4">
<div class="flex flex-col gap-y-4">
<button class="h-11 rounded-md bg-(--bg) text-(--fg)">Opacity</button>
<button
class="h-11 rounded-md bg-(--bg) text-(--fg) opacity-50"
disabled>Opacity</button
>
</div>
<div class="flex flex-col gap-y-4">
<button class="h-11 rounded-md bg-(--bg) text-(--fg)">Color Mix</button>
<button
class="h-11 rounded-md bg-[color-mix(in_srgb,var(--bg)_50%,var(--color-background))] text-[color-mix(in_srgb,var(--fg)_50%,var(--color-background))]"
disabled>Color Mix</button
>
</div>
</div>
</div>
</DemoLayout>
================================================
FILE: src/pages/demos/button-tint.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
---
<DemoLayout>
<div class="flex flex-1 items-center justify-center gap-4">
<button class="blue">Hello world</button>
<button class="neutral">Hello world</button>
</div>
</DemoLayout>
<style>
button {
--bg: transparent;
--fg: currentColor;
--am: 0%;
appearance: none;
background-color: color-mix(in srgb, var(--bg), var(--fg) var(--am));
color: var(--fg);
border-radius: 6px;
border: 1px solid transparent;
height: 32px;
width: fit-content;
padding-inline: 16px;
font-size: 14px;
transition: background-color ease 0.2s;
&:hover {
--am: 10%;
}
&:active {
--am: 20%;
}
}
.blue {
--bg: oklch(48.8% 0.243 264.376);
--fg: oklch(1 0 0);
}
.neutral {
--bg: oklch(92.9% 0.013 255.508);
--fg: oklch(27.8% 0.033 256.848);
}
</style>
================================================
FILE: src/pages/demos/hanging-punctuation.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
---
<DemoLayout>
<div class="m-auto w-full max-w-lg">
<p class="relative inline-block text-xl">
<span class="text-accent absolute right-full">“</span>Lorem ipsum dolor
sit amet consectetur adipisicing elit. Iste officia quasi fugiat, dolores
ab nam repellendus voluptate.<span class="text-accent">”</span>
</p>
</div>
</DemoLayout>
================================================
FILE: src/pages/demos/inline-trailing-icon.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
import { Icon } from "astro-icon/components";
---
<DemoLayout>
<div class="m-auto max-w-lg">
<p class="relative inline-block pr-[1.5em]">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia, ducimus<span
class="absolute ml-[.25em] inline-flex h-lh items-center"
><Icon name="arrow-up-right" class="text-accent size-[1em]" /></span
>
</p>
</div>
</DemoLayout>
================================================
FILE: src/pages/demos/list-format-to-parts.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
const tags = ["HTML", "CSS", "JavaScript"];
---
<DemoLayout>
<p class="m-auto max-w-xl">
{
new Intl.ListFormat("en-US")
.formatToParts(tags)
.map(({ type, value }) => {
if (type === "element") {
return (
// prettier-ignore
<a href="" class="underline">{value}</a>
);
}
return value;
})
}
</p>
</DemoLayout>
================================================
FILE: src/pages/demos/list-item-icon-baseline.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
import { Icon } from "astro-icon/components";
---
<DemoLayout>
<div class="m-auto max-w-sm">
<ul class="flex flex-col gap-2">
{
[
"List item 1",
"List item 2 that is longer than the others and wraps to two lines",
"List item 3",
].map((i) => (
<li class="flex gap-2">
<span class="flex h-lh items-center">
<Icon
class="text-accent size-[1em] flex-none"
name="badge-check"
/>
</span>{" "}
{i}
</li>
))
}
</ul>
</div>
</DemoLayout>
================================================
FILE: src/pages/demos/modal-aspect-ratio.astro
================================================
---
import DemoLayout from "@/layouts/demo-layout.astro";
---
<DemoLayout>
<div class="box bg-grid"></div>
</DemoLayout>
<style>
.box {
margin: auto;
position: relative;
aspect-ratio: 3 / 2;
height: min(calc(100vh - 4rem), calc((100vw - 4rem) * 2 / 3));
border-radius: var(--radius-lg);
}
</style>
================================================
FILE: src/pages/index.astro
================================================
---
import slugify from "@sindresorhus/slugify";
import { getCollection, render } from "astro:content";
import { Icon } from "astro-icon/components";
import BaseLayout from "@/layouts/base-layout.astro";
import { getEntry } from "astro:content";
import { enforceExhaustive } from "@/lib/utils";
import ExternalLink from "@/components/external-link.astro";
const jobs = await getCollection("jobs");
const recommendations = await getCollection("recommendations");
const ossContributions = await getCollection("ossContributions");
const renderedJobs = await Promise.all(
jobs.map(async (job) => {
const { Content } = await render(job);
return { job, Content };
}),
);
const renderedRecommendations = await Promise.all(
recommendations.map(async (recommendation) => {
const { Content } = await render(recommendation);
const company = await getEntry(recommendation.data.company);
return { recommendation, company, Content };
}),
);
---
<BaseLayout>
<section class="mt-40 px-4">
<div class="relative mx-auto max-w-prose">
<h2 class="text-muted-foreground text-balance">
<span class="text-foreground">Steward of the web</span> building composable
interfaces for humans and agents.
</h2>
<p class="text-muted-foreground text-justify indent-10">
Currently on the SDK team at Clerk, I work on UI components and theming
infrastructure-building systems for customization, improving component
APIs, and creating developer tooling that makes authentication
interfaces more flexible and accessible.
</p>
<p class="text-muted-foreground text-justify indent-10">
I do my best work on small, highly collaborative teams that ship often
and embrace the convergence of human craftsmanship with AI tooling. I
care deeply about design and believe the best interfaces emerge when
designers, engineers, and intelligent systems work together.
</p>
<a
href="/notes"
class="text-muted-foreground hover:text-foreground mt-10 inline-flex gap-1"
>View notes<Icon name="arrow-right" class="h-5 w-2.5" /></a
>
</div>
</section>
<section class="border-separator mt-20 border-t px-4 pt-20 lg:px-20">
<div
class="mx-auto grid max-w-prose grid-cols-1 items-start gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]"
>
<h2>Projects</h2>
<ul
class:list={[
"grid gap-5",
"lg:col-span-2 lg:grid-cols-3 lg:gap-10",
"max-lg:snap-x max-lg:snap-mandatory max-lg:auto-cols-[calc(100%---spacing(20))] max-lg:grid-flow-col max-lg:overflow-x-auto max-lg:scroll-smooth max-lg:[scrollbar-width:none]",
]}
>
{
[
{
logomark: (
<svg
viewBox="0 0 600 598"
fill="none"
class="h-10"
aria-hidden="true"
>
<path
d="M0 2.50752H38.6373V594.866H0V2.50752ZM144.119 295.866C162.394 299.626 170.225 315.298 170.225 337.864V539.078C170.225 575.434 150.907 594.866 120.623 594.866H64.7556V2.50752H121.146C149.862 2.50752 168.137 21.3121 168.137 55.7882V251.361C168.137 274.553 161.871 288.344 144.119 292.731V295.866ZM116.446 44.5052H103.393V275.807H116.446C124.8 275.807 129.5 269.539 129.5 257.002V62.0569C129.5 50.1467 124.8 44.5052 116.446 44.5052ZM115.924 314.671H103.393V552.867H115.924C125.844 552.867 131.066 545.973 131.066 532.183V334.73C131.066 321.566 125.844 314.671 115.924 314.671ZM269.502 422.487L274.202 280.195L289.343 2.50752H343.123V594.866H308.14L308.662 386.13L311.273 149.186H308.14L287.255 594.866H247.573L227.211 149.186H224.077L226.688 386.13V594.866H192.228V2.50752H247.052L261.67 280.195L266.37 422.487H269.502ZM419.818 0C454.278 0 472.552 22.5658 472.552 63.9371V532.809C472.552 574.807 453.756 598 419.818 598C385.357 598 367.083 574.807 367.083 532.809V63.9371C367.083 22.5658 385.357 0 419.818 0ZM432.871 537.197V60.1759C432.871 48.8929 428.172 41.3713 419.818 41.3713C411.464 41.3713 406.764 48.2665 406.764 60.1759V537.197C406.764 549.107 411.986 556.629 419.818 556.629C427.65 556.629 432.871 548.48 432.871 537.197ZM600 308.403V537.197C600 576.688 582.248 598 548.309 598C514.372 598 495.575 574.807 495.575 532.809V63.9371C495.575 22.5658 514.372 0 548.309 0C582.248 0 600 20.6857 600 59.5494V260.763H560.841V60.1759C560.841 48.8929 556.663 41.3713 548.309 41.3713C540.477 41.3713 535.256 48.2665 535.256 60.1759V537.197C535.256 549.107 540.477 556.629 548.309 556.629C556.663 556.629 560.841 548.48 560.841 537.197V308.403H600Z"
fill="currentColor"
/>
</svg>
),
name: "I Brew My Own Coffee",
link: "https://ibrewmyown.coffee",
description:
// prettier-ignore
<>
A curated collection of home coffee setups. Built with{" "}
{new Intl.ListFormat("en-US", {
style: "long",
type: "conjunction",
}).format(["Astro", "Tailwind CSS", "Base UI"])}.
</>,
},
{
logomark: (
<svg
fill="none"
viewBox="0 0 935 186"
class="h-5.5"
aria-hidden="true"
>
<>
<path
fill="currentColor"
d="M859.11 163.4c-29.6 0-43.6-24.4-43.6-52.8 0-28.2 14-52.4 43.6-52.4 25.2 0 34.2 12.8 39.4 25.8h8.6l-6.6-19V14.4h34.2v147h-34.2v-4.8l6.6-19.2h-8.6c-5.2 13.2-14.2 26-39.4 26Zm-9.4-52.8c0 15.8 8.8 22.6 25.6 22.6s25.2-6.6 25.2-22.6c0-15.6-8.4-22.2-25.2-22.2-16.8 0-25.6 6.6-25.6 22.2ZM702.593 110.4c0-32.6 22.4-52.2 53.8-52.2 35 0 55.2 19 55.2 50.8v9h-74.6c1.2 12.6 8.6 19.6 21 19.6 10.6 0 17.8-4.2 19.8-12h34c-2.8 22.4-22.6 37.8-53.8 37.8-34.2 0-55.4-21.4-55.4-53Zm34.4-10h41.2c-1-11.2-8.8-17.4-20.8-17.4-11.8 0-19.6 6.2-20.4 17.4ZM702.411 131.2v30.2h-54.4c-11.2 0-17.4-6.2-17.4-17.6V90.4h-24.4V60.2h24.4v-18l34.2-10.8v28.8h33.6v30.2h-33.6v41l37.6-.2ZM495.369 133.8c0-17.6 10.2-28 41.2-31.2l30.8-3.4c0-10.6-6-14.8-17.6-14.8-12.4 0-19.4 5.2-19.8 14.2h-32.2c.6-26.6 23-40.4 50.8-40.4 28 0 53.2 12.6 53.2 41.8v31.2h14.6v30.2h-23.2c-9.8 0-15.6-5.6-15.6-15.4v-1l5.4-12.2h-8.4c-3.8 16.4-13.4 30.6-41.8 30.6-32 0-37.4-19.2-37.4-29.6Zm33.6-5.8c0 6.6 6 8.2 12.6 8.2 14.2 0 25.8-7.6 25.8-19.8l-24.4 3.6c-9 1.4-14 2.4-14 8Z"
/>
<path
fill="currentColor"
d="M413.052 77.6c0-11.4 6.4-17.4 17.8-17.4h67.8v30.2h-51.2v71h-34.4V77.6ZM330.369 163.4c-26.2 0-42.2-17.4-42.2-49.4V60.2h34.4v48.2c0 18.2 5 24.8 20.2 24.8 16.6 0 27.8-7.8 27.8-36.4V60.2h34.2v101.2h-34.2V150l6.4-19h-8.4c-4 18-16.6 32.4-38.2 32.4ZM171.89 110.6c0-34.4 23.6-52.4 55.8-52.4 29 0 54.2 16 55.4 44.4h-34.4c-1-7.6-8.4-14.2-21-14.2-12 0-21.6 7.6-21.6 22.2 0 14.8 9.6 22.6 21.6 22.6 12.6 0 20-6.6 21-14.2h34.4c-1.2 28.4-26.4 44.4-55.4 44.4-32.2 0-55.8-18.2-55.8-52.8ZM173.152 0l25.4 11.2-82.2 174-25.4-11.4L173.152 0ZM0 128c0-26 10.8-42 29-42 19.6 0 33.2 15.8 43.6 15.8 6.4 0 9-4 9-16h25.6c0 26-10.8 41.8-29.2 41.8-19.4 0-33-15.8-43.4-15.8-6.4 0-9 4.2-9 16.2H0Z"
/>
</>
</svg>
),
name: "Curated",
link: "https://curated.alexcarpenter.me",
description:
// prettier-ignore
<>
Weekly curated items including design inspiration, tools, and
resources. Built with{" "}
{new Intl.ListFormat("en-US", {
style: "long",
type: "conjunction",
}).format(["Astro"])}.
</>,
},
{
logomark: (
<svg
viewBox="0 0 1035 86"
fill="none"
class="h-3"
aria-hidden="true"
>
<path
d="M0.8 3H62.9C82.5 3 94 12.6 94 28.9C94 41.4 86 50.1 72.8 52.9L95.7 83H62.2L41.8 53.9H30.2V83H0.8V3ZM30.2 39.1H54.8C61 39.1 64.7 36.2 64.7 31.2C64.7 26.2 61 23.3 54.8 23.3H30.2V39.1ZM103.241 3H185.241V23.6H132.641V34.8H179.441V51.8H132.641V62.4H184.241V83H103.241V3ZM221.257 55.6C222.257 61.6 227.757 64.2 239.457 64.2C251.957 64.2 256.857 62.1 256.857 57C256.857 53.2 253.757 51.6 245.957 51.2L219.157 49.9C201.157 49 192.257 41.5 192.257 27.6C192.257 9.6 207.957 0.699999 239.657 0.699999C271.757 0.699999 287.257 10.8 288.157 31.9H259.157C257.957 24.7 252.257 21.8 240.057 21.8C227.157 21.8 221.957 23.9 221.957 29.2C221.957 33 225.357 34.8 233.557 35.2L258.357 36.3C277.157 37.2 286.657 44.7 286.657 58.9C286.657 76.3 270.557 85.3 239.757 85.3C207.857 85.3 192.557 75.8 191.457 55.6H221.257ZM295.82 3H325.22V83H295.82V3ZM336.835 3H366.235V60.6H417.835V83H336.835V3ZM423.945 3H453.345V83H423.945V3ZM464.96 3H546.96V23.6H494.36V34.8H541.16V51.8H494.36V62.4H545.96V83H464.96V3ZM555.976 3H600.476L626.776 70.6L625.976 26.6V3H653.176V83H608.576L582.376 15.4L583.176 59.4V83H555.976V3ZM659.265 3H753.965V25.4H721.265V83H691.965V25.4H659.265V3ZM757.177 33.8H891.177V52.2H757.177V33.8ZM900.019 3H929.419V48.2C929.419 57.9 936.219 63.4 947.719 63.4C959.219 63.4 966.019 57.7 966.019 47.7V3H994.019V48.7C994.019 72.3 977.419 85.3 947.019 85.3C916.719 85.3 900.019 72.3 900.019 48.7V3ZM1004.71 3H1034.11V83H1004.71V3Z"
fill="currentColor"
/>
</svg>
),
name: "RESILIENT—UI",
link: "https://resilient-ui.com",
description:
// prettier-ignore
<>
Resources for building resilient user interfaces. Built with{" "}
{new Intl.ListFormat("en-US", {
style: "long",
type: "conjunction",
}).format([
"Next.js",
"Tailwind CSS",
"Base UI",
"shadcn/ui",
"Fumadocs",
])}.
</>,
},
{
logomark: (
<svg
viewBox="0 0 586 194"
fill="none"
class="h-7"
aria-hidden="true"
>
<>
<path
fill="currentColor"
d="M550.211 148.539q-8.704 0-15.232-2.688-6.528-2.816-10.24-7.68-3.584-4.864-3.584-11.392h18.816q0 3.584 2.688 5.376t7.552 1.792h5.632q5.249 0 8.064-1.792 2.944-1.792 2.944-5.12 0-3.2-2.304-4.608-2.304-1.536-7.04-2.048l-16.128-1.92q-9.344-1.152-14.336-6.912-4.992-5.887-4.992-14.592 0-21.376 27.392-21.376h6.016q12.928 0 20.608 5.632 7.68 5.633 7.68 15.104h-18.816q0-3.072-2.56-4.736-2.431-1.665-6.912-1.664h-6.016q-4.48 0-6.784 1.664t-2.304 4.608 2.176 3.968q2.304 1.024 6.4 1.536l15.36 1.92q10.368 1.28 15.616 7.296t5.248 15.872q0 10.368-7.424 16.128-7.423 5.632-21.888 5.632zM476.533 148.539q-9.345 0-16.384-3.456-6.912-3.584-10.752-9.856-3.712-6.4-3.712-14.848v-16.64q0-8.448 3.712-14.72 3.84-6.4 10.752-9.856 7.04-3.584 16.384-3.584t16.128 3.584q6.912 3.457 10.624 9.856 3.84 6.272 3.84 14.72v13.056h-43.136v3.584q0 6.656 3.072 9.728 3.2 3.072 9.472 3.072 4.096 0 7.168-1.28t3.584-3.84h18.816q-2.048 9.216-10.112 14.848t-19.456 5.632m-12.544-44.8v1.664l24.832-.256v-1.664q0-6.528-3.072-10.112-2.945-3.585-9.216-3.584-6.273 0-9.472 3.712-3.072 3.712-3.072 10.24M411.815 147.259q-7.425 0-13.056-3.072-5.632-3.2-8.832-8.832-3.072-5.633-3.072-13.056v-51.2h-22.4v-17.28h41.6v67.84q0 3.712 2.048 6.016 2.175 2.304 5.632 2.304h19.84v17.28zM293.465 147.259v-17.28h23.296v-35.84h-20.736v-17.28h39.936v53.12h20.224v17.28zm32-81.536q-5.248 0-8.448-2.688-3.072-2.688-3.072-7.296t3.072-7.296q3.2-2.688 8.448-2.688t8.32 2.688q3.2 2.688 3.2 7.296t-3.2 7.296q-3.073 2.688-8.32 2.688M232.075 147.259v-43.52h-18.688v-17.28h18.688v-10.88q0-9.856 6.656-15.744 6.783-6.016 17.664-6.016h20.608v16.64h-19.968q-2.56 0-4.224 1.664-1.536 1.537-1.536 4.096v10.24h25.728v17.28h-25.728v43.52z"
/>
<path
fill="currentColor"
fill-opacity=".7"
d="M152.616 193.51 0 152.616 40.893 0 193.51 40.893zM107.591 58.247c-21.606-5.789-43.813 7.033-49.603 28.638s7.033 43.813 28.638 49.602c21.606 5.789 43.813-7.032 49.602-28.638 5.79-21.605-7.032-43.813-28.637-49.602"
/>
<path
fill="currentColor"
d="M175.755 175.755h-158v-158h158zm-78.5-119c-22.368 0-40.5 18.132-40.5 40.5s18.132 40.5 40.5 40.5 40.5-18.133 40.5-40.5-18.133-40.5-40.5-40.5"
/>
</>
</svg>
),
name: "dotfiles",
link: "https://curated.alexcarpenter.me",
description:
// prettier-ignore
<>
My home config, scripts and installation process. Managed with{" "}
{new Intl.ListFormat("en-US", {
style: "long",
type: "conjunction",
}).format(["Stow"])}.
</>,
},
]
.filter((p) => p.name !== "dotfiles")
.map((project) => (
<li class="snap-center">
<figure class="bg-grid grid aspect-3/2 place-content-center">
{project.logomark}
</figure>
<h3 class="mt-5">
<ExternalLink href={project.link} text={project.name} />
</h3>
<p class="text-muted-foreground text-pretty">
{project.description}
</p>
</li>
))
}
</ul>
</div>
</section>
<section class="border-separator mt-20 border-t px-4 pt-20 lg:px-20">
<div
class="mx-auto grid max-w-prose grid-cols-1 items-start gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]"
>
<h2>Experience</h2>
<ul class="flex flex-col gap-5">
{
renderedJobs
.sort(
(a, b) =>
Date.parse(b.job.data.startDate.toString()) -
Date.parse(a.job.data.startDate.toString()),
)
.map(({ job, Content }, index) => (
<li>
<details
name="accordion"
class="group/accordion"
open={index === 0 ? true : false}
>
<summary class="grid grid-cols-[1fr_max-content_0.625rem] gap-2">
<header class="flex gap-2">
<Icon
name={slugify(job.data.company, {
decamelize: false,
})}
class="h-5 w-4 flex-none"
/>
<div>
<h3>{job.data.company}</h3>
<p class="text-muted-foreground flex items-center gap-1">
<Icon
name="corner-down-right"
class="h-5 w-2.5 flex-none"
/>{" "}
{job.data.title}
</p>
</div>
</header>
<p class="text-muted-foreground">
<time datetime={job.data.startDate.toISOString()}>
<span class="hidden sm:inline">
{new Date(job.data.startDate).toLocaleDateString(
"en-US",
{
year: "numeric",
month: "short",
},
)}
</span>
<span class="sm:hidden">
{new Date(job.data.startDate).toLocaleDateString(
"en-US",
{
year: "numeric",
},
)}
</span>
</time>
–
{job.data.endDate ? (
<time datetime={job.data.endDate.toISOString()}>
<span class="hidden sm:inline">
{new Date(job.data.endDate).toLocaleDateString(
"en-US",
{
year: "numeric",
month: "short",
},
)}
</span>
<span class="sm:hidden">
{new Date(job.data.endDate).toLocaleDateString(
"en-US",
{
year: "numeric",
},
)}
</span>
</time>
) : (
<time datetime={new Date().toISOString()}>Now</time>
)}
</p>
<Icon
name="chevron-down"
class="text-muted-foreground h-5 w-2.5 flex-none transition-transform group-open/accordion:rotate-180"
/>
</summary>
<div class="ps-6 pbs-2">
<Content />
{job.data.tools ? (
<p class="text-muted-foreground mbs-2">
{new Intl.ListFormat("en-US", {
style: "long",
type: "conjunction",
}).format(job.data.tools)}
</p>
) : null}
</div>
</details>
</li>
))
}
</ul>
<footer class="flex lg:justify-end">
<ExternalLink
href="https://www.linkedin.com/in/imalexcarpenter"
text="Connect on LinkedIn"
class="text-muted-foreground hover:text-foreground"
/>
</footer>
</div>
</section>
<section class="border-separator mt-20 border-t px-4 pt-20 lg:px-20">
<div
class="mx-auto grid max-w-prose grid-cols-1 gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]"
>
<h2>Recommendations</h2>
<ul>
{
renderedRecommendations
.sort(
(a, b) =>
Date.parse(b.recommendation.data.published.toString()) -
Date.parse(a.recommendation.data.published.toString()),
)
.filter(
({ recommendation }) => recommendation.data.status === "visible",
)
.map(({ recommendation, company, Content }) => (
<li class="not-first:mt-10">
<Content />
<p class="mt-5">{recommendation.data.name}</p>
<p class="text-muted-foreground flex items-center gap-1">
<Icon name="corner-down-right" class="h-5 w-2.5 flex-none" />{" "}
{recommendation.data.title}, {company.data.company}
</p>
</li>
))
}
</ul>
</div>
</section>
<section class="border-separator mt-20 border-t px-4 pt-20 lg:px-20">
<div
class="mx-auto grid max-w-prose grid-cols-1 items-start gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]"
>
<h2>
<abbr title="Open source software" class="no-underline">OSS</abbr> Contributions
</h2>
<ul>
{
ossContributions
.sort(
(a, b) =>
Date.parse(b.data.date.toString()) -
Date.parse(a.data.date.toString()),
)
.map((contribution) => {
const match = contribution.data.link.match(
/github\.com\/([^\/]+\/[^\/]+)/,
);
const repo = match ? match[1] : null;
if (!repo) return;
return (
<li class="not-first:mt-5">
<header class="flex gap-2">
<Icon
class:list={[
"h-5 w-4 flex-none",
{
"text-green": contribution.data.status === "open",
"text-purple": contribution.data.status === "merged",
"text-red": contribution.data.status === "closed",
},
]}
name={(() => {
switch (contribution.data.status) {
case "open":
return "git-pull-request-arrow";
case "merged":
return "git-merge";
case "closed":
return "git-pull-request-closed";
default:
enforceExhaustive(
contribution.data.status,
"Unknown contribution status",
);
}
})()}
/>
<h3>
<ExternalLink
href={contribution.data.link}
text={repo.toLowerCase()}
/>
</h3>
</header>
<p class="text-muted-foreground line-clamp-2 pl-6 text-pretty">
{contribution.data.description}
</p>
</li>
);
})
}
</ul>
<footer class="flex lg:justify-end">
<ExternalLink
href="https://github.com/alexcarpenter"
text="Follow on GitHub"
class="text-muted-foreground hover:text-foreground"
/>
</footer>
</div>
</section>
</BaseLayout>
<style>
details {
--duration: 0.2s;
@media (prefers-reduced-motion: no-preference) {
interpolate-size: allow-keywords;
}
&::details-content {
opacity: 0;
block-size: 0;
overflow-y: clip;
filter: blur(2px);
transition:
content-visibility var(--duration) allow-discrete,
opacity var(--duration),
block-size var(--duration),
filter var(--duration);
}
&[open]::details-content {
opacity: 1;
block-size: auto;
filter: blur(0px);
}
}
summary {
cursor: pointer;
user-select: none;
outline-offset: 4px;
}
</style>
================================================
FILE: src/pages/notes/[...page].astro
================================================
---
import type { GetStaticPaths, Page } from "astro";
import { getCollection, render } from "astro:content";
import type { CollectionEntry } from "astro:content";
import { Icon } from "astro-icon/components";
import BaseLayout from "@/layouts/base-layout.astro";
import { formatLinkHostname } from "@/lib/utils";
import ExternalLink from "@/components/external-link.astro";
import Demo from "@/components/demo.astro";
type Props = {
page: Page<CollectionEntry<"notes">>;
};
export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
const notes = await getCollection("notes");
const sortedNotes = notes.sort(
(a, b) =>
Date.parse(b.data.published.toString()) -
Date.parse(a.data.published.toString()),
);
return paginate(sortedNotes, { pageSize: 20 });
};
const { page } = Astro.props as Props;
const renderedNotes = await Promise.all(
page.data.map(async (note) => {
const { Content } = await render(note);
return { note, Content };
}),
);
const heading = page.currentPage === 1 ? "Latest" : `Page ${page.currentPage}`;
---
<BaseLayout
title="Notes"
description="Notes on engineering, developer experience, design systems, and agentic engineering."
>
<section class="mt-40 px-4">
<div class="mx-auto max-w-prose">
<h2 class="text-muted-foreground text-balance">
<span class="text-foreground">Notes</span> on engineering, developer experience,
design systems, and agentic engineering.
</h2>
</div>
</section>
<section class="border-separator mt-20 border-t px-4 pt-20 lg:px-20">
<div
class="mx-auto grid max-w-prose grid-cols-1 items-start gap-y-20 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]"
>
<h2>{heading}</h2>
<ul>
{
renderedNotes.map(({ note, Content }) => {
const isExternalLink = note.data.link && note.data.title;
return (
<li class="not-first:border-separator not-first:mt-20 not-first:border-t not-first:pt-20">
<article>
<a href={`/notes/${note.id}`}>
<time
class="text-muted-foreground hover:text-foreground"
datetime={note.data.published.toISOString()}
>
{new Date(note.data.published).toLocaleDateString(
"en-US",
{
year: "numeric",
month: "short",
day: "2-digit",
weekday: "short",
},
)}
</time>
</a>
{note.data.demo ? (
<Demo
class="mt-5"
src={note.data.demo}
title={note.data.title}
/>
) : null}
<h2 class="text-foreground mt-5">
{isExternalLink ? (
<ExternalLink
href={note.data.link}
text={note.data.title}
/>
) : (
note.data.title
)}
</h2>
{isExternalLink && note.data.link ? (
<p class="text-muted-foreground">
{formatLinkHostname(note.data.link)}
</p>
) : null}
{note.body ? (
<div class="prose mt-5">
<Content />
</div>
) : null}
</article>
</li>
);
})
}
</ul>
<footer class="flex flex-col gap-10 lg:items-end">
<a
href="/notes/rss.xml"
class="text-muted-foreground hover:text-foreground inline-flex items-center gap-1"
target="_blank"
rel="noopener noreferrer"
>Subscribe via RSS <Icon
name="rss"
aria-hidden="true"
class="h-5 w-2.5 flex-none"
/></a
>
</footer>
{
(page.url.prev || page.url.next) && (
<nav class="grid grid-cols-3 gap-5 lg:col-start-2">
{page.url.prev && (
<span class="flex">
<a
href={page.url.prev}
class="text-muted-foreground hover:text-foreground inline-flex gap-1"
>
<Icon
name="arrow-left"
aria-hidden="true"
class="h-5 w-2.5 flex-none"
/>{" "}
Prev
</a>
</span>
)}
<span class="text-muted-foreground col-start-2 text-center">
{page.currentPage} / {page.lastPage}
</span>
{page.url.next && (
<span class="flex justify-end">
<a
href={page.url.next}
class="text-muted-foreground hover:text-foreground inline-flex gap-1"
>
Next{" "}
<Icon
name="arrow-right"
aria-hidden="true"
class="h-5 w-2.5 flex-none"
/>
</a>
</span>
)}
</nav>
)
}
</div>
</section>
</BaseLayout>
================================================
FILE: src/pages/notes/[...slug].astro
================================================
---
import { getCollection, render } from "astro:content";
// import slugify from "@sindresorhus/slugify";
import BaseLayout from "@/layouts/base-layout.astro";
import ExternalLink from "@/components/external-link.astro";
import { formatLinkHostname } from "@/lib/utils";
import Demo from "@/components/demo.astro";
export async function getStaticPaths() {
const notes = await getCollection("notes");
return notes.map((n) => ({
params: { slug: n.id },
props: n,
}));
}
const note = Astro.props;
const { Content } = await render(note);
---
<BaseLayout title={note.data.title && !note.data.link ? note.data.title : null}
><section class="border-separator mt-20 border-t px-4 pt-20 lg:px-20">
<div
class="mx-auto grid max-w-prose grid-cols-1 items-start gap-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]"
>
<time
class="text-muted-foreground"
datetime={note.data.published.toISOString()}
>
{
new Date(note.data.published).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "2-digit",
weekday: "short",
})
}
</time>
<article>
{
note.data.title ? (
<header>
<h2>
{note.data.link ? (
<ExternalLink href={note.data.link} text={note.data.title} />
) : (
note.data.title
)}
</h2>
{note.data.link ? (
<p class="text-muted-foreground">
{formatLinkHostname(note.data.link)}
</p>
) : null}
</header>
) : null
}
{
note.data.demo ? (
<Demo
class:list={[
"mb-5",
{
"mt-5": note.data.title,
},
]}
src={note.data.demo}
title={note.data.title}
/>
) : null
}
{
note.body ? (
<div class="prose mt-5">
<Content />
</div>
) : null
}
</article>
<!-- {
note.data.tags ? (
<footer class="text-muted-foreground flex lg:justify-end">
<dl>
<dt class="sr-only">Tagged</dt>
<dd class="text-right text-balance">
{new Intl.ListFormat("en-US")
.formatToParts(note.data.tags)
.map(({ type, value }) => {
const slug = slugify(value);
if (type === "element") {
return <a href={`/tagged/${slug}`}>#{slug}</a>;
}
return value;
})}
</dd>
</dl>
</footer>
) : null
} -->
</div>
</section>
</BaseLayout>
================================================
FILE: src/pages/notes/rss.xml.js
================================================
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import sanitizeHtml from "sanitize-html";
import { getMarkdownParser } from "@/lib/markdown.js";
const parser = getMarkdownParser();
export async function GET(context) {
const notes = await getCollection("notes");
return rss({
title: "Notes - Alex Carpenter",
description:
"Notes on engineering, developer experience, design systems, and accessibility.",
site: context.site,
items: notes
.sort((a, b) => b.data.published - a.data.published)
.map((post) => ({
title: post.data.title,
description: post.data.description,
pubDate: post.data.published,
link: `/notes/${post.id}`,
content:
post.body &&
sanitizeHtml(parser.render(post.body), {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
}),
})),
});
}
================================================
FILE: src/pages/uses/index.astro
================================================
---
import { getCollection } from "astro:content";
import BaseLayout from "@/layouts/base-layout.astro";
import { GEAR_CATEGORY_MAP, GEAR_CATEGORIES } from "@/consts";
import ExternalLink from "@/components/external-link.astro";
import slugify from "@sindresorhus/slugify";
const gear = await getCollection("gear");
const activeGear = gear.filter((item) => item.data.status === "active");
const groupedGear = Array.from(
activeGear.reduce((map, item) => {
const key = item.data.category;
if (!map.has(key)) {
map.set(key, []);
}
map.get(key)!.push(item);
return map;
}, new Map<string, typeof activeGear>()),
).sort(([categoryA], [categoryB]) => {
return (
GEAR_CATEGORIES.indexOf(categoryA as (typeof GEAR_CATEGORIES)[number]) -
GEAR_CATEGORIES.indexOf(categoryB as (typeof GEAR_CATEGORIES)[number])
);
});
---
<BaseLayout title="Uses">
<section class="mt-40 px-4">
<div class="mx-auto max-w-prose">
<h2 class="text-muted-foreground text-balance">
<span class="text-foreground">Uses</span>
</h2>
</div>
</section>
{
groupedGear.map(([category, items]) => {
const categoryInfo =
GEAR_CATEGORY_MAP[category as keyof typeof GEAR_CATEGORY_MAP];
if (!categoryInfo || items.length === 0) return null;
return (
<section
class="border-separator mt-20 border-t px-4 pt-20 lg:px-20"
id={slugify(categoryInfo.title)}
>
<div class="mx-auto grid max-w-prose grid-cols-1 gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]">
<h2>{categoryInfo.title}</h2>
<ul class="grid grid-cols-2 gap-10 lg:col-span-2 lg:grid-cols-4">
{items.map((item) => (
<li>
<div>
{item.data.eyebrow && (
<p class="text-muted-foreground text-[0.625rem] tracking-widest uppercase">
{item.data.eyebrow}
</p>
)}
<h3>
{item.data.link ? (
<ExternalLink
href={item.data.link}
text={item.data.name}
/>
) : (
item.data.name
)}
</h3>
{item.data.link && (
<p class="text-muted-foreground">
{new URL(item.data.link).hostname}
</p>
)}
</div>
</li>
))}
</ul>
</div>
</section>
);
})
}
</BaseLayout>
================================================
FILE: src/styles/demos.css
================================================
@import "tailwindcss";
@variant dark (@media (prefers-color-scheme: dark));
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: --alpha(var(--color-foreground) / 65%);
--color-accent: var(--accent);
}
:root {
--background: var(--color-white);
--foreground: var(--color-black);
--muted: var(--color-neutral-200);
--accent: var(--color-blue-600);
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--background: var(--color-black);
--foreground: var(--color-white);
--muted: var(--color-neutral-800);
--accent: var(--color-blue-400);
}
}
@layer utilities {
.bg-grid {
background: conic-gradient(
from 90deg at 50% 50%,
transparent 0deg 90deg,
var(--color-muted) 90deg 180deg,
transparent 180deg 270deg,
var(--color-muted) 270deg 360deg
);
background-size: 8px 8px;
}
}
================================================
FILE: src/styles/global.css
================================================
@import "tailwindcss";
@font-face {
font-family: "GeistMono";
src: url("/fonts/GeistMono[wght].woff2") format("woff2");
font-weight: 400 500;
font-style: normal;
font-display: swap;
}
@variant dark (@media (prefers-color-scheme: dark));
@theme {
--font-*: initial;
--font-mono:
"GeistMono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-separator: var(--separator);
--color-red: var(--red);
--color-blue: var(--blue);
--color-green: var(--green);
--color-orange: var(--orange);
--color-purple: var(--purple);
}
:root {
--background: var(--color-zinc-50);
--foreground: var(--color-zinc-800);
--muted: var(--color-zinc-200);
--muted-foreground: var(--color-zinc-500);
--separator: var(--color-zinc-200);
--red: var(--color-red-600);
--blue: var(--color-blue-600);
--green: var(--color-green-600);
--orange: var(--color-orange-600);
--purple: var(--color-purple-600);
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--background: var(--color-zinc-950);
--foreground: var(--color-zinc-50);
--muted: var(--color-zinc-800);
--muted-foreground: var(--color-zinc-400);
--separator: var(--color-zinc-800);
--red: var(--color-red-500);
--blue: var(--color-blue-500);
--green: var(--color-green-500);
--orange: var(--color-orange-500);
--purple: var(--color-purple-500);
}
.astro-code,
.astro-code span {
color: var(--shiki-dark) !important;
}
}
@layer base {
html {
height: 100%;
background-color: var(--color-background);
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
flex-direction: column;
min-height: 100%;
background-color: var(--color-background);
color: var(--color-foreground);
}
mark,
:not(pre) > code {
background-color: var(--color-muted);
color: inherit;
padding-inline: var(--spacing);
box-decoration-break: clone;
border-radius: var(--radius-sm);
}
pre {
--pre-background-color: light-dark(
var(--color-zinc-100),
var(--color-zinc-900)
);
padding-block: calc(var(--spacing) * 4);
font-size: var(--text-xs);
line-height: 1.5;
background-color: var(--pre-background-color) !important;
border-radius: var(--radius-md);
border: 1px solid var(--color-separator);
scrollbar-width: thin;
scrollbar-color: var(--color-separator) transparent;
outline-offset: 4px;
}
pre code {
isolation: isolate;
display: block;
width: fit-content;
min-width: 100%;
counter-reset: line;
& .line {
--line-background-color: var(--pre-background-color);
--line-border-color: var(--line-background-color);
display: inline-block;
width: 100%;
counter-increment: line;
background-color: var(--line-background-color);
@media (hover: hover) {
&:hover {
--line-background-color: light-dark(
var(--color-zinc-200),
var(--color-zinc-800)
);
}
}
&::before {
pointer-events: none;
position: sticky;
left: 0;
z-index: 20;
display: inline-block;
width: 2.75rem;
background: linear-gradient(
to right,
var(--line-background-color) 80%,
transparent
);
padding-right: 1rem;
text-align: right;
color: var(--color-muted-foreground);
content: counter(line);
user-select: none;
border-left: 2px solid var(--line-border-color);
}
&::after {
pointer-events: none;
position: sticky;
right: 0;
z-index: 10;
display: inline-block;
width: 1.5rem;
background: linear-gradient(
to left,
var(--line-background-color) 35%,
transparent
);
content: " ";
user-select: none;
}
&.highlighted,
&.remove,
&.add {
--line-background-color: color-mix(
in oklab,
var(--line-border-color),
var(--pre-background-color) 90%
);
@media (hover: hover) {
&:hover {
--line-background-color: color-mix(
in oklab,
var(--line-border-color),
var(--pre-background-color) 80%
);
}
}
&::before {
color: var(--line-border-color);
}
}
&.highlighted {
--line-border-color: light-dark(
oklch(0.4949 0.180091 257.6013),
oklch(0.7685 0.1214 252.34)
);
}
&.remove {
--line-border-color: light-dark(
oklch(0.5879 0.1927 20.47),
oklch(0.7214 0.1616 15.49)
);
&::before {
content: "-";
}
}
&.add {
--line-border-color: light-dark(
oklch(0.5464 0.1442 147.32),
oklch(0.8512 0.1406 150.34)
);
&::before {
content: "+";
}
}
}
}
pre[data-language="bash"] {
.line::before {
content: "$";
}
}
}
@layer components {
.prose {
color: var(--color-muted-foreground);
& > :where(* + *) {
margin-top: calc(var(--spacing) * 5);
}
& :where(a) {
color: var(--color-foreground);
text-decoration: underline;
text-decoration-color: var(--color-muted-foreground);
}
& :where(blockquote) {
display: flex;
column-gap: var(--spacing);
&::before {
content: "\BB";
}
}
& :where(:is(h1, h2, h3)) {
color: var(--color-foreground);
}
& :where(ul) {
display: flex;
flex-direction: column;
row-gap: var(--spacing);
}
& :where(li) {
position: relative;
padding-inline-start: calc(1ch + var(--spacing));
&::before {
content: "\203A";
position: absolute;
top: 0;
left: 0;
}
}
}
}
@layer utilities {
.bg-grid {
background: conic-gradient(
from 90deg at 50% 50%,
transparent 0deg 90deg,
var(--color-muted) 90deg 180deg,
transparent 180deg 270deg,
var(--color-muted) 270deg 360deg
);
background-size: 8px 8px;
}
}
================================================
FILE: tsconfig.json
================================================
{
"extends": "astro/tsconfigs/base",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"]
},
"moduleResolution": "bundler"
}
}
gitextract_15dbuwo8/ ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode/ │ └── settings.json ├── Makefile ├── README.md ├── astro.config.mjs ├── bin/ │ └── chezmoi ├── package.json ├── public/ │ ├── humans.txt │ └── robots.txt ├── src/ │ ├── components/ │ │ ├── demo.astro │ │ └── external-link.astro │ ├── consts.ts │ ├── content/ │ │ ├── gear/ │ │ │ ├── aer-day-sling-3.md │ │ │ ├── anker-laptop-charger.md │ │ │ ├── apple-studio-display.md │ │ │ ├── aqara-presence-sensor-fp2.md │ │ │ ├── aqara-smart-home-hub-m3.md │ │ │ ├── aqara-smart-lock-u100.md │ │ │ ├── aqara-water-leak-sensor.md │ │ │ ├── aqara-wireless-mini-switch.md │ │ │ ├── benchmade-mini-bugout.md │ │ │ ├── breville-barista-express-espresso-machine.md │ │ │ ├── caldigit-ts4-thunderbolt-4-dock.md │ │ │ ├── carepod-mini-ultrasonic-cool-mist-humidifier.md │ │ │ ├── cerave-hydrating-facial-cleanser.md │ │ │ ├── chris-reeve-small-sebenza-31.md │ │ │ ├── coway-airmega-air-purifier.md │ │ │ ├── coway-airmega-hepa-purifier.md │ │ │ ├── ecobee-smart-thermostat.md │ │ │ ├── eltamd-uv-daily-face-sunscreen.md │ │ │ ├── emr-tek-inferno-red-light.md │ │ │ ├── eufy-robot-lawn-mower-e15.md │ │ │ ├── eufy-security-camera-floodlight-camera-e340.md │ │ │ ├── eufy-security-homebase-s380.md │ │ │ ├── eufy-video-doorbell-e340.md │ │ │ ├── evergoods-civic-access-pouch-2l.md │ │ │ ├── evergoods-civic-half-zip.md │ │ │ ├── evergoods-civic-panel-loader-16l.md │ │ │ ├── fellow-atmos-vacuum-coffee-canister.md │ │ │ ├── focusrite-scarlett-audio-interface.md │ │ │ ├── foursevens-mini-turbo-mk-iii.md │ │ │ ├── global-6-inch-chef-s-knife.md │ │ │ ├── goruck-bullet-15l.md │ │ │ ├── hds-systems-edc-tactical.md │ │ │ ├── herman-miller-sayl-chair.md │ │ │ ├── hurom-h400-cold-press-juicer.md │ │ │ ├── ironmaster-super-bench-pro-v2.md │ │ │ ├── kalita-wave.md │ │ │ ├── kinesis-advantage-360-keyboard.md │ │ │ ├── knipex-cobra-water-pump-pliers.md │ │ │ ├── knipex-pliers-wrench.md │ │ │ ├── la-roche-posay-lipikar-ap-triple-repair-moisturizing-cream.md │ │ │ ├── lutron-aurora-smart-bulb-dimmer-switch.md │ │ │ ├── m4-mac-mini.md │ │ │ ├── maestri-house-rechargeable-milk-frother-with-stand.md │ │ │ ├── neutrogena-hydro-boost-water-cream.md │ │ │ ├── nitecore-nb-air.md │ │ │ ├── peak-design-packing-cube.md │ │ │ ├── pica-dry-longlife-automatic-pencil.md │ │ │ ├── powerblock-elite-exp-adjustable-dumbbells.md │ │ │ ├── prometheus-beta-qrv2-titanium.md │ │ │ ├── rocket-espresso-appartamento-tca-espresso-machine.md │ │ │ ├── rode-podmic-microphone.md │ │ │ ├── roka-oslo-2-0-wind-down.md │ │ │ ├── roost-laptop-stand.md │ │ │ ├── snow-peak-ti-double-h200-stacking-mug.md │ │ │ ├── sony-wh-1000xm4-headphones.md │ │ │ ├── spyderco-dragonfly-2.md │ │ │ ├── subminimal-flick-wdt-espresso-distribution-tool.md │ │ │ ├── synology-ds224.md │ │ │ ├── tactile-turn-apollo-flashlight.md │ │ │ ├── tactile-turn-rockwall-thumbstud.md │ │ │ ├── technivorm-moccamaster.md │ │ │ ├── the-james-brand-midland.md │ │ │ ├── timemore-sculptor-064s-coffee-grinder.md │ │ │ ├── tom-bihn-synik-22.md │ │ │ ├── tom-bihn-truck.md │ │ │ ├── toto-washlet-s5-electronic-bidet.md │ │ │ ├── tp-link-16-port-gigabit-poe-switch.md │ │ │ ├── tp-link-6e-mesh-system.md │ │ │ ├── unifi-dream-router-7.md │ │ │ ├── wera-bitholding-screwdriver.md │ │ │ ├── wera-tools-hex-set.md │ │ │ ├── yubikey-5c-nfc.md │ │ │ └── zebralight-sc5c-ii-le.md │ │ ├── jobs/ │ │ │ ├── clerk.md │ │ │ ├── hashicorp.md │ │ │ ├── masuga-design.md │ │ │ ├── mighty-in-the-midwest.md │ │ │ ├── nationbuilder.md │ │ │ └── watershed.md │ │ ├── notes/ │ │ │ ├── 2024-12-05-1.md │ │ │ ├── 2025-02-17-1.md │ │ │ ├── 2025-02-19-2.md │ │ │ ├── 2025-02-25-1.md │ │ │ ├── 2025-03-22-1.md │ │ │ ├── 2025-04-13-1.md │ │ │ ├── 2025-04-14-1.md │ │ │ ├── 2025-04-14-2.md │ │ │ ├── 2025-04-15-1.md │ │ │ ├── 2025-04-16-1.md │ │ │ ├── 2025-04-17-1.md │ │ │ ├── 2025-04-18-1.md │ │ │ ├── 2025-04-18-2.md │ │ │ ├── 2025-04-19-1.md │ │ │ ├── 2025-04-19-2.md │ │ │ ├── 2025-04-30-1.md │ │ │ ├── 2025-05-01-1.md │ │ │ ├── 2025-05-02-1.md │ │ │ ├── 2025-05-14-1.md │ │ │ ├── 2025-05-26-1.md │ │ │ ├── 2025-06-18-1.md │ │ │ ├── 2025-06-18-2.md │ │ │ ├── 2025-06-18-3.md │ │ │ ├── 2025-06-18-4.md │ │ │ ├── 2025-06-21-1.md │ │ │ ├── 2025-06-24-1.md │ │ │ ├── 2025-06-27-1.md │ │ │ ├── 2025-07-01-1.md │ │ │ ├── 2025-07-02-1.md │ │ │ ├── 2025-07-08-1.md │ │ │ ├── 2025-07-11-1.md │ │ │ ├── 2025-07-15-1.md │ │ │ ├── 2025-07-17-1.md │ │ │ ├── 2025-07-17-2.md │ │ │ ├── 2025-07-18-1.md │ │ │ ├── 2025-07-18-2.md │ │ │ ├── 2025-07-21-1.md │ │ │ ├── 2025-07-23-1.md │ │ │ ├── 2025-07-29-1.md │ │ │ ├── 2025-08-05-1.md │ │ │ ├── 2025-08-13-1.md │ │ │ ├── 2025-08-13-2.md │ │ │ ├── 2025-08-13-3.md │ │ │ ├── 2025-08-14-1.md │ │ │ ├── 2025-08-18-1.md │ │ │ ├── 2025-08-18-2.md │ │ │ ├── 2025-08-23-1.md │ │ │ ├── 2025-08-25-1.md │ │ │ ├── 2025-08-26-1.md │ │ │ ├── 2025-08-26-2.md │ │ │ ├── 2025-09-05-1.md │ │ │ ├── 2025-09-05-2.md │ │ │ ├── 2025-09-06-1.md │ │ │ ├── 2025-09-12-1.md │ │ │ ├── 2025-09-19-1.md │ │ │ ├── 2025-09-20-1.md │ │ │ ├── 2025-09-28-1.md │ │ │ ├── 2025-10-23-1.md │ │ │ ├── 2025-11-05-1.md │ │ │ ├── 2025-11-05-2.md │ │ │ ├── 2025-11-18-1.md │ │ │ ├── 2025-12-03-1.md │ │ │ ├── 2025-12-15-1.md │ │ │ ├── 2026-01-06-1.md │ │ │ ├── 2026-01-17-01.md │ │ │ ├── 2026-01-19-1.md │ │ │ ├── 2026-01-22-1.md │ │ │ ├── 2026-01-26-1.md │ │ │ ├── 2026-01-28-1.md │ │ │ ├── 2026-01-29-1.md │ │ │ ├── 2026-02-03-1.md │ │ │ ├── 2026-02-03-2.md │ │ │ ├── 2026-02-06-1.md │ │ │ ├── 2026-02-06-2.md │ │ │ ├── 2026-02-09-1.md │ │ │ ├── 2026-02-11-1.md │ │ │ ├── 2026-02-11-2.md │ │ │ ├── 2026-02-12-1.md │ │ │ ├── 2026-02-13-1.md │ │ │ ├── 2026-02-14-1.md │ │ │ ├── 2026-02-15-1.md │ │ │ ├── 2026-02-15-2.md │ │ │ ├── 2026-02-16-1.md │ │ │ ├── 2026-02-16-2.md │ │ │ ├── 2026-02-24-1.md │ │ │ ├── 2026-03-01-1.md │ │ │ ├── 2026-03-01-2.md │ │ │ ├── 2026-03-04-1.md │ │ │ ├── 2026-03-05-1.md │ │ │ ├── 2026-03-06-1.md │ │ │ ├── 2026-03-06-2.md │ │ │ ├── 2026-03-08-1.md │ │ │ ├── 2026-03-11-1.md │ │ │ ├── 2026-03-15-1.md │ │ │ ├── 2026-03-17-1.md │ │ │ ├── 2026-03-20-1.md │ │ │ ├── 2026-03-21-1.md │ │ │ ├── 2026-03-21-2.md │ │ │ ├── 2026-03-27-1.md │ │ │ ├── 2026-04-04-1.md │ │ │ └── 2026-04-26-1.md │ │ ├── oss-contributions.json │ │ └── recommendations/ │ │ ├── amy-stuart.md │ │ ├── andrew-possehl.md │ │ ├── benjamin-kohl.md │ │ ├── danielle-dunn.md │ │ ├── david-mosher.md │ │ ├── jimmy-merritello.md │ │ ├── kyle-luck.md │ │ └── melissa-taylor.md │ ├── content.config.ts │ ├── env.d.ts │ ├── layouts/ │ │ ├── base-layout.astro │ │ └── demo-layout.astro │ ├── lib/ │ │ ├── markdown.js │ │ └── utils.ts │ ├── pages/ │ │ ├── 404.astro │ │ ├── demos/ │ │ │ ├── bg-repeat-round.astro │ │ │ ├── box-decoration-break-clone.astro │ │ │ ├── button-disabled-color-mix.astro │ │ │ ├── button-tint.astro │ │ │ ├── hanging-punctuation.astro │ │ │ ├── inline-trailing-icon.astro │ │ │ ├── list-format-to-parts.astro │ │ │ ├── list-item-icon-baseline.astro │ │ │ └── modal-aspect-ratio.astro │ │ ├── index.astro │ │ ├── notes/ │ │ │ ├── [...page].astro │ │ │ ├── [...slug].astro │ │ │ └── rss.xml.js │ │ └── uses/ │ │ └── index.astro │ └── styles/ │ ├── demos.css │ └── global.css └── tsconfig.json
SYMBOL INDEX (7 symbols across 4 files)
FILE: src/consts.ts
constant GEAR_CATEGORIES (line 1) | const GEAR_CATEGORIES = [
type GearCategory (line 17) | type GearCategory = (typeof GEAR_CATEGORIES)[number];
constant GEAR_CATEGORY_MAP (line 19) | const GEAR_CATEGORY_MAP: Record<
FILE: src/lib/markdown.js
function getMarkdownParser (line 5) | function getMarkdownParser() {
FILE: src/lib/utils.ts
function enforceExhaustive (line 1) | function enforceExhaustive(
function formatLinkHostname (line 8) | function formatLinkHostname(urlString: string): string {
FILE: src/pages/notes/rss.xml.js
function GET (line 7) | async function GET(context) {
Condensed preview — 228 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (140K chars).
[
{
"path": ".gitignore",
"chars": 238,
"preview": "# build output\ndist/\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn"
},
{
"path": ".npmrc",
"chars": 18,
"preview": "min-release-age=7\n"
},
{
"path": ".prettierignore",
"chars": 43,
"preview": "node_modules/**\npackage.json\npnpm-lock.yaml"
},
{
"path": ".prettierrc.json",
"chars": 187,
"preview": "{\n \"plugins\": [\"prettier-plugin-astro\", \"prettier-plugin-tailwindcss\"],\n \"overrides\": [\n {\n \"files\": \"*.astro\""
},
{
"path": ".vscode/settings.json",
"chars": 127,
"preview": "{\n \"prettier.documentSelectors\": [\"**/*.astro\"],\n \"[astro]\": {\n \"editor.defaultFormatter\": \"esbenp.prettier-vscode\""
},
{
"path": "Makefile",
"chars": 145,
"preview": "checkpoint:\n\t@git add .\n\t@git commit -m \"checkpoint at $$(date '+%Y-%m-%dT%H:%M:%S%z')\"\n\t@git push\n\t@echo Checkpoint cre"
},
{
"path": "README.md",
"chars": 223,
"preview": "# alexcarpenter.me\n\n- Framework: Astro\n- Deployment: Vercel\n- Styling: Tailwind CSS\n\n## Running locally\n\n```bash\ngit clo"
},
{
"path": "astro.config.mjs",
"chars": 1040,
"preview": "import sitemap from \"@astrojs/sitemap\";\nimport {\n transformerMetaHighlight,\n transformerMetaWordHighlight,\n transform"
},
{
"path": "package.json",
"chars": 1124,
"preview": "{\n \"name\": \"alexcarpenter-me\",\n \"type\": \"module\",\n \"version\": \"7.0.0\",\n \"engines\": {\n \"node\": \">=22\"\n },\n \"pnpm"
},
{
"path": "public/humans.txt",
"chars": 120,
"preview": "TECHNOLOGIES\n\n- Astro: https://astro.build\n- Tailwind CSS: https://tailwindcss.com\n- Geist Font: https://vercel.com/font"
},
{
"path": "public/robots.txt",
"chars": 51,
"preview": "Sitemap: https://alexcarpenter.me/sitemap-index.xml"
},
{
"path": "src/components/demo.astro",
"chars": 1102,
"preview": "---\nimport { Icon } from \"astro-icon/components\";\n\ntype Props = {\n src: string;\n title?: string;\n class?: string;\n a"
},
{
"path": "src/components/external-link.astro",
"chars": 382,
"preview": "---\nimport { Icon } from \"astro-icon/components\";\nconst { href, text, class: className } = Astro.props;\n---\n\n{/* prettie"
},
{
"path": "src/consts.ts",
"chars": 1908,
"preview": "export const GEAR_CATEGORIES = [\n \"bag\",\n \"coffee\",\n \"edc\",\n \"gym\",\n \"home\",\n \"wellness\",\n \"kitchen\",\n \"knife\",\n"
},
{
"path": "src/content/gear/aer-day-sling-3.md",
"chars": 130,
"preview": "---\neyebrow: \"Diaper bag\"\nname: Aer Day Sling 3\ncategory: bag\nlink: https://aersf.com/collections/slings/products/day-sl"
},
{
"path": "src/content/gear/anker-laptop-charger.md",
"chars": 106,
"preview": "---\neyebrow: Laptop charger\nname: Anker Laptop Charger\ncategory: travel\nlink: https://amzn.to/3JbHQdS\n---\n"
},
{
"path": "src/content/gear/apple-studio-display.md",
"chars": 116,
"preview": "---\neyebrow: Display\nname: Apple Studio Display\ncategory: workspace\nlink: https://www.apple.com/studio-display/\n---\n"
},
{
"path": "src/content/gear/aqara-presence-sensor-fp2.md",
"chars": 110,
"preview": "---\neyebrow: Presence Sensor\nname: Aqara Presence Sensor FP2\ncategory: home\nlink: https://amzn.to/4me46ma\n---\n"
},
{
"path": "src/content/gear/aqara-smart-home-hub-m3.md",
"chars": 96,
"preview": "---\neyebrow: Hub\nname: Aqara Smart Home Hub M3\ncategory: home\nlink: https://amzn.to/4nJzDNP\n---\n"
},
{
"path": "src/content/gear/aqara-smart-lock-u100.md",
"chars": 111,
"preview": "---\neyebrow: Lock\nname: Aqara Smart Lock U100\ncategory: home\nlink: https://amzn.to/3ImFIzM\nstatus: retired\n---\n"
},
{
"path": "src/content/gear/aqara-water-leak-sensor.md",
"chars": 105,
"preview": "---\neyebrow: Water sensor\nname: Aqara Water Leak Sensor\ncategory: home\nlink: https://amzn.to/3IxiqHj\n---\n"
},
{
"path": "src/content/gear/aqara-wireless-mini-switch.md",
"chars": 102,
"preview": "---\neyebrow: Switch\nname: Aqara Wireless Mini Switch\ncategory: home\nlink: https://amzn.to/4lSIE5r\n---\n"
},
{
"path": "src/content/gear/benchmade-mini-bugout.md",
"chars": 130,
"preview": "---\neyebrow: Lightweight\nname: Benchmade mini bugout\ncategory: knife\nlink: https://benchmade.com/products/533bk-1-mini-b"
},
{
"path": "src/content/gear/breville-barista-express-espresso-machine.md",
"chars": 145,
"preview": "---\nstatus: retired\neyebrow: Espresso machine\nname: Breville Barista Express Espresso Machine\ncategory: coffee\nlink: htt"
},
{
"path": "src/content/gear/caldigit-ts4-thunderbolt-4-dock.md",
"chars": 121,
"preview": "---\neyebrow: Thunderbolt Doc\nname: CalDigit TS4 Thunderbolt 4 Dock\ncategory: workspace\nlink: https://amzn.to/4p7vy73\n---"
},
{
"path": "src/content/gear/carepod-mini-ultrasonic-cool-mist-humidifier.md",
"chars": 128,
"preview": "---\neyebrow: Humidifier\nname: Carepod Mini Ultrasonic Cool Mist Humidifier\ncategory: wellness\nlink: https://amzn.to/4p7v"
},
{
"path": "src/content/gear/cerave-hydrating-facial-cleanser.md",
"chars": 119,
"preview": "---\neyebrow: Face Cleanser\nname: CeraVe Hydrating Facial Cleanser\ncategory: wellness\nlink: https://amzn.to/4rf3wH9\n---\n"
},
{
"path": "src/content/gear/chris-reeve-small-sebenza-31.md",
"chars": 144,
"preview": "---\neyebrow: Titanium\nname: Chris Reeve Small Sebenza 31\ncategory: knife\nlink: https://chrisreeve.com/collections/sebenz"
},
{
"path": "src/content/gear/coway-airmega-air-purifier.md",
"chars": 108,
"preview": "---\neyebrow: Air purifier\nname: Coway Airmega Air Purifier\ncategory: home\nlink: https://amzn.to/4lJGy7I\n---\n"
},
{
"path": "src/content/gear/coway-airmega-hepa-purifier.md",
"chars": 131,
"preview": "---\neyebrow: Air purifier\nname: Coway Airmega AP-1512HH(W) True HEPA Purifier\ncategory: wellness\nlink: https://amzn.to/4"
},
{
"path": "src/content/gear/ecobee-smart-thermostat.md",
"chars": 119,
"preview": "---\neyebrow: Thermostat\nname: ecobee Smart Thermostat\ncategory: home\nlink: https://amzn.to/40hxmiV\nstatus: retired\n---\n"
},
{
"path": "src/content/gear/eltamd-uv-daily-face-sunscreen.md",
"chars": 113,
"preview": "---\neyebrow: Sunscreen\nname: EltaMD UV Daily Face Sunscreen\ncategory: wellness\nlink: https://amzn.to/4aCSO6n\n---\n"
},
{
"path": "src/content/gear/emr-tek-inferno-red-light.md",
"chars": 136,
"preview": "---\neyebrow: Red light therapy\nname: EMR-TEK Inferno Red Light Panel\ncategory: wellness\nlink: https://www.emr-tek.com/al"
},
{
"path": "src/content/gear/eufy-robot-lawn-mower-e15.md",
"chars": 115,
"preview": "---\neyebrow: Lawnmower\nname: eufy Robot Lawn Mower E15\ncategory: home\nlink: https://eufy.com/products/t28801a1\n---\n"
},
{
"path": "src/content/gear/eufy-security-camera-floodlight-camera-e340.md",
"chars": 144,
"preview": "---\neyebrow: Security Camera\nname: eufy Security Camera Floodlight Camera E340\ncategory: home\nlink: https://amzn.to/451O"
},
{
"path": "src/content/gear/eufy-security-homebase-s380.md",
"chars": 111,
"preview": "---\neyebrow: Camera storage\nname: eufy Security HomeBase S380\ncategory: home\nlink: https://amzn.to/44Mcfqb\n---\n"
},
{
"path": "src/content/gear/eufy-video-doorbell-e340.md",
"chars": 119,
"preview": "---\neyebrow: Video doorbell\nname: eufy Video Doorbell E340\ncategory: home\nlink: https://eufy.com/products/t8214111\n---\n"
},
{
"path": "src/content/gear/evergoods-civic-access-pouch-2l.md",
"chars": 158,
"preview": "---\neyebrow: Tech pouch\nname: EVERGOODS CIVIC Access Pouch 2L\ncategory: bag\nlink: https://evergoods.us/collections/pouch"
},
{
"path": "src/content/gear/evergoods-civic-half-zip.md",
"chars": 151,
"preview": "---\neyebrow: Everday bag\nname: EVERGOODS CIVIC Half Zip 22L\ncategory: bag\nlink: https://evergoods.us/collections/backpac"
},
{
"path": "src/content/gear/evergoods-civic-panel-loader-16l.md",
"chars": 181,
"preview": "---\neyebrow: \"Tech bag\"\nname: EVERGOODS CIVIC Panel Loader 16L\ncategory: bag\nlink: https://evergoods.us/collections/back"
},
{
"path": "src/content/gear/fellow-atmos-vacuum-coffee-canister.md",
"chars": 123,
"preview": "---\neyebrow: \"Coffee Storage\"\nname: Fellow Atmos Vacuum Coffee Canister\ncategory: coffee\nlink: https://amzn.to/4qIHQ5c\n-"
},
{
"path": "src/content/gear/focusrite-scarlett-audio-interface.md",
"chars": 124,
"preview": "---\neyebrow: Audio Interface\nname: Focusrite Scarlett Audio Interface\ncategory: workspace\nlink: https://amzn.to/3HW1OsZ\n"
},
{
"path": "src/content/gear/foursevens-mini-turbo-mk-iii.md",
"chars": 145,
"preview": "---\neyebrow: Mini\nname: Foursevens Mini Turbo Mk. III\ncategory: flashlight\nlink: https://darksucks.com/products/mini-tur"
},
{
"path": "src/content/gear/global-6-inch-chef-s-knife.md",
"chars": 125,
"preview": "---\neyebrow: Chefs knife\nname: Global 6 inch Chef's Knife\ncategory: kitchen\nlink: https://amzn.to/46GLh5z\nfavorite: true"
},
{
"path": "src/content/gear/goruck-bullet-15l.md",
"chars": 114,
"preview": "---\neyebrow: Ruck\nname: Goruck Bullet 15L\ncategory: bag\nlink: https://www.goruck.com/products/bullet-ruck-15l\n---\n"
},
{
"path": "src/content/gear/hds-systems-edc-tactical.md",
"chars": 142,
"preview": "---\neyebrow: Rotary UI\nname: HDS Systems EDC Tactical\ncategory: flashlight\nlink: https://hdssystems.com/Products/Tactica"
},
{
"path": "src/content/gear/herman-miller-sayl-chair.md",
"chars": 104,
"preview": "---\neyebrow: Chair\nname: Herman Miller Sayl chair\ncategory: workspace\nlink: https://amzn.to/4p6gl5X\n---\n"
},
{
"path": "src/content/gear/hurom-h400-cold-press-juicer.md",
"chars": 108,
"preview": "---\neyebrow: Juicer\nname: Hurom H400 Cold Press Juicer\ncategory: wellness\nlink: https://amzn.to/4pyDeiQ\n---\n"
},
{
"path": "src/content/gear/ironmaster-super-bench-pro-v2.md",
"chars": 146,
"preview": "---\neyebrow: Adjustable Bench\nname: Ironmaster Super Bench PRO V2\ncategory: gym\nlink: https://www.ironmaster.com/product"
},
{
"path": "src/content/gear/kalita-wave.md",
"chars": 92,
"preview": "---\neyebrow: Pour over\nname: Kalita Wave\ncategory: coffee\nlink: https://amzn.to/3JdOd0e\n---\n"
},
{
"path": "src/content/gear/kinesis-advantage-360-keyboard.md",
"chars": 138,
"preview": "---\neyebrow: Keyboard\nname: Kinesis Advantage 360 Keyboard\ncategory: workspace\nlink: https://kinesis-ergo.com/keyboards/"
},
{
"path": "src/content/gear/knipex-cobra-water-pump-pliers.md",
"chars": 103,
"preview": "---\neyebrow: Pliers\nname: KNIPEX Cobra Pliers Wrench\ncategory: tools\nlink: https://amzn.to/4scQBq2\n---\n"
},
{
"path": "src/content/gear/knipex-pliers-wrench.md",
"chars": 97,
"preview": "---\neyebrow: Pliers\nname: KNIPEX Pliers Wrench\ncategory: tools\nlink: https://amzn.to/3MCR3xL\n---\n"
},
{
"path": "src/content/gear/la-roche-posay-lipikar-ap-triple-repair-moisturizing-cream.md",
"chars": 149,
"preview": "---\neyebrow: Body Moisturizer\nname: La Roche-Posay Lipikar AP+ Triple Repair Moisturizing Cream\ncategory: wellness\nlink:"
},
{
"path": "src/content/gear/lutron-aurora-smart-bulb-dimmer-switch.md",
"chars": 137,
"preview": "---\neyebrow: Dimmer switch\nname: Lutron Aurora Smart Bulb Dimmer Switch\ncategory: home\nlink: https://amzn.to/44wCl1N\nsta"
},
{
"path": "src/content/gear/m4-mac-mini.md",
"chars": 103,
"preview": "---\neyebrow: Personal computer\nname: M4 Mac Mini\ncategory: workspace\nlink: https://amzn.to/4d1AExL\n---\n"
},
{
"path": "src/content/gear/maestri-house-rechargeable-milk-frother-with-stand.md",
"chars": 130,
"preview": "---\neyebrow: Frother\nname: Maestri House Rechargeable Milk Frother with Stand\ncategory: kitchen\nlink: https://amzn.to/4p"
},
{
"path": "src/content/gear/neutrogena-hydro-boost-water-cream.md",
"chars": 124,
"preview": "---\neyebrow: Face Moisturizer\nname: Neutrogena Hydro Boost Water Cream\ncategory: wellness\nlink: https://amzn.to/4685v7O\n"
},
{
"path": "src/content/gear/nitecore-nb-air.md",
"chars": 97,
"preview": "---\neyebrow: Power bank\nname: Nitecore NB Air\ncategory: travel\nlink: https://amzn.to/4mf8YYf\n---\n"
},
{
"path": "src/content/gear/peak-design-packing-cube.md",
"chars": 108,
"preview": "---\neyebrow: Packing cube\nname: Peak Design Packing Cube\ncategory: travel\nlink: https://amzn.to/4mdapq8\n---\n"
},
{
"path": "src/content/gear/pica-dry-longlife-automatic-pencil.md",
"chars": 111,
"preview": "---\neyebrow: Pencil\nname: Pica-Dry Longlife Automatic Pencil\ncategory: tools\nlink: https://amzn.to/4s37SSq\n---\n"
},
{
"path": "src/content/gear/powerblock-elite-exp-adjustable-dumbbells.md",
"chars": 130,
"preview": "---\neyebrow: Adjustable Dumbbells\nname: PowerBlock Elite EXP Adjustable Dumbbells\ncategory: gym\nlink: https://amzn.to/4r"
},
{
"path": "src/content/gear/prometheus-beta-qrv2-titanium.md",
"chars": 141,
"preview": "---\neyebrow: Flashlight\nname: Prometheus Beta QRv2 Titanium\ncategory: edc\nlink: https://darksucks.com/products/beta-qrv3"
},
{
"path": "src/content/gear/rocket-espresso-appartamento-tca-espresso-machine.md",
"chars": 137,
"preview": "---\neyebrow: Espresso machine\nname: Rocket Espresso Appartamento TCA Espresso Machine\ncategory: coffee\nlink: https://amz"
},
{
"path": "src/content/gear/rode-podmic-microphone.md",
"chars": 107,
"preview": "---\neyebrow: Microphone\nname: Rode PodMic Microphone\ncategory: workspace\nlink: https://amzn.to/42h7wMM\n---\n"
},
{
"path": "src/content/gear/roka-oslo-2-0-wind-down.md",
"chars": 163,
"preview": "---\neyebrow: Red lens filter glasses\nname: ROKA Oslo 2.0 Wind Down\ncategory: wellness\nlink: https://www.roka.com/product"
},
{
"path": "src/content/gear/roost-laptop-stand.md",
"chars": 102,
"preview": "---\neyebrow: Laptop stand\nname: Roost Laptop Stand\ncategory: travel\nlink: https://amzn.to/4oDH49T\n---\n"
},
{
"path": "src/content/gear/snow-peak-ti-double-h200-stacking-mug.md",
"chars": 168,
"preview": "---\neyebrow: Mug\nname: snow peak Ti-Double H200 Stacking Mug\ncategory: coffee\nlink: https://snowpeak.com/products/ti-dou"
},
{
"path": "src/content/gear/sony-wh-1000xm4-headphones.md",
"chars": 113,
"preview": "---\neyebrow: Noise canceling\nname: Sony WH-1000XM4 Headphones\ncategory: travel\nlink: https://amzn.to/4fIJDmY\n---\n"
},
{
"path": "src/content/gear/spyderco-dragonfly-2.md",
"chars": 133,
"preview": "---\neyebrow: Knife\nname: Spyderco Dragonfly 2\ncategory: edc\nlink: https://spyderco.com/products/dragonfly%E2%84%A2-2-lig"
},
{
"path": "src/content/gear/subminimal-flick-wdt-espresso-distribution-tool.md",
"chars": 138,
"preview": "---\neyebrow: \"Distribution Tool\"\nname: Subminimal Flick WDT Espresso Distribution Tool\ncategory: coffee\nlink: https://am"
},
{
"path": "src/content/gear/synology-ds224.md",
"chars": 92,
"preview": "---\neyebrow: Storage\nname: Synology DS224+\ncategory: home\nlink: https://amzn.to/3MV10GO\n---\n"
},
{
"path": "src/content/gear/tactile-turn-apollo-flashlight.md",
"chars": 142,
"preview": "---\neyebrow: Titanium\nname: Tactile Turn Apollo\ncategory: flashlight\nlink: https://tactileturn.com/products/apollo?varia"
},
{
"path": "src/content/gear/tactile-turn-rockwall-thumbstud.md",
"chars": 138,
"preview": "---\neyebrow: Titanium\nname: Tactile Turn Rockwall Thumbstud\ncategory: knife\nlink: https://tactileknife.co/products/rockw"
},
{
"path": "src/content/gear/technivorm-moccamaster.md",
"chars": 121,
"preview": "---\neyebrow: Coffee maker\nname: Technivorm Moccamaster\ncategory: coffee\nlink: https://amzn.to/3HvPY8x\nfavorite: true\n---"
},
{
"path": "src/content/gear/the-james-brand-midland.md",
"chars": 129,
"preview": "---\neyebrow: Key Management\nname: The James Brand Midland\ncategory: edc\nlink: https://thejamesbrand.com/products/the-mid"
},
{
"path": "src/content/gear/timemore-sculptor-064s-coffee-grinder.md",
"chars": 128,
"preview": "---\neyebrow: Espresso grinder\nname: TIMEMORE Sculptor 064S Flat Burr Grinder\ncategory: coffee\nlink: https://amzn.to/46mb"
},
{
"path": "src/content/gear/tom-bihn-synik-22.md",
"chars": 175,
"preview": "---\neyebrow: \"Family bag\"\nname: TOM BIHN Synik 22\ncategory: bag\nlink: https://tombihn.com/collections/best-sellers/produ"
},
{
"path": "src/content/gear/tom-bihn-truck.md",
"chars": 171,
"preview": "---\neyebrow: \"Grocery bag\"\nname: TOM BIHN Truck\ncategory: bag\nlink: https://tombihn.com/collections/tote-bags/products/t"
},
{
"path": "src/content/gear/toto-washlet-s5-electronic-bidet.md",
"chars": 111,
"preview": "---\neyebrow: Bidet\nname: TOTO WASHLET S5 Electronic Bidet\ncategory: wellness\nlink: https://amzn.to/4pvfagv\n---\n"
},
{
"path": "src/content/gear/tp-link-16-port-gigabit-poe-switch.md",
"chars": 134,
"preview": "---\neyebrow: Network switch\nname: TP-Link 16 Port Gigabit PoE Switch\ncategory: home\nlink: https://amzn.to/44E00gN\nstatus"
},
{
"path": "src/content/gear/tp-link-6e-mesh-system.md",
"chars": 120,
"preview": "---\neyebrow: Mesh network\nname: TP-Link 6E Mesh System\ncategory: home\nlink: https://amzn.to/4eQdCc1\nstatus: retired\n---\n"
},
{
"path": "src/content/gear/unifi-dream-router-7.md",
"chars": 123,
"preview": "---\neyebrow: Router\nname: Unifi Dream Router 7\ncategory: home\nlink: https://techspecs.ui.com/unifi/cloud-gateways/udr7\n-"
},
{
"path": "src/content/gear/wera-bitholding-screwdriver.md",
"chars": 116,
"preview": "---\neyebrow: Screwdriver\nname: Wera 7-In-1 Bitholding Screwdriver\ncategory: tools\nlink: https://amzn.to/49uQRcD\n---\n"
},
{
"path": "src/content/gear/wera-tools-hex-set.md",
"chars": 96,
"preview": "---\neyebrow: Hex Set\nname: Wera Tools Hex Set\ncategory: tools\nlink: https://amzn.to/4pHnQQG\n---\n"
},
{
"path": "src/content/gear/yubikey-5c-nfc.md",
"chars": 95,
"preview": "---\neyebrow: Security key\nname: YubiKey 5C NFC\ncategory: edc\nlink: https://amzn.to/41zE4Bi\n---\n"
},
{
"path": "src/content/gear/zebralight-sc5c-ii-le.md",
"chars": 181,
"preview": "---\neyebrow: Classic\nname: Zebralight SC5c II LE\ncategory: flashlight\nlink: https://zebralight.com/sc5c-mk-ii-le-aa-flas"
},
{
"path": "src/content/jobs/clerk.md",
"chars": 3085,
"preview": "---\ntitle: Staff UI Engineer\ncompany: Clerk\nstartDate: 2024-01-04\nprojects:\n - title: Component theme editor\n link: "
},
{
"path": "src/content/jobs/hashicorp.md",
"chars": 330,
"preview": "---\ntitle: Senior Lead Web Engineer\ncompany: HashiCorp\nstartDate: 2021-07-01\nendDate: 2023-07-01\ntools:\n - Next.js\n - "
},
{
"path": "src/content/jobs/masuga-design.md",
"chars": 298,
"preview": "---\ntitle: Front-end Developer\ncompany: Masuga Design\nstartDate: 2012-02-01\nendDate: 2015-08-01\ntools:\n - ExpressionEng"
},
{
"path": "src/content/jobs/mighty-in-the-midwest.md",
"chars": 613,
"preview": "---\ntitle: Senior Web Engineer\ncompany: Mighty in the Midwest\nstartDate: 2015-08-01\nendDate: 2018-11-01\nprojects:\n - ti"
},
{
"path": "src/content/jobs/nationbuilder.md",
"chars": 280,
"preview": "---\ntitle: UI Engineer\ncompany: NationBuilder\nstartDate: 2018-11-01\nendDate: 2021-07-01\ntools:\n - React\n - Rails\n - B"
},
{
"path": "src/content/jobs/watershed.md",
"chars": 576,
"preview": "---\ntitle: Lead Web Engineer\ncompany: Watershed\nstartDate: 2023-08-01\nendDate: 2023-12-01\nprojects:\n - title: Watershed"
},
{
"path": "src/content/notes/2024-12-05-1.md",
"chars": 137,
"preview": "---\npublished: 2024-12-05T19:25:15-0400\ntitle: Just use children\nlink: https://www.sid.st/post/just-use-children/\ntags:\n"
},
{
"path": "src/content/notes/2025-02-17-1.md",
"chars": 171,
"preview": "---\npublished: 2025-02-17T19:21:04-0400\ntitle: The \"everything bagel\" of components\nlink: https://dio.la/article/the-eve"
},
{
"path": "src/content/notes/2025-02-19-2.md",
"chars": 870,
"preview": "---\npublished: 2025-02-19T16:37:34-0500\ntags:\n - tip\n---\n\nReact Aria exposing state through the className is super hand"
},
{
"path": "src/content/notes/2025-02-25-1.md",
"chars": 133,
"preview": "---\npublished: 2025-02-25T19:26:08-0400\ntitle: Sanding UI\nlink: https://blog.jim-nielsen.com/2024/sanding-ui/\ntags:\n - "
},
{
"path": "src/content/notes/2025-03-22-1.md",
"chars": 180,
"preview": "---\npublished: 2025-03-22T19:25:15-0400\ntitle: Naming things in design systems–and why it’s the worst\nlink: https://pjon"
},
{
"path": "src/content/notes/2025-04-13-1.md",
"chars": 228,
"preview": "---\npublished: 2025-04-13T20:14:22-0400\ntitle: Write code that is easy to delete, not easy to extend\nlink: https://progr"
},
{
"path": "src/content/notes/2025-04-14-1.md",
"chars": 532,
"preview": "---\npublished: 2025-04-14T12:41:36-0400\ntitle: Animate height from 0 to auto\ntags:\n - tip\n - css\n - animation\n---\n\n``"
},
{
"path": "src/content/notes/2025-04-14-2.md",
"chars": 598,
"preview": "---\npublished: 2025-04-14T20:50:41-0400\ntitle: Create a list of links from an array using Intl.ListFormat with formatToP"
},
{
"path": "src/content/notes/2025-04-15-1.md",
"chars": 1412,
"preview": "---\npublished: 2025-04-15T08:20:41-0400\ntags:\n - css\n---\n\nThis was a fun layout challenge for some dashboard UI we've b"
},
{
"path": "src/content/notes/2025-04-16-1.md",
"chars": 320,
"preview": "---\npublished: 2025-04-16T08:45:17-0400\ntags:\n - css\n - tip\n---\n\nStop vertically aligning your checkboxes with `center"
},
{
"path": "src/content/notes/2025-04-17-1.md",
"chars": 368,
"preview": "---\npublished: 2025-04-17T08:18:43-0400\ntags:\n - css\n - tip\n---\n\nQuick little improvement for elements that render dar"
},
{
"path": "src/content/notes/2025-04-18-1.md",
"chars": 354,
"preview": "---\npublished: 2025-04-18T07:59:33-0400\ntags:\n - css\n - tip\n---\n\nQuick little improvement for wrapping highlighted tex"
},
{
"path": "src/content/notes/2025-04-18-2.md",
"chars": 196,
"preview": "---\npublished: 2025-04-18T08:02:53-0400\ntitle: \"OKLCH in CSS: why we moved from RGB and HSL\"\nlink: https://evilmartians."
},
{
"path": "src/content/notes/2025-04-19-1.md",
"chars": 159,
"preview": "---\npublished: 2025-04-19T07:24:05-0400\ntitle: The Wet Codebase\nlink: https://www.deconstructconf.com/2019/dan-abramov-t"
},
{
"path": "src/content/notes/2025-04-19-2.md",
"chars": 143,
"preview": "---\npublished: 2025-04-19T07:29:11-0400\ntitle: Adaptive dotted pattern\nlink: https://codepen.io/myf/pen/poXYLNB?editors="
},
{
"path": "src/content/notes/2025-04-30-1.md",
"chars": 928,
"preview": "---\npublished: 2025-04-30T08:04:10-0400\ntags:\n - tip\n---\n\nNot uncommon to see folks add form submit handlers on the sub"
},
{
"path": "src/content/notes/2025-05-01-1.md",
"chars": 304,
"preview": "---\npublished: 2025-05-01T08:19:09-0400\ntitle: useMaskedScroll hook\nlink: https://github.com/sambecker/exif-photo-blog/b"
},
{
"path": "src/content/notes/2025-05-02-1.md",
"chars": 230,
"preview": "---\npublished: 2025-05-02T07:32:39-0400\ntitle: CSS Spring Easing Generator\nlink: https://www.kvin.me/css-springs\ntags:\n "
},
{
"path": "src/content/notes/2025-05-14-1.md",
"chars": 145,
"preview": "---\npublished: 2025-05-14T09:10:48-0400\ntitle: never just\nlink: https://www.neverjust.net/\ntags:\n - bookmark\n---\n\n> it'"
},
{
"path": "src/content/notes/2025-05-26-1.md",
"chars": 397,
"preview": "---\npublished: 2025-05-26T12:25:57-0400\ntitle: Evil Martions Harmonizer\nlink: https://harmonizer.evilmartians.com\ntags:\n"
},
{
"path": "src/content/notes/2025-06-18-1.md",
"chars": 501,
"preview": "---\npublished: 2025-06-18T07:43:51-0400\ntags:\n - css\n---\n\nAutomatic foreground color contrast based on the provided bac"
},
{
"path": "src/content/notes/2025-06-18-2.md",
"chars": 331,
"preview": "---\ntitle: The Prettify Helper\nlink: https://www.totaltypescript.com/concepts/the-prettify-helper\npublished: 2025-06-18T"
},
{
"path": "src/content/notes/2025-06-18-3.md",
"chars": 722,
"preview": "---\ntitle: Tiny polyfill for CSS scroll driven animations\nlink: https://x.com/devongovett/status/1932478787507425405\npub"
},
{
"path": "src/content/notes/2025-06-18-4.md",
"chars": 430,
"preview": "---\ntitle: Handle all potential cases in a switch statement\nlink: https://x.com/housecor/status/1927688347754881245\npubl"
},
{
"path": "src/content/notes/2025-06-21-1.md",
"chars": 526,
"preview": "---\npublished: 2025-06-21T07:59:56-0400\ntags:\n - css\n - work\n---\n\nWorking on some updates to make it easier to theme C"
},
{
"path": "src/content/notes/2025-06-24-1.md",
"chars": 192,
"preview": "---\ntitle: On moving from implicit to explicit coupling\nlink: https://ineffable.co/writing/moving-from-implicit-to-expli"
},
{
"path": "src/content/notes/2025-06-27-1.md",
"chars": 169,
"preview": "---\ntitle: keyux\ndescription: JS library to improve keyboard UI of web apps\nlink: https://github.com/ai/keyux\npublished:"
},
{
"path": "src/content/notes/2025-07-01-1.md",
"chars": 237,
"preview": "---\ntitle: apcach\ndescription: JS color calculator for composing colors with consistent APCA contrast ratio.\nlink: https"
},
{
"path": "src/content/notes/2025-07-02-1.md",
"chars": 195,
"preview": "---\ntitle: That boolean should probably be something else\nlink: https://ntietz.com/blog/that-boolean-should-probably-be-"
},
{
"path": "src/content/notes/2025-07-08-1.md",
"chars": 203,
"preview": "---\ntitle: culori\ndescription: A comprehensive color library for JavaScript.\nlink: https://github.com/Evercoder/culori\np"
},
{
"path": "src/content/notes/2025-07-11-1.md",
"chars": 151,
"preview": "---\ntitle: What's the \"Geometry\" of Colours?\nlink: https://www.youtube.com/watch?v=7KYwi2F5Ce4\npublished: 2025-07-11T10:"
},
{
"path": "src/content/notes/2025-07-15-1.md",
"chars": 793,
"preview": "---\ntitle: Clerk CSS variables are now available!\nlink: https://clerk.com/changelog/2025-07-15-clerk-css-variables-suppo"
},
{
"path": "src/content/notes/2025-07-17-1.md",
"chars": 507,
"preview": "---\ntitle: Agentic Engineering\ndescription: Combining human craftsmanship with AI tools to build better software.\nlink: "
},
{
"path": "src/content/notes/2025-07-17-2.md",
"chars": 182,
"preview": "---\ntitle: Building your ideas with Claude Code and Figma MCP with Dan Hollick\nlink: https://www.youtube.com/watch?v=7Xx"
},
{
"path": "src/content/notes/2025-07-18-1.md",
"chars": 159,
"preview": "---\ntitle: Jonas Brinkhoff's personal website\nlink: https://www.jonasbrinkhoff.com/\npublished: 2025-07-18T08:16:47-0400\n"
},
{
"path": "src/content/notes/2025-07-18-2.md",
"chars": 142,
"preview": "---\ntitle: Chase McCoy's personal website\nlink: https://chsmc.org/\npublished: 2025-07-18T08:18:50-0400\ntags:\n - bookmar"
},
{
"path": "src/content/notes/2025-07-21-1.md",
"chars": 228,
"preview": "---\ntitle: Write like you talk\nlink: https://paulgraham.com/talk.html\npublished: 2025-07-21T08:37:45-0400\ntags:\n - book"
},
{
"path": "src/content/notes/2025-07-23-1.md",
"chars": 286,
"preview": "---\ntitle: shadcn/ui theme compatibility is now available\nlink: https://clerk.com/changelog/2025-07-23-shadcn-theme\npubl"
},
{
"path": "src/content/notes/2025-07-29-1.md",
"chars": 843,
"preview": "---\npublished: 2025-07-29T08:25:58-0400\ntags:\n - css\n - tip\n---\n\nYour component library ships bundled CSS via CSS-in-J"
},
{
"path": "src/content/notes/2025-08-05-1.md",
"chars": 244,
"preview": "---\npublished: 2025-08-05T19:26:48-0400\ntags:\n - work\n---\n\nHad a quick chat with Hamed about the recent work we have do"
},
{
"path": "src/content/notes/2025-08-13-1.md",
"chars": 393,
"preview": "---\npublished: 2025-08-13T07:54:31-0400\ndemo: /demos/bg-repeat-round\ntags:\n - demo\n - css\n---\n\nUsing `background-repea"
},
{
"path": "src/content/notes/2025-08-13-2.md",
"chars": 357,
"preview": "---\npublished: 2025-08-13T08:10:08-0400\ndemo: /demos/box-decoration-break-clone\ntags:\n - demo\n - css\n---\n\nFor when `<m"
},
{
"path": "src/content/notes/2025-08-13-3.md",
"chars": 509,
"preview": "---\npublished: 2025-08-13T16:34:12-0400\ntitle: We shipped a shadcn/ui registry\nlink: https://clerk.com/changelog/2025-08"
},
{
"path": "src/content/notes/2025-08-14-1.md",
"chars": 738,
"preview": "---\ntitle: How to properly align icons within list items\npublished: 2025-08-14T15:49:05-0400\ndemo: /demos/list-item-icon"
},
{
"path": "src/content/notes/2025-08-18-1.md",
"chars": 632,
"preview": "---\npublished: 2025-08-18T14:56:44-0400\ndemo: /demos/inline-trailing-icon\ntags:\n - demo\n - css\n---\n\nEnsure the trailin"
},
{
"path": "src/content/notes/2025-08-18-2.md",
"chars": 276,
"preview": "---\npublished: 2025-08-18T17:20:03-0400\ntitle: use-stick-to-bottom\ndescription: A lightweight React Hook intended mainly"
},
{
"path": "src/content/notes/2025-08-23-1.md",
"chars": 154,
"preview": "---\npublished: 2025-08-23T13:53:44-0400\ntitle: Silk\ndescription: Native‑like swipeable sheets on the web\nlink: https://s"
},
{
"path": "src/content/notes/2025-08-25-1.md",
"chars": 515,
"preview": "---\npublished: 2025-08-25T08:39:17-0400\ndemo: /demos/hanging-punctuation\ntags:\n - css\n - demo\n---\n\nAnother use case fo"
},
{
"path": "src/content/notes/2025-08-26-1.md",
"chars": 137,
"preview": "---\npublished: 2025-08-26T11:32:27-0400\ntitle: What are OKLCH colors?\nlink: https://jakub.kr/components/oklch-colors\ntag"
},
{
"path": "src/content/notes/2025-08-26-2.md",
"chars": 157,
"preview": "---\npublished: 2025-08-26T11:34:53-0400\ntitle: A Clock That Doesn't Snap\nlink: https://ethanniser.dev/blog/a-clock-that-"
},
{
"path": "src/content/notes/2025-09-05-1.md",
"chars": 718,
"preview": "---\ntitle: My shadcn/ui registry\nlink: https://ui.alexcarpenter.me\npublished: 2025-09-05T08:31:49-0400\ntags:\n - javascr"
},
{
"path": "src/content/notes/2025-09-05-2.md",
"chars": 178,
"preview": "---\npublished: 2025-09-05T08:58:10-0400\ntags:\n - inspiration\n---\n\nTwo fantastic pieces of web inspiration:\n\n- [operate."
},
{
"path": "src/content/notes/2025-09-06-1.md",
"chars": 250,
"preview": "---\npublished: 2025-09-06T15:33:30-0400\ntags:\n - work\n---\n\nFixing folks list alignment one PR at a time.\n\n- [midday/pul"
},
{
"path": "src/content/notes/2025-09-12-1.md",
"chars": 215,
"preview": "---\npublished: 2025-09-12T18:19:56-0400\ntags:\n - work\n---\n\nI've been fortunate to work with some amazing people over th"
},
{
"path": "src/content/notes/2025-09-19-1.md",
"chars": 239,
"preview": "---\npublished: 2025-09-19T08:23:59-0400\ntitle: ▲ Vercel Web Interface Guidelines\nlink: https://vercel.com/design/guideli"
},
{
"path": "src/content/notes/2025-09-20-1.md",
"chars": 146,
"preview": "---\npublished: 2025-09-20T08:39:55-0400\ntitle: Dither plugin for TailwindCSS\nlink: https://dither.floriankiem.com/\ntags:"
},
{
"path": "src/content/notes/2025-09-28-1.md",
"chars": 547,
"preview": "---\npublished: 2025-09-28T09:44:13-0400\ndemo: /demos/button-disabled-color-mix\ntags:\n - demo\n - css\n---\n\nIt's always f"
},
{
"path": "src/content/notes/2025-10-23-1.md",
"chars": 583,
"preview": "---\npublished: 2025-10-23T08:20:33-0400\ntitle: Preserve modal aspect ratio across viewport sizes\ndemo: /demos/modal-aspe"
},
{
"path": "src/content/notes/2025-11-05-1.md",
"chars": 272,
"preview": "---\ntitle: \"RESILIENT—UI interview #001 with Dima Belyaev\"\ndescription: Staff front-end engineer based in Amsterdam, wor"
},
{
"path": "src/content/notes/2025-11-05-2.md",
"chars": 190,
"preview": "---\ntitle: The Web Animation Performance Tier List\nlink: https://motion.dev/blog/web-animation-performance-tier-list\npub"
},
{
"path": "src/content/notes/2025-11-18-1.md",
"chars": 276,
"preview": "---\ntitle: \"RESILIENT—UI interview #002 with Hayden Bleasel\"\ndescription: Australian Design Engineer who loves working o"
},
{
"path": "src/content/notes/2025-12-03-1.md",
"chars": 329,
"preview": "---\ntitle: \"2025 Holiday Coffee Gift Guide\"\ndescription: Discover the best coffee gifts for the holidays—espresso machin"
},
{
"path": "src/content/notes/2025-12-15-1.md",
"chars": 282,
"preview": "---\ntitle: How to target Safari with a CSS @supports media query\ndescription: Easiest method for targeting Safari with C"
},
{
"path": "src/content/notes/2026-01-06-1.md",
"chars": 1227,
"preview": "---\npublished: 2026-01-06T17:02:58-0500\ntags:\n - css\n - demo\n---\n\nCSS only scroll fade example that I implemented on ["
},
{
"path": "src/content/notes/2026-01-17-01.md",
"chars": 160,
"preview": "---\ntitle: 21 Lessons From 14 Years at Google\nlink: https://addyosmani.com/blog/21-lessons/\npublished: 2026-01-17T08:17:"
},
{
"path": "src/content/notes/2026-01-19-1.md",
"chars": 146,
"preview": "---\ntitle: Annotating for agents\npublished: 2026-01-19T12:28:08-0500\nlink: https://benji.org/annotating\ntags:\n - bookma"
},
{
"path": "src/content/notes/2026-01-22-1.md",
"chars": 174,
"preview": "---\npublished: 2026-01-22T16:17:27-0500\ntitle: Greppability is an underrated code metric\nlink: https://morizbuesing.com/"
},
{
"path": "src/content/notes/2026-01-26-1.md",
"chars": 402,
"preview": "---\npublished: 2026-01-26T14:34:05-0500\ntags:\n - til\n---\n\nTIL you can slow down animations in Chrome DevTools.\n\nPress <"
},
{
"path": "src/content/notes/2026-01-28-1.md",
"chars": 305,
"preview": "---\npublished: 2026-01-28T06:47:36-0500\ntitle: When life gives you lemons, write better error messages\nlink: https://wix"
},
{
"path": "src/content/notes/2026-01-29-1.md",
"chars": 242,
"preview": "---\ntitle: Composition is all you need.\nlink: https://www.youtube.com/watch?v=4KvbVq3Eg5w\npublished: 2026-01-29T07:21:29"
},
{
"path": "src/content/notes/2026-02-03-1.md",
"chars": 128,
"preview": "---\npublished: 2026-02-03T07:06:47-0500\ntitle: before-and-after\nlink: https://www.jm.sv/before-and-after\ntags:\n - bookm"
},
{
"path": "src/content/notes/2026-02-03-2.md",
"chars": 170,
"preview": "---\npublished: 2026-02-03T08:43:34-0500\ntitle: Alexander Vilinskyy's personal website\nlink: https://www.vilinskyy.com/\nt"
},
{
"path": "src/content/notes/2026-02-06-1.md",
"chars": 173,
"preview": "---\ntitle: A React trick to improve exit animations\nlink: https://barvian.me/react-exit-animations\npublished: 2026-02-06"
},
{
"path": "src/content/notes/2026-02-06-2.md",
"chars": 189,
"preview": "---\ntitle: Building Bulletproof React Components\nlink: https://shud.in/thoughts/build-bulletproof-react-components\npubli"
},
{
"path": "src/content/notes/2026-02-09-1.md",
"chars": 189,
"preview": "---\ntitle: RESILIENT-UI SKILLS\nlink: https://www.resilient-ui.com/skills\npublished: 2026-02-09T10:49:31-0500\ntags:\n - j"
},
{
"path": "src/content/notes/2026-02-11-1.md",
"chars": 167,
"preview": "---\ntitle: Matt Rothenberg's personal website\nlink: https://mattrothenberg.com/\npublished: 2026-02-11T07:43:56-0500\ntags"
},
{
"path": "src/content/notes/2026-02-11-2.md",
"chars": 343,
"preview": "---\ntitle: eslint-plugin-react-render-types\nlink: https://github.com/HorusGoul/eslint-plugin-react-render-types\npublishe"
},
{
"path": "src/content/notes/2026-02-12-1.md",
"chars": 151,
"preview": "---\ntitle: Digital hygiene\nlink: https://karpathy.bearblog.dev/digital-hygiene/\npublished: 2026-02-12T07:43:49-0500\ntags"
},
{
"path": "src/content/notes/2026-02-13-1.md",
"chars": 142,
"preview": "---\ntitle: How I Use Claude Code\nlink: https://boristane.com/blog/how-i-use-claude-code/\npublished: 2026-02-13T08:12:56-"
},
{
"path": "src/content/notes/2026-02-14-1.md",
"chars": 187,
"preview": "---\ntitle: \"ASCII characters are not pixels: a deep dive into ASCII rendering\"\nlink: https://alexharri.com/blog/ascii-re"
},
{
"path": "src/content/notes/2026-02-15-1.md",
"chars": 232,
"preview": "---\ntitle: react-prehydrate\nlink: https://github.com/javivelasco/react-prehydrate\npublished: 2026-02-15T07:59:10-0500\nta"
},
{
"path": "src/content/notes/2026-02-15-2.md",
"chars": 266,
"preview": "---\ntitle: \"@nano_kit/store\"\nlink: https://nano_kit.js.org/store/\npublished: 2026-02-15T08:41:03-0500\ntags:\n - bookmark"
},
{
"path": "src/content/notes/2026-02-16-1.md",
"chars": 177,
"preview": "---\ntitle: Building Type-Safe Compound Components\nlink: https://tkdodo.eu/blog/building-type-safe-compound-components\npu"
},
{
"path": "src/content/notes/2026-02-16-2.md",
"chars": 159,
"preview": "---\ntitle: <TextMorph />\nlink: https://torph.lochie.me/\npublished: 2026-02-16T11:48:22-0500\ntags:\n - bookmark\n---\n\n> De"
},
{
"path": "src/content/notes/2026-02-24-1.md",
"chars": 128,
"preview": "---\ntitle: ESLint complexity rule\nlink: https://eslint.org/docs/latest/rules/complexity\npublished: 2026-02-24T15:50:52-0"
},
{
"path": "src/content/notes/2026-03-01-1.md",
"chars": 147,
"preview": "---\ntitle: Anton Repponen's personal website\nlink: https://repponen.com\npublished: 2026-03-01T09:30:41-0500\ntags:\n - in"
},
{
"path": "src/content/notes/2026-03-01-2.md",
"chars": 143,
"preview": "---\ntitle: Naz Hamid's personal website\nlink: https://nazhamid.com/\npublished: 2026-03-01T11:25:34-0500\ntags:\n - inspir"
},
{
"path": "src/content/notes/2026-03-04-1.md",
"chars": 172,
"preview": "---\ntitle: How to implement beautiful shadow borders\nlink: https://x.com/PixelJanitor/status/1623358514440859649\npublish"
},
{
"path": "src/content/notes/2026-03-05-1.md",
"chars": 115,
"preview": "---\ntitle: History of Software Design\nlink: https://historyofsoftware.org/\npublished: 2026-03-05T14:20:10-0500\n---\n"
},
{
"path": "src/content/notes/2026-03-06-1.md",
"chars": 2341,
"preview": "---\ntitle: Smooth Dot Indicators with Embla Carousel and CSS color-mix()\ndescription: How I implemented smooth carousel "
},
{
"path": "src/content/notes/2026-03-06-2.md",
"chars": 96,
"preview": "---\ntitle: React Grab\nlink: https://www.react-grab.com/\npublished: 2026-03-06T19:23:27-0500\n---\n"
},
{
"path": "src/content/notes/2026-03-08-1.md",
"chars": 246,
"preview": "---\ntitle: Agentic Engineering Patterns\nlink: https://simonwillison.net/guides/agentic-engineering-patterns/\npublished: "
},
{
"path": "src/content/notes/2026-03-11-1.md",
"chars": 141,
"preview": "---\ntitle: The Anatomy of an Agent Harness\nlink: https://x.com/Vtrivedy10/status/2031408954517971368\npublished: 2026-03-"
},
{
"path": "src/content/notes/2026-03-15-1.md",
"chars": 120,
"preview": "---\ntitle: Daniel Griesser's PI Config\nlink: https://github.com/HazAT/pi-config\npublished: 2026-03-15T19:53:02-0400\n---\n"
},
{
"path": "src/content/notes/2026-03-17-1.md",
"chars": 223,
"preview": "---\ntitle: \"Managing Context Windows with pi /tree: Branches, Summaries, and Subagent-like Workflows\"\nlink: https://stac"
},
{
"path": "src/content/notes/2026-03-20-1.md",
"chars": 164,
"preview": "---\ntitle: Using Container Queries in a CSS Grid to Detect Truncation\nlink: https://v0-css-container-query-test.vercel.a"
},
{
"path": "src/content/notes/2026-03-21-1.md",
"chars": 120,
"preview": "---\ntitle: William Jansson's personal website\nlink: https://williamjansson.com/\npublished: 2026-03-21T08:29:03-0400\n---\n"
},
{
"path": "src/content/notes/2026-03-21-2.md",
"chars": 108,
"preview": "---\ntitle: Nguyễn Ngọc Ánh's personal website\nlink: https://anh.ng/\npublished: 2026-03-21T08:31:11-0400\n---\n"
},
{
"path": "src/content/notes/2026-03-27-1.md",
"chars": 126,
"preview": "---\ntitle: Nikhil Anand's personal website\nlink: https://nikhil.io/\npublished: 2026-03-27T14:49:41-0400\ntags:\n - websit"
},
{
"path": "src/content/notes/2026-04-04-1.md",
"chars": 210,
"preview": "---\ntitle: Tree structure using CSS anchor positioning\nlink: https://lab.chsmc.org/anchor-positioned-tree\npublished: 202"
},
{
"path": "src/content/notes/2026-04-26-1.md",
"chars": 567,
"preview": "---\npublished: 2026-04-26T09:58:20-0400\ndemo: /demos/button-tint\ntags:\n - demo\n - css\n---\n\nTint buttons based on their"
},
{
"path": "src/content/oss-contributions.json",
"chars": 3594,
"preview": "[\n {\n \"date\": \"2026-03-18\",\n \"link\": \"https://github.com/developit/kinu/pull/102\",\n \"description\": \"Allow clic"
},
{
"path": "src/content/recommendations/amy-stuart.md",
"chars": 559,
"preview": "---\nname: Amy Stuart\ntitle: Senior Designer\ncompany: nationbuilder\npublished: 2021-01-02\n---\n\nAlex and I worked together"
},
{
"path": "src/content/recommendations/andrew-possehl.md",
"chars": 311,
"preview": "---\nname: Andrew Possehl\ntitle: Senior Designer\ncompany: nationbuilder\npublished: 2021-01-01\n---\n\nI worked with Alex on "
},
{
"path": "src/content/recommendations/benjamin-kohl.md",
"chars": 228,
"preview": "---\nname: Benjamin Kohl\ntitle: Back-end Developer\ncompany: masuga-design\npublished: 2015-01-01\nstatus: hidden\n---\n\nAlex "
},
{
"path": "src/content/recommendations/danielle-dunn.md",
"chars": 352,
"preview": "---\nname: Danielle Dunn\ntitle: Project Manager\ncompany: mighty-in-the-midwest\npublished: 2018-01-02\n---\n\nAlex is incredi"
},
{
"path": "src/content/recommendations/david-mosher.md",
"chars": 532,
"preview": "---\nname: David Mosher\ntitle: Staff Software Engineer\ncompany: clerk\npublished: 2024-10-14\n---\n\nIf you are seeking someo"
}
]
// ... and 28 more files (download for full content)
About this extraction
This page contains the full source code of the alexcarpenter/alexcarpenter.me GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 228 files (33.2 MB), approximately 43.0k tokens, and a symbol index with 7 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.