/* =========================
   Palette
   ========================= */

:root {
	color-scheme: light;
	--paper: #faf6ec;
	--grid-line: rgba(93, 138, 186, 0.32);
	--grid-line-bold: #4a7ab0;
	--cell-border: #3d6690;
	--ink: #1c2430;
	--ink-soft: rgba(28, 36, 48, 0.68);
	--grid-size: 32px;
	/* Overwritten by JS to whatever cell size divides the current viewport
	   exactly, so box borders always land precisely on a grid line instead
	   of a fraction of a cell short. Falls back to --grid-size pre-JS. */
	--grid-size-x: var(--grid-size);
	--grid-size-y: var(--grid-size);
	--box-bg: rgba(250, 246, 236, 0.94);
	--font-marker: 'Permanent Marker', cursive;
	--font-kalam: 'Kalam', cursive;
}

/* =========================
   Reset
   ========================= */

*,
*::before,
*::after {
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}

html {
	overflow-x: hidden;
	max-width: 100vw;
}

body {
	font-family: var(--font-kalam);
	font-size: 18pt;
	/* One grid row per line of text, so body copy sits on the ruling. */
	line-height: var(--grid-size-y);
	color: var(--ink);
	background-color: var(--paper);
	background-image:
		linear-gradient(var(--grid-line) 1px, transparent 1px),
		linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
	background-size: var(--grid-size-x) var(--grid-size-y);
	background-attachment: fixed;
	position: relative;
	overflow-x: hidden;
	-webkit-overflow-scrolling: touch;
}

body.is-preload * {
	animation: none !important;
	transition: none !important;
}

@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

a, a:visited, a:hover, a:active { color: var(--ink); }

a {
	display: inline-block;
}

a:hover {
	font-style: italic;
}

h1, h2, h3 {
	color: var(--ink);
	font-family: var(--font-marker);
	font-weight: 400;
	/* Two grid rows tall, so headings still land on a ruling line even when
	   they wrap to a second line. */
	line-height: calc(var(--grid-size-y) * 2);
	letter-spacing: 0.5px;
}

h1 { font-size: 2.1em; }
h2 { font-size: 1.4em; }
h3 { font-size: 1em; }

p { margin-bottom: 0; line-height: var(--grid-size-y); }
strong { font-weight: 600; }
em { font-style: italic; }

/* =========================
   Site mark (small, corner — replaces old header)
   ========================= */

/* Position, size, and border all sit on exact grid-cell multiples — same
   treatment as the content boxes — so this corner box reads as one more
   cell on the page rather than a UI element floating apart from it. */
.site-mark {
	position: fixed;
	top: var(--grid-size-y);
	left: var(--grid-size-x);
	z-index: 300;
	width: calc(var(--grid-size-x) * 7);
	height: var(--grid-size-y);
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
	font-size: 13px;
	letter-spacing: 0.6px;
	color: var(--ink);
	text-decoration: none;
	border: 1.5px solid var(--cell-border);
	background: var(--box-bg);
	padding: 0 8px;
}

.site-mark:hover {
	transform: none;
	background: var(--paper);
}

/* =========================
   Easter egg: double-click "things i've made" to reveal
   ========================= */

/* Small and muted on purpose — a quiet aside, not a shout — but genuinely
   legible whenever it's shown, never relying on being unreadable. */
.easter-egg {
	position: fixed;
	top: var(--grid-size-y);
	right: var(--grid-size-x);
	z-index: 300;
	max-width: calc(var(--grid-size-x) * 7);
	font-family: var(--font-kalam);
	font-size: 11px;
	line-height: 1.4;
	text-align: right;
	color: var(--ink-soft);
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.5s ease;
}

.easter-egg.is-visible {
	opacity: 1;
}

@media (max-width: 860px) {
	.easter-egg { display: none; }
}

/* =========================
   Nav: handwritten to-do list, top-left under the site mark
   ========================= */

.todo-nav {
	position: fixed;
	top: calc(var(--grid-size-y) * 3);
	left: 28px;
	z-index: 300;
	max-width: 220px;
}

.todo-list {
	list-style: none;
	display: flex;
	flex-direction: column;
}

