Gravatar profile image

Brandon Rodkewitz

Developer • Teacher • Photographer

I’ve been a photographer for 15 years, documenting weddings and portraits with genuine intention and care. I enjoy the creative outlet and the constant pursuit of honing my craft. At its core, my work is that of a thoughtful observer—documenting life’s experiences with empathy and purpose, and creating images that truly matter to the people I serve.

I taught undergraduate web design and development for 10 years. I thrived on the shared enthusiasm of my students, and created environments where they felt comfortable failing, asking questions, and growing. My love of learning naturally extended into designing experiences that helped others learn. I refined my lessons and demos every semester, because the greatest reward was seeing students experience those “eureka” moments exactly as intended.

Now I’m pursuing a career as a developer. Coding was the subject of my teaching and has been a keen personal interest for most of my life. I’m eager to contribute to a team where curiosity, collaboration, and problem-solving shape the work. I’m excited to approach development with the same dedication—honing my craft, learning deeply, and creating tools and experiences that people genuinely value.

@charset "utf-8";

/* ****************************** */
/* RESETS */

* {
	box-sizing: border-box;
	margin-block: initial;
}

body {
	margin: initial;
	padding: initial;
}

img {
	display: block;
	max-width: 100%;
	height: auto;
}

figure {
	margin-inline: initial;
}

blockquote {
	margin-inline: initial;
}

pre {
	tab-size: 4;
}

button {
	cursor: pointer;
}

/* ****************************** */
/* TYPOGRAPHY */

@font-face {
	font-family: "Switzer Variable";
	src: local("Switzer Variable"),
		url("/fonts/Switzer-Variable.woff2") format("woff2");
	font-weight: 100 900;
	font-display: swap;
}

@font-face {
	font-family: "Switzer Variable";
	src: local("Switzer Variable Italic"),
		url("/fonts/Switzer-VariableItalic.woff2") format("woff2");
	font-weight: 100 900;
	font-style: italic;
	font-display: swap;
}

@font-face {
	font-family: "Source Serif 4";
	src: local("Source Serif 4"),
		url("/fonts/SourceSerif4-VariableFont_opsz,wght.woff2") format("woff2");
	font-display: swap;
}

@font-face {
	font-family: "iA Writer Quattro V";
	src: local("iA Writer Quattro V"),
		url("/fonts/iAWriterQuattroV.woff2") format("woff2");
	font-display: swap;
}

