- スキャンID:
- 471e96cc-029b-4f9d-9a11-c289303ae903終了
- 送信済みURL:
- https://olets.dev/
- レポート終了日:
リンク · 6件検出
ページから特定された発信リンク
リンク | テキスト |
---|---|
https://www.codeberg.org/olets | Codeberg |
https://www.github.com/olets | GitHub |
https://www.reddit.com/u/olets | |
https://hachyderm.io/@olets | Mastodon |
https://www.linkedin.com/in/henry-bley-vroman/ | |
https://creativecommons.org/licenses/by/4.0/?ref=chooser-v1 | CC BY 4.0 |
JavaScript変数 · 11件検出
ページのウィンドウオブジェクトにロードされたグローバルのJavaScript変数は関数以外の場所で宣言された変数で、現在のスコープ内であればコードのどこからでもアクセス可能です
名前 | 規模 |
---|---|
onbeforetoggle | object |
documentPictureInPicture | object |
onscrollend | object |
checkMobileUserAgent | function |
restoreColorSchemePreference | function |
storeColorSchemePreference | function |
__cp_domReady | function |
__CPEmbed | function |
PagefindUI | function |
A11yDialog | function |
コンソールログメッセージ · 0件検出
Webコンソールにログ記録されたメッセージ
HTML
未加工のHTMLページ本文
<!DOCTYPE html><html class="antialiased scroll-smooth [&[data-search-dialog-open]]:[scrollbar-gutter:stable]" lang="en-US" dir="ltr"><head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>olets.dev</title>
<script src="https://cdn.usefathom.com/script.js" data-site="FSYFQZQY" defer=""></script>
<link rel="preload" href="/fonts/HeyAugust-subset.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="preload" href="/fonts/FiraCode-Regular.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#ffc40d">
<meta name="theme-color" content="#ffffff">
<meta name="author" content="Henry Bley-Vroman">
<meta name="description" content="Henry Bley-Vroman (olets)">
<meta property="fediverse:creator" content="@[email protected]">
<meta property="og:title" content="olets.dev">
<meta property="og:description" content="Henry Bley-Vroman (olets)">
<meta property="og:url" content="https://olets.dev/">
<meta property="og:site_name" content="">
<meta property="og:type" content="website">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image" content="https://olets.imgix.net/oletsdev-og--domed-falsify-overact.jpg?auto=compress,format&h=630&w=1200">
<meta name="twitter:title" content="olets.dev">
<meta name="twitter:creator" content="@olets">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="Henry Bley-Vroman (olets)">
<meta property="twitter:image" content="https://olets.imgix.net/oletsdev-og--domed-falsify-overact.jpg?auto=compress,format&h=471&w=942">
<link rel="stylesheet" href="/css/views.css">
<link href="mailto:[email protected]" rel="me">
<link href="https://twitter.com/olets" rel="me">
<link href="https://github.com/olets" rel="me">
<link href="https://hachyderm.io/@olets" rel="me">
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="olets.dev">
<link rel="alternate" href="/feed/feed.json" type="application/json" title="olets.dev">
<meta data-pagefind-default-meta="image[content]" content="https://olets.imgix.net/beach.jpg?auto=compress,format&w=216">
<link rel="webmention" href="https://webmention.io/www.olets.dev/webmention">
</head>
<body class="flex flex-col md:text-lg min-h-screen px-4 md:px-10 [[data-search-dialog-open]_&]:overflow-y-clip" data-track-click-title-transform-value="olets.dev" data-js-modules="codeblock-gradient-position markdown-copy-buttons track-click track-search" data-track-search-filter-class="pagefind-ui__filter-checkbox" data-track-search-hit-class="pagefind-ui__result-link">
<label aria-hidden="true" class="hidden" hidden="">
Device has a touchscreen
<input aria-hidden="true" class="hidden" hidden="" name="touchscreen" type="checkbox" disabled="">
</label>
<script>
function checkMobileUserAgent() {
/*
* begin https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
* retrieved 2024-01-26
*/
let hasTouchScreen = false;
if ("maxTouchPoints" in navigator) {
hasTouchScreen = navigator.maxTouchPoints > 0;
} else if ("msMaxTouchPoints" in navigator) {
hasTouchScreen = navigator.msMaxTouchPoints > 0;
} else {
const mQ = matchMedia?.("(pointer:coarse)");
if (mQ?.media === "(pointer:coarse)") {
hasTouchScreen = !!mQ.matches;
} else if ("orientation" in window) {
hasTouchScreen = true; // deprecated, but good fallback
} else {
// Only as a last resort, fall back to user agent sniffing
const UA = navigator.userAgent;
hasTouchScreen =
/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA);
}
}
/*
* end
*/
if (hasTouchScreen) {
const input = document.querySelector('[name="touchscreen"]');
input.setAttribute("checked", "");
}
}
checkMobileUserAgent();
</script>
<a class="bg-white block left-4 focus:p-4 ring ring-link sr-only top-4 focus:not-sr-only focus:!fixed focus:z-50" href="#content" data-track-click-id-value="Click|olets.dev|Skip to content">
<div class="absolute inset-0 bg-link/5 w-full"></div>
Skip to content
</a>
<div class="flex-grow">
<div>
<div class="container mx-auto" data-pagefind-ignore="">
<div class="sidebar:float-right pb-14 sidebar:pb-0 pt-6 sidebar:pt-0 sidebar:w-[16rem] sidebar:pl-4">
<div class="py-4 sidebar:py-10 !pb-0">
<div class="flex flex-col gap-4 text-right sidebar:gap-7">
<header role="banner" class="flex flex-col gap-4 items-space-between sidebar:gap-7">
<div class="sidebar:relative">
<a class="aspect-square block group relative rounded-full w-16 overflow-hidden sidebar:mx-auto sidebar:w-24 lg:w-32" data-track-click-id-value="Click|Nav|Avatar" href="/">
<picture><source type="image/avif" srcset="/TND8NpELkm-128.avif 128w"><source type="image/webp" srcset="/TND8NpELkm-128.webp 128w"><img alt="" class="absolute inset-0 w-full" src="/TND8NpELkm-128.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/portrait-photo-paddler-bw.jpg?auto=compress,format&ar=1:1&fit=crop&w=128');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1:1&fit=crop&w=128';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="128" data-preload="true" height="128"></picture>
<span class="!block" style="display: none">
<picture><source type="image/avif" srcset="/2B1qo7e_aH-128.avif 128w"><source type="image/webp" srcset="/2B1qo7e_aH-128.webp 128w"><img alt="" class="absolute group-focus:opacity-100 hover:opacity-100 inset-0 opacity-0 transition-opacity w-full" src="/2B1qo7e_aH-128.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/portrait-photo-paddler.jpg?auto=compress,format&ar=1:1&fit=crop&w=128');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1:1&fit=crop&w=128';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="128" height="128"></picture>
</span>
<br class="hidden">
<span class="sr-only">Homepage</span>
</a>
<a href="mailto:[email protected]" class="inline-block absolute px-4 py-2 bg-background text-blue-600 z-40 rounded-[100%] top-2 sidebar:top-auto sidebar:-bottom-5 right-6 -rotate-12 text-base focus-visible:bg-blue-600 focus-visible:text-background hover:bg-blue-600 hover:text-background transition-all outline outline-blue-600 outline-2 focus-visible:outline-offset-8" data-track-click-id-value="Click|olets.dev|hire me ✉️" rel="noopener noreferrer">hire me ✉️</a>
</div>
<section class="mt-2 text-center">
<h2 class="sr-only">Social links</h2>
<ul class="flex sidebar:inline-grid gap-2 grid-cols-2 list-none p-1 lg:grid-cols-3">
<li>
<a class="flex flex-col items-center justify-center p-2 " data-track-click-id-value="Click|Nav|Social|Codeberg" href="https://www.codeberg.org/olets" rel="noopener noreferrer">
<span class="mx-auto inline-block transition-colors focus:text-link hover:text-link [&>svg]:w-8 [&>svg]:h-8" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 4.23 4.23">
<defs>
<linearGradient id="a">
<stop offset="0" stop-color="currentColor" stop-opacity="0"></stop>
<stop offset=".5" stop-color="currentColor" stop-opacity="0.48923996"></stop>
<stop offset="1" stop-color="currentColor" stop-opacity="0.63279623"></stop>
</linearGradient>
<linearGradient xlink:href="#b" id="c" x1="42519.29" x2="42575.34" y1="-7078.79" y2="-6966.93" gradientUnits="userSpaceOnUse"></linearGradient>
<linearGradient id="b">
<stop offset="0" stop-color="currentColor" stop-opacity="0"></stop>
<stop offset=".5" stop-color="currentColor" stop-opacity="0.30000001"></stop>
<stop offset="1" stop-color="currentColor" stop-opacity="0.30000001"></stop>
</linearGradient>
</defs>
<path d="M42519.29-7078.79a.76.57 0 0 0-.74.67l33.58 125.9a87.18 87.18 0 0 0 39.38-33.77l-71.56-92.52a.76.57 0 0 0-.66-.28z" style="fill:url(#c)" color="currentColor" transform="matrix(0.06551432,0,0,0.06551432,-2.232417,-1.431776) matrix(0.37058478,0,0,0.37058478,-15690.065,2662.0533)"></path>
<path d="M11249.46-1883.7a23.07 23.07 0 0 0-19.54 35.32l19.23-24.86c.14-.18.48-.18.62 0l19.24 24.86a23.07 23.07 0 0 0-19.55-35.32z" color="currentColor" transform="matrix(0.06551432,0,0,0.06551432,-2.232417,-1.431776) matrix(1.4006354,0,0,1.4006354,-15690.065,2662.0533)"></path>
</svg>
</span>
<span class="sr-only">Codeberg</span>
</a>
</li>
<li>
<a class="flex flex-col items-center justify-center p-2 " data-track-click-id-value="Click|Nav|Social|GitHub" href="https://www.github.com/olets" rel="noopener noreferrer">
<span class="mx-auto inline-block transition-colors focus:text-link hover:text-link [&>svg]:w-8 [&>svg]:h-8" aria-hidden="true">
<svg viewBox="0 0 24 24" width="32" height="32" xmlns="http://www.w3.org/2000/svg">
<path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.07 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3"></path>
</svg>
</span>
<span class="sr-only">GitHub</span>
</a>
</li>
<li>
<a class="flex flex-col items-center justify-center p-2 " data-track-click-id-value="Click|Nav|Social|Reddit" href="https://www.reddit.com/u/olets" rel="noopener noreferrer">
<span class="mx-auto inline-block transition-colors focus:text-link hover:text-link [&>svg]:w-8 [&>svg]:h-8" aria-hidden="true">
<svg viewBox="0 0 24 24" width="32" height="32" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.74c.69 0 1.25.56 1.25 1.25a1.25 1.25 0 0 1-2.5.06l-2.6-.55-.8 3.75c1.83.07 3.48.63 4.68 1.49.3-.31.73-.5 1.2-.5.97 0 1.76.8 1.76 1.76 0 .72-.43 1.33-1.01 1.61a3.11 3.11 0 0 1 .04.52c0 2.7-3.13 4.87-7 4.87-3.88 0-7-2.17-7-4.87 0-.18 0-.36.04-.53A1.75 1.75 0 0 1 4.03 12a1.75 1.75 0 0 1 2.96-1.26 8.52 8.52 0 0 1 4.74-1.5l.89-4.17a.34.34 0 0 1 .14-.2.35.35 0 0 1 .24-.04l2.9.62a1.21 1.21 0 0 1 1.11-.7zM9.25 12a1.25 1.25 0 1 0 1.25 1.25c0-.69-.56-1.25-1.25-1.25zm5.5 0a1.25 1.25 0 0 0 0 2.5 1.25 1.25 0 0 0 0-2.5zm-5.47 3.99a.33.33 0 0 0-.23.1.33.33 0 0 0 0 .46c.84.84 2.49.91 2.96.91.48 0 2.1-.06 2.96-.91a.36.36 0 0 0 .03-.47.33.33 0 0 0-.46 0c-.55.54-1.68.73-2.51.73-.83 0-1.98-.2-2.51-.73a.33.33 0 0 0-.24-.1z"></path>
</svg>
</span>
<span class="sr-only">Reddit</span>
</a>
</li>
<li>
<a class="flex flex-col items-center justify-center p-2 " data-track-click-id-value="Click|Nav|Social|Mastodon" href="https://hachyderm.io/@olets" rel="noopener noreferrer">
<span class="mx-auto inline-block transition-colors focus:text-link hover:text-link [&>svg]:w-8 [&>svg]:h-8" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="32" height="32">
<path d="M433 179.11c0-97.2-63.71-125.7-63.71-125.7-62.52-28.7-228.56-28.4-290.48 0 0 0-63.72 28.5-63.72 125.7 0 115.7-6.6 259.4 105.63 289.1 40.51 10.7 75.32 13 103.33 11.4 50.81-2.8 79.32-18.1 79.32-18.1l-1.7-36.9s-36.31 11.4-77.12 10.1c-40.41-1.4-83-4.4-89.63-54a102.54 102.54 0 0 1-.9-13.9c85.63 20.9 158.65 9.1 178.75 6.7 56.12-6.7 105-41.3 111.23-72.9 9.8-49.8 9-121.5 9-121.5zm-75.12 125.2h-46.63v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.33V197c0-58.5-64-56.6-64-6.9v114.2H90.19c0-122.1-5.2-147.9 18.41-175 25.9-28.9 79.82-30.8 103.83 6.1l11.6 19.5 11.6-19.5c24.11-37.1 78.12-34.8 103.83-6.1 23.71 27.3 18.4 53 18.4 175z"></path>
</svg>
</span>
<span class="sr-only">Mastodon</span>
</a>
</li>
<li>
<a class="flex flex-col items-center justify-center p-2 " data-track-click-id-value="Click|Nav|Social|LinkedIn" href="https://www.linkedin.com/in/henry-bley-vroman/" rel="noopener noreferrer">
<span class="mx-auto inline-block transition-colors focus:text-link hover:text-link [&>svg]:w-8 [&>svg]:h-8" aria-hidden="true">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<path d="M20.45 20.45h-3.56v-5.57c0-1.32-.02-3.03-1.85-3.03-1.85 0-2.13 1.44-2.13 2.94v5.66H9.35V9h3.42v1.56h.04a3.75 3.75 0 0 1 3.37-1.85c3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zm1.78 13.02H3.56V9h3.56v11.45zM22.22 0H1.78C.8 0 0 .77 0 1.73v20.54C0 23.23.8 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.73V1.73C24 .77 23.2 0 22.22 0z"></path>
</svg>
</span>
<span class="sr-only">LinkedIn</span>
</a>
</li>
<li>
<a class="flex flex-col items-center justify-center p-2 max-sidebar:hidden" data-track-click-id-value="Click|Nav|Social|Email" href="mailto:[email protected]" rel="noopener noreferrer">
<span class="mx-auto inline-block transition-colors focus:text-link hover:text-link [&>svg]:w-8 [&>svg]:h-8" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="32" height="32">
<path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"></path>
</svg>
</span>
<span class="sr-only">Email</span>
</a>
</li>
</ul>
</section>
<label class="!flex sidebar:flex-col items-center sidebar:justify-center gap-2" id="color-scheme" style="display: none">
<div id="color-scheme-picker-label">Appearance:</div>
<select aria-labelledby="color-scheme-picker-label" class="border border-link border-1 rounded text-center">
<option value="system" selected="">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<script>
function restoreColorSchemePreference() {
const preferredColorScheme = localStorage.getItem(colorSchemeSelectorStorageItem);
if (!preferredColorScheme) {
return;
}
const option = colorSchemeSelectorEl.querySelector(`[value=${preferredColorScheme}]`);
if (!option) {
localStorage.removeItem(colorSchemeSelectorStorageItem);
return;
}
option.selected = true;
}
function storeColorSchemePreference({ target }) {
const preferredColorScheme = target.querySelector(":checked").value;
localStorage.setItem(colorSchemeSelectorStorageItem, preferredColorScheme);
}
const colorSchemeSelectorStorageItem = "preferredColorScheme";
const colorSchemeSelectorEl = document.querySelector("#color-scheme");
if (colorSchemeSelectorEl) {
restoreColorSchemePreference();
colorSchemeSelectorEl.addEventListener("input", storeColorSchemePreference)
}
</script>
</header>
<nav aria-labelledby="nav-aria-label" class="text-center" data-pagefind-ignore="" role="navigation">
<h2 class="sr-only" id="nav-aria-label">Site Navigation</h2>
<ul class="flex flex-wrap gap-4 sidebar:grid-cols-2 sidebar:inline-grid sidebar:justify-center">
<li class="!block col-span-2 text-center" style="display: none">
<button data-a11y-dialog-show="search-dialog" data-track-click-id-value="Click|Search Dialog Trigger" type="button">
<span class="link">Search</span>
<div class="inline text-sm touchscreen:hidden"><kbd>/</kbd> or <kbd><span class="meta-key-symbol">⌘</span>K</kbd></div>
</button>
<script>
document.body.insertAdjacentHTML("beforeend", `
<div
aria-hidden="true"
aria-labelledby="search-dialog-title"
class="fixed inset-0 z-[100] w-full !flex overscroll-contain aria-hidden:!hidden"
id="search-dialog"
data-js-modules="search-dialog"
style="display: none"
>
<div
class="animate-fade-in bg-foreground fixed inset-0 opacity-60 transition-opacity w-full"
data-a11y-dialog-hide
></div>
<div
class="animate-fade-in container flex justify-between mx-auto w-full"
role="document"
>
<div data-a11y-dialog-hide class="hidden xl:block w-48 flex-shrink-0"></div>
<div data-a11y-dialog-hide class="w-6 md:w-14 flex-shrink-0"></div>
<div class="flex flex-col justify-center flex-grow min-w-0">
<div data-a11y-dialog-hide class="flex-shrink-0 h-12"></div>
<div data-a11y-dialog-hide class="flex-shrink-0 flex-grow"></div>
<div class="relative z-10 mx-auto w-full">
<h1
class="sr-only"
id="search-dialog-title"
>
Search
</h1>
<button
class="absolute aspect-square bg-background flex font-bold items-center justify-center leading-none opacity-75 p-2 rounded-lg right-0 -top-12 text-2xl transition-opacity w-10 z-10 focus-visible:opacity-100 hover:opacity-100"
data-a11y-dialog-hide aria-label="Close dialog"
type="button"
>
<span>×</span>
</button>
<div class="bg-background rounded-lg shadow [text-align:initial]">
<div class="h-100dvh max-h-[70dvh] overflow-y-auto overscroll-y-contain" tabindex="0">
<div
class="search-box leading-snug max-h-full overflow-y-auto p-4 md:p-6 [&_.pagefind-ui]:[--pagefind-ui-scale:0.9]"
tabindex="0"
></div>
</div>
<div class="border-t p-4 md:px-6 md:py-3 text-sm touchscreen:hidden">
Type <kbd>/</kbd> or <kbd><span class="meta-key-symbol">⌘</span>K</kbd> to open, <kbd>esc</kbd> to close
</div>
</div>
</div>
<div data-a11y-dialog-hide class="flex-shrink-0 flex-grow"></div>
</div>
<div data-a11y-dialog-hide class="w-6 md:w-14 flex-shrink-0"></div>
<div data-a11y-dialog-hide class="md:order-last hidden xl:block w-48 flex-shrink-0"></div>
</div>
</div>
`);
</script>
</li>
<li class="hidden">
<a href="/search" data-track-click-id-value="Click|olets.dev|Search">Search</a>
</li>
<li class="text-center">
<a data-track-click-id-value="Click|Nav|Home" href="/">Home</a>
</li>
<li class="text-center">
<a data-track-click-id-value="Click|Nav|Articles" href="/posts/">Articles</a>
</li>
<li class="text-center">
<a data-track-click-id-value="Click|Nav|Software" href="/software/">Software</a>
</li>
<li class="text-center">
<a data-track-click-id-value="Click|Nav|Feed" href="/feed/feed.xml">
Feed
</a>
</li>
<li class="text-center">
<a data-track-click-id-value="Click|Nav|Uses" href="/uses">
Uses
</a>
</li>
<li class="text-center">
<a data-track-click-id-value="Click|Nav|Colophon" href="/colophon">
Colophon
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<hr class="hidden">
<main class="py-4 sidebar:py-10" role="main">
<div id="content">
<h1 class="heading-1 pb-4">Henry <span class="whitespace-nowrap">Bley-Vroman</span></h1>
<section class="my-2 xl:mt-16 xl:mb-32">
<h2 class="sr-only">Bio</h2>
<div class="max-w-4xl relative">
(pronouns he/him, pronounced /ˈhɛn.ɹi blaɪ ˈvɹəʊmən/
<button class="" data-js-modules="audio-player" type="button">🔈<span class="sr-only"></span><audio class="hidden" src="/audio/ˈhɛnɹi-blaɪ-ˈvɹəʊmən.mp3"></audio></button>
— last name rhymes with "sly Roman")
is a <strong>dev team lead, developer of accessible UIs, mentor and coach, open source maintainer and contributor, and DEI advocate</strong>. He builds things you see on the web, creates tools to improve other builders' daily experience, teaches and mentors at work and in the tech community, and strives to support safe, kind, fair, and welcoming cultures. Most recently: established Senior UI Developer, mentor to the UI Developer apprentice, and chair of the DEI Advisory Committee at Viget. Past lives: ReCYCLEry community bike mechanic and advocate, Boston University cognitive neurobiology doctoral candidate. Current passions: Anglo concertina, outrigger canoe racing. Online as olets (pronounced /oʊ lɛts/
<button class="" data-js-modules="audio-player" type="button">🔈<span class="sr-only"></span><audio class="hidden" src="/audio/oʊ-lɛts.mp3"></audio></button>
— "oh let's").
</div>
</section>
</div>
<nav aria-labelledby="page-nav-aria-label" class="border flex flex-col gap-2 mt-12 p-6 rounded-md sm:w-fit md:mt-16 lg:hidden lg:mt-20">
<div class="font-bold" id="page-nav-aria-label">On this page:</div>
<ul class="flex gap-4">
<li><a href="#latest" data-track-click-id-value="Click|olets.dev|Latest Articles">Latest Articles</a></li>
<li><a href="#software" data-track-click-id-value="Click|olets.dev|Select Software">Select Software</a></li>
<li><a href="#select-clients" data-track-click-id-value="Click|olets.dev|Select Clients">Select Clients</a></li>
</ul>
</nav>
<div class="gap-24 mb-24 grid mt-12 md:mt-16 lg:grid-cols-2 lg:mt-20">
<section class="flex flex-col gap-8" id="latest">
<h2 class="heading-2 pb-4 sm:heading-3">Latest Articles</h2>
<div class="text-2xl lg:text-3xl">
<div class="max-w-4xl ">
See
<a href="/posts" data-track-click-id-value="Click|Home|Articles">all articles</a>
</div>
</div>
<ul class="flex flex-col gap-12">
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/posts/migrate-from-vuepress-to-vitepress/" data-track-click-id-value="Click|Home|Post Card 1 of 6|Migrating from VuePress to VitePress">
<div class="sr-only">Migrating from VuePress to VitePress</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/ZxENU6zBkI-240.avif 240w"><source type="image/webp" srcset="/ZxENU6zBkI-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/ZxENU6zBkI-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/thomas-buffalo-crossing.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>Migrating from VuePress to VitePress</span>
</p>
<p class="mt-2">Get better search DX, and Vue.js team support</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/posts/styling-vitepress-with-tailwind/" data-track-click-id-value="Click|Home|Post Card 2 of 6|Styling VitePress with Tailwind">
<div class="sr-only">Styling VitePress with Tailwind</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/8icpe202wL-240.avif 240w"><source type="image/webp" srcset="/8icpe202wL-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/8icpe202wL-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/cider-press-17th-century.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>Styling VitePress with Tailwind</span>
</p>
<p class="mt-2">Configure Tailwind as a PostCSS plugin to use it in VitePress Markdown, JS/TS, and Vue files</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/posts/ssg-astro-with-headless-craft-cms-content-fetched-at-build-time-or-cached-in-advance/" data-track-click-id-value="Click|Home|Post Card 3 of 6|SSG Astro with Headless Craft CMS Content Fetched At Build Time Or Cached In Advance">
<div class="sr-only">SSG Astro with Headless Craft CMS Content Fetched At Build Time Or Cached In Advance</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/uNOKEh_5Cn-240.avif 240w"><source type="image/webp" srcset="/uNOKEh_5Cn-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/uNOKEh_5Cn-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/Shuttle_in_outer_space_by_NASA.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>SSG Astro with Headless Craft CMS Content Fetched At Build Time Or Cached In Advance</span>
</p>
<p class="mt-2">Astro on the front, Craft on the back. Craft can be local only, and the build environment doesn't have to connect to the CMS.</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/posts/ssg-astro-with-headless-craft-cms-content-fetched-at-build-time/" data-track-click-id-value="Click|Home|Post Card 4 of 6|SSG Astro with Headless Craft CMS Content Fetched At Build Time">
<div class="sr-only">SSG Astro with Headless Craft CMS Content Fetched At Build Time</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/GEbTaw79Rr-240.avif 240w"><source type="image/webp" srcset="/GEbTaw79Rr-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/GEbTaw79Rr-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/rocket-express-toy.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>SSG Astro with Headless Craft CMS Content Fetched At Build Time</span>
</p>
<p class="mt-2">Astro on the front, Craft on the back. And Craft can be local-only!</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/posts/my-zshrc-zsh-configuration-annotated/" data-track-click-id-value="Click|Home|Post Card 5 of 6|My .zshrc Zsh Configuration, Annotated">
<div class="sr-only">My .zshrc Zsh Configuration, Annotated</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/o2sYn5Ew3j-240.avif 240w"><source type="image/webp" srcset="/o2sYn5Ew3j-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/o2sYn5Ew3j-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/scott-79-shells.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>My .zshrc Zsh Configuration, Annotated</span>
</p>
<p class="mt-2">How I set up my interactive shell</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/posts/ssr-astro-with-headless-craft-cms/" data-track-click-id-value="Click|Home|Post Card 6 of 6|SSR Astro With Headless Craft CMS">
<div class="sr-only">SSR Astro With Headless Craft CMS</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/JSO8xw2-Sw-240.avif 240w"><source type="image/webp" srcset="/JSO8xw2-Sw-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/JSO8xw2-Sw-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/hydrogen-fueled-rocket-glide-transport.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>SSR Astro With Headless Craft CMS</span>
</p>
<p class="mt-2">Craft’s great data modelling and admin experience, with a server-rendered Astro front end.</p>
</div>
</div>
</article>
</li>
</ul>
</section>
<section class="flex flex-col gap-8" id="software">
<h2 class="heading-2 pb-4 sm:heading-3">Select Software</h2>
<div class="text-2xl lg:text-3xl">
<div class="max-w-4xl ">
See
<a href="/software" data-track-click-id-value="Click|Home|Software">all software</a>
</div>
</div>
<ul class="flex flex-col gap-12">
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/software/zsh-window-title/" data-track-click-id-value="Click|Home|Software Card 1 of 6|undefined">
<div class="sr-only">zsh-window-title</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/IlY0LPJLR4-240.avif 240w"><source type="image/webp" srcset="/IlY0LPJLR4-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/IlY0LPJLR4-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/zsh-window-title.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>zsh-window-title</span>
</p>
<p class="mt-2">A zsh plugin for informative terminal window titles</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/software/zsh-test-runner/" data-track-click-id-value="Click|Home|Software Card 2 of 6|undefined">
<div class="sr-only">zsh-test-runner</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/VoZcSsCi0A-240.avif 240w"><source type="image/webp" srcset="/VoZcSsCi0A-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/VoZcSsCi0A-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/zsh-test-runner.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>zsh-test-runner</span>
</p>
<p class="mt-2">Lightweight dependable tests and coverage reports for zsh software. Use it for csh, ksh, and sh too</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/software/zsh-abbr/" data-track-click-id-value="Click|Home|Software Card 3 of 6|undefined">
<div class="sr-only">zsh-abbr</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/O9YnK59TzM-240.avif 240w"><source type="image/webp" srcset="/O9YnK59TzM-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/O9YnK59TzM-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/zsh-abbr.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>zsh-abbr</span>
</p>
<p class="mt-2">Save keystrokes in the terminal, but -unlike aliases- without forgetting the full command or making your history unintelligible</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/software/unocss-preset-css/" data-track-click-id-value="Click|Home|Software Card 4 of 6|undefined">
<div class="sr-only">unocss-preset-css</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/PFo7BM1ZVq-240.avif 240w"><source type="image/webp" srcset="/PFo7BM1ZVq-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/PFo7BM1ZVq-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/unocss-preset-css-card.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>unocss-preset-css</span>
</p>
<p class="mt-2">A front-end tool which unlocks writing something very close to vanilla CSS directly in the `class` attribute</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/software/hometown-prompt/" data-track-click-id-value="Click|Home|Software Card 5 of 6|undefined">
<div class="sr-only">Hometown Prompt</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/GJROYY-koe-240.avif 240w"><source type="image/webp" srcset="/GJROYY-koe-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/GJROYY-koe-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/hometown.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>Hometown Prompt</span>
</p>
<p class="mt-2">A beautiful Git-focused zsh theme with a high data-to-ink ratio and dynamic content/styles.</p>
</div>
</div>
</article>
</li>
<li>
<article class="flex flex-col gap-2">
<div class="
flex gap-3 group relative
">
<a class="absolute focus:outline-none inset-0 peer rounded-2xl w-full z-10" href="/software/git-replay/" data-track-click-id-value="Click|Home|Software Card 6 of 6|undefined">
<div class="sr-only">git-replay</div>
</a>
<div class="
h-fit relative shrink-0 w-1/3 shadow transition-shadow ring-offset-2 peer-focus:ring peer-hover:ring peer-focus:[&_img]:hue-rotate-[120deg] peer-hover:[&_img]:hue-rotate-[120deg] peer-focus:[&_img]:sepia-[0.2] peer-hover:[&_img]:sepia-[0.2]
rounded-t-md
rounded-b-md
">
<picture><source type="image/avif" srcset="/82WpIzmUu1-240.avif 240w"><source type="image/webp" srcset="/82WpIzmUu1-240.webp 240w"><img alt="" class="aspect-card border transition w-full
rounded-t-md
rounded-b-md
" src="/82WpIzmUu1-240.jpeg" onerror="
console.log('Error loading https://olets.imgix.net/git-replay.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&ar=1280:640&fit=crop&w=240';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" width="240" height="120"></picture>
</div>
<div class="
rounded-t-md rounded-b-2xl transition-colors
peer-focus:text-link
peer-hover:text-link
">
<p aria-hidden="true" class="block font-semibold underline
">
<span>git-replay</span>
</p>
<p class="mt-2">Automate the rebasing of Git branches and creation of stage branches</p>
</div>
</div>
</article>
</li>
</ul>
</section>
</div>
<section class="flex flex-col gap-4 mt-12 pt-12 md:mt-16 lg:mt-20 lg:pt-0" id="select-clients">
<h2 class="heading-2 pb-4 sm:heading-3">Select Clients</h2>
<p class="text-2xl pb-8">
It's been my priviledge to work as a developer and to lead projects for some great clients, including
</p>
<ul class="grid grid-cols-2 gap-x-8 gap-y-10 sm:flex sm:flex-wrap sm:gap-x-12 sm:gap-y-14 lg:justify-start">
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="United Way logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/UnitedWay_logo_horz_fullblue_RGB.png?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/UnitedWay_logo_horz_fullblue_RGB.png?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">United Way</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="New York Aquarium logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/new-york-aquarium-logo.png?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/new-york-aquarium-logo.png?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">New York Aquarium</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="Bronx Zoo logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/bronx-zoo-logo.png?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/bronx-zoo-logo.png?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">Bronx Zoo</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="National Park Foundation logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/national-park-foundation-logo.svg?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/national-park-foundation-logo.svg?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">National Park Foundation</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="Emerson Collective logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/emerson-collective-logo.svg?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/emerson-collective-logo.svg?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">Emerson Collective</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="Project on Government Oversight logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/pogo-logo.jpg?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/pogo-logo.jpg?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">Project on Government Oversight</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="The Human Rights Campaign logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/the-human-rights-campaign-logo.svg?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/the-human-rights-campaign-logo.svg?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">The Human Rights Campaign</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="AARP logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/aarp-logo.svg?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/aarp-logo.svg?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">AARP</figcaption>
</figure>
</li>
<li>
<figure class="flex flex-col gap-2 items-start md:w-auto">
<img alt="The Ad Council logo" class="bg-white object-contain p-1 justify-self-center h-[3.5rem] max-w-full rounded sm:max-w-[10rem] md:max-w-[27rem]" src="https://olets.imgix.net/the-ad-council-logo.jpg?auto=compress,format&h=56" onerror="
console.log('Error loading https://olets.imgix.net/the-ad-council-logo.jpg?auto=compress,format&h=56');
this.onerror=null;
this.src='https://olets.imgix.net/beach.jpg?auto=compress,format&h=56';
this.classList=[this.classList, 'error-image'].join(' ');
this.alt='';
" height="56">
<figcaption class="block text-xs md:text-sm">The Ad Council</figcaption>
</figure>
</li>
<li aria-hidden="true" class="flex-grow"></li>
</ul>
</section>
</main>
</div>
</div>
</div>
<div aria-hidden="true" aria-labelledby="search-dialog-title" class="fixed inset-0 z-[100] w-full !flex overscroll-contain aria-hidden:!hidden" id="search-dialog" data-js-modules="search-dialog" style="display: none" aria-modal="true" tabindex="-1" role="dialog">
<div class="animate-fade-in bg-foreground fixed inset-0 opacity-60 transition-opacity w-full" data-a11y-dialog-hide=""></div>
<div class="animate-fade-in container flex justify-between mx-auto w-full" role="document">
<div data-a11y-dialog-hide="" class="hidden xl:block w-48 flex-shrink-0"></div>
<div data-a11y-dialog-hide="" class="w-6 md:w-14 flex-shrink-0"></div>
<div class="flex flex-col justify-center flex-grow min-w-0">
<div data-a11y-dialog-hide="" class="flex-shrink-0 h-12"></div>
<div data-a11y-dialog-hide="" class="flex-shrink-0 flex-grow"></div>
<div class="relative z-10 mx-auto w-full">
<h1 class="sr-only" id="search-dialog-title">
Search
</h1>
<button class="absolute aspect-square bg-background flex font-bold items-center justify-center leading-none opacity-75 p-2 rounded-lg right-0 -top-12 text-2xl transition-opacity w-10 z-10 focus-visible:opacity-100 hover:opacity-100" data-a11y-dialog-hide="" aria-label="Close dialog" type="button">
<span>×</span>
</button>
<div class="bg-background rounded-lg shadow [text-align:initial]">
<div class="h-100dvh max-h-[70dvh] overflow-y-auto overscroll-y-contain" tabindex="0">
<div class="search-box leading-snug max-h-full overflow-y-auto p-4 md:p-6 [&_.pagefind-ui]:[--pagefind-ui-scale:0.9]" tabindex="0"><div class="pagefind-ui svelte-e9gkc3"><form class="pagefind-ui__form svelte-e9gkc3" role="search" aria-label="Search this site" action="javascript:void(0);"><label for="search-dialog-search-input" class="sr-only">Search for:</label><input class="pagefind-ui__search-input svelte-e9gkc3" type="text" placeholder="Search" autocapitalize="none" enterkeyhint="search" autofocus="" id="search-dialog-search-input"> <button class="pagefind-ui__search-clear svelte-e9gkc3 pagefind-ui__suppressed">Clear</button> <div class="pagefind-ui__drawer svelte-e9gkc3 pagefind-ui__hidden"> </div></form></div></div>
</div>
<div class="border-t p-4 md:px-6 md:py-3 text-sm touchscreen:hidden">
Type <kbd>/</kbd> or <kbd><span class="meta-key-symbol">Ctrl</span>K</kbd> to open, <kbd>esc</kbd> to close
</div>
</div>
</div>
<div data-a11y-dialog-hide="" class="flex-shrink-0 flex-grow"></div>
</div>
<div data-a11y-dialog-hide="" class="w-6 md:w-14 flex-shrink-0"></div>
<div data-a11y-dialog-hide="" class="md:order-last hidden xl:block w-48 flex-shrink-0"></div>
</div>
</div>
<hr class="hidden">
<footer class="container flex flex-col gap-4 mx-auto pb-5 pt-40 relative" data-pagefind-ignore="" role="contentinfo">
<p class="max-w-4xl" xmlns:cc="http://creativecommons.org/ns#">
With the exception of snippets excerpted from work released under a different copyright and/or license, code snippets on this site are © 2024 by
<a rel="noopener noreferrer" property="cc:attributionName" href="https://olets.dev" data-track-click-id-value="Click|olets.dev|Henry Bley-Vroman">
Henry Bley-Vroman
</a>
and licensed under
<a class="gap-1 inline-flex items-center" href="https://creativecommons.org/licenses/by/4.0/?ref=chooser-v1" rel="noopener noreferrer" data-track-click-id-value="Click|olets.dev|CC BY 4.0">
CC BY 4.0
<picture><source type="image/avif" srcset="/rlZNrnUzqj-64.avif 64w"><source type="image/webp" srcset="/rlZNrnUzqj-64.webp 64w"><img class="align-baseline h-[1.25ch] w-[1.25ch]" src="/rlZNrnUzqj-64.jpeg" alt="Icon: the letters C C, in a circle" width="64" height="64"></picture>
<picture><source type="image/avif" srcset="/mfiZ3Oerkz-64.avif 64w"><source type="image/webp" srcset="/mfiZ3Oerkz-64.webp 64w"><img class="align-baseline h-[1.25ch] w-[1.25ch]" src="/mfiZ3Oerkz-64.jpeg" alt="Icon: ideogram of a person, in a circle" width="64" height="64"></picture>
</a>
unless otherwise noted.
</p>
<p>
<a href="/accessibility-statement" data-track-click-id-value="Click|olets.dev|Accessibility statement">Accessibility statement</a>.
</p>
<p>
Report bugs, typos, etc. in the
<a class="external-link whitespace-nowrap" href="https://github.com/olets/olets.dev-issues" data-track-click-id-value="Click|olets.dev|Footer|olets.dev-issues">olets.dev-issues repo</a>.
</p>
</footer>
<script src="//instant.page/5.2.0" type="module" integrity="sha384-jnZyxPjiipYXnSU0ygqeac2q7CVYMbh84q0uHVRRxEtvFPiQYbXWUorga2aqZJ0z"></script>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<script src="https://unpkg.com/a11y-dialog@8/dist/a11y-dialog.min.js"></script>
<script src="/js/index.js"></script>
<style>
.codeblock::after {
bottom: 10px;
}
</style></body></html>