.todo-item {
	-webkit-appearance: none;
	appearance: none;
	background: none;
	border: none;
	padding: 0;
	line-height: var(--grid-size-y);
	font-family: var(--font-kalam);
	font-size: 1.1em;
	text-align: left;
	color: var(--ink-soft);
	cursor: pointer;
	transition: color 0.3s ease;
}

.todo-item::before {
	content: '\2013\a0';
}

.todo-item.is-active {
	color: var(--ink);
	text-decoration: underline;
	text-decoration-color: var(--cell-border);
	text-underline-offset: 4px;
}

.todo-item.is-done {
	color: var(--ink-soft);
	text-decoration: line-through;
	text-decoration-color: var(--ink-soft);
	text-decoration-thickness: 1.5px;
	opacity: 0.6;
}

.todo-item:hover { color: var(--ink); font-style: italic; }

.todo-overview {
	-webkit-appearance: none;
	appearance: none;
	display: block;
	background: none;
	border: none;
	margin-top: var(--grid-size-y);
	padding: 0;
	line-height: var(--grid-size-y);
	font-family: var(--font-kalam);
	font-size: 0.95em;
	color: var(--ink-soft);
	cursor: pointer;
	transition: color 0.3s ease;
}

.todo-overview:hover,
.todo-overview.is-active {
	color: var(--ink);
}

.todo-overview:hover { font-style: italic; }

/* =========================
   Box shared styling
   ========================= */

.box {
	background-color: var(--paper);
	background-image:
		linear-gradient(var(--grid-line) 1px, transparent 1px),
		linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
	/* Matches the viewport-fitted cell size (see body) so the border below
	   lands exactly on a grid line instead of a fraction of a cell short. */
	background-size: var(--grid-size-x) var(--grid-size-y);
	border: 2px solid var(--cell-border);
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
}

.box .box-inner {
	width: 100%;
	max-width: 1300px;
	padding: 0 2.5em;
	max-height: 100%;
	overflow-y: auto;
	/* Keep the ability to scroll if content ever runs long, without the
	   scrollbar itself showing up inside the box. */
	scrollbar-width: none;
	-ms-overflow-style: none;
}

.box .box-inner::-webkit-scrollbar {
	display: none;
}

.box-heading {
	font-size: 2.1em;
	margin-bottom: var(--grid-size-y);
}

/* =========================
   Desktop: scroll-snap track + sticky FLIP stage
   ========================= */