:root {
	/* Base font */
	--font-stack-sans-serif: "Switzer Variable", system-ui, sans-serif;
	--font-stack-serif: "Source Serif 4", Georgia, Times, serif;
	--font-stack-code: "iA Writer Quattro V", Menlo, monospace;
	font-size: 1.0625em;
	font-family: var(--font-stack-sans-serif);
	--font-weight-light-mode: 350;
	--font-weight-dark-mode: 250;
	font-weight: var(--font-weight-light-mode);
	transition: font-weight var(--theme-transition-duration) ease-out;

	/* ***** Vertical rhythm ***** */
	--root-line-height: 1.5;
	line-height: var(--root-line-height);

	/* Block spacing factor. If used, this should be in increments of 0.5 to
	   maintain rhythm */
	--flow-space-multiplier: 0.5;
	--flow-space: calc(var(--flow-space-multiplier) * 1rlh);

	/* Type scale */
	-webkit-text-size-adjust: none;
	text-size-adjust: none;
	/* Set up size ratios as raw, unitless numbers */
	--size-ratio: 1.3;
	--size-1: 1;
	--size-2: calc(var(--size-1) * var(--size-ratio));
	--size-3: calc(var(--size-2) * var(--size-ratio));
	--size-4: calc(var(--size-3) * var(--size-ratio));
	--size-5: calc(var(--size-4) * var(--size-ratio));

	/* Assign units for usage */
	--size-1-rem: calc(var(--size-1) * 1rem);
	--size-2-rem: calc(var(--size-2) * 1rem);
	--size-3-rem: calc(var(--size-3) * 1rem);
	--size-4-rem: calc(var(--size-4) * 1rem);
	--size-5-rem: calc(var(--size-5) * 1rem);

	/* Fit headings into our rlh baseline grid.
	
	   The line-height for our headings should be the minimum number of
	   baseline grid units that their font-size fits into.

	   - Calculate how many baseline grid units a given font size fits into
	   - Round up to the nearest whole baseline grid unit
	   - Apply units (rlh) to the final value for usage

	   NOTE: This idiosyncrasy with calc() has bitten me before. Via MDN:
	   > Current implementations require that, when using the * and /
	     operators, one of the operands must be unitless. For /, the right
		 operand must be unitless. For example font-size: calc(1.25rem / 1.25)
		 is valid but font-size: calc(1.25rem / 125%) is invalid.

	   So for simplicity with calculations involving the baseline grid, work
	   exclusively with raw numbers until the final step, and apply length
	   units at the end.
	*/
	--size-1-rlh: calc(
		round(
				up,
				/* font-size into line-height */
					calc(var(--size-1) / var(--root-line-height)),
				/* Round up to nearest whole baseline grid unit */
					var(--flow-space-multiplier)
					/* Apply length units to final value */
			) * 1rlh
	);
	--size-2-rlh: calc(
		round(
				up,
				calc(var(--size-2) / var(--root-line-height)),
				var(--flow-space-multiplier)
			) * 1rlh
	);
	--size-3-rlh: calc(
		round(
				up,
				calc(var(--size-3) / var(--root-line-height)),
				var(--flow-space-multiplier)
			) * 1rlh
	);
	--size-4-rlh: calc(
		round(
				up,
				calc(var(--size-4) / var(--root-line-height)),
				var(--flow-space-multiplier)
			) * 1rlh
	);
	--size-5-rlh: calc(
		round(
				up,
				calc(var(--size-5) / var(--root-line-height)),
				var(--flow-space-multiplier)
			) * 1rlh
	);

	/* Ensure that gutters leave enough space for outside bullets */
	--inline-gutter: 2rem;

	/* Set a readable line length, accounting for gutter (padding) with
	box-sizing border-box. Note that Firefox doesn't currently (250830)
	support rch units. */
	--wrap-width: calc(var(--inline-gutter) * 2 + 72ch);
}

h1,
h2,
h3,
h4,
h5,
h6 {
	/* <3 but not licensed for web use 😔 */
	/* font-family: "Hiragino Mincho ProN"; */
	/* font-weight: 600; */

	font-family: var(--font-stack-serif);
	font-feature-settings: "onum";
	font-weight: 500;

	/* Ensure headings have their spacing, even when they're the first item in
	   a flow section (.flow > * + *)
	*/
	margin-block-start: var(--flow-space, 1em);
}

:root[data-theme="dark"] :is(h1, h2, h3, h4, h5, h6) {
	font-weight: 450;
}

h1 {
	font-size: var(--size-4-rem, 2rem);
	line-height: var(--size-4-rlh);
	--flow-space: 0;
}

h2 {
	font-size: var(--size-3-rem, 1.5rem);
	line-height: var(--size-3-rlh);
	--flow-space: 3rlh;
}

h3 {
	font-size: var(--size-2-rem, 1.25rem);
	line-height: var(--size-2-rlh);
	--flow-space: 2rlh;
}

h4 {
	font-size: var(--size-1-rem, 1rem);
	line-height: var(--size-1-rlh);
	--flow-space: 0;
}

ul,
ol {
	padding-inline-start: 0;
	list-style-position: outside; /* Default, but being explicit */
}

li > :is(ul, ol, dl) {
	padding-inline-start: var(--inline-gutter, 2em);
}

b,
strong {
	font-weight: 550;
	transition: font-weight var(--theme-transition-duration) ease-out;
}

:root[data-theme="dark"] :is(b, strong) {
	font-weight: 500;
}

button {
	font-size: 0.875rem;
	padding: 0.25em;
}

code {
	font-family: var(--font-family-code);
	font-weight: 400;
}

figcaption {
	padding-inline: var(--inline-gutter, 2em);
}

blockquote {
	font-style: italic;
	/* Italicized block text reads better in standard weight, even in dark
	   mode */
	/* font-weight: var(--font-weight-light-mode); */
	font-weight: calc(var(--font-weight-dark-mode) + 50);
}

main {
	margin-block: 3rlh;
}

