[
  {
    "path": ".gitignore",
    "content": "# build output\ndist/\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\nAGENT.md\n"
  },
  {
    "path": ".npmrc",
    "content": "min-release-age=7\n"
  },
  {
    "path": ".prettierignore",
    "content": "node_modules/**\npackage.json\npnpm-lock.yaml"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"plugins\": [\"prettier-plugin-astro\", \"prettier-plugin-tailwindcss\"],\n  \"overrides\": [\n    {\n      \"files\": \"*.astro\",\n      \"options\": {\n        \"parser\": \"astro\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"prettier.documentSelectors\": [\"**/*.astro\"],\n  \"[astro]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  }\n}\n"
  },
  {
    "path": "Makefile",
    "content": "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 created and pushed to remote"
  },
  {
    "path": "README.md",
    "content": "# alexcarpenter.me\n\n- Framework: Astro\n- Deployment: Vercel\n- Styling: Tailwind CSS\n\n## Running locally\n\n```bash\ngit clone https://github.com/alexcarpenter/alexcarpenter.me.git\ncd alexcarpenter.me\npnpm install\npnpm dev\n```\n"
  },
  {
    "path": "astro.config.mjs",
    "content": "import sitemap from \"@astrojs/sitemap\";\nimport {\n  transformerMetaHighlight,\n  transformerMetaWordHighlight,\n  transformerNotationDiff,\n} from \"@shikijs/transformers\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport icon from \"astro-icon\";\nimport { defineConfig } from \"astro/config\";\nimport rehypeExternalLinks from \"rehype-external-links\";\n\nexport default defineConfig({\n  site: \"https://alexcarpenter.me\",\n  vite: {\n    plugins: [tailwindcss()],\n    define: {\n      \"import.meta.env.COMMIT_SHA\": JSON.stringify(\n        process.env.VERCEL_GIT_COMMIT_SHA ||\n          process.env.COMMIT_SHA ||\n          \"56720e6\",\n      ),\n    },\n  },\n  integrations: [sitemap(), icon()],\n  markdown: {\n    rehypePlugins: [[rehypeExternalLinks, { target: \"_blank\" }]],\n    shikiConfig: {\n      themes: {\n        light: \"github-light\",\n        dark: \"github-dark\",\n      },\n      transformers: [\n        transformerNotationDiff(),\n        transformerMetaHighlight(),\n        transformerMetaWordHighlight(),\n      ],\n      cache: true,\n    },\n  },\n});\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"alexcarpenter-me\",\n  \"type\": \"module\",\n  \"version\": \"7.0.0\",\n  \"engines\": {\n    \"node\": \">=22\"\n  },\n  \"pnpm\": {\n    \"overrides\": {\n      \"lodash\": \"4.17.23\"\n    }\n  },\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"start\": \"astro dev\",\n    \"build\": \"astro check && astro build\",\n    \"preview\": \"astro preview\",\n    \"astro\": \"astro\",\n    \"format\": \"prettier --write .\",\n    \"check\": \"astro check\"\n  },\n  \"dependencies\": {\n    \"@astrojs/check\": \"0.9.9\",\n    \"@astrojs/rss\": \"4.0.18\",\n    \"@astrojs/sitemap\": \"3.7.2\",\n    \"@shikijs/transformers\": \"^4.1.0\",\n    \"@sindresorhus/slugify\": \"^3.0.0\",\n    \"@tailwindcss/vite\": \"^4.3.0\",\n    \"astro\": \"6.3.6\",\n    \"astro-icon\": \"^1.1.5\",\n    \"markdown-it\": \"^14.1.1\",\n    \"rehype-external-links\": \"^3.0.0\",\n    \"sanitize-html\": \"^2.17.4\",\n    \"sharp\": \"^0.34.5\",\n    \"tailwindcss\": \"4.2.4\",\n    \"typescript\": \"^6.0.3\"\n  },\n  \"devDependencies\": {\n    \"@types/markdown-it\": \"^14.1.2\",\n    \"@types/sanitize-html\": \"^2.16.1\",\n    \"prettier\": \"^3.8.3\",\n    \"prettier-plugin-astro\": \"^0.14.1\",\n    \"prettier-plugin-tailwindcss\": \"^0.7.4\"\n  },\n  \"packageManager\": \"pnpm@10.33.0\"\n}\n"
  },
  {
    "path": "public/humans.txt",
    "content": "TECHNOLOGIES\n\n- Astro: https://astro.build\n- Tailwind CSS: https://tailwindcss.com\n- Geist Font: https://vercel.com/font"
  },
  {
    "path": "public/robots.txt",
    "content": "Sitemap: https://alexcarpenter.me/sitemap-index.xml"
  },
  {
    "path": "src/components/demo.astro",
    "content": "---\nimport { Icon } from \"astro-icon/components\";\n\ntype Props = {\n  src: string;\n  title?: string;\n  class?: string;\n  aspectRatio?: \"16:9\" | \"4:3\" | \"1:1\";\n};\n\nconst { src, title, class: className, aspectRatio = \"16:9\" } = Astro.props;\n---\n\n<div\n  class:list={[\n    \"border-separator bg-grid group/demo relative overflow-hidden rounded-md border\",\n    aspectRatio === \"16:9\" && \"aspect-video\",\n    aspectRatio === \"4:3\" && \"aspect-4/3\",\n    aspectRatio === \"1:1\" && \"aspect-square\",\n    className,\n  ]}\n>\n  <a\n    href={src}\n    target=\"_blank\"\n    class:list={[\n      \"text-muted-foreground absolute top-0 right-0 grid size-8 place-content-center\",\n      \"hover:text-foreground opacity-0 transition-[opacity,color] group-hover/demo:opacity-100 focus:opacity-100\",\n    ]}\n  >\n    <span class=\"sr-only\">Open demo in a new tab</span>\n    <Icon name=\"expand\" class=\"size-3\" aria-hidden=\"true\" />\n  </a>\n  <div class=\"h-full max-w-full min-w-80 resize-x overflow-x-auto\">\n    <iframe\n      src={src}\n      class=\"h-full w-full\"\n      title={title || \"Demo\"}\n      loading=\"lazy\"></iframe>\n  </div>\n</div>\n"
  },
  {
    "path": "src/components/external-link.astro",
    "content": "---\nimport { Icon } from \"astro-icon/components\";\nconst { href, text, class: className } = Astro.props;\n---\n\n{/* prettier-ignore */}\n<a\n  href={href}\n  target=\"_blank\"\n  rel=\"noopener noreferrer\"\n  class:list={[\"relative inline-block pr-3.5\", className]}\n  >{text}<Icon\n    name=\"arrow-up-right\"\n    aria-hidden=\"true\"\n    class=\"absolute ml-1 inline h-5 w-2.5 flex-none\"\n  /></a\n>\n"
  },
  {
    "path": "src/consts.ts",
    "content": "export const GEAR_CATEGORIES = [\n  \"bag\",\n  \"coffee\",\n  \"edc\",\n  \"gym\",\n  \"home\",\n  \"wellness\",\n  \"kitchen\",\n  \"knife\",\n  \"flashlight\",\n  \"misc\",\n  \"tools\",\n  \"travel\",\n  \"workspace\",\n] as const;\n\ntype GearCategory = (typeof GEAR_CATEGORIES)[number];\n\nexport const GEAR_CATEGORY_MAP: Record<\n  GearCategory,\n  {\n    title: string;\n    description: string;\n    slug: string;\n  }\n> = {\n  bag: {\n    title: \"Bags\",\n    description: \"My current rotation of bags.\",\n    slug: \"bags\",\n  },\n  coffee: {\n    title: \"Coffee\",\n    description: \"My favorite coffee gear and accessories.\",\n    slug: \"coffee\",\n  },\n  edc: {\n    title: \"Everyday Carry\",\n    description: \"Tools I carry daily that fit into my pockets.\",\n    slug: \"everyday-carry\",\n  },\n  flashlight: {\n    title: \"Flashlights\",\n    description: \"My favorite flashlights and lighting gear.\",\n    slug: \"flashlights\",\n  },\n  gym: {\n    title: \"Home Gym\",\n    description: \"My home gym equipment and setup.\",\n    slug: \"home-gym\",\n  },\n  wellness: {\n    title: \"Health & Wellness\",\n    description: \"\",\n    slug: \"health-and-wellness\",\n  },\n  home: {\n    title: \"Home\",\n    description: \"My home automation setup.\",\n    slug: \"home\",\n  },\n  kitchen: {\n    title: \"Kitchen\",\n    description: \"My favorite kitchen gadgets and tools.\",\n    slug: \"kitchen\",\n  },\n  knife: {\n    title: \"Knives\",\n    description: \"Pocket knives and multi-tools.\",\n    slug: \"knives\",\n  },\n  tools: {\n    title: \"Tools\",\n    description: \"Homeowner tools and equipment.\",\n    slug: \"tools\",\n  },\n  travel: {\n    title: \"Travel\",\n    description: \"Gear I use while traveling and out and about.\",\n    slug: \"travel\",\n  },\n  workspace: {\n    title: \"Workspace\",\n    description: \"The tools I use for work.\",\n    slug: \"workspace\",\n  },\n  misc: {\n    title: \"Miscellaneous\",\n    description: \"Other gear that doesn't fit into a specific category.\",\n    slug: \"miscellaneous\",\n  },\n};\n"
  },
  {
    "path": "src/content/gear/aer-day-sling-3.md",
    "content": "---\neyebrow: \"Diaper bag\"\nname: Aer Day Sling 3\ncategory: bag\nlink: https://aersf.com/collections/slings/products/day-sling-3\n---\n"
  },
  {
    "path": "src/content/gear/anker-laptop-charger.md",
    "content": "---\neyebrow: Laptop charger\nname: Anker Laptop Charger\ncategory: travel\nlink: https://amzn.to/3JbHQdS\n---\n"
  },
  {
    "path": "src/content/gear/apple-studio-display.md",
    "content": "---\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",
    "content": "---\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",
    "content": "---\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",
    "content": "---\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",
    "content": "---\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",
    "content": "---\neyebrow: Switch\nname: Aqara Wireless Mini Switch\ncategory: home\nlink: https://amzn.to/4lSIE5r\n---\n"
  },
  {
    "path": "src/content/gear/benchmade-mini-bugout.md",
    "content": "---\neyebrow: Lightweight\nname: Benchmade mini bugout\ncategory: knife\nlink: https://benchmade.com/products/533bk-1-mini-bugout\n---\n"
  },
  {
    "path": "src/content/gear/breville-barista-express-espresso-machine.md",
    "content": "---\nstatus: retired\neyebrow: Espresso machine\nname: Breville Barista Express Espresso Machine\ncategory: coffee\nlink: https://amzn.to/4lpyCbt\n---\n"
  },
  {
    "path": "src/content/gear/caldigit-ts4-thunderbolt-4-dock.md",
    "content": "---\neyebrow: Thunderbolt Doc\nname: CalDigit TS4 Thunderbolt 4 Dock\ncategory: workspace\nlink: https://amzn.to/4p7vy73\n---\n"
  },
  {
    "path": "src/content/gear/carepod-mini-ultrasonic-cool-mist-humidifier.md",
    "content": "---\neyebrow: Humidifier\nname: Carepod Mini Ultrasonic Cool Mist Humidifier\ncategory: wellness\nlink: https://amzn.to/4p7vy73\n---\n"
  },
  {
    "path": "src/content/gear/cerave-hydrating-facial-cleanser.md",
    "content": "---\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",
    "content": "---\neyebrow: Titanium\nname: Chris Reeve Small Sebenza 31\ncategory: knife\nlink: https://chrisreeve.com/collections/sebenza-31\nfavorite: true\n---\n"
  },
  {
    "path": "src/content/gear/coway-airmega-air-purifier.md",
    "content": "---\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",
    "content": "---\neyebrow: Air purifier\nname: Coway Airmega AP-1512HH(W) True HEPA Purifier\ncategory: wellness\nlink: https://amzn.to/4gVxZWt\n---\n"
  },
  {
    "path": "src/content/gear/ecobee-smart-thermostat.md",
    "content": "---\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",
    "content": "---\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",
    "content": "---\neyebrow: Red light therapy\nname: EMR-TEK Inferno Red Light Panel\ncategory: wellness\nlink: https://www.emr-tek.com/alexcarpenter\n---\n"
  },
  {
    "path": "src/content/gear/eufy-robot-lawn-mower-e15.md",
    "content": "---\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",
    "content": "---\neyebrow: Security Camera\nname: eufy Security Camera Floodlight Camera E340\ncategory: home\nlink: https://amzn.to/451OziV\nstatus: retired\n---\n"
  },
  {
    "path": "src/content/gear/eufy-security-homebase-s380.md",
    "content": "---\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",
    "content": "---\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",
    "content": "---\neyebrow: Tech pouch\nname: EVERGOODS CIVIC Access Pouch 2L\ncategory: bag\nlink: https://evergoods.us/collections/pouches/products/civic-access-pouch-2l\n---\n"
  },
  {
    "path": "src/content/gear/evergoods-civic-half-zip.md",
    "content": "---\neyebrow: Everday bag\nname: EVERGOODS CIVIC Half Zip 22L\ncategory: bag\nlink: https://evergoods.us/collections/backpacks/products/civic-half-zip\n---\n"
  },
  {
    "path": "src/content/gear/evergoods-civic-panel-loader-16l.md",
    "content": "---\neyebrow: \"Tech bag\"\nname: EVERGOODS CIVIC Panel Loader 16L\ncategory: bag\nlink: https://evergoods.us/collections/backpacks/products/civic-panel-loader?variant=42250173153414\n---\n"
  },
  {
    "path": "src/content/gear/fellow-atmos-vacuum-coffee-canister.md",
    "content": "---\neyebrow: \"Coffee Storage\"\nname: Fellow Atmos Vacuum Coffee Canister\ncategory: coffee\nlink: https://amzn.to/4qIHQ5c\n---\n"
  },
  {
    "path": "src/content/gear/focusrite-scarlett-audio-interface.md",
    "content": "---\neyebrow: Audio Interface\nname: Focusrite Scarlett Audio Interface\ncategory: workspace\nlink: https://amzn.to/3HW1OsZ\n---\n"
  },
  {
    "path": "src/content/gear/foursevens-mini-turbo-mk-iii.md",
    "content": "---\neyebrow: Mini\nname: Foursevens Mini Turbo Mk. III\ncategory: flashlight\nlink: https://darksucks.com/products/mini-turbo-mkiii-ti-two-tone\n---\n"
  },
  {
    "path": "src/content/gear/global-6-inch-chef-s-knife.md",
    "content": "---\neyebrow: Chefs knife\nname: Global 6 inch Chef's Knife\ncategory: kitchen\nlink: https://amzn.to/46GLh5z\nfavorite: true\n---\n"
  },
  {
    "path": "src/content/gear/goruck-bullet-15l.md",
    "content": "---\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",
    "content": "---\neyebrow: Rotary UI\nname: HDS Systems EDC Tactical\ncategory: flashlight\nlink: https://hdssystems.com/Products/Tactical/\nfavorite: true\n---\n"
  },
  {
    "path": "src/content/gear/herman-miller-sayl-chair.md",
    "content": "---\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",
    "content": "---\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",
    "content": "---\neyebrow: Adjustable Bench\nname: Ironmaster Super Bench PRO V2\ncategory: gym\nlink: https://www.ironmaster.com/products/super-bench-pro-v2/\n---\n"
  },
  {
    "path": "src/content/gear/kalita-wave.md",
    "content": "---\neyebrow: Pour over\nname: Kalita Wave\ncategory: coffee\nlink: https://amzn.to/3JdOd0e\n---\n"
  },
  {
    "path": "src/content/gear/kinesis-advantage-360-keyboard.md",
    "content": "---\neyebrow: Keyboard\nname: Kinesis Advantage 360 Keyboard\ncategory: workspace\nlink: https://kinesis-ergo.com/keyboards/advantage360/\n---\n"
  },
  {
    "path": "src/content/gear/knipex-cobra-water-pump-pliers.md",
    "content": "---\neyebrow: Pliers\nname: KNIPEX Cobra Pliers Wrench\ncategory: tools\nlink: https://amzn.to/4scQBq2\n---\n"
  },
  {
    "path": "src/content/gear/knipex-pliers-wrench.md",
    "content": "---\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",
    "content": "---\neyebrow: Body Moisturizer\nname: La Roche-Posay Lipikar AP+ Triple Repair Moisturizing Cream\ncategory: wellness\nlink: https://amzn.to/4bVp6fc\n---\n"
  },
  {
    "path": "src/content/gear/lutron-aurora-smart-bulb-dimmer-switch.md",
    "content": "---\neyebrow: Dimmer switch\nname: Lutron Aurora Smart Bulb Dimmer Switch\ncategory: home\nlink: https://amzn.to/44wCl1N\nstatus: retired\n---\n"
  },
  {
    "path": "src/content/gear/m4-mac-mini.md",
    "content": "---\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",
    "content": "---\neyebrow: Frother\nname: Maestri House Rechargeable Milk Frother with Stand\ncategory: kitchen\nlink: https://amzn.to/4p1RnUi\n---\n"
  },
  {
    "path": "src/content/gear/neutrogena-hydro-boost-water-cream.md",
    "content": "---\neyebrow: Face Moisturizer\nname: Neutrogena Hydro Boost Water Cream\ncategory: wellness\nlink: https://amzn.to/4685v7O\n---\n"
  },
  {
    "path": "src/content/gear/nitecore-nb-air.md",
    "content": "---\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",
    "content": "---\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",
    "content": "---\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",
    "content": "---\neyebrow: Adjustable Dumbbells\nname: PowerBlock Elite EXP Adjustable Dumbbells\ncategory: gym\nlink: https://amzn.to/4rti9Hr\n---\n"
  },
  {
    "path": "src/content/gear/prometheus-beta-qrv2-titanium.md",
    "content": "---\neyebrow: Flashlight\nname: Prometheus Beta QRv2 Titanium\ncategory: edc\nlink: https://darksucks.com/products/beta-qrv3-titanium-6al-4v\n---\n"
  },
  {
    "path": "src/content/gear/rocket-espresso-appartamento-tca-espresso-machine.md",
    "content": "---\neyebrow: Espresso machine\nname: Rocket Espresso Appartamento TCA Espresso Machine\ncategory: coffee\nlink: https://amzn.to/49Oc3uw\n---\n"
  },
  {
    "path": "src/content/gear/rode-podmic-microphone.md",
    "content": "---\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",
    "content": "---\neyebrow: Red lens filter glasses\nname: ROKA Oslo 2.0 Wind Down\ncategory: wellness\nlink: https://www.roka.com/products/oslo-2-0-huberman-blue-light-glasses\n---\n"
  },
  {
    "path": "src/content/gear/roost-laptop-stand.md",
    "content": "---\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",
    "content": "---\neyebrow: Mug\nname: snow peak Ti-Double H200 Stacking Mug\ncategory: coffee\nlink: https://snowpeak.com/products/ti-double-h200-stacking-mug-tw-124\nfavorite: true\n---\n"
  },
  {
    "path": "src/content/gear/sony-wh-1000xm4-headphones.md",
    "content": "---\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",
    "content": "---\neyebrow: Knife\nname: Spyderco Dragonfly 2\ncategory: edc\nlink: https://spyderco.com/products/dragonfly%E2%84%A2-2-lightweight\n---\n"
  },
  {
    "path": "src/content/gear/subminimal-flick-wdt-espresso-distribution-tool.md",
    "content": "---\neyebrow: \"Distribution Tool\"\nname: Subminimal Flick WDT Espresso Distribution Tool\ncategory: coffee\nlink: https://amzn.to/3OioxCm\n---\n"
  },
  {
    "path": "src/content/gear/synology-ds224.md",
    "content": "---\neyebrow: Storage\nname: Synology DS224+\ncategory: home\nlink: https://amzn.to/3MV10GO\n---\n"
  },
  {
    "path": "src/content/gear/tactile-turn-apollo-flashlight.md",
    "content": "---\neyebrow: Titanium\nname: Tactile Turn Apollo\ncategory: flashlight\nlink: https://tactileturn.com/products/apollo?variant=41733727813737\n---\n"
  },
  {
    "path": "src/content/gear/tactile-turn-rockwall-thumbstud.md",
    "content": "---\neyebrow: Titanium\nname: Tactile Turn Rockwall Thumbstud\ncategory: knife\nlink: https://tactileknife.co/products/rockwall-thumbstud\n---\n"
  },
  {
    "path": "src/content/gear/technivorm-moccamaster.md",
    "content": "---\neyebrow: Coffee maker\nname: Technivorm Moccamaster\ncategory: coffee\nlink: https://amzn.to/3HvPY8x\nfavorite: true\n---\n"
  },
  {
    "path": "src/content/gear/the-james-brand-midland.md",
    "content": "---\neyebrow: Key Management\nname: The James Brand Midland\ncategory: edc\nlink: https://thejamesbrand.com/products/the-midland\n---\n"
  },
  {
    "path": "src/content/gear/timemore-sculptor-064s-coffee-grinder.md",
    "content": "---\neyebrow: Espresso grinder\nname: TIMEMORE Sculptor 064S Flat Burr Grinder\ncategory: coffee\nlink: https://amzn.to/46mbhTa\n---\n"
  },
  {
    "path": "src/content/gear/tom-bihn-synik-22.md",
    "content": "---\neyebrow: \"Family bag\"\nname: TOM BIHN Synik 22\ncategory: bag\nlink: https://tombihn.com/collections/best-sellers/products/synik-22?variant=44784969253053\nfavorite: true\n---\n"
  },
  {
    "path": "src/content/gear/tom-bihn-truck.md",
    "content": "---\neyebrow: \"Grocery bag\"\nname: TOM BIHN Truck\ncategory: bag\nlink: https://tombihn.com/collections/tote-bags/products/the-truck?variant=42944967246013\nfavorite: true\n---\n"
  },
  {
    "path": "src/content/gear/toto-washlet-s5-electronic-bidet.md",
    "content": "---\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",
    "content": "---\neyebrow: Network switch\nname: TP-Link 16 Port Gigabit PoE Switch\ncategory: home\nlink: https://amzn.to/44E00gN\nstatus: retired\n---\n"
  },
  {
    "path": "src/content/gear/tp-link-6e-mesh-system.md",
    "content": "---\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",
    "content": "---\neyebrow: Router\nname: Unifi Dream Router 7\ncategory: home\nlink: https://techspecs.ui.com/unifi/cloud-gateways/udr7\n---\n"
  },
  {
    "path": "src/content/gear/wera-bitholding-screwdriver.md",
    "content": "---\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",
    "content": "---\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",
    "content": "---\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",
    "content": "---\neyebrow: Classic\nname: Zebralight SC5c II LE\ncategory: flashlight\nlink: https://zebralight.com/sc5c-mk-ii-le-aa-flashlight-neutral-white-high-cri-limited-edition_p_246.html\n---\n"
  },
  {
    "path": "src/content/jobs/clerk.md",
    "content": "---\ntitle: Staff UI Engineer\ncompany: Clerk\nstartDate: 2024-01-04\nprojects:\n  - title: Component theme editor\n    link: https://clerk.com/components/theme-editor\n    published: 2026-03-06\n  - title: Improve RTL support within components\n    link: https://github.com/clerk/javascript/pull/7718\n    published: 2026-01-30\n  - title: Add UNSAFE_PortalProvider component\n    link: https://github.com/clerk/javascript/pull/7310\n    published: 2026-01-21\n  - title: Add light-dark theme support\n    link: https://github.com/clerk/javascript/pull/7560\n    published: 2026-01-20\n  - title: Interactive docs inline theme editing\n    link: https://x.com/alexcarp_me/status/1970189970414399723\n    published: 2025-11-22\n  - title: shadcn/ui registry\n    description: Get started with Clerk authentication in Next.js apps using the shadcn/ui CLI\n    link: https://clerk.com/changelog/2025-08-13-shadcn-registry\n    published: 2025-08-13\n  - title: New simple theme for easier customization\n    description: A minimal theme with stripped-down styling that provides a clean foundation for custom designs.\n    link: https://clerk.com/changelog/2025-07-29-theme-simple\n    published: 2025-07-29\n  - title: shadcn/ui theme compatibility\n    description: Introducing a new Clerk theme based on shadcn/ui that styles Clerk's components according to your shadcn/ui theme.\n    link: https://clerk.com/changelog/2025-07-23-shadcn-theme\n    published: 2025-07-23\n  - title: Clerk CSS variables support\n    description: Clerk now supports theming via Clerk CSS variables.\n    link: https://clerk.com/changelog/2025-07-15-clerk-css-variables-support\n    published: 2025-07-15\n  - title: Clerk appearance object CSS variables support\n    description: Clerk's appearance system now supports CSS variables for seamless design system integration and dynamic theming.\n    link: https://clerk.com/changelog/2025-07-08-css-variables-support\n    published: 2025-07-08\n  - title: CSS layer name support\n    description: Introducing the cssLayerName option for compatibility with Tailwind CSS v4, allowing Clerk styles to be wrapped in a dedicated CSS cascade layer.\n    link: https://clerk.com/changelog/2025-06-17-css-layer-name\n    published: 2025-06-17\n  - title: Clerk Billing components\n    link: https://clerk.com/docs/components/pricing-table\n    published:\n  - title: Combined sign-in-or-up\n    description: Start collecting sign-in and sign-ups within a single flow.\n    link: https://clerk.com/changelog/2025-01-16-sign-in-or-up\n    published: 2025-01-16\n  - title: Clerk Elements\n    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.\n    link: \"https://clerk.com/changelog/2024-05-02-elements-beta\"\n    published: 2024-05-02\n  - title: Clerk.com user auth page\n    link: \"https://clerk.com/user-authentication\"\ntools:\n  - Next.js\n  - Tailwind CSS\n  - React Aria Components\n  - TypeScript\n  - Motion\n  - Floating UI\n---\n\nUI Engineer apart of the SDK team, working on UI Components and Dashboard UI.\n"
  },
  {
    "path": "src/content/jobs/hashicorp.md",
    "content": "---\ntitle: Senior Lead Web Engineer\ncompany: HashiCorp\nstartDate: 2021-07-01\nendDate: 2023-07-01\ntools:\n  - Next.js\n  - Dato CMS\n  - Reach UI\n  - TypeScript\n  - CSS modules\n  - Framer Motion\n---\n\nLead Web Engineer on the core web team, helped build and maintain public-facing HashiCorp websites and web applications with Next.js.\n"
  },
  {
    "path": "src/content/jobs/masuga-design.md",
    "content": "---\ntitle: Front-end Developer\ncompany: Masuga Design\nstartDate: 2012-02-01\nendDate: 2015-08-01\ntools:\n  - ExpressionEngine\n  - Craft CMS\n  - SCSS\n  - jQuery\n---\n\nFront-end Developer building user interfaces for clients like A&E Networks, Image Comics, and Fox Networks Info with ExpressionEngine.\n"
  },
  {
    "path": "src/content/jobs/mighty-in-the-midwest.md",
    "content": "---\ntitle: Senior Web Engineer\ncompany: Mighty in the Midwest\nstartDate: 2015-08-01\nendDate: 2018-11-01\nprojects:\n  - title: Consrevation Legacy\n    link: \"https://conservationlegacy.org\"\n  - title: Dake Corp\n    link: \"https://dakecorp.com\"\n  - title: Brunswick Bowling\n    link: \"https://brunswickbowling.com\"\ntools:\n  - Craft CMS\n  - Shopify\n  - SCSS\n  - jQuery\n---\n\nSenior 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.\n"
  },
  {
    "path": "src/content/jobs/nationbuilder.md",
    "content": "---\ntitle: UI Engineer\ncompany: NationBuilder\nstartDate: 2018-11-01\nendDate: 2021-07-01\ntools:\n  - React\n  - Rails\n  - Bootstrap\n---\n\nUI Engineer working on the design team, collaborating closely with engineers implementing new features and building out our design system Radius.\n"
  },
  {
    "path": "src/content/jobs/watershed.md",
    "content": "---\ntitle: Lead Web Engineer\ncompany: Watershed\nstartDate: 2023-08-01\nendDate: 2023-12-01\nprojects:\n  - title: Watershed homepage\n    link: \"https://watershed.com\"\n  - title: Watershed customers page\n    link: \"https://watershed.com/customers\"\n  - title: Watershed events page\n    link: \"https://watershed.com/events\"\n  - title: Watershed change campaign page\n    link: \"https://watershed.com/change\"\ntools:\n  - Next.js\n  - Sanity CMS\n  - CSS Modules\n  - Framer Motion\n---\n\nWeb Engineer apart of the marketing department, built and maintained everything across watershed.com.\n"
  },
  {
    "path": "src/content/notes/2024-12-05-1.md",
    "content": "---\npublished: 2024-12-05T19:25:15-0400\ntitle: Just use children\nlink: https://www.sid.st/post/just-use-children/\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-02-17-1.md",
    "content": "---\npublished: 2025-02-17T19:21:04-0400\ntitle: The \"everything bagel\" of components\nlink: https://dio.la/article/the-everything-bagel-of-components\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-02-19-2.md",
    "content": "---\npublished: 2025-02-19T16:37:34-0500\ntags:\n  - tip\n---\n\nReact 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).\n\n```tsx {6-11,13}\n<TooltipTrigger isOpen={open} onOpenChange={setOpen}>\n  <Button>Trigger</Button>\n  <AnimatePresence>\n    {open ? (\n      <MotionTooltip\n        className={({ placement }) =>\n          cx({\n            \"[--y:4px]\": placement === \"top\",\n            \"[--y:-4px]\": placement === \"bottom\",\n          })\n        }\n        offset={6}\n        initial={{ opacity: 0, y: \"var(--y)\" }}\n        animate={{ opacity: 1, y: 0 }}\n      >\n        Content\n      </MotionTooltip>\n    ) : null}\n  </AnimatePresence>\n</TooltipTrigger>\n```\n"
  },
  {
    "path": "src/content/notes/2025-02-25-1.md",
    "content": "---\npublished: 2025-02-25T19:26:08-0400\ntitle: Sanding UI\nlink: https://blog.jim-nielsen.com/2024/sanding-ui/\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-03-22-1.md",
    "content": "---\npublished: 2025-03-22T19:25:15-0400\ntitle: Naming things in design systems–and why it’s the worst\nlink: https://pjonori.blog/posts/design-system-naming/\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-04-13-1.md",
    "content": "---\npublished: 2025-04-13T20:14:22-0400\ntitle: Write code that is easy to delete, not easy to extend\nlink: https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-04-14-1.md",
    "content": "---\npublished: 2025-04-14T12:41:36-0400\ntitle: Animate height from 0 to auto\ntags:\n  - tip\n  - css\n  - animation\n---\n\n```css\n.element {\n  display: grid;\n  grid-template-rows: 0fr;\n  transition: grid-template-rows 0.5s ease-in-out;\n}\n\n.element.open {\n  grid-template-rows: 1fr;\n}\n```\n\n## References:\n\n- [How to animate an element's height with CSS grid](https://www.stefanjudis.com/snippets/how-to-animate-height-with-css-grid/)\n- [Animating CSS Grid (How To + Examples) ](https://css-tricks.com/animating-css-grid-how-to-examples/)\n"
  },
  {
    "path": "src/content/notes/2025-04-14-2.md",
    "content": "---\npublished: 2025-04-14T20:50:41-0400\ntitle: Create a list of links from an array using Intl.ListFormat with formatToParts\ndemo: /demos/list-format-to-parts\ntags:\n  - demo\n  - javascript\n  - tip\n---\n\n```tsx\nconst tags = [\"HTML\", \"CSS\", \"JavaScript\"];\n\n{\n  new Intl.ListFormat(\"en-US\").formatToParts(tags).map(({ type, value }) => {\n    if (type === \"element\") {\n      return <a href={`/${slugify(value)}`}>{value}</a>;\n    }\n    return value;\n  });\n}\n```\n\nwhich returns the following markup:\n\n```html\n<a href=\"/html\">HTML</a>, <a href=\"/css\">CSS</a>, and\n<a href=\"/javascript\">JavaScript</a>\n```\n"
  },
  {
    "path": "src/content/notes/2025-04-15-1.md",
    "content": "---\npublished: 2025-04-15T08:20:41-0400\ntags:\n  - css\n---\n\nThis 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.\n\nSimplified markup example of what we are working with:\n\n```html\n<div class=\"header\"></div>\n<div class=\"nav\"></div>\n\n<div class=\"layout\">\n  <div class=\"sidebar\"></div>\n  <div class=\"content\">\n    <div class=\"data\"></div>\n  </div>\n</div>\n```\n\nThe 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.\n\nTo 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.\n\n```css {29}\nbody {\n  min-height: 100%;\n  display: flex;\n  flex-direction: column;\n  flex: 1;\n}\n\n.header {\n  width: 100%;\n  height: 56px;\n}\n\n.nav {\n  width: 100%;\n  height: 56px;\n  position: sticky;\n}\n\n.layout {\n  flex: 1;\n  display: grid;\n  grid-template-columns: 360px 1fr;\n}\n\n.sidebar {...}\n\n.content {\n  overflow: auto;\n  contain: size;\n}\n\n/* This is simply to cause an overflow to demonstrate the table size */\n.data {\n  width: 200vw;\n  height: 200vh;\n}\n```\n\nView the demo CodePen [here](https://codepen.io/alexcarpenter/pen/emmOLoN?editors=0100).\n"
  },
  {
    "path": "src/content/notes/2025-04-16-1.md",
    "content": "---\npublished: 2025-04-16T08:45:17-0400\ntags:\n  - css\n  - tip\n---\n\nStop vertically aligning your checkboxes with `center`. Instead use `baseline` to keep it aligned with the first line of the label text.\n\n```scss\nlabel {\n  display: flex;\n  align-items: center; // [!code --]\n  align-items: baseline; // [!code ++]\n}\n```\n"
  },
  {
    "path": "src/content/notes/2025-04-17-1.md",
    "content": "---\npublished: 2025-04-17T08:18:43-0400\ntags:\n  - css\n  - tip\n---\n\nQuick 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.\n\n```css\npre {\n  color-scheme: dark;\n}\n```\n\nView demo on [Twitter](https://x.com/alexcarp_me/status/1823406174034649137/video/1).\n"
  },
  {
    "path": "src/content/notes/2025-04-18-1.md",
    "content": "---\npublished: 2025-04-18T07:59:33-0400\ntags:\n  - css\n  - tip\n---\n\nQuick little improvement for wrapping highlighted text. Make use of `box-decoration-break: clone;` to ensure elements fragments break across lines.\n\n```css\nmark {\n  box-decoration-break: clone;\n}\n```\n\nView demo on [Twitter](https://x.com/alexcarp_me/status/1821533842228089056/video/1).\n"
  },
  {
    "path": "src/content/notes/2025-04-18-2.md",
    "content": "---\npublished: 2025-04-18T08:02:53-0400\ntitle: \"OKLCH in CSS: why we moved from RGB and HSL\"\nlink: https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl\ntags:\n  - css\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-04-19-1.md",
    "content": "---\npublished: 2025-04-19T07:24:05-0400\ntitle: The Wet Codebase\nlink: https://www.deconstructconf.com/2019/dan-abramov-the-wet-codebase\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-04-19-2.md",
    "content": "---\npublished: 2025-04-19T07:29:11-0400\ntitle: Adaptive dotted pattern\nlink: https://codepen.io/myf/pen/poXYLNB?editors=0100\ntags:\n  - css\n---\n"
  },
  {
    "path": "src/content/notes/2025-04-30-1.md",
    "content": "---\npublished: 2025-04-30T08:04:10-0400\ntags:\n  - tip\n---\n\nNot 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.\n\n```jsx\n<form>\n  <button onClick={handleSubmit} />\n</form>\n```\n\nInstead, 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.\n\n```jsx\n<form onSubmit={handleSubmit}>\n  <button type=\"submit\" />\n</form>\n```\n\nIf 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.\n\n```jsx\n<form id=\"contactForm\" onSubmit={handleSubmit}>\n</form>\n<button form=\"contactForm\" type=\"submit\" />\n```\n"
  },
  {
    "path": "src/content/notes/2025-05-01-1.md",
    "content": "---\npublished: 2025-05-01T08:19:09-0400\ntitle: useMaskedScroll hook\nlink: https://github.com/sambecker/exif-photo-blog/blob/main/src/components/useMaskedScroll.ts\ntags:\n  - css\n  - bookmark\n---\n\nVery nice scroll mask implementation using registered @ properties by [Sam Becker](https://x.com/sambecker).\n"
  },
  {
    "path": "src/content/notes/2025-05-02-1.md",
    "content": "---\npublished: 2025-05-02T07:32:39-0400\ntitle: CSS Spring Easing Generator\nlink: https://www.kvin.me/css-springs\ntags:\n  - css\n  - bookmark\n---\n\nAlso the companion [tailwindcss-spring](https://tailwindcss-spring.kvin.me/) plugin.\n"
  },
  {
    "path": "src/content/notes/2025-05-14-1.md",
    "content": "---\npublished: 2025-05-14T09:10:48-0400\ntitle: never just\nlink: https://www.neverjust.net/\ntags:\n  - bookmark\n---\n\n> it's never just that simple\n"
  },
  {
    "path": "src/content/notes/2025-05-26-1.md",
    "content": "---\npublished: 2025-05-26T12:25:57-0400\ntitle: Evil Martions Harmonizer\nlink: https://harmonizer.evilmartians.com\ntags:\n  - bookmark\n---\n\n> 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.\n"
  },
  {
    "path": "src/content/notes/2025-06-18-1.md",
    "content": "---\npublished: 2025-06-18T07:43:51-0400\ntags:\n  - css\n---\n\nAutomatic foreground color contrast based on the provided background color.\n\n```css\nbutton {\n  --background: black;\n  --foreground: color(\n    from var(--background) xyz round(up, min(1, max(0, 0.18 - y)))\n      round(up, min(1, max(0, 0.18 - y))) round(up, min(1, max(0, 0.18 - y)))\n  );\n\n  background-color: var(--background);\n  color: var(--foreground);\n}\n```\n\nvia [blog.damato.design](https://blog.damato.design/posts/css-only-contrast/)\n"
  },
  {
    "path": "src/content/notes/2025-06-18-2.md",
    "content": "---\ntitle: The Prettify Helper\nlink: https://www.totaltypescript.com/concepts/the-prettify-helper\npublished: 2025-06-18T07:55:18-0400\ntags:\n  - bookmark\n---\n\n> The Prettify helper is a utility type that takes an object type and makes the hover overlay more readable.\n\n```ts\ntype Prettify<T> = {\n  [K in keyof T]: T[K];\n} & {};\n```\n"
  },
  {
    "path": "src/content/notes/2025-06-18-3.md",
    "content": "---\ntitle: Tiny polyfill for CSS scroll driven animations\nlink: https://x.com/devongovett/status/1932478787507425405\npublished: 2025-06-18T08:04:20-0400\ntags:\n  - tip\n  - javascript\n---\n\n```js\nlet animationRange = [0, 62];\n\nif (!CSS.supports(\"(animation-timeline: scroll())\")) {\n  let [start, end] = animationRange;\n  let animations = header.getAnimations();\n  let onScroll = () => {\n    // Calculate animation time based on percentage of animationRange * duration.\n    let time =\n      Math.max(0, Math.min(end, window.scrollY - start) / (end - start)) * 1000;\n    for (let animation of animations) {\n      animation.currentTime = time;\n    }\n  };\n\n  window.addEventListener(\"scroll\", onScroll, { passive: true });\n}\n```\n"
  },
  {
    "path": "src/content/notes/2025-06-18-4.md",
    "content": "---\ntitle: Handle all potential cases in a switch statement\nlink: https://x.com/housecor/status/1927688347754881245\npublished: 2025-06-18T08:08:25-0400\ntags:\n  - bookmark\n---\n\n```ts {12}\ntype Cat = { kind: 'cat' }\ntype Dog = { kind: 'dog' }\ntype Pet = Cat | Dog\n\nfunction example(pet: Pet) {\n  switch (pet.kind) {\n    case: 'cat':\n      return ...\n    case: 'dog'\n      return ...\n    default:\n      pet satisfies never\n  }\n}\n```\n"
  },
  {
    "path": "src/content/notes/2025-06-21-1.md",
    "content": "---\npublished: 2025-06-21T07:59:56-0400\ntags:\n  - css\n  - work\n---\n\nWorking on some updates to make it easier to theme Clerk components from your existing CSS variables.\n\nGenerating a color palette using relative color syntax and color-mix.\n\n```css\n:root {\n  --brand-color: oklch(49.1% 0.27 292.581);\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --brand-color: oklch(54.1% 0.281 293.009);\n  }\n}\n```\n\n```tsx\n<ClerkProvider\n  appearance={{\n    variables: {\n      colorPrimary: \"var(--brand-color)\",\n    },\n  }}\n/>\n```\n"
  },
  {
    "path": "src/content/notes/2025-06-24-1.md",
    "content": "---\ntitle: On moving from implicit to explicit coupling\nlink: https://ineffable.co/writing/moving-from-implicit-to-explicit-coupling\npublished: 2025-06-24T08:49:00-0400\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-06-27-1.md",
    "content": "---\ntitle: keyux\ndescription: JS library to improve keyboard UI of web apps\nlink: https://github.com/ai/keyux\npublished: 2025-06-27T07:36:28-0400\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-01-1.md",
    "content": "---\ntitle: apcach\ndescription: JS color calculator for composing colors with consistent APCA contrast ratio.\nlink: https://github.com/antiflasher/apcach\npublished: 2025-07-01T07:23:59-0400\ntags:\n  - bookmark\n  - javascript\n  - color\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-02-1.md",
    "content": "---\ntitle: That boolean should probably be something else\nlink: https://ntietz.com/blog/that-boolean-should-probably-be-something-else/\npublished: 2025-07-02T08:42:00-0400\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-08-1.md",
    "content": "---\ntitle: culori\ndescription: A comprehensive color library for JavaScript.\nlink: https://github.com/Evercoder/culori\npublished: 2025-07-08T08:36:45-0400\ntags:\n  - bookmark\n  - javascript\n  - color\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-11-1.md",
    "content": "---\ntitle: What's the \"Geometry\" of Colours?\nlink: https://www.youtube.com/watch?v=7KYwi2F5Ce4\npublished: 2025-07-11T10:55:45-0400\ntags:\n  - color\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-15-1.md",
    "content": "---\ntitle: Clerk CSS variables are now available!\nlink: https://clerk.com/changelog/2025-07-15-clerk-css-variables-support\npublished: 2025-07-15T15:21:54-0400\ntags:\n  - work\n---\n\nTheme your Clerk components directly from your CSS files where your design tokens live – no more CSS-in-JS required.\n\n```css\n:root {\n  --clerk-color-primary: #6d47ff;\n}\n```\n\nWe 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.\n\nNow 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.\n"
  },
  {
    "path": "src/content/notes/2025-07-17-1.md",
    "content": "---\ntitle: Agentic Engineering\ndescription: Combining human craftsmanship with AI tools to build better software.\nlink: https://zed.dev/agentic-engineering\npublished: 2025-07-17T20:49:48-0400\ntags:\n  - ai\n---\n\n> 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.\n"
  },
  {
    "path": "src/content/notes/2025-07-17-2.md",
    "content": "---\ntitle: Building your ideas with Claude Code and Figma MCP with Dan Hollick\nlink: https://www.youtube.com/watch?v=7XxhMR6-0BY\npublished: 2025-07-17T20:57:35-0400\ntags:\n  - ai\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-18-1.md",
    "content": "---\ntitle: Jonas Brinkhoff's personal website\nlink: https://www.jonasbrinkhoff.com/\npublished: 2025-07-18T08:16:47-0400\ntags:\n  - bookmark\n  - inspiration\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-18-2.md",
    "content": "---\ntitle: Chase McCoy's personal website\nlink: https://chsmc.org/\npublished: 2025-07-18T08:18:50-0400\ntags:\n  - bookmark\n  - inspiration\n---\n"
  },
  {
    "path": "src/content/notes/2025-07-21-1.md",
    "content": "---\ntitle: Write like you talk\nlink: https://paulgraham.com/talk.html\npublished: 2025-07-21T08:37:45-0400\ntags:\n  - bookmark\n---\n\n> Here's a simple trick for getting more people to read what you write: write in spoken language.\n"
  },
  {
    "path": "src/content/notes/2025-07-23-1.md",
    "content": "---\ntitle: shadcn/ui theme compatibility is now available\nlink: https://clerk.com/changelog/2025-07-23-shadcn-theme\npublished: 2025-07-23T11:44:32-0400\ntags:\n  - work\n---\n\nWe shipped a new Clerk theme based on shadcn/ui that styles Clerk's components according to your shadcn/ui theme.\n"
  },
  {
    "path": "src/content/notes/2025-07-29-1.md",
    "content": "---\npublished: 2025-07-29T08:25:58-0400\ntags:\n  - css\n  - tip\n---\n\nYour 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.\n\nUse CSS var toggle hack?\n\n```css\n/* Component library styles */\n:root {\n  --dark-mode: ;\n}\n\nbutton {\n  padding: 1rem;\n  background-color: var(--dark-mode, white) black;\n  color: var(--dark-mode, black) white;\n}\n\n/* User styles turn on dark mode */\n.dark {\n  --dark-mode: initial;\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --dark-mode: initial;\n  }\n}\n```\n\nThis gives the user the ability to enable dark mode based on how they have their app configured. @⁠media query, class, data-attr, etc.\n\nThey can even be very targeted on which components this is enabled for.\n"
  },
  {
    "path": "src/content/notes/2025-08-05-1.md",
    "content": "---\npublished: 2025-08-05T19:26:48-0400\ntags:\n  - work\n---\n\nHad 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).\n"
  },
  {
    "path": "src/content/notes/2025-08-13-1.md",
    "content": "---\npublished: 2025-08-13T07:54:31-0400\ndemo: /demos/bg-repeat-round\ntags:\n  - demo\n  - css\n---\n\nUsing `background-repeat: round;` to get that repeated dot background to fit perfectly across differing viewport widths.\n\n```css {5}\ndiv {\n  background-image: radial-gradient(red 1px, transparent 1.3px);\n  background-size: 24px 24px;\n  background-position: 0 0;\n  background-repeat: round;\n}\n```\n"
  },
  {
    "path": "src/content/notes/2025-08-13-2.md",
    "content": "---\npublished: 2025-08-13T08:10:08-0400\ndemo: /demos/box-decoration-break-clone\ntags:\n  - demo\n  - css\n---\n\nFor when `<mark />` elements wrap to multiple lines, use `box-decoration-break: clone;` to render each fragment with their own specified border, padding, and margin.\n\n```css {2}\nmark {\n  box-decoration-break: clone;\n  padding-inline: 0.25rem;\n}\n```\n"
  },
  {
    "path": "src/content/notes/2025-08-13-3.md",
    "content": "---\npublished: 2025-08-13T16:34:12-0400\ntitle: We shipped a shadcn/ui registry\nlink: https://clerk.com/changelog/2025-08-13-shadcn-registry\ntags:\n  - work\n---\n\n```bash\nnpx shadcn@latest add https://clerk.com/r/nextjs-quickstart.json\n```\n\nThis single command will install:\n\n- App layout with ClerkProvider and theme integration\n- Sign-in and sign-up pages with catch-all routes\n- Clerk middleware for route protection\n- Header component with authentication buttons\n- Theme provider for dark/light mode support\n"
  },
  {
    "path": "src/content/notes/2025-08-14-1.md",
    "content": "---\ntitle: How to properly align icons within list items\npublished: 2025-08-14T15:49:05-0400\ndemo: /demos/list-item-icon-baseline\ntags:\n  - demo\n  - css\n---\n\nMy 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.\n\n```tsx {3-5}\n<ul>\n  <li class=\"flex gap-2\">\n    <span class=\"flex h-[1lh] items-center\">\n      <Icon class=\"size-[1em] flex-none\" name=\"badge-check\" />\n    </span>\n    List item 2 that is longer than the others and wraps to two lines\n  </li>\n</ul>\n```\n\nWith this approach, you can apply a font size to the list item, and the icon will scale accordingly.\n"
  },
  {
    "path": "src/content/notes/2025-08-18-1.md",
    "content": "---\npublished: 2025-08-18T14:56:44-0400\ndemo: /demos/inline-trailing-icon\ntags:\n  - demo\n  - css\n---\n\nEnsure 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.\n\n```tsx {3-5}\n<p class=\"relative inline-block pr-[1.25em]\">\n  Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia, ducimus\n  <span class=\"absolute ml-[.25em] inline-flex h-[1lh] items-center\">\n    <Icon name=\"arrow-up-right\" class=\"size-[1em]\" />\n  </span>\n</p>\n```\n\nPicked this tip of from [John Phamous](https://x.com/JohnPhamous) some time ago.\n"
  },
  {
    "path": "src/content/notes/2025-08-18-2.md",
    "content": "---\npublished: 2025-08-18T17:20:03-0400\ntitle: use-stick-to-bottom\ndescription: A lightweight React Hook intended mainly for AI chat applications, for smoothly sticking to bottom of messages.\nlink: https://github.com/stackblitz-labs/use-stick-to-bottom\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-08-23-1.md",
    "content": "---\npublished: 2025-08-23T13:53:44-0400\ntitle: Silk\ndescription: Native‑like swipeable sheets on the web\nlink: https://silkhq.com/\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-08-25-1.md",
    "content": "---\npublished: 2025-08-25T08:39:17-0400\ndemo: /demos/hanging-punctuation\ntags:\n  - css\n  - demo\n---\n\nAnother use case for absolute positioning elements within inline text to support hanging punctuation.\n\nSupports varying sizes of text, without the need for custom negative text indent values.\n\n```html\n<p class=\"relative inline-block\">\n  <span class=\"absolute right-full\">“</span>Lorem ipsum dolor sit amet\n  consectetur adipisicing elit. Iste officia quasi fugiat, dolores ab nam\n  repellendus voluptate”\n</p>\n```\n"
  },
  {
    "path": "src/content/notes/2025-08-26-1.md",
    "content": "---\npublished: 2025-08-26T11:32:27-0400\ntitle: What are OKLCH colors?\nlink: https://jakub.kr/components/oklch-colors\ntags:\n  - color\n---\n"
  },
  {
    "path": "src/content/notes/2025-08-26-2.md",
    "content": "---\npublished: 2025-08-26T11:34:53-0400\ntitle: A Clock That Doesn't Snap\nlink: https://ethanniser.dev/blog/a-clock-that-doesnt-snap\ntags:\n  - javascript\n---\n"
  },
  {
    "path": "src/content/notes/2025-09-05-1.md",
    "content": "---\ntitle: My shadcn/ui registry\nlink: https://ui.alexcarpenter.me\npublished: 2025-09-05T08:31:49-0400\ntags:\n  - javascript\n  - work\n---\n\nI'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:\n\n- [`<InfoList />`](https://ui.alexcarpenter.me/#info-list)\\\n  A list component with support for items, headers, and icons slots.\n- [`<InlineText />`](https://ui.alexcarpenter.me/#inline-text)\\\n  An inline text component with an icon slot that wraps perfectly with the text.\n- [`<PricingTable />`](https://ui.alexcarpenter.me/#pricing-table)\\\n  A pricing table component with support for monthly/yearly pricing.\n"
  },
  {
    "path": "src/content/notes/2025-09-05-2.md",
    "content": "---\npublished: 2025-09-05T08:58:10-0400\ntags:\n  - inspiration\n---\n\nTwo fantastic pieces of web inspiration:\n\n- [operate.so](https://operate.so)\n- [tempo.xyz](https://tempo.xyz/)\n"
  },
  {
    "path": "src/content/notes/2025-09-06-1.md",
    "content": "---\npublished: 2025-09-06T15:33:30-0400\ntags:\n  - work\n---\n\nFixing folks list alignment one PR at a time.\n\n- [midday/pull/606](https://github.com/midday-ai/midday/pull/606)\n- [billingsdk/pull/144](https://github.com/dodopayments/billingsdk/pull/144)\n"
  },
  {
    "path": "src/content/notes/2025-09-12-1.md",
    "content": "---\npublished: 2025-09-12T18:19:56-0400\ntags:\n  - work\n---\n\nI'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.\n"
  },
  {
    "path": "src/content/notes/2025-09-19-1.md",
    "content": "---\npublished: 2025-09-19T08:23:59-0400\ntitle: ▲ Vercel Web Interface Guidelines\nlink: https://vercel.com/design/guidelines\ntags:\n  - bookmark\n  - inspiration\n---\n\nSee also Rauno's [Web Interface Guidelines](https://interfaces.rauno.me/).\n"
  },
  {
    "path": "src/content/notes/2025-09-20-1.md",
    "content": "---\npublished: 2025-09-20T08:39:55-0400\ntitle: Dither plugin for TailwindCSS\nlink: https://dither.floriankiem.com/\ntags:\n  - bookmark\n  - css\n---\n"
  },
  {
    "path": "src/content/notes/2025-09-28-1.md",
    "content": "---\npublished: 2025-09-28T09:44:13-0400\ndemo: /demos/button-disabled-color-mix\ntags:\n  - demo\n  - css\n---\n\nIt's always felt weird using opacity for disabled buttons for situations where its not placed on a solid background.\n\nIn this alternative approach I am using color-mix to create a similar disabled effect without the opacity.\n\n```html\n<button\n  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))]\"\n  disabled\n>\n  Hello world\n</button>\n```\n"
  },
  {
    "path": "src/content/notes/2025-10-23-1.md",
    "content": "---\npublished: 2025-10-23T08:20:33-0400\ntitle: Preserve modal aspect ratio across viewport sizes\ndemo: /demos/modal-aspect-ratio\ntags:\n  - demo\n  - css\n---\n\nThis 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.\n\n```css\n.modal {\n  aspect-ratio: 3 / 2;\n  height: min(calc(100vh - 2rem), calc((100vw - 2rem) * 2 / 3));\n  margin: auto;\n  position: relative;\n}\n```\n\nSee it in action on [I Brew My Own Coffee](https://www.ibrewmyown.coffee).\n"
  },
  {
    "path": "src/content/notes/2025-11-05-1.md",
    "content": "---\ntitle: \"RESILIENT—UI interview #001 with Dima Belyaev\"\ndescription: Staff front-end engineer based in Amsterdam, working on AI assistants at Shopify.\nlink: https://www.resilient-ui.com/interviews/dima-belyaev\npublished: 2025-11-05T07:52:09-0500\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-11-05-2.md",
    "content": "---\ntitle: The Web Animation Performance Tier List\nlink: https://motion.dev/blog/web-animation-performance-tier-list\npublished: 2025-11-05T10:20:46-0500\ntags:\n  - bookmark\n  - animation\n---\n"
  },
  {
    "path": "src/content/notes/2025-11-18-1.md",
    "content": "---\ntitle: \"RESILIENT—UI interview #002 with Hayden Bleasel\"\ndescription: Australian Design Engineer who loves working on open source software for the web.\nlink: https://www.resilient-ui.com/interviews/hayden-bleasel\npublished: 2025-11-18T08:30:55-0500\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-12-03-1.md",
    "content": "---\ntitle: \"2025 Holiday Coffee Gift Guide\"\ndescription: Discover the best coffee gifts for the holidays—espresso machines, drippers, brewers, and bean subscriptions that coffee lovers will actually use every day.\nlink: https://ibrewmyown.coffee/2025-holiday-gift-guide\npublished: 2025-12-03T07:49:22-0500\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2025-12-15-1.md",
    "content": "---\ntitle: How to target Safari with a CSS @supports media query\ndescription: Easiest method for targeting Safari with CSS and Tailwind in 2025.\nlink: https://wojtek.im/journal/targeting-safari-with-css-media-query\npublished: 2025-12-15T08:03:15-0500\ntags:\n  - bookmark\n  - css\n---\n"
  },
  {
    "path": "src/content/notes/2026-01-06-1.md",
    "content": "---\npublished: 2026-01-06T17:02:58-0500\ntags:\n  - css\n  - demo\n---\n\nCSS only scroll fade example that I implemented on [ibrewmyown.coffee](https://github.com/alexcarpenter/ibrewmyown.coffee/commit/b5458aa1d151b14b085394c7fa9f2263320f1384):\n\n```css\n@supports (animation-timeline: scroll()) {\n  @property --fade-left {\n    syntax: \"<length>\";\n    inherits: false;\n    initial-value: 0px;\n  }\n  @property --fade-right {\n    syntax: \"<length>\";\n    inherits: false;\n    initial-value: 0px;\n  }\n\n  .scroll-fade {\n    --fade-distance: 40px;\n    mask-image: linear-gradient(\n      to right,\n      transparent 0,\n      #000 var(--fade-left),\n      #000 calc(100% - var(--fade-right)),\n      transparent 100%\n    );\n    mask-size: 100% 100%;\n    mask-repeat: no-repeat;\n    animation:\n      fade-in-left 1 linear both,\n      fade-out-right 1 linear both;\n    animation-timeline: scroll(x self), scroll(x self);\n    animation-range:\n      0% 12%,\n      88% 100%;\n  }\n\n  @keyframes fade-in-left {\n    from {\n      --fade-left: 0px;\n    }\n    to {\n      --fade-left: var(--fade-distance);\n    }\n  }\n\n  @keyframes fade-out-right {\n    from {\n      --fade-right: var(--fade-distance);\n    }\n    to {\n      --fade-right: 0px;\n    }\n  }\n}\n```\n"
  },
  {
    "path": "src/content/notes/2026-01-17-01.md",
    "content": "---\ntitle: 21 Lessons From 14 Years at Google\nlink: https://addyosmani.com/blog/21-lessons/\npublished: 2026-01-17T08:17:52-0500\ntags:\n  - bookmark\n  - work\n---\n"
  },
  {
    "path": "src/content/notes/2026-01-19-1.md",
    "content": "---\ntitle: Annotating for agents\npublished: 2026-01-19T12:28:08-0500\nlink: https://benji.org/annotating\ntags:\n  - bookmark\n  - ai\n  - privacy\n---\n"
  },
  {
    "path": "src/content/notes/2026-01-22-1.md",
    "content": "---\npublished: 2026-01-22T16:17:27-0500\ntitle: Greppability is an underrated code metric\nlink: https://morizbuesing.com/blog/greppability-code-metric/\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2026-01-26-1.md",
    "content": "---\npublished: 2026-01-26T14:34:05-0500\ntags:\n  - til\n---\n\nTIL you can slow down animations in Chrome DevTools.\n\nPress <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.\n"
  },
  {
    "path": "src/content/notes/2026-01-28-1.md",
    "content": "---\npublished: 2026-01-28T06:47:36-0500\ntitle: When life gives you lemons, write better error messages\nlink: https://wix-ux.com/when-life-gives-you-lemons-write-better-error-messages-46c5223e1a2f\ntags:\n  - bookmark\n---\n\n1. Say what happened\n1. Say why it's a problem\n1. Point out how to solve the problem\n"
  },
  {
    "path": "src/content/notes/2026-01-29-1.md",
    "content": "---\ntitle: Composition is all you need.\nlink: https://www.youtube.com/watch?v=4KvbVq3Eg5w\npublished: 2026-01-29T07:21:29-0500\ntags:\n  - bookmark\n---\n\nSee also `vercel-composition-patterns`\n\n```bash\nnpx skills add vercel-labs/agent-skills\n```\n"
  },
  {
    "path": "src/content/notes/2026-02-03-1.md",
    "content": "---\npublished: 2026-02-03T07:06:47-0500\ntitle: before-and-after\nlink: https://www.jm.sv/before-and-after\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-03-2.md",
    "content": "---\npublished: 2026-02-03T08:43:34-0500\ntitle: Alexander Vilinskyy's personal website\nlink: https://www.vilinskyy.com/\ntags:\n  - bookmark\n  - inspiration\n  - website\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-06-1.md",
    "content": "---\ntitle: A React trick to improve exit animations\nlink: https://barvian.me/react-exit-animations\npublished: 2026-02-06T07:50:26-0500\ntags:\n  - bookmark\n  - javascript\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-06-2.md",
    "content": "---\ntitle: Building Bulletproof React Components\nlink: https://shud.in/thoughts/build-bulletproof-react-components\npublished: 2026-02-06T07:50:48-0500\ntags:\n  - bookmark\n  - javascript\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-09-1.md",
    "content": "---\ntitle: RESILIENT-UI SKILLS\nlink: https://www.resilient-ui.com/skills\npublished: 2026-02-09T10:49:31-0500\ntags:\n  - javascript\n---\n\n```bash\nnpx skills add alexcarpenter/resilient-ui\n```\n"
  },
  {
    "path": "src/content/notes/2026-02-11-1.md",
    "content": "---\ntitle: Matt Rothenberg's personal website\nlink: https://mattrothenberg.com/\npublished: 2026-02-11T07:43:56-0500\ntags:\n  - bookmark\n  - inspiration\n  - website\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-11-2.md",
    "content": "---\ntitle: eslint-plugin-react-render-types\nlink: https://github.com/HorusGoul/eslint-plugin-react-render-types\npublished: 2026-02-11T07:47:53-0500\ntags:\n  - bookmark\n  - javascript\n---\n\n> ESLint plugin that brings Flow's Render Types to TypeScript via JSDoc. Enforce component composition constraints like `@renders {MenuItem}` at lint time.\n"
  },
  {
    "path": "src/content/notes/2026-02-12-1.md",
    "content": "---\ntitle: Digital hygiene\nlink: https://karpathy.bearblog.dev/digital-hygiene/\npublished: 2026-02-12T07:43:49-0500\ntags:\n  - bookmark\n  - privacy\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-13-1.md",
    "content": "---\ntitle: How I Use Claude Code\nlink: https://boristane.com/blog/how-i-use-claude-code/\npublished: 2026-02-13T08:12:56-0500\ntags:\n  - ai\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-14-1.md",
    "content": "---\ntitle: \"ASCII characters are not pixels: a deep dive into ASCII rendering\"\nlink: https://alexharri.com/blog/ascii-rendering\npublished: 2026-02-14T19:56:51-0500\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-15-1.md",
    "content": "---\ntitle: react-prehydrate\nlink: https://github.com/javivelasco/react-prehydrate\npublished: 2026-02-15T07:59:10-0500\ntags:\n  - bookmark\n---\n\n> Eliminate flash-of-incorrect-state for user preferences in React Server Component apps.\n"
  },
  {
    "path": "src/content/notes/2026-02-15-2.md",
    "content": "---\ntitle: \"@nano_kit/store\"\nlink: https://nano_kit.js.org/store/\npublished: 2026-02-15T08:41:03-0500\ntags:\n  - bookmark\n---\n\n> `@nano_kit/store` is a lightweight state management library inspired by Nano Stores and built around a push-pull based reactivity system.\n"
  },
  {
    "path": "src/content/notes/2026-02-16-1.md",
    "content": "---\ntitle: Building Type-Safe Compound Components\nlink: https://tkdodo.eu/blog/building-type-safe-compound-components\npublished: 2026-02-16T08:21:11-0500\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2026-02-16-2.md",
    "content": "---\ntitle: <TextMorph />\nlink: https://torph.lochie.me/\npublished: 2026-02-16T11:48:22-0500\ntags:\n  - bookmark\n---\n\n> Dependency-free animated text component.\n"
  },
  {
    "path": "src/content/notes/2026-02-24-1.md",
    "content": "---\ntitle: ESLint complexity rule\nlink: https://eslint.org/docs/latest/rules/complexity\npublished: 2026-02-24T15:50:52-0500\n---\n"
  },
  {
    "path": "src/content/notes/2026-03-01-1.md",
    "content": "---\ntitle: Anton Repponen's personal website\nlink: https://repponen.com\npublished: 2026-03-01T09:30:41-0500\ntags:\n  - inspiration\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2026-03-01-2.md",
    "content": "---\ntitle: Naz Hamid's personal website\nlink: https://nazhamid.com/\npublished: 2026-03-01T11:25:34-0500\ntags:\n  - inspiration\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2026-03-04-1.md",
    "content": "---\ntitle: How to implement beautiful shadow borders\nlink: https://x.com/PixelJanitor/status/1623358514440859649\npublished: 2026-03-04T11:25:34-0500\ntags:\n  - bookmark\n---\n"
  },
  {
    "path": "src/content/notes/2026-03-05-1.md",
    "content": "---\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",
    "content": "---\ntitle: Smooth Dot Indicators with Embla Carousel and CSS color-mix()\ndescription: How I implemented smooth carousel pagination indicators that respond to scroll progress\npublished: 2026-03-06T08:43:22-0500\n---\n\nThe 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.\n\n## Implementation\n\n[Embla Carousel](https://www.embla-carousel.com/) exposes scroll progress, making this possible:\n\n```typescript\nconst onScroll = (): void => {\n  const snapList = emblaApi.scrollSnapList();\n  const progress = emblaApi.scrollProgress();\n\n  if (snapList.length < 2) return;\n\n  let lower = snapList.length - 2;\n  for (let i = 0; i < snapList.length - 1; i++) {\n    if (progress >= snapList[i] && progress <= snapList[i + 1]) {\n      lower = i;\n      break;\n    }\n  }\n\n  const upper = lower + 1;\n  const range = snapList[upper] - snapList[lower];\n  const t = range === 0 ? 0 : (progress - snapList[lower]) / range;\n\n  dotNodes.forEach((_, i) => {\n    if (i === lower) setDotProgress(i, 1 - t);\n    else if (i === upper) setDotProgress(i, t);\n    else setDotProgress(i, 0);\n  });\n};\n```\n\nAs `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.\n\n## Width and Color\n\nThe dot width expands as you approach it:\n\n```css\nwidth: calc(var(--spacing) * 2 + var(--spacing) * 3 * var(--dot-progress, 0));\n```\n\nAnd the color blends between two states using `color-mix()`:\n\n```css\nbackground-color: color-mix(\n  in srgb,\n  var(--color-neutral-600) calc(var(--dot-progress, 0) * 100%),\n  var(--color-neutral-300)\n);\n```\n\nThis approach preserves contrast, keeping the active dot darkest and the inactive lightest. The browser handles both interpolations without additional JS overhead.\n\nHat 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?\"\n"
  },
  {
    "path": "src/content/notes/2026-03-06-2.md",
    "content": "---\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",
    "content": "---\ntitle: Agentic Engineering Patterns\nlink: https://simonwillison.net/guides/agentic-engineering-patterns/\npublished: 2026-03-08T09:19:33-0400\n---\n\n> Patterns for getting the best results out of coding agents like Claude Code and OpenAI Codex.\n"
  },
  {
    "path": "src/content/notes/2026-03-11-1.md",
    "content": "---\ntitle: The Anatomy of an Agent Harness\nlink: https://x.com/Vtrivedy10/status/2031408954517971368\npublished: 2026-03-11T07:18:23-0400\n---\n"
  },
  {
    "path": "src/content/notes/2026-03-15-1.md",
    "content": "---\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",
    "content": "---\ntitle: \"Managing Context Windows with pi /tree: Branches, Summaries, and Subagent-like Workflows\"\nlink: https://stacktoheap.com/blog/2026/02/26/pi-tree-context-window-management/\npublished: 2026-03-17T07:19:43-0400\n---\n"
  },
  {
    "path": "src/content/notes/2026-03-20-1.md",
    "content": "---\ntitle: Using Container Queries in a CSS Grid to Detect Truncation\nlink: https://v0-css-container-query-test.vercel.app/\npublished: 2026-03-20T08:37:14-0400\n---\n"
  },
  {
    "path": "src/content/notes/2026-03-21-1.md",
    "content": "---\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",
    "content": "---\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",
    "content": "---\ntitle: Nikhil Anand's personal website\nlink: https://nikhil.io/\npublished: 2026-03-27T14:49:41-0400\ntags:\n  - website\n---\n"
  },
  {
    "path": "src/content/notes/2026-04-04-1.md",
    "content": "---\ntitle: Tree structure using CSS anchor positioning\nlink: https://lab.chsmc.org/anchor-positioned-tree\npublished: 2026-04-04T14:35:11-0400\n---\n\nOne of the coolest uses of CSS anchor positioning I have seen.\n"
  },
  {
    "path": "src/content/notes/2026-04-26-1.md",
    "content": "---\npublished: 2026-04-26T09:58:20-0400\ndemo: /demos/button-tint\ntags:\n  - demo\n  - css\n---\n\nTint buttons based on their foreground color for `:hover` and `:active` states.\n\n```css\nbutton {\n  --bg: transparent;\n  --fg: currentColor;\n  --am: 0%;\n  background-color: color-mix(in srgb, var(--bg), var(--fg) var(--am));\n  color: var(--fg);\n\n  &:hover {\n    --am: 10%;\n  }\n    \n  &:active {\n    --am: 20%;\n  }\n}\n\n.blue {\n  --bg: oklch(48.8% 0.243 264.376);\n  --fg: oklch(1 0 0);\n}\n\n.neutral {\n  --bg: oklch(92.9% 0.013 255.508);\n  --fg: oklch(27.8% 0.033 256.848);\n}\n```\n"
  },
  {
    "path": "src/content/oss-contributions.json",
    "content": "[\n  {\n    \"date\": \"2026-03-18\",\n    \"link\": \"https://github.com/developit/kinu/pull/102\",\n    \"description\": \"Allow clicking between radio/checkbox and labels\",\n    \"status\": \"closed\"\n  },\n  {\n    \"date\": \"2026-02-17\",\n    \"link\": \"https://github.com/anomalyco/opencode/pull/13987\",\n    \"description\": \"Fix homepage video layout shift\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2026-02-17\",\n    \"link\": \"https://github.com/benjitaylor/agentation/pull/102\",\n    \"description\": \"Fix keyboard interactions when collapsed, allow toggling via keyboard\",\n    \"status\": \"closed\"\n  },\n  {\n    \"date\": \"2026-02-13\",\n    \"link\": \"https://github.com/TanStack/tanstack.com/pull/712\",\n    \"description\": \"Fix docs feature grid alignment and icon sizing\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2026-02-05\",\n    \"link\": \"https://github.com/cloudflare/kumo/pull/19\",\n    \"description\": \"Wrap pagination counts with tabular-nums to avoid shifts\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2026-01-29\",\n    \"link\": \"https://github.com/vercel/next.js/pull/89245\",\n    \"description\": \"Improve templates layout flexibility\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2026-01-05\",\n    \"link\": \"https://github.com/facebook/stylex/pull/1439\",\n    \"description\": \"Fix confirm dialog closing when clicking inside.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2026-01-04\",\n    \"link\": \"https://github.com/facebook/stylex/pull/1434\",\n    \"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.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2026-01-04\",\n    \"link\": \"https://github.com/facebook/stylex/pull/1433\",\n    \"description\": \"Align docs callout component icon to the first line of text.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2025-09-30\",\n    \"link\": \"https://github.com/fuma-nama/fumadocs/pull/2416\",\n    \"description\": \"Add missing ring classes for buttonVariants to ensure focus states are visible for buttons.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2025-09-29\",\n    \"link\": \"https://github.com/vercel/ai-chatbot/pull/1252\",\n    \"description\": \"Ensure both PureModelSelectorCompact and VisibilitySelector have visible outlines when focus visible.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2025-09-29\",\n    \"link\": \"https://github.com/haydenbleasel/kibo/pull/266\",\n    \"description\": \"Fixes icon shrinking and alignment within pricing block when the feature text wraps to two lines.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2025-09-24\",\n    \"link\": \"https://github.com/fuma-nama/fumadocs/pull/2399\",\n    \"description\": \"Updates copy button to preserve the active state while in checked state.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2025-09-06\",\n    \"link\": \"https://github.com/midday-ai/midday/pull/606\",\n    \"description\": \"Ensure icons are aligned on the first line vs being vertically centered within the whole item.\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2025-09-06\",\n    \"link\": \"https://github.com/dodopayments/billingsdk/pull/144\",\n    \"description\": \"Fix icon alignment within pricing-table-one\",\n    \"status\": \"merged\"\n  },\n  {\n    \"date\": \"2025-07-04\",\n    \"link\": \"https://github.com/shadcn-ui/ui/pull/7944\",\n    \"description\": \"Add support for css imports\",\n    \"status\": \"closed\"\n  },\n  {\n    \"date\": \"2023-09-03\",\n    \"link\": \"https://github.com/rauchg/blog/pull/133\",\n    \"description\": \"Defines color scheme when in light/dark modes.\",\n    \"status\": \"merged\"\n  }\n]\n"
  },
  {
    "path": "src/content/recommendations/amy-stuart.md",
    "content": "---\nname: Amy Stuart\ntitle: Senior Designer\ncompany: nationbuilder\npublished: 2021-01-02\n---\n\nAlex 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.\n"
  },
  {
    "path": "src/content/recommendations/andrew-possehl.md",
    "content": "---\nname: Andrew Possehl\ntitle: Senior Designer\ncompany: nationbuilder\npublished: 2021-01-01\n---\n\nI 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>.\n"
  },
  {
    "path": "src/content/recommendations/benjamin-kohl.md",
    "content": "---\nname: Benjamin Kohl\ntitle: Back-end Developer\ncompany: masuga-design\npublished: 2015-01-01\nstatus: hidden\n---\n\nAlex is a true autodidact that is dedicated to keeping up with the latest development tools, methods and trends.\n"
  },
  {
    "path": "src/content/recommendations/danielle-dunn.md",
    "content": "---\nname: Danielle Dunn\ntitle: Project Manager\ncompany: mighty-in-the-midwest\npublished: 2018-01-02\n---\n\nAlex 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.\n"
  },
  {
    "path": "src/content/recommendations/david-mosher.md",
    "content": "---\nname: David Mosher\ntitle: Staff Software Engineer\ncompany: clerk\npublished: 2024-10-14\n---\n\nIf 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.\n"
  },
  {
    "path": "src/content/recommendations/jimmy-merritello.md",
    "content": "---\nname: Jimmy Merritello\ntitle: Web Engineer\ncompany: hashicorp\npublished: 2023-01-01\n---\n\nAlex 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.\n"
  },
  {
    "path": "src/content/recommendations/kyle-luck.md",
    "content": "---\nname: Kyle Luck\ntitle: Developer\ncompany: mighty-in-the-midwest\npublished: 2018-01-03\n---\n\nAlex 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.\n"
  },
  {
    "path": "src/content/recommendations/melissa-taylor.md",
    "content": "---\nname: Melissa Taylor\ntitle: Director Client Services\ncompany: mighty-in-the-midwest\npublished: 2018-01-01\n---\n\nI 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!\n"
  },
  {
    "path": "src/content.config.ts",
    "content": "import { defineCollection, reference } from \"astro:content\";\nimport { z } from \"astro/zod\";\nimport { file, glob } from \"astro/loaders\";\nimport { GEAR_CATEGORIES } from \"./consts\";\n\nconst gear = defineCollection({\n  loader: glob({ pattern: \"**/*.md\", base: \"./src/content/gear\" }),\n  schema: z.object({\n    name: z.string(),\n    category: z.enum(GEAR_CATEGORIES),\n    eyebrow: z.string().optional(),\n    link: z.url().optional(),\n    favorite: z.boolean().optional().default(false),\n    status: z.enum([\"active\", \"retired\"]).default(\"active\"),\n  }),\n});\n\nconst notes = defineCollection({\n  loader: glob({ pattern: \"**/*.md\", base: \"./src/content/notes\" }),\n  schema: z.object({\n    published: z.coerce.date(),\n    title: z.string().optional(),\n    description: z.string().optional(),\n    link: z.string().optional(),\n    tags: z.array(z.string()).optional(),\n    demo: z.string().optional(),\n  }),\n});\n\nconst jobs = defineCollection({\n  loader: glob({ pattern: \"**/*.md\", base: \"./src/content/jobs\" }),\n  schema: z.object({\n    title: z.string(),\n    company: z.string(),\n    startDate: z.coerce.date(),\n    endDate: z.coerce.date().optional(),\n    tools: z.array(z.string()).optional(),\n    projects: z\n      .array(\n        z.object({\n          title: z.string(),\n          description: z.string().optional(),\n          link: z.string(),\n          published: z.coerce.date().optional(),\n        }),\n      )\n      .optional(),\n  }),\n});\n\nconst recommendations = defineCollection({\n  loader: glob({ pattern: \"**/*.md\", base: \"./src/content/recommendations\" }),\n  schema: z.object({\n    name: z.string(),\n    title: z.string(),\n    company: reference(\"jobs\"),\n    published: z.coerce.date(),\n    avatar: z.string().optional(),\n    status: z.enum([\"visible\", \"hidden\"]).default(\"visible\"),\n  }),\n});\n\nconst ossContributions = defineCollection({\n  loader: file(\"src/content/oss-contributions.json\", {\n    parser: (fileContent) => {\n      const data = JSON.parse(fileContent);\n      return data.map((item: Record<string, unknown>, index: number) => ({\n        id: `contribution-${index + 1}`,\n        ...item,\n      }));\n    },\n  }),\n  schema: z.object({\n    link: z.url(),\n    date: z.coerce.date(),\n    description: z.string(),\n    status: z.enum([\"open\", \"merged\", \"closed\"]).default(\"open\"),\n  }),\n});\n\nexport const collections = {\n  gear,\n  jobs,\n  notes,\n  ossContributions,\n  recommendations,\n};\n"
  },
  {
    "path": "src/env.d.ts",
    "content": "/// <reference path=\"../.astro/types.d.ts\" />\n/// <reference types=\"astro/client\" />\n"
  },
  {
    "path": "src/layouts/base-layout.astro",
    "content": "---\nimport \"@/styles/global.css\";\nconst canonicalURL = new URL(Astro.url.pathname, Astro.site);\nconst { title, description } = Astro.props;\nconst metaTitle = title ? `${title} // Alex Carpenter` : \"Alex Carpenter\";\nconst metaDescription = description\n  ? description\n  : \"Staff UI Engineer at Clerk\";\nimport { version } from \"../../package.json\";\nimport ExternalLink from \"@/components/external-link.astro\";\nconst COMMIT_SHA = import.meta.env.COMMIT_SHA.slice(0, 7);\n---\n\n<html lang=\"en\">\n  <head>\n    <link\n      rel=\"preload\"\n      href=\"/fonts/GeistMono[wght].woff2\"\n      as=\"font\"\n      type=\"font/woff2\"\n      crossorigin=\"anonymous\"\n    />\n\n    <!-- Global Metadata -->\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n    <meta name=\"generator\" content={Astro.generator} />\n\n    <!-- Theme Color -->\n    <meta\n      name=\"theme-color\"\n      content=\"#fafafa\"\n      media=\"(prefers-color-scheme: light)\"\n    />\n    <meta\n      name=\"theme-color\"\n      content=\"#0a0a0a\"\n      media=\"(prefers-color-scheme: dark)\"\n    />\n\n    <!-- Canonical URL -->\n    <link rel=\"canonical\" href={canonicalURL} />\n\n    <!-- Primary Meta Tags -->\n    <title>{metaTitle}</title>\n    <meta name=\"title\" content={metaTitle} />\n    <meta name=\"description\" content={metaDescription} />\n\n    <!-- Open Graph / Facebook -->\n    <meta property=\"og:type\" content=\"website\" />\n    <meta property=\"og:url\" content={Astro.url} />\n    <meta property=\"og:title\" content={metaTitle} />\n    <meta property=\"og:description\" content={metaDescription} />\n    <meta property=\"og:image\" content={new URL(\"/og.jpg\", Astro.site)} />\n\n    <!-- Twitter -->\n    <meta property=\"twitter:card\" content=\"summary_large_image\" />\n    <meta property=\"twitter:url\" content={Astro.url} />\n    <meta property=\"twitter:title\" content={metaTitle} />\n    <meta property=\"twitter:description\" content={metaDescription} />\n    <meta property=\"twitter:image\" content={new URL(\"/og.jpg\", Astro.site)} />\n\n    <!-- Sitemap -->\n    <link rel=\"sitemap\" href={new URL(\"/sitemap-index.xml\", Astro.site)} />\n\n    <!-- RSS Feed -->\n    <link\n      rel=\"alternate\"\n      type=\"application/rss+xml\"\n      title=\"Notes - Alex Carpenter\"\n      href={new URL(\"/notes/rss.xml\", Astro.site)}\n    />\n  </head>\n\n  <body class=\"py-20 font-mono text-xs/5\">\n    <a\n      href=\"#main\"\n      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\"\n      >Skip to content</a\n    >\n    <header class=\"px-4\">\n      <div class=\"mx-auto max-w-prose\">\n        <h1>\n          <a href=\"/\">Alex Carpenter</a>\n        </h1>\n        <p class=\"text-muted-foreground\">Staff UI Engineer at Clerk</p>\n        <p class=\"text-muted-foreground\">Grand Rapids, MI.</p>\n      </div>\n    </header>\n    <main class=\"flex-1\" id=\"main\">\n      <slot />\n    </main>\n    <footer class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\">\n      <div\n        class=\"mx-auto grid max-w-prose grid-cols-1 gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]\"\n      >\n        <h2>Connect</h2>\n        <dl>\n          {\n            [\n              {\n                title: \"GitHub\",\n                username: \"@alexcarpenter\",\n                link: \"https://github.com/alexcarpenter\",\n              },\n              {\n                title: \"Twitter\",\n                username: \"@alexcarp_me\",\n                link: \"https://twitter.com/alexcarp_me\",\n              },\n              {\n                title: \"Bluesky\",\n                username: \"@alexcarpenter.me\",\n                link: \"https://bsky.app/profile/alexcarpenter.me\",\n              },\n              {\n                title: \"LinkedIn\",\n                username: \"@alexcarpenter\",\n                link: \"https://www.linkedin.com/in/imalexcarpenter/\",\n              },\n            ].map((social) => {\n              return (\n                <Fragment>\n                  <dt class=\"text-muted-foreground not-first:mt-5\">\n                    {social.title}\n                  </dt>\n                  <dd>\n                    <ExternalLink href={social.link} text={social.username} />\n                  </dd>\n                </Fragment>\n              );\n            })\n          }\n        </dl>\n        <div class=\"flex flex-col lg:items-end\">\n          <dl class=\"[&_dt]:text-muted-foreground lg:text-right\">\n            <dt>Version</dt>\n            <dd>v{version}</dd>\n            <dt class=\"mt-5\">Last Modified</dt>\n            <dd>\n              {\n                new Date().toLocaleDateString(\"en-US\", {\n                  year: \"numeric\",\n                  month: \"short\",\n                  day: \"2-digit\",\n                })\n              }\n            </dd>\n            <dt class=\"mt-5\">Commit</dt>\n            <dd>\n              <ExternalLink\n                href={`https://github.com/alexcarpenter/alexcarpenter.me/commit/${COMMIT_SHA}`}\n                text={COMMIT_SHA}\n              />\n            </dd>\n          </dl>\n        </div>\n      </div>\n    </footer>\n  </body>\n</html>\n"
  },
  {
    "path": "src/layouts/demo-layout.astro",
    "content": "---\nimport \"@/styles/demos.css\";\n---\n\n<html lang=\"en\" class=\"h-full\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n  </head>\n\n  <body class=\"bg-background text-foreground flex min-h-full flex-col p-4\">\n    <slot />\n  </body>\n</html>\n"
  },
  {
    "path": "src/lib/markdown.js",
    "content": "import MarkdownIt from \"markdown-it\";\n\nlet markdownParser = null;\n\nexport function getMarkdownParser() {\n  if (!markdownParser) {\n    markdownParser = new MarkdownIt({\n      html: true,\n      linkify: true,\n      typographer: true,\n    });\n  }\n  return markdownParser;\n}\n"
  },
  {
    "path": "src/lib/utils.ts",
    "content": "export function enforceExhaustive(\n  value: never,\n  message = \"Unexpected value\",\n): never {\n  throw new Error(`${message} '${value}'`);\n}\n\nexport function formatLinkHostname(urlString: string): string {\n  const url = new URL(urlString);\n\n  // For Twitter/X links, show x.com/username or twitter.com/username\n  if (url.hostname.includes(\"twitter.com\") || url.hostname.includes(\"x.com\")) {\n    const username = url.pathname.split(\"/\")[1];\n    return username ? `${url.hostname}/${username}` : url.hostname;\n  }\n\n  // For all other links, just show the hostname\n  return url.hostname;\n}\n"
  },
  {
    "path": "src/pages/404.astro",
    "content": "---\nimport BaseLayout from \"@/layouts/base-layout.astro\";\n---\n\n<BaseLayout title=\"404\" description=\"Page not found\">\n  <section class=\"mt-20 px-4\">\n    <div class=\"mx-auto max-w-prose\">\n      <h2 class=\"text-muted-foreground text-balance\">\n        <span class=\"text-foreground\">404</span> Page not found.\n      </h2>\n    </div>\n  </section>\n</BaseLayout>\n"
  },
  {
    "path": "src/pages/demos/bg-repeat-round.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\n---\n\n<DemoLayout>\n  <div\n    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\"\n  >\n  </div>\n</DemoLayout>\n"
  },
  {
    "path": "src/pages/demos/box-decoration-break-clone.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\n---\n\n<DemoLayout>\n  <p class=\"my-auto max-w-xl leading-relaxed\">\n    Lorem ipsum dolor sit amet, <mark\n      class=\"rounded bg-yellow-500 [box-decoration-break:clone] px-1\"\n      >consectetur adipisicing elit. Deleniti, repellendus aspernatur quisquam\n      inventore aliquam</mark\n    > maxime laboriosam maiores provident illum.\n  </p>\n</DemoLayout>\n"
  },
  {
    "path": "src/pages/demos/button-disabled-color-mix.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\n---\n\n<DemoLayout>\n  <div\n    class=\"bg-grid absolute inset-0 p-2 [&:not(:has(input:checked))]:[background:transparent]\"\n  >\n    <label class=\"flex items-center gap-x-2 font-mono text-xs select-none\">\n      <input type=\"checkbox\" checked />\n      Toggle background\n    </label>\n  </div>\n  <div\n    class:list={[\n      \"relative m-auto w-full max-w-lg p-8\",\n      \"[--bg:var(--color-blue-500)] [--fg:var(--color-white)]\",\n      \"dark:[--bg:var(--color-blue-700)]\",\n    ]}\n  >\n    <div class=\"grid grid-cols-2 gap-4\">\n      <div class=\"flex flex-col gap-y-4\">\n        <button class=\"h-11 rounded-md bg-(--bg) text-(--fg)\">Opacity</button>\n        <button\n          class=\"h-11 rounded-md bg-(--bg) text-(--fg) opacity-50\"\n          disabled>Opacity</button\n        >\n      </div>\n      <div class=\"flex flex-col gap-y-4\">\n        <button class=\"h-11 rounded-md bg-(--bg) text-(--fg)\">Color Mix</button>\n        <button\n          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))]\"\n          disabled>Color Mix</button\n        >\n      </div>\n    </div>\n  </div>\n</DemoLayout>\n"
  },
  {
    "path": "src/pages/demos/button-tint.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\n---\n\n<DemoLayout>\n  <div class=\"flex flex-1 items-center justify-center gap-4\">\n    <button class=\"blue\">Hello world</button>\n    <button class=\"neutral\">Hello world</button>\n  </div>\n</DemoLayout>\n\n<style>\n  button {\n    --bg: transparent;\n    --fg: currentColor;\n    --am: 0%;\n    appearance: none;\n    background-color: color-mix(in srgb, var(--bg), var(--fg) var(--am));\n    color: var(--fg);\n    border-radius: 6px;\n    border: 1px solid transparent;\n    height: 32px;\n    width: fit-content;\n    padding-inline: 16px;\n    font-size: 14px;\n    transition: background-color ease 0.2s;\n\n    &:hover {\n      --am: 10%;\n    }\n\n    &:active {\n      --am: 20%;\n    }\n  }\n\n  .blue {\n    --bg: oklch(48.8% 0.243 264.376);\n    --fg: oklch(1 0 0);\n  }\n\n  .neutral {\n    --bg: oklch(92.9% 0.013 255.508);\n    --fg: oklch(27.8% 0.033 256.848);\n  }\n</style>\n"
  },
  {
    "path": "src/pages/demos/hanging-punctuation.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\n---\n\n<DemoLayout>\n  <div class=\"m-auto w-full max-w-lg\">\n    <p class=\"relative inline-block text-xl\">\n      <span class=\"text-accent absolute right-full\">“</span>Lorem ipsum dolor\n      sit amet consectetur adipisicing elit. Iste officia quasi fugiat, dolores\n      ab nam repellendus voluptate.<span class=\"text-accent\">”</span>\n    </p>\n  </div>\n</DemoLayout>\n"
  },
  {
    "path": "src/pages/demos/inline-trailing-icon.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\nimport { Icon } from \"astro-icon/components\";\n---\n\n<DemoLayout>\n  <div class=\"m-auto max-w-lg\">\n    <p class=\"relative inline-block pr-[1.5em]\">\n      Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia, ducimus<span\n        class=\"absolute ml-[.25em] inline-flex h-lh items-center\"\n        ><Icon name=\"arrow-up-right\" class=\"text-accent size-[1em]\" /></span\n      >\n    </p>\n  </div>\n</DemoLayout>\n"
  },
  {
    "path": "src/pages/demos/list-format-to-parts.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\nconst tags = [\"HTML\", \"CSS\", \"JavaScript\"];\n---\n\n<DemoLayout>\n  <p class=\"m-auto max-w-xl\">\n    {\n      new Intl.ListFormat(\"en-US\")\n        .formatToParts(tags)\n        .map(({ type, value }) => {\n          if (type === \"element\") {\n            return (\n              // prettier-ignore\n              <a href=\"\" class=\"underline\">{value}</a>\n            );\n          }\n          return value;\n        })\n    }\n  </p>\n</DemoLayout>\n"
  },
  {
    "path": "src/pages/demos/list-item-icon-baseline.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\nimport { Icon } from \"astro-icon/components\";\n---\n\n<DemoLayout>\n  <div class=\"m-auto max-w-sm\">\n    <ul class=\"flex flex-col gap-2\">\n      {\n        [\n          \"List item 1\",\n          \"List item 2 that is longer than the others and wraps to two lines\",\n          \"List item 3\",\n        ].map((i) => (\n          <li class=\"flex gap-2\">\n            <span class=\"flex h-lh items-center\">\n              <Icon\n                class=\"text-accent size-[1em] flex-none\"\n                name=\"badge-check\"\n              />\n            </span>{\" \"}\n            {i}\n          </li>\n        ))\n      }\n    </ul>\n  </div>\n</DemoLayout>\n"
  },
  {
    "path": "src/pages/demos/modal-aspect-ratio.astro",
    "content": "---\nimport DemoLayout from \"@/layouts/demo-layout.astro\";\n---\n\n<DemoLayout>\n  <div class=\"box bg-grid\"></div>\n</DemoLayout>\n\n<style>\n  .box {\n    margin: auto;\n    position: relative;\n    aspect-ratio: 3 / 2;\n    height: min(calc(100vh - 4rem), calc((100vw - 4rem) * 2 / 3));\n    border-radius: var(--radius-lg);\n  }\n</style>\n"
  },
  {
    "path": "src/pages/index.astro",
    "content": "---\nimport slugify from \"@sindresorhus/slugify\";\nimport { getCollection, render } from \"astro:content\";\nimport { Icon } from \"astro-icon/components\";\nimport BaseLayout from \"@/layouts/base-layout.astro\";\nimport { getEntry } from \"astro:content\";\nimport { enforceExhaustive } from \"@/lib/utils\";\nimport ExternalLink from \"@/components/external-link.astro\";\n\nconst jobs = await getCollection(\"jobs\");\nconst recommendations = await getCollection(\"recommendations\");\nconst ossContributions = await getCollection(\"ossContributions\");\n\nconst renderedJobs = await Promise.all(\n  jobs.map(async (job) => {\n    const { Content } = await render(job);\n    return { job, Content };\n  }),\n);\nconst renderedRecommendations = await Promise.all(\n  recommendations.map(async (recommendation) => {\n    const { Content } = await render(recommendation);\n    const company = await getEntry(recommendation.data.company);\n    return { recommendation, company, Content };\n  }),\n);\n---\n\n<BaseLayout>\n  <section class=\"mt-40 px-4\">\n    <div class=\"relative mx-auto max-w-prose\">\n      <h2 class=\"text-muted-foreground text-balance\">\n        <span class=\"text-foreground\">Steward of the web</span> building composable\n        interfaces for humans and agents.\n      </h2>\n      <p class=\"text-muted-foreground text-justify indent-10\">\n        Currently on the SDK team at Clerk, I work on UI components and theming\n        infrastructure-building systems for customization, improving component\n        APIs, and creating developer tooling that makes authentication\n        interfaces more flexible and accessible.\n      </p>\n      <p class=\"text-muted-foreground text-justify indent-10\">\n        I do my best work on small, highly collaborative teams that ship often\n        and embrace the convergence of human craftsmanship with AI tooling. I\n        care deeply about design and believe the best interfaces emerge when\n        designers, engineers, and intelligent systems work together.\n      </p>\n      <a\n        href=\"/notes\"\n        class=\"text-muted-foreground hover:text-foreground mt-10 inline-flex gap-1\"\n        >View notes<Icon name=\"arrow-right\" class=\"h-5 w-2.5\" /></a\n      >\n    </div>\n  </section>\n\n  <section class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\">\n    <div\n      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]\"\n    >\n      <h2>Projects</h2>\n      <ul\n        class:list={[\n          \"grid gap-5\",\n          \"lg:col-span-2 lg:grid-cols-3 lg:gap-10\",\n          \"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]\",\n        ]}\n      >\n        {\n          [\n            {\n              logomark: (\n                <svg\n                  viewBox=\"0 0 600 598\"\n                  fill=\"none\"\n                  class=\"h-10\"\n                  aria-hidden=\"true\"\n                >\n                  <path\n                    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\"\n                    fill=\"currentColor\"\n                  />\n                </svg>\n              ),\n              name: \"I Brew My Own Coffee\",\n              link: \"https://ibrewmyown.coffee\",\n              description:\n                // prettier-ignore\n                <>\n                  A curated collection of home coffee setups. Built with{\" \"}\n                  {new Intl.ListFormat(\"en-US\", {\n                    style: \"long\",\n                    type: \"conjunction\",\n                  }).format([\"Astro\", \"Tailwind CSS\", \"Base UI\"])}.\n                </>,\n            },\n            {\n              logomark: (\n                <svg\n                  fill=\"none\"\n                  viewBox=\"0 0 935 186\"\n                  class=\"h-5.5\"\n                  aria-hidden=\"true\"\n                >\n                  <>\n                    <path\n                      fill=\"currentColor\"\n                      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\"\n                    />\n                    <path\n                      fill=\"currentColor\"\n                      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\"\n                    />\n                  </>\n                </svg>\n              ),\n              name: \"Curated\",\n              link: \"https://curated.alexcarpenter.me\",\n              description:\n                // prettier-ignore\n                <>\n                  Weekly curated items including design inspiration, tools, and\n                  resources. Built with{\" \"}\n                  {new Intl.ListFormat(\"en-US\", {\n                    style: \"long\",\n                    type: \"conjunction\",\n                  }).format([\"Astro\"])}.\n                </>,\n            },\n            {\n              logomark: (\n                <svg\n                  viewBox=\"0 0 1035 86\"\n                  fill=\"none\"\n                  class=\"h-3\"\n                  aria-hidden=\"true\"\n                >\n                  <path\n                    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\"\n                    fill=\"currentColor\"\n                  />\n                </svg>\n              ),\n              name: \"RESILIENT—UI\",\n              link: \"https://resilient-ui.com\",\n              description:\n                // prettier-ignore\n                <>\n                  Resources for building resilient user interfaces. Built with{\" \"}\n                  {new Intl.ListFormat(\"en-US\", {\n                    style: \"long\",\n                    type: \"conjunction\",\n                  }).format([\n                    \"Next.js\",\n                    \"Tailwind CSS\",\n                    \"Base UI\",\n                    \"shadcn/ui\",\n                    \"Fumadocs\",\n                  ])}.\n                </>,\n            },\n            {\n              logomark: (\n                <svg\n                  viewBox=\"0 0 586 194\"\n                  fill=\"none\"\n                  class=\"h-7\"\n                  aria-hidden=\"true\"\n                >\n                  <>\n                    <path\n                      fill=\"currentColor\"\n                      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\"\n                    />\n                    <path\n                      fill=\"currentColor\"\n                      fill-opacity=\".7\"\n                      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\"\n                    />\n                    <path\n                      fill=\"currentColor\"\n                      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\"\n                    />\n                  </>\n                </svg>\n              ),\n              name: \"dotfiles\",\n              link: \"https://curated.alexcarpenter.me\",\n              description:\n                // prettier-ignore\n                <>\n                  My home config, scripts and installation process. Managed with{\" \"}\n                  {new Intl.ListFormat(\"en-US\", {\n                    style: \"long\",\n                    type: \"conjunction\",\n                  }).format([\"Stow\"])}.\n                </>,\n            },\n          ]\n            .filter((p) => p.name !== \"dotfiles\")\n            .map((project) => (\n              <li class=\"snap-center\">\n                <figure class=\"bg-grid grid aspect-3/2 place-content-center\">\n                  {project.logomark}\n                </figure>\n                <h3 class=\"mt-5\">\n                  <ExternalLink href={project.link} text={project.name} />\n                </h3>\n                <p class=\"text-muted-foreground text-pretty\">\n                  {project.description}\n                </p>\n              </li>\n            ))\n        }\n      </ul>\n    </div>\n  </section>\n\n  <section class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\">\n    <div\n      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]\"\n    >\n      <h2>Experience</h2>\n      <ul class=\"flex flex-col gap-5\">\n        {\n          renderedJobs\n            .sort(\n              (a, b) =>\n                Date.parse(b.job.data.startDate.toString()) -\n                Date.parse(a.job.data.startDate.toString()),\n            )\n            .map(({ job, Content }, index) => (\n              <li>\n                <details\n                  name=\"accordion\"\n                  class=\"group/accordion\"\n                  open={index === 0 ? true : false}\n                >\n                  <summary class=\"grid grid-cols-[1fr_max-content_0.625rem] gap-2\">\n                    <header class=\"flex gap-2\">\n                      <Icon\n                        name={slugify(job.data.company, {\n                          decamelize: false,\n                        })}\n                        class=\"h-5 w-4 flex-none\"\n                      />\n                      <div>\n                        <h3>{job.data.company}</h3>\n                        <p class=\"text-muted-foreground flex items-center gap-1\">\n                          <Icon\n                            name=\"corner-down-right\"\n                            class=\"h-5 w-2.5 flex-none\"\n                          />{\" \"}\n                          {job.data.title}\n                        </p>\n                      </div>\n                    </header>\n                    <p class=\"text-muted-foreground\">\n                      <time datetime={job.data.startDate.toISOString()}>\n                        <span class=\"hidden sm:inline\">\n                          {new Date(job.data.startDate).toLocaleDateString(\n                            \"en-US\",\n                            {\n                              year: \"numeric\",\n                              month: \"short\",\n                            },\n                          )}\n                        </span>\n                        <span class=\"sm:hidden\">\n                          {new Date(job.data.startDate).toLocaleDateString(\n                            \"en-US\",\n                            {\n                              year: \"numeric\",\n                            },\n                          )}\n                        </span>\n                      </time>\n                      –\n                      {job.data.endDate ? (\n                        <time datetime={job.data.endDate.toISOString()}>\n                          <span class=\"hidden sm:inline\">\n                            {new Date(job.data.endDate).toLocaleDateString(\n                              \"en-US\",\n                              {\n                                year: \"numeric\",\n                                month: \"short\",\n                              },\n                            )}\n                          </span>\n                          <span class=\"sm:hidden\">\n                            {new Date(job.data.endDate).toLocaleDateString(\n                              \"en-US\",\n                              {\n                                year: \"numeric\",\n                              },\n                            )}\n                          </span>\n                        </time>\n                      ) : (\n                        <time datetime={new Date().toISOString()}>Now</time>\n                      )}\n                    </p>\n\n                    <Icon\n                      name=\"chevron-down\"\n                      class=\"text-muted-foreground h-5 w-2.5 flex-none transition-transform group-open/accordion:rotate-180\"\n                    />\n                  </summary>\n\n                  <div class=\"ps-6 pbs-2\">\n                    <Content />\n                    {job.data.tools ? (\n                      <p class=\"text-muted-foreground mbs-2\">\n                        {new Intl.ListFormat(\"en-US\", {\n                          style: \"long\",\n                          type: \"conjunction\",\n                        }).format(job.data.tools)}\n                      </p>\n                    ) : null}\n                  </div>\n                </details>\n              </li>\n            ))\n        }\n      </ul>\n      <footer class=\"flex lg:justify-end\">\n        <ExternalLink\n          href=\"https://www.linkedin.com/in/imalexcarpenter\"\n          text=\"Connect on LinkedIn\"\n          class=\"text-muted-foreground hover:text-foreground\"\n        />\n      </footer>\n    </div>\n  </section>\n\n  <section class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\">\n    <div\n      class=\"mx-auto grid max-w-prose grid-cols-1 gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]\"\n    >\n      <h2>Recommendations</h2>\n      <ul>\n        {\n          renderedRecommendations\n            .sort(\n              (a, b) =>\n                Date.parse(b.recommendation.data.published.toString()) -\n                Date.parse(a.recommendation.data.published.toString()),\n            )\n            .filter(\n              ({ recommendation }) => recommendation.data.status === \"visible\",\n            )\n            .map(({ recommendation, company, Content }) => (\n              <li class=\"not-first:mt-10\">\n                <Content />\n                <p class=\"mt-5\">{recommendation.data.name}</p>\n                <p class=\"text-muted-foreground flex items-center gap-1\">\n                  <Icon name=\"corner-down-right\" class=\"h-5 w-2.5 flex-none\" />{\" \"}\n                  {recommendation.data.title}, {company.data.company}\n                </p>\n              </li>\n            ))\n        }\n      </ul>\n    </div>\n  </section>\n  <section class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\">\n    <div\n      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]\"\n    >\n      <h2>\n        <abbr title=\"Open source software\" class=\"no-underline\">OSS</abbr> Contributions\n      </h2>\n      <ul>\n        {\n          ossContributions\n            .sort(\n              (a, b) =>\n                Date.parse(b.data.date.toString()) -\n                Date.parse(a.data.date.toString()),\n            )\n            .map((contribution) => {\n              const match = contribution.data.link.match(\n                /github\\.com\\/([^\\/]+\\/[^\\/]+)/,\n              );\n              const repo = match ? match[1] : null;\n              if (!repo) return;\n              return (\n                <li class=\"not-first:mt-5\">\n                  <header class=\"flex gap-2\">\n                    <Icon\n                      class:list={[\n                        \"h-5 w-4 flex-none\",\n                        {\n                          \"text-green\": contribution.data.status === \"open\",\n                          \"text-purple\": contribution.data.status === \"merged\",\n                          \"text-red\": contribution.data.status === \"closed\",\n                        },\n                      ]}\n                      name={(() => {\n                        switch (contribution.data.status) {\n                          case \"open\":\n                            return \"git-pull-request-arrow\";\n                          case \"merged\":\n                            return \"git-merge\";\n                          case \"closed\":\n                            return \"git-pull-request-closed\";\n                          default:\n                            enforceExhaustive(\n                              contribution.data.status,\n                              \"Unknown contribution status\",\n                            );\n                        }\n                      })()}\n                    />\n                    <h3>\n                      <ExternalLink\n                        href={contribution.data.link}\n                        text={repo.toLowerCase()}\n                      />\n                    </h3>\n                  </header>\n                  <p class=\"text-muted-foreground line-clamp-2 pl-6 text-pretty\">\n                    {contribution.data.description}\n                  </p>\n                </li>\n              );\n            })\n        }\n      </ul>\n      <footer class=\"flex lg:justify-end\">\n        <ExternalLink\n          href=\"https://github.com/alexcarpenter\"\n          text=\"Follow on GitHub\"\n          class=\"text-muted-foreground hover:text-foreground\"\n        />\n      </footer>\n    </div>\n  </section>\n</BaseLayout>\n\n<style>\n  details {\n    --duration: 0.2s;\n\n    @media (prefers-reduced-motion: no-preference) {\n      interpolate-size: allow-keywords;\n    }\n\n    &::details-content {\n      opacity: 0;\n      block-size: 0;\n      overflow-y: clip;\n      filter: blur(2px);\n      transition:\n        content-visibility var(--duration) allow-discrete,\n        opacity var(--duration),\n        block-size var(--duration),\n        filter var(--duration);\n    }\n\n    &[open]::details-content {\n      opacity: 1;\n      block-size: auto;\n      filter: blur(0px);\n    }\n  }\n\n  summary {\n    cursor: pointer;\n    user-select: none;\n    outline-offset: 4px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/notes/[...page].astro",
    "content": "---\nimport type { GetStaticPaths, Page } from \"astro\";\nimport { getCollection, render } from \"astro:content\";\nimport type { CollectionEntry } from \"astro:content\";\nimport { Icon } from \"astro-icon/components\";\nimport BaseLayout from \"@/layouts/base-layout.astro\";\nimport { formatLinkHostname } from \"@/lib/utils\";\nimport ExternalLink from \"@/components/external-link.astro\";\nimport Demo from \"@/components/demo.astro\";\n\ntype Props = {\n  page: Page<CollectionEntry<\"notes\">>;\n};\n\nexport const getStaticPaths: GetStaticPaths = async ({ paginate }) => {\n  const notes = await getCollection(\"notes\");\n  const sortedNotes = notes.sort(\n    (a, b) =>\n      Date.parse(b.data.published.toString()) -\n      Date.parse(a.data.published.toString()),\n  );\n  return paginate(sortedNotes, { pageSize: 20 });\n};\n\nconst { page } = Astro.props as Props;\n\nconst renderedNotes = await Promise.all(\n  page.data.map(async (note) => {\n    const { Content } = await render(note);\n    return { note, Content };\n  }),\n);\n\nconst heading = page.currentPage === 1 ? \"Latest\" : `Page ${page.currentPage}`;\n---\n\n<BaseLayout\n  title=\"Notes\"\n  description=\"Notes on engineering, developer experience, design systems, and agentic engineering.\"\n>\n  <section class=\"mt-40 px-4\">\n    <div class=\"mx-auto max-w-prose\">\n      <h2 class=\"text-muted-foreground text-balance\">\n        <span class=\"text-foreground\">Notes</span> on engineering, developer experience,\n        design systems, and agentic engineering.\n      </h2>\n    </div>\n  </section>\n\n  <section class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\">\n    <div\n      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]\"\n    >\n      <h2>{heading}</h2>\n      <ul>\n        {\n          renderedNotes.map(({ note, Content }) => {\n            const isExternalLink = note.data.link && note.data.title;\n            return (\n              <li class=\"not-first:border-separator not-first:mt-20 not-first:border-t not-first:pt-20\">\n                <article>\n                  <a href={`/notes/${note.id}`}>\n                    <time\n                      class=\"text-muted-foreground hover:text-foreground\"\n                      datetime={note.data.published.toISOString()}\n                    >\n                      {new Date(note.data.published).toLocaleDateString(\n                        \"en-US\",\n                        {\n                          year: \"numeric\",\n                          month: \"short\",\n                          day: \"2-digit\",\n                          weekday: \"short\",\n                        },\n                      )}\n                    </time>\n                  </a>\n                  {note.data.demo ? (\n                    <Demo\n                      class=\"mt-5\"\n                      src={note.data.demo}\n                      title={note.data.title}\n                    />\n                  ) : null}\n                  <h2 class=\"text-foreground mt-5\">\n                    {isExternalLink ? (\n                      <ExternalLink\n                        href={note.data.link}\n                        text={note.data.title}\n                      />\n                    ) : (\n                      note.data.title\n                    )}\n                  </h2>\n                  {isExternalLink && note.data.link ? (\n                    <p class=\"text-muted-foreground\">\n                      {formatLinkHostname(note.data.link)}\n                    </p>\n                  ) : null}\n                  {note.body ? (\n                    <div class=\"prose mt-5\">\n                      <Content />\n                    </div>\n                  ) : null}\n                </article>\n              </li>\n            );\n          })\n        }\n      </ul>\n      <footer class=\"flex flex-col gap-10 lg:items-end\">\n        <a\n          href=\"/notes/rss.xml\"\n          class=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          >Subscribe via RSS <Icon\n            name=\"rss\"\n            aria-hidden=\"true\"\n            class=\"h-5 w-2.5 flex-none\"\n          /></a\n        >\n      </footer>\n      {\n        (page.url.prev || page.url.next) && (\n          <nav class=\"grid grid-cols-3 gap-5 lg:col-start-2\">\n            {page.url.prev && (\n              <span class=\"flex\">\n                <a\n                  href={page.url.prev}\n                  class=\"text-muted-foreground hover:text-foreground inline-flex gap-1\"\n                >\n                  <Icon\n                    name=\"arrow-left\"\n                    aria-hidden=\"true\"\n                    class=\"h-5 w-2.5 flex-none\"\n                  />{\" \"}\n                  Prev\n                </a>\n              </span>\n            )}\n            <span class=\"text-muted-foreground col-start-2 text-center\">\n              {page.currentPage} / {page.lastPage}\n            </span>\n            {page.url.next && (\n              <span class=\"flex justify-end\">\n                <a\n                  href={page.url.next}\n                  class=\"text-muted-foreground hover:text-foreground inline-flex gap-1\"\n                >\n                  Next{\" \"}\n                  <Icon\n                    name=\"arrow-right\"\n                    aria-hidden=\"true\"\n                    class=\"h-5 w-2.5 flex-none\"\n                  />\n                </a>\n              </span>\n            )}\n          </nav>\n        )\n      }\n    </div>\n  </section>\n</BaseLayout>\n"
  },
  {
    "path": "src/pages/notes/[...slug].astro",
    "content": "---\nimport { getCollection, render } from \"astro:content\";\n// import slugify from \"@sindresorhus/slugify\";\nimport BaseLayout from \"@/layouts/base-layout.astro\";\nimport ExternalLink from \"@/components/external-link.astro\";\nimport { formatLinkHostname } from \"@/lib/utils\";\nimport Demo from \"@/components/demo.astro\";\n\nexport async function getStaticPaths() {\n  const notes = await getCollection(\"notes\");\n  return notes.map((n) => ({\n    params: { slug: n.id },\n    props: n,\n  }));\n}\n\nconst note = Astro.props;\nconst { Content } = await render(note);\n---\n\n<BaseLayout title={note.data.title && !note.data.link ? note.data.title : null}\n  ><section class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\">\n    <div\n      class=\"mx-auto grid max-w-prose grid-cols-1 items-start gap-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]\"\n    >\n      <time\n        class=\"text-muted-foreground\"\n        datetime={note.data.published.toISOString()}\n      >\n        {\n          new Date(note.data.published).toLocaleDateString(\"en-US\", {\n            year: \"numeric\",\n            month: \"short\",\n            day: \"2-digit\",\n            weekday: \"short\",\n          })\n        }\n      </time>\n      <article>\n        {\n          note.data.title ? (\n            <header>\n              <h2>\n                {note.data.link ? (\n                  <ExternalLink href={note.data.link} text={note.data.title} />\n                ) : (\n                  note.data.title\n                )}\n              </h2>\n              {note.data.link ? (\n                <p class=\"text-muted-foreground\">\n                  {formatLinkHostname(note.data.link)}\n                </p>\n              ) : null}\n            </header>\n          ) : null\n        }\n        {\n          note.data.demo ? (\n            <Demo\n              class:list={[\n                \"mb-5\",\n                {\n                  \"mt-5\": note.data.title,\n                },\n              ]}\n              src={note.data.demo}\n              title={note.data.title}\n            />\n          ) : null\n        }\n        {\n          note.body ? (\n            <div class=\"prose mt-5\">\n              <Content />\n            </div>\n          ) : null\n        }\n      </article>\n      <!-- {\n        note.data.tags ? (\n          <footer class=\"text-muted-foreground flex lg:justify-end\">\n            <dl>\n              <dt class=\"sr-only\">Tagged</dt>\n              <dd class=\"text-right text-balance\">\n                {new Intl.ListFormat(\"en-US\")\n                  .formatToParts(note.data.tags)\n                  .map(({ type, value }) => {\n                    const slug = slugify(value);\n                    if (type === \"element\") {\n                      return <a href={`/tagged/${slug}`}>#{slug}</a>;\n                    }\n                    return value;\n                  })}\n              </dd>\n            </dl>\n          </footer>\n        ) : null\n      } -->\n    </div>\n  </section>\n</BaseLayout>\n"
  },
  {
    "path": "src/pages/notes/rss.xml.js",
    "content": "import rss from \"@astrojs/rss\";\nimport { getCollection } from \"astro:content\";\nimport sanitizeHtml from \"sanitize-html\";\nimport { getMarkdownParser } from \"@/lib/markdown.js\";\nconst parser = getMarkdownParser();\n\nexport async function GET(context) {\n  const notes = await getCollection(\"notes\");\n  return rss({\n    title: \"Notes - Alex Carpenter\",\n    description:\n      \"Notes on engineering, developer experience, design systems, and accessibility.\",\n    site: context.site,\n    items: notes\n      .sort((a, b) => b.data.published - a.data.published)\n      .map((post) => ({\n        title: post.data.title,\n        description: post.data.description,\n        pubDate: post.data.published,\n        link: `/notes/${post.id}`,\n        content:\n          post.body &&\n          sanitizeHtml(parser.render(post.body), {\n            allowedTags: sanitizeHtml.defaults.allowedTags.concat([\"img\"]),\n          }),\n      })),\n  });\n}"
  },
  {
    "path": "src/pages/uses/index.astro",
    "content": "---\nimport { getCollection } from \"astro:content\";\nimport BaseLayout from \"@/layouts/base-layout.astro\";\nimport { GEAR_CATEGORY_MAP, GEAR_CATEGORIES } from \"@/consts\";\nimport ExternalLink from \"@/components/external-link.astro\";\nimport slugify from \"@sindresorhus/slugify\";\n\nconst gear = await getCollection(\"gear\");\nconst activeGear = gear.filter((item) => item.data.status === \"active\");\n\nconst groupedGear = Array.from(\n  activeGear.reduce((map, item) => {\n    const key = item.data.category;\n    if (!map.has(key)) {\n      map.set(key, []);\n    }\n    map.get(key)!.push(item);\n    return map;\n  }, new Map<string, typeof activeGear>()),\n).sort(([categoryA], [categoryB]) => {\n  return (\n    GEAR_CATEGORIES.indexOf(categoryA as (typeof GEAR_CATEGORIES)[number]) -\n    GEAR_CATEGORIES.indexOf(categoryB as (typeof GEAR_CATEGORIES)[number])\n  );\n});\n---\n\n<BaseLayout title=\"Uses\">\n  <section class=\"mt-40 px-4\">\n    <div class=\"mx-auto max-w-prose\">\n      <h2 class=\"text-muted-foreground text-balance\">\n        <span class=\"text-foreground\">Uses</span>\n      </h2>\n    </div>\n  </section>\n  {\n    groupedGear.map(([category, items]) => {\n      const categoryInfo =\n        GEAR_CATEGORY_MAP[category as keyof typeof GEAR_CATEGORY_MAP];\n      if (!categoryInfo || items.length === 0) return null;\n\n      return (\n        <section\n          class=\"border-separator mt-20 border-t px-4 pt-20 lg:px-20\"\n          id={slugify(categoryInfo.title)}\n        >\n          <div class=\"mx-auto grid max-w-prose grid-cols-1 gap-y-10 lg:max-w-none lg:grid-cols-[1fr_65ch_1fr]\">\n            <h2>{categoryInfo.title}</h2>\n            <ul class=\"grid grid-cols-2 gap-10 lg:col-span-2 lg:grid-cols-4\">\n              {items.map((item) => (\n                <li>\n                  <div>\n                    {item.data.eyebrow && (\n                      <p class=\"text-muted-foreground text-[0.625rem] tracking-widest uppercase\">\n                        {item.data.eyebrow}\n                      </p>\n                    )}\n                    <h3>\n                      {item.data.link ? (\n                        <ExternalLink\n                          href={item.data.link}\n                          text={item.data.name}\n                        />\n                      ) : (\n                        item.data.name\n                      )}\n                    </h3>\n                    {item.data.link && (\n                      <p class=\"text-muted-foreground\">\n                        {new URL(item.data.link).hostname}\n                      </p>\n                    )}\n                  </div>\n                </li>\n              ))}\n            </ul>\n          </div>\n        </section>\n      );\n    })\n  }\n</BaseLayout>\n"
  },
  {
    "path": "src/styles/demos.css",
    "content": "@import \"tailwindcss\";\n\n@variant dark (@media (prefers-color-scheme: dark));\n\n@theme {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: --alpha(var(--color-foreground) / 65%);\n  --color-accent: var(--accent);\n}\n\n:root {\n  --background: var(--color-white);\n  --foreground: var(--color-black);\n  --muted: var(--color-neutral-200);\n  --accent: var(--color-blue-600);\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    color-scheme: dark;\n    --background: var(--color-black);\n    --foreground: var(--color-white);\n    --muted: var(--color-neutral-800);\n    --accent: var(--color-blue-400);\n  }\n}\n\n@layer utilities {\n  .bg-grid {\n    background: conic-gradient(\n      from 90deg at 50% 50%,\n      transparent 0deg 90deg,\n      var(--color-muted) 90deg 180deg,\n      transparent 180deg 270deg,\n      var(--color-muted) 270deg 360deg\n    );\n    background-size: 8px 8px;\n  }\n}\n"
  },
  {
    "path": "src/styles/global.css",
    "content": "@import \"tailwindcss\";\n\n@font-face {\n  font-family: \"GeistMono\";\n  src: url(\"/fonts/GeistMono[wght].woff2\") format(\"woff2\");\n  font-weight: 400 500;\n  font-style: normal;\n  font-display: swap;\n}\n\n@variant dark (@media (prefers-color-scheme: dark));\n\n@theme {\n  --font-*: initial;\n  --font-mono:\n    \"GeistMono\", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,\n    \"Liberation Mono\", \"Courier New\", monospace;\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-separator: var(--separator);\n  --color-red: var(--red);\n  --color-blue: var(--blue);\n  --color-green: var(--green);\n  --color-orange: var(--orange);\n  --color-purple: var(--purple);\n}\n\n:root {\n  --background: var(--color-zinc-50);\n  --foreground: var(--color-zinc-800);\n  --muted: var(--color-zinc-200);\n  --muted-foreground: var(--color-zinc-500);\n  --separator: var(--color-zinc-200);\n  --red: var(--color-red-600);\n  --blue: var(--color-blue-600);\n  --green: var(--color-green-600);\n  --orange: var(--color-orange-600);\n  --purple: var(--color-purple-600);\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    color-scheme: dark;\n    --background: var(--color-zinc-950);\n    --foreground: var(--color-zinc-50);\n    --muted: var(--color-zinc-800);\n    --muted-foreground: var(--color-zinc-400);\n    --separator: var(--color-zinc-800);\n    --red: var(--color-red-500);\n    --blue: var(--color-blue-500);\n    --green: var(--color-green-500);\n    --orange: var(--color-orange-500);\n    --purple: var(--color-purple-500);\n  }\n\n  .astro-code,\n  .astro-code span {\n    color: var(--shiki-dark) !important;\n  }\n}\n\n@layer base {\n  html {\n    height: 100%;\n    background-color: var(--color-background);\n  }\n\n  body {\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    display: flex;\n    flex-direction: column;\n    min-height: 100%;\n    background-color: var(--color-background);\n    color: var(--color-foreground);\n  }\n\n  mark,\n  :not(pre) > code {\n    background-color: var(--color-muted);\n    color: inherit;\n    padding-inline: var(--spacing);\n    box-decoration-break: clone;\n    border-radius: var(--radius-sm);\n  }\n\n  pre {\n    --pre-background-color: light-dark(\n      var(--color-zinc-100),\n      var(--color-zinc-900)\n    );\n    padding-block: calc(var(--spacing) * 4);\n    font-size: var(--text-xs);\n    line-height: 1.5;\n    background-color: var(--pre-background-color) !important;\n    border-radius: var(--radius-md);\n    border: 1px solid var(--color-separator);\n    scrollbar-width: thin;\n    scrollbar-color: var(--color-separator) transparent;\n    outline-offset: 4px;\n  }\n\n  pre code {\n    isolation: isolate;\n    display: block;\n    width: fit-content;\n    min-width: 100%;\n    counter-reset: line;\n\n    & .line {\n      --line-background-color: var(--pre-background-color);\n      --line-border-color: var(--line-background-color);\n      display: inline-block;\n      width: 100%;\n      counter-increment: line;\n      background-color: var(--line-background-color);\n\n      @media (hover: hover) {\n        &:hover {\n          --line-background-color: light-dark(\n            var(--color-zinc-200),\n            var(--color-zinc-800)\n          );\n        }\n      }\n\n      &::before {\n        pointer-events: none;\n        position: sticky;\n        left: 0;\n        z-index: 20;\n        display: inline-block;\n        width: 2.75rem;\n        background: linear-gradient(\n          to right,\n          var(--line-background-color) 80%,\n          transparent\n        );\n        padding-right: 1rem;\n        text-align: right;\n        color: var(--color-muted-foreground);\n        content: counter(line);\n        user-select: none;\n        border-left: 2px solid var(--line-border-color);\n      }\n\n      &::after {\n        pointer-events: none;\n        position: sticky;\n        right: 0;\n        z-index: 10;\n        display: inline-block;\n        width: 1.5rem;\n        background: linear-gradient(\n          to left,\n          var(--line-background-color) 35%,\n          transparent\n        );\n        content: \" \";\n        user-select: none;\n      }\n\n      &.highlighted,\n      &.remove,\n      &.add {\n        --line-background-color: color-mix(\n          in oklab,\n          var(--line-border-color),\n          var(--pre-background-color) 90%\n        );\n\n        @media (hover: hover) {\n          &:hover {\n            --line-background-color: color-mix(\n              in oklab,\n              var(--line-border-color),\n              var(--pre-background-color) 80%\n            );\n          }\n        }\n\n        &::before {\n          color: var(--line-border-color);\n        }\n      }\n\n      &.highlighted {\n        --line-border-color: light-dark(\n          oklch(0.4949 0.180091 257.6013),\n          oklch(0.7685 0.1214 252.34)\n        );\n      }\n\n      &.remove {\n        --line-border-color: light-dark(\n          oklch(0.5879 0.1927 20.47),\n          oklch(0.7214 0.1616 15.49)\n        );\n        &::before {\n          content: \"-\";\n        }\n      }\n\n      &.add {\n        --line-border-color: light-dark(\n          oklch(0.5464 0.1442 147.32),\n          oklch(0.8512 0.1406 150.34)\n        );\n        &::before {\n          content: \"+\";\n        }\n      }\n    }\n  }\n\n  pre[data-language=\"bash\"] {\n    .line::before {\n      content: \"$\";\n    }\n  }\n}\n\n@layer components {\n  .prose {\n    color: var(--color-muted-foreground);\n\n    & > :where(* + *) {\n      margin-top: calc(var(--spacing) * 5);\n    }\n\n    & :where(a) {\n      color: var(--color-foreground);\n      text-decoration: underline;\n      text-decoration-color: var(--color-muted-foreground);\n    }\n\n    & :where(blockquote) {\n      display: flex;\n      column-gap: var(--spacing);\n      &::before {\n        content: \"\\BB\";\n      }\n    }\n\n    & :where(:is(h1, h2, h3)) {\n      color: var(--color-foreground);\n    }\n\n    & :where(ul) {\n      display: flex;\n      flex-direction: column;\n      row-gap: var(--spacing);\n    }\n\n    & :where(li) {\n      position: relative;\n      padding-inline-start: calc(1ch + var(--spacing));\n      &::before {\n        content: \"\\203A\";\n        position: absolute;\n        top: 0;\n        left: 0;\n      }\n    }\n  }\n}\n\n@layer utilities {\n  .bg-grid {\n    background: conic-gradient(\n      from 90deg at 50% 50%,\n      transparent 0deg 90deg,\n      var(--color-muted) 90deg 180deg,\n      transparent 180deg 270deg,\n      var(--color-muted) 270deg 360deg\n    );\n    background-size: 8px 8px;\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"astro/tsconfigs/base\",\n  \"include\": [\".astro/types.d.ts\", \"**/*\"],\n  \"exclude\": [\"dist\"],\n  \"compilerOptions\": {\n    \"baseUrl\": \"./src\",\n    \"paths\": {\n      \"@/*\": [\"*\"]\n    },\n    \"moduleResolution\": \"bundler\"\n  }\n}\n"
  }
]