@media (min-width: 861px) {

	/* No real scrolling happens here at all — the wheel/trackpad is read as
	   discrete forward/backward paging (see graphpaper.js) and every
	   transition is driven by the FLIP animation instead of scroll
	   position. This is just a fixed full-viewport wrapper. */
	.scroll-track {
		height: 100vh;
		overflow: hidden;
	}

	.snap-point {
		height: 0;
	}

	/* Top-anchor box content (instead of vertical centering) so every section,
	   regardless of how much content it holds, gets the same predictable
	   clearance below the fixed to-do nav in the top-left. */
	.box {
		align-items: flex-start;
	}

	.box .box-inner {
		padding-top: calc(var(--grid-size-y) * 9);
	}

	#stage {
		position: fixed;
		inset: 0;
		height: 100vh;
		overflow: hidden;
		z-index: 10;
		pointer-events: none;
	}

	#stage-grid {
		position: absolute;
		top: 0;
		left: 0;
		display: grid;
		grid-template-columns: 100vw 100vw;
		grid-template-rows: 100vh 100vh;
		width: 200vw;
		height: 200vh;
		will-change: transform;
	}

	#stage-grid .box {
		pointer-events: auto;
	}

	/* Grid-overview mode: real 2x2 layout with gaps, centered on screen.
	   Card width/height/gap are set by JS (syncGridCardSize) to exact
	   multiples of the base grid cell, so every card border lands on a
	   real grid line instead of stopping a fraction of a cell short —
	   the fallback values here are themselves clean 32px multiples. */
	#stage[data-mode="grid"] #stage-grid {
		grid-template-columns: var(--grid-card-w, 416px) var(--grid-card-w, 416px);
		grid-template-rows: var(--grid-card-h, 352px) var(--grid-card-h, 352px);
		width: max-content;
		height: max-content;
		gap: var(--grid-card-gap, 32px);
		position: fixed;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
	}

	#stage[data-mode="grid"] #stage-grid .box {
		cursor: pointer;
		align-items: center;
		/* Cards are small and their size isn't viewport-derived, so the
		   dynamic per-viewport cell size doesn't apply here — back to the
		   fixed base cell (JS phase-aligns position separately). */
		background-size: var(--grid-size) var(--grid-size);
	}

	#stage[data-mode="grid"] #stage-grid .box .box-inner {
		pointer-events: none;
		padding-top: 0;
	}

	#stage[data-mode="grid"] .box-heading { font-size: 1.1em; }

	/* Body-level copy also needs to shrink in the small card view — at full
	   size it overflows/wraps awkwardly in a card this small. */
	#stage[data-mode="grid"] #stage-grid .box[data-box="contact"] .contact-note {
		font-size: 0.55em;
	}

	#stage[data-mode="grid"] #stage-grid .box[data-box="overview"] p {
		font-size: 0.6em;
	}

	#stage[data-mode="grid"] #stage-grid .box[data-box="overview"] .intro-list li {
		font-size: 0.55em;
	}

	#stage[data-mode="grid"] #stage-grid .box[data-box="overview"] .exclaim-mark {
		width: 10px;
		height: 22px;
	}

	/* Shrink the LinkedIn carousel to a small thumbnail strip in the grid
	   overview instead of showing an oversized, clipped embed. The wrapper
	   keeps its real (small, fixed) footprint and clips; the row of cards
	   inside it is scaled down and widened to compensate, so it still spans
	   the wrapper edge-to-edge after shrinking. */
	#stage[data-mode="grid"] #stage-grid .box[data-box="projects"] .linkedin-carousel-wrapper {
		height: 120px;
		overflow: hidden;
	}

	#stage[data-mode="grid"] #stage-grid .box[data-box="projects"] .linkedin-carousel {
		width: 320%;
		transform: scale(0.3125);
		transform-origin: top left;
	}
}

/* =========================
   Mobile / tablet: simple vertical stack
   ========================= */

@media (max-width: 860px) {

	body { font-size: 15.5pt; }

	.todo-nav { display: none; }

	#grid { display: none; }
	#stage { display: none; }

	.scroll-track { height: auto; }

	.snap-point {
		min-height: 100vh;
		display: flex;
		align-items: center;
		justify-content: center;
		padding: 90px 0 40px;
	}

	.box {
		border-width: 2px;
		height: auto;
		min-height: calc(100vh - 130px);
	}

	.box .box-inner { padding: 0 1.4em; }

	.box-heading { font-size: 1.6em; }
}

/* =========================
   Intro
   ========================= */

.box[data-box="overview"] .box-inner { text-align: left; }

/* Scaled back down from the other sections' 1.7em/2-grid-line heading —
   with the full bio + achievement list below, everything needs to fit in
   one screen, so the heading trades a little size for room. */
.box[data-box="overview"] h1 {
	font-size: 1.5em;
	line-height: var(--grid-size-y);
	margin-bottom: 0;
}

.box[data-box="overview"] p {
	font-size: 1.1em;
	color: var(--ink);
	margin-top: 0.45em;
	line-height: 1.25;
}

.intro-list {
	list-style: none;
	margin-top: 0.5em;
	display: flex;
	flex-direction: column;
	gap: 0.2em;
}

.intro-list li {
	font-family: var(--font-kalam);
	font-size: 1em;
	line-height: 1.25;
	color: var(--ink);
	padding-left: 1.1em;
	position: relative;
}

.intro-list li::before {
	content: '\2013\a0';
	position: absolute;
	left: 0;
}

/* A hand-drawn exclamation point after "show" — stem first, then the dot
   pops in right after, like someone punctuating it by hand. Reset (not
   just drawn) when you leave Intro, and replayable on hover. */
.show-word {
	position: relative;
	display: inline-block;
}