@media print {
	:root {
		font-size: 0.875em;
		--wrap-width: 80ch;
	}

	/* Reduce vertical space between sections */
	h2 {
		--flow-space: 2rlh;
	}

	h3 {
		--flow-space: 1rlh;
	}
}

/* ****************************** */
/* COLOR AND THEME */

:root {
	/* Primitive colors */
	--color-soft-pure-white: oklch(97.6% 0 89.9);
	--color-soft-pure-black: oklch(14.9% 0 69.8);
	--color-dark-blue: oklch(36.8% 0.0965 258.5);
	--color-midnight-blue: oklch(31.9% 0.0509 258.7);
	--color-light-blue: oklch(89.9% 0.0186 250.6);
	--color-bone-oatmeal-1: oklch(97.6% 0.0232 90.8);
	--color-bone-oatmeal-2: oklch(98.5% 0.0159 88.4);
	--color-tawny-rust-dark: oklch(61.8% 0.1797 44);
	--color-tawny-rust-light: oklch(70.8% 0.197 46.7);

	/* Semantic colors */
	/* TODO: Try to use semantic variables everywhere outside of this selector.
	         Search --color and see how many special use cases we're dealing
	         with. We may have to compromise to avoid creating too many one-off
	         semantic variables. */
	/* TODO: Make sure we're using the colors consistently (e.g. are we mixing
	         the two bone colors incorrectly?) */
	--color-bg: var(--color-soft-pure-white);
	--color-fg: var(--color-soft-pure-black);
	--color-background: light-dark(var(--color-bone-oatmeal-2), var(--color-midnight-blue));
	--color-base-text: light-dark(var(--color-fg), var(--color-bone-oatmeal-1));
	--color-links-buttons: light-dark(
		var(--color-dark-blue, inherit),
		var(--color-light-blue, inherit)
	);
	--color-accent: light-dark(var(--color-tawny-rust-dark), var(--color-tawny-rust-light));
	--color-selection-fg: light-dark(var(--color-dark-blue), var(--color-light-blue));
	--color-selection-bg: light-dark(var(--color-tawny-rust-light), var(--color-tawny-rust-dark));

	/* Enable dark mode switching */
	color-scheme: light dark;
	--theme-transition-duration: 500ms;
}

/* Enable explicit light-dark toggle */
:root[data-theme="light"] {
	color-scheme: light;
	font-weight: var(--font-weight-light-mode);
}

:root[data-theme="dark"] {
	color-scheme: dark;
	font-weight: var(--font-weight-dark-mode);
	/* Letter spacing also helps dark mode readability but disables
	   ligatures */
	/* letter-spacing: 0.0015em; */
}

body {
	background-color: var(--color-background);
	color: var(--color-base-text);
	transition-property: color, background-color;
	transition-duration: var(--theme-transition-duration);
	transition-timing-function: ease-out;
}

:any-link {
	color: var(--color-links-buttons, initial);
	text-decoration-color: currentColor;
	text-decoration-thickness: 0.05em;
	text-underline-offset: 0.08em;
}

:any-link:hover {
	text-decoration-color: var(--color-accent);
	text-decoration-thickness: 0.08em;
	text-underline-offset: 0.09em;
}

::marker {
	color: light-dark(
		var(--color-fg, hsl(0 0% 20%)),
		var(--color-bg, hsl(0 0% 85%))
	);
	transition: color var(--theme-transition-duration) ease-out;
}

::selection {
	color: var(--color-selection-fg);
	background-color: var(--color-selection-bg);
}

button {
	color: var(--color-base-text);
	border: 1px solid var(--color-base-text);
	border-radius: 0.5em;
}

@media print {
	/* Undo *all* dark mode changes. The print media query only changes
	   light-dark() colors to light - it doesn't affect anything else we've
	   changed for dark mode.

	   `color-scheme: light` handles resetting all the color variables, which
	   will not only set text to light mode, but i.e. the svg logo as well.

	   Note we don't have to reset the toggle button because it's hidden for
	   print.
	*/
	:root[data-theme="dark"] {
		color-scheme: light;
		font-weight: 350;
	}

	:root[data-theme="dark"] :is(b, strong) {
		font-weight: 550;
	}

	:root[data-theme="dark"] :is(h1, h2, h3, h4, h5, h6) {
		font-weight: 500;
	}

	/* Render link urls in markdown syntax when printing */
	/* ...we can, but do we want to? Some of the links are long. */
	/*
	:any-link::before {
		content: "[";
	}

	:any-link::after {
		content: "](" attr(href) ")";
	}
	*/
}

