Brandon Rodkewitz

Montclair, NJ
resume@brndnr.com
(862) 261-0556
LinkedIn | Github

Professional Summary

Frontend developer and former educator with 10 years of experience designing and delivering web development curricula. Skilled in semantic HTML, CSS, responsive layout, and building lightweight, maintainable systems. Known for clear communication, structured thinking, and an eye for visual clarity. Eager to grow within a collaborative studio team and deepen experience with modern frontend frameworks, tooling, and design systems.

Experience

Adjunct Professor — Web Design & Development

The College of New Jersey | 2014-2023
Ewing, NJ (Hybrid)

Student feedback

Owner & Photographer

Brandon Rodkewitz Photography LLC | 2008–present
Wharton/Montclair, NJ (Remote & On-Location)

Client testimonials

Work Samples

Teaching Materials

Brandon Rodkewitz Photography

Proficiencies

Languages, Frameworks, & Tools

Proficient: HTML, CSS, Python, Git, Templating Languages, Command Line, Regex (PCRE), BBEdit, Nova
Working Knowledge: JavaScript (ES6+), Node.js, Eleventy, Flask, uv, SQL (SQLite, MySQL), SVG, GSAP (GreenSock), Nginx, PHP, Shell scripting, VSCode, Vim

Web Development Skills

Proficient: Image optimization, file management, web performance, static site generation
Working Knowledge: UX patterns, Accessibility, SEO

Creative & Media Applications

Proficient: Capture One, Lightroom, Photo Mechanic, InDesign, Illustrator
Working Knowledge: Photoshop, Affinity Designer, Affinity Publisher, Adobe XD, Final Cut Pro, Premiere Pro, ffmpeg, Exiftool

Education

BFA, Digital Art
The College of New Jersey, School of Arts and Communication

@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;
}