.exclaim-mark {
	display: inline-block;
	width: 15px;
	height: 34px;
	margin-left: 2px;
	vertical-align: -8px;
	overflow: visible;
	cursor: default;
}

.exclaim-stem {
	fill: none;
	stroke: var(--ink);
	stroke-width: 2.4;
	stroke-linecap: round;
	stroke-dasharray: 1;
	stroke-dashoffset: 1;
	filter: url(#dry-brush);
	transition: stroke-dashoffset 0.3s ease;
}

.exclaim-dot {
	fill: var(--ink);
	opacity: 0;
	transform-box: fill-box;
	transform-origin: center;
	transform: scale(0);
	transition: opacity 0.2s ease, transform 0.2s ease;
	transition-delay: 0.3s;
}

.exclaim-mark.is-active .exclaim-stem {
	stroke-dashoffset: 0;
}

.exclaim-mark.is-active .exclaim-dot {
	opacity: 1;
	transform: scale(1);
}

.box[data-box="overview"] .box-inner {
	opacity: 0;
	transform: translateY(24px);
	transition: opacity 0.9s ease 0.2s, transform 0.9s ease 0.2s;
}

.box[data-box="overview"] .box-inner.fade-in {
	opacity: 1;
	transform: translateY(0);
}

/* =========================
   Projects — LinkedIn carousel
   ========================= */

.projects-outer {
	width: 100%;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--grid-size-y);
}

.projects-heading {
	text-align: left;
	align-self: flex-start;
	margin: 0;
}

/* A little celebratory flourish off the top-right of the final "e" — like
   a few quick action lines flicked in by hand, trailing off the end of the
   word. Positioned relative to the glyph's actual visible top, not the
   (taller) line-box it sits in — the heading's line-height is 2 grid units
   but the letter itself is only ~1.7em, so it's vertically centered with
   room above it we have to account for. Mirrored horizontally so the fanned
   tips point up and away to the right, past the end of the word, instead
   of back over the letter. */
.made-e {
	position: relative;
	display: inline-block;
}

.celebrate-lines {
	position: absolute;
	top: -14px;
	right: -30px;
	width: 46px;
	height: 46px;
	overflow: visible;
	/* Auto (not none) so this whole box — not just the thin drawn strokes —
	   is a hoverable target that replays the animation (see graphpaper.js). */
	pointer-events: auto;
	cursor: default;
}