/* ****************************** */
/* UTILITIES */

.wrapper {
	max-width: var(--wrap-width);
	padding-inline: var(--inline-gutter, 2rem);
	margin-inline: auto;
}

@media print {
	/* Reduce margin; there will already be print margins */
	.wrapper {
		max-width: 100%;
		margin-block: var(--flow-space);
	}
}

.flow > * + * {
	margin-block-start: var(--flow-space, 1em);
}

.text-align-start {
	text-align: start;
}

.text-align-center {
	text-align: center;
}

.text-align-end {
	text-align: end;
}

.text-serif {
	font-family: var(--font-stack-serif);
	font-feature-settings: "onum";
}

.text-sans {
	font-family: var(--font-stack-sans-serif);
	/* font-feature-settings: "onum"; */
}

/* ****************************** */
/* COMPONENT: Site Nav */

.site-nav {
	display: flex;
	flex-flow: row wrap;
	justify-content: end;
	align-items: center;
	gap: 0.5rlh;
	padding-block: 0.2rlh;
	padding-inline: 1rlh;
	position: sticky;
	top: 0;

	color: var(--color-links-buttons);
	background-color: var(--color-background);
	/* background-image: linear-gradient(
		135deg,
		var(--color-background) 0rlh, 0.5rlh,
		light-dark(var(--color-dark-blue), var(--color-bone-oatmeal-1)) 0.5rlh, 1.5rlh,
		var(--color-light-blue) 1.5rlh, 2.5rlh,
		var(--color-tawny-rust-dark) 2.5rlh, 3.5rlh,
		var(--color-background) 3.5rlh
	); */
	/* background-image: linear-gradient(
		135deg,
		var(--color-background) 0rlh, 0.75rlh,
		light-dark(var(--color-dark-blue), var(--color-bone-oatmeal-1)) 0.75rlh, 1.5rlh,
		var(--color-light-blue) 1.5rlh, 2.25rlh,
		var(--color-tawny-rust-dark) 2.25rlh, 3rlh,
		var(--color-background) 3rlh
	); */
	transition-property: color, background-color;
	transition-duration: var(--theme-transition-duration);
	transition-timing-function: ease-out;

	/* Layering system:
	   Base content gets z-index 0-9
	   Nav gets 10-19
	   Modals etc get 20+
	*/
	z-index: 10;
}

.site-nav > * {
	flex: 0 0 auto;
}

.site-nav .current-page {
	text-decoration: underline;
	text-decoration-color: var(--color-accent);
	text-decoration-thickness: 0.08em;
	text-underline-offset: 0.09em;
}

.site-nav button {
	width: 2.25rem;
	height: 2.25rem;
	color: inherit;
	padding: 0.15em;
	text-align: inherit;
	font: unset;
	border: none;
	background-color: inherit;
}

/* Dark mode toggle button */
button#themeToggle {
	--theme-icon-rotation: 0deg;
}

:root[data-theme="dark"] button#themeToggle {
	--theme-icon-rotation: -180deg;
}

@media print {
	.site-nav {
		display: none;
	}
}

/* ****************************** */
/* COMPONENT: Homepage */

.homepage-main {
	--wrap-width: 120ch;
	font-size: var(--size-2-rem);
	--flow-space: 1rlh;

	display: grid;
	grid-template-columns: 1fr;
	gap: 2rlh;
	align-items: start;
	justify-items: center;
}

.homepage-main .homepage-bio-image {
	max-width: 24rem;
	margin-inline: auto;
}

.homepage-main header h1 {
	font-size: var(--size-5-rem);
	line-height: var(--size-5-rlh);

	/* Avoid wrapping on most phones and all the way up.
	   Scale to upper bound asap. */
	font-size: clamp(var(--size-3-rem), calc(var(--size-1-rem) + 3.8vw), var(--size-5-rem));
	/* We can stray from the vertical rhythm here */
	line-height: 1;
}