.celebrate-lines path {
	fill: none;
	stroke: var(--cell-border);
	stroke-width: 2;
	stroke-linecap: round;
	stroke-dasharray: 1;
	stroke-dashoffset: 1;
	filter: url(#dry-brush);
	transition: stroke-dashoffset 0.35s ease;
}

/* Drawn one at a time, like someone flicking them in by hand, each a beat
   after the last. Reset (not just "already drawn") each time you leave
   Projects, so scrolling back in plays it again. */
.celebrate-lines.is-active path:nth-child(1) { transition-delay: 0.15s; }
.celebrate-lines.is-active path:nth-child(2) { transition-delay: 0.4s; }
.celebrate-lines.is-active path:nth-child(3) { transition-delay: 0.65s; }

.celebrate-lines.is-active path {
	stroke-dashoffset: 0;
}

.linkedin-carousel-wrapper {
	width: 100%;
	max-width: 1000px;
	overflow: hidden;
}

.linkedin-carousel {
	display: flex;
	gap: 20px;
	overflow-x: scroll;
	scroll-snap-type: x mandatory;
	-webkit-overflow-scrolling: touch;
	scroll-behavior: smooth;
	scrollbar-width: none;
	-ms-overflow-style: none;
	width: 100%;
	touch-action: pan-x;
}

.linkedin-carousel::-webkit-scrollbar { display: none; }

.linkedin-card {
	scroll-snap-align: center;
	flex-shrink: 0;
	border: 1.5px solid var(--grid-line-bold);
	background: var(--paper);
	height: min(50vh, 430px);
	width: 340px;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
}

.linkedin-card iframe {
	display: block;
	width: 100% !important;
}

@media (max-width: 600px) {
	.linkedin-card {
		width: calc(100vw - 90px);
		height: min(46vh, 400px);
	}
}

/* =========================
   Resume link + hover circle
   ========================= */

/* Centered on the page, same treatment as Contact's heading + notes. */
.box[data-box="resume"] .box-inner { text-align: center; }

.resume-button-container {
	position: relative;
	display: inline-block;
}

/* Margin-bottom comes from the shared .box-heading class (also applied to
   this link in the markup). Font-size is set larger here since this box
   otherwise holds only this one line of text. */
.resume-link {
	display: inline-block;
	font-size: 4.2em;
	line-height: calc(var(--grid-size-y) * 2);
	font-family: var(--font-marker);
	font-weight: 400;
	letter-spacing: 0.5px;
	color: var(--ink);
	text-decoration: underline;
	text-decoration-color: var(--cell-border);
	text-decoration-thickness: 1.5px;
	text-underline-offset: 5px;
	cursor: pointer;
	padding: 0 2px;
	position: relative;
	z-index: 1;
	transition: text-decoration-color 0.25s ease, color 0.25s ease;
}

.resume-button-container:hover .resume-link {
	text-decoration-color: var(--ink);
}

/* Hand-drawn circle that "draws itself" around the link on hover, using
   the same pathLength/stroke-dashoffset trick as the old thread-line. */
.resume-circle {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 145%;
	height: 170%;
	transform: translate(-50%, -50%);
	overflow: visible;
	pointer-events: none;
	z-index: 0;
}

.resume-circle path {
	fill: none;
	stroke: var(--cell-border);
	stroke-width: 2;
	stroke-linecap: round;
	stroke-dasharray: 1;
	stroke-dashoffset: 1;
	filter: url(#dry-brush);
	transition: stroke-dashoffset 0.6s ease;
}

/* Triggered by JS proximity (see graphpaper.js) rather than a plain :hover,
   so the circle starts drawing well before the cursor actually reaches the
   tightly-sized text. */
.resume-circle.is-near path {
	stroke-dashoffset: 0;
}

/* =========================
   Contact
   ========================= */

.box[data-box="contact"] .box-inner { text-align: center; }

/* No font-size override here anymore — inherits .box-heading's 1.7em so
   it matches the other three section headings. */

.contact-links-container {
	position: relative;
	display: inline-block;
}

/* Hand-drawn arrow from "Let's Connect" down to the bullets below it,
   sitting in the empty space to the left. It draws itself in (rather than
   just fading) whenever the cursor comes near — not just a direct hover —
   using the same pathLength/stroke-dashoffset trick as the resume circle. */
.connect-arrow {
	position: absolute;
	top: 0;
	left: -100px;
	width: 100px;
	height: 100%;
	overflow: visible;
	pointer-events: none;
}

.connect-arrow path {
	fill: none;
	stroke: var(--cell-border);
	stroke-width: 3;
	stroke-linecap: round;
	stroke-linejoin: round;
	stroke-dasharray: 1;
	stroke-dashoffset: 1;
	filter: url(#dry-brush);
	transition: stroke-dashoffset 1.4s ease;
}

.connect-arrow.is-near path {
	stroke-dashoffset: 0;
}

.connect-arrow-head {
	transition-delay: 0.2s;
}

@media (max-width: 860px) {
	.connect-arrow { display: none; }
}

ul.actions { list-style: none; }

.contact-notes {
	list-style: none;
	/* inline-flex shrink-wraps to the widest line, so the two notes align to
	   each other on the left while the block as a whole still centers under
	   the (text-align:center) heading. */
	display: inline-flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 0;
	margin-top: var(--grid-size-y);
}

.contact-note {
	display: inline-block;
	line-height: var(--grid-size-y);
	font-family: var(--font-kalam);
	font-size: 1.6em;
	color: var(--ink);
	text-decoration: underline;
	text-decoration-color: var(--cell-border);
	text-underline-offset: 5px;
	transition: text-decoration-color 0.25s ease;
}

.contact-note::before {
	content: '\2013\a0';
}

.contact-note:hover {
	text-decoration-color: var(--ink);
}

@media (max-width: 480px) {
	.contact-note { font-size: 1.1em; word-break: break-word; }
}