/* Make homepage h2 subheading look like body text */
.homepage-main header h2 {
	font-size: var(--size-1-rem);
	line-height: var(--size-1-rlh);
	margin-block-start: 0.5rlh;
}

:root[data-theme="light"] .homepage-main header h2 {
	font-weight: var(--font-weight-light-mode);
}

:root[data-theme="dark"] .homepage-main header h2 {
	font-weight: var(--font-weight-dark-mode);
}

.homepage-main .homepage-intro-text {
	/* Use with serif */
	--wrap-width: 58ch;
	/* Use with sans-serif */
	/* --wrap-width: 50ch; */
	/* Use with serif */
	margin-block-start: 4rlh;
	/* Use with sans-serif */
	/* margin-block-start: 2rlh;	 */

	padding-inline: 0;
	--flow-space: 1rlh;
}

@media (min-width: 52em) {
	.homepage-main {
		grid-template-columns: 2fr 3fr;
		justify-items: start;
	}

	.homepage-main header {
		margin-block-start: 4rlh;
	}

	.homepage-main .homepage-bio-image {
		margin-inline-end: 0;
	}
}

/* ****************************** */
/* COMPONENT: Resume */

/* Use Grid so we don't have to style the child elements as well */
.resume-header {
	display: grid;
	grid-template-columns: 1fr;
	gap: 0.5rlh;
	align-items: start;
}

.logo-svg {
	max-width: 6rem;
	margin-inline: auto;
}

@media (min-width: 24em) {
	.resume-header {
		grid-template-columns: minmax(5rem, 1fr) 4fr;
		gap: 1rlh;
	}

	.logo-svg {
		max-width: 100%;
	}
}

@media screen and (min-width: 60em) {
	.resume-main.wrapper {
		margin-inline: 15% auto;
	}
}

/* Workaround: markdown lists don't have flow class */
.resume-main li {
	margin-block-start: var(--flow-space, 1em);
}

/* Dialog element */
.resume-css-dialog {
	position: fixed;
	/* Be on top of the nav */
	z-index: 20;
	top: max(1rlh, env(safe-area-inset-top, 1rlh));
	left: 0;
	padding: 0;
	/* max-width: 85em; */
	height: 100%;
	max-height: calc(
		100% - max(1rlh, env(safe-area-inset-top, 1rlh)) -
			max(1rlh, env(safe-area-inset-bottom, 1rlh))
	);
	border: 3px solid var(--color-fg);
	border: none;
	box-shadow: 0 0 2.5em -0.5em light-dark(var(--color-midnight-blue), var(--color-light-blue));

	background-color: #1e1e2e; /* catppuccin-mocha bg */
	background-color: #22272e; /* github-dark-dimmed bg */
}

/* Dialog when it's in modal state */
.resume-css-dialog:modal {
	display: flex;
	flex-flow: column nowrap;
	align-items: center;
}

button#closeStylesheet {
	margin: 0.5rlh;
}

.resume-css-dialog .shiki {
	padding: 0.5rlh;
	overflow: auto;
	scrollbar-gutter: stable;
	max-inline-size: 100%;
	max-block-size: 100%;
}

@media print {
	.resume-header {
		grid-template-columns: 5rem 1fr;
		gap: 1rlh;
	}
}

/* ****************************** */
/* COMPONENT: Student Feedback */

.student-feedback-main blockquote p:nth-child(2) {
	position: relative;
	--flow-space: 0;
}

.student-feedback-main blockquote p:nth-child(2)::before {
	content: "“";
	font-size: 5rem;
	line-height: 1rem;
	/* I like the light blue in both modes, but it's not enough contrast
	   on the bone/oatmeal background (white was fine) */
	/* color: var(--color-light-blue); */
	color: light-dark(var(--color-dark-blue), var(--color-light-blue));
	position: absolute;
	left: -0.5em;
	top: 0.3em;
}

.student-feedback-main article {
	--flow-space: 1rlh;
}

.blockquote-semester::before {
	display: none;
}

.chart-container {
	aspect-ratio: 2;
}

.chart-canvas {
	background-color: var(--color-bone-oatmeal-2);
	max-width: 100%;
}

.chart-caption {
	font-size: 0.875em;
}