:root {
	--poppins: "Poppins", Arial, Helvetica, sans-serif;
	--lato: "Lato", Arial, Helvetica, sans-serif;
	--black: #363540;
	--surface: #FFFFFF;
	--ink: var(--black);
	--radius-full: 50%;
	--duration-jump: 1400ms;     /* one bounce of the curtain's dot */
	--duration-hold: 120ms;      /* the curtain holding the colour it landed on */
	--duration-iris: 1500ms;     /* and the hole at the dot opening past the corners */
	--ease-house: ease-in-out;
	--grey: #505050;
	--blue: #77C4FF;
	--green: #88D498;
	--pink: #F499B2;
	--yellow: #EEB868;
	--project-spotify: #356349;
	--project-findr: #EB7078;
	--project-pokedoke: #0F3C5D;
	--project-dailyui-collection-01: #142D8C;
	--animate: all 500ms ease-in-out;

	/* Layout. The content column is capped and centred rather than pushed around
	   by viewport-relative margins: the old ladder of vw margins meant the column
	   took a smaller share of the screen the wider the screen got. --bleed-pad
	   lines a full-bleed section's contents up with that column -- the percentage
	   resolves against the section's own width, so a full-width element lands on
	   the same inner edge the container does. */
	--gutter: clamp(1.25rem, 0.8333rem + 1.852vw, 2.5rem);
	--container: 80rem;
	--bleed-pad: max(var(--gutter), (100% - var(--container)) / 2 + var(--gutter));
	--measure: 68ch;
	--header-height: 4rem;

	/* Type. Each step interpolates between its two bounds across 360px-1440px and
	   then stops, so nothing keeps growing on a television and nothing collapses
	   on a small phone. This replaces both the calc(rem + vw) headings, which had
	   no ceiling, and the root font-size ladder, which moved every rem in the
	   stylesheet at four arbitrary widths. */
	--text-2xs: clamp(0.75rem, 0.7292rem + 0.093vw, 0.8125rem);
	--text-xs: clamp(0.8125rem, 0.7917rem + 0.093vw, 0.875rem);
	--text-sm: clamp(0.9375rem, 0.9167rem + 0.093vw, 1rem);
	--text-base: clamp(1rem, 0.9792rem + 0.093vw, 1.0625rem);
	--text-md: clamp(1.0625rem, 1.0208rem + 0.185vw, 1.1875rem);
	--text-lg: clamp(1.1875rem, 1.1042rem + 0.37vw, 1.4375rem);
	--text-xl: clamp(1.375rem, 1.2708rem + 0.463vw, 1.6875rem);
	--text-2xl: clamp(1.5625rem, 1.3958rem + 0.741vw, 2.0625rem);
	--text-3xl: clamp(1.8125rem, 1.5417rem + 1.204vw, 2.625rem);
	--text-4xl: clamp(2.125rem, 1.7083rem + 1.852vw, 3.375rem);

	/* Spacing. The small steps are fixed -- the gap between a tag and its
	   neighbour is the same job on any screen -- and the large ones are fluid,
	   because that is where the difference between a phone and a desktop tells. */
	--space-3xs: 0.25rem;
	--space-2xs: 0.5rem;
	--space-xs: 0.75rem;
	--space-s: 1rem;
	--space-m: clamp(1.25rem, 1.1667rem + 0.37vw, 1.5rem);
	--space-l: clamp(1.75rem, 1.5rem + 1.111vw, 2.5rem);
	--space-xl: clamp(2.5rem, 2rem + 2.222vw, 4rem);
	--space-2xl: clamp(3.5rem, 2.6667rem + 3.704vw, 6rem);
	--duration-quick: 150ms;
	--duration-swing: 3200ms;
	/* A hand levelling a frame that never came off its hook -- the hover
	   straightening one. Kept at a ratio of the swing rather than as a literal,
	   so retuning the swing carries it.
	
	   sign.js levels a sign that has been thrown and put back with the same
	   gesture and the same curve, but over its own FIX_SPEEDUP and at twice
	   this duration. The two used to be one number, on the reasoning that one
	   gesture deserves one duration. They are still one gesture; they are not
	   one situation. Rehanging is the end of a trip across the page and can
	   afford to be deliberate. A hover is a reply to a pointer, and a reply
	   that takes 1.28s reads as the page being slow rather than the sign being
	   heavy. */
	--duration-catch: calc(var(--duration-swing) / 5);
	--ease-swing: ease-in-out;
	/* ease-out cubic, the curve sign.js levels on. Monotonic, so the angle closes
	   on straight and stops without crossing it: correcting is not swinging. The
	   quint this replaced put nearly all its travel in the first third, which over
	   a correction this long read as a snap followed by a crawl. */
	--ease-catch: cubic-bezier(0.33, 1, 0.68, 1);
	/* Ease-in cubic: anything picking up speed as it falls. */
	--ease-fall: cubic-bezier(0.32, 0, 0.67, 0);
	/* The iris. Ease-out would have the hole at half the screen before the eye
	   has found it; this holds it at the dot for a beat, then carries it out
	   and lets it run off the corners rather than braking into them. */
	--ease-iris: cubic-bezier(0.6, 0, 0.3, 1);
	/* Two layers each -- a wide soft throw plus a tight one close to the object.
	   Tinted with --black and translucent, so they pick up the surface behind them. */
	--shadow-sign-plumb: 0 5px 12px rgba(54, 53, 64, 0.22), 0 1px 3px rgba(54, 53, 64, 0.20);
	--shadow-sign-swing: 6px 11px 20px rgba(54, 53, 64, 0.22), 2px 3px 6px rgba(54, 53, 64, 0.16);
	--shadow-sign-rest: 3px 7px 14px rgba(54, 53, 64, 0.22), 1px 2px 4px rgba(54, 53, 64, 0.18);
	--shadow-card-hover: 0 10px 24px rgba(54, 53, 64, 0.28), 0 3px 8px rgba(54, 53, 64, 0.20);
	--shadow-portrait: 0 12px 28px rgba(54, 53, 64, 0.26), 0 4px 10px rgba(54, 53, 64, 0.16);
}

/* Generic Styling **************************************************************************************/

html {
	scroll-behavior: smooth;
	/* Never override this at a breakpoint. It is the reader's own setting, and
	   every rem in the stylesheet is measured from it. */
	font-size: 100%;
	/* So an anchor jump does not land under the fixed navbar. */
	scroll-padding-top: calc(var(--header-height) + var(--space-s));
}

body,
html {
	margin: 0;
	padding: 0;
	width: 100%;
}

body {
	font-family: var(--lato);
	color: var(--black);
	overflow-x: hidden;
	font-size: var(--text-base);
	font-weight: normal;
	line-height: 1.5;
	-webkit-text-size-adjust: 100%;
	-webkit-tap-highlight-color: transparent;
	min-width: 320px;
}

.loading *,
.loading *:before,
.loading *:after {
	transition: none !important;
	-webkit-animation-play-state: paused !important;
	        animation-play-state: paused !important;
}

.content-wrapper {
	display: flex;
}

/* The one content column: capped, centred, and padded by the gutter. */

.container-fluid {
	width: 100%;
	max-width: var(--container);
	display: flex;
	flex-direction: column;
	margin-inline: auto;
	padding-inline: var(--gutter);
	margin-top: calc(var(--header-height) + var(--space-s));
}

h1 {
	font-size: var(--text-4xl);
}

h2 {
	font-size: var(--text-3xl);
}

h3 {
	font-size: var(--text-2xl);
}

h4 {
	font-size: var(--text-xl);
}

h6 {
	font-size: var(--text-md);
}

h1, h2, h3, h4, h5, h6 {
	font-weight: 600;
	font-family: var(--poppins);
	line-height: 1.15;
	text-wrap: balance;
}

a, h1, h2, h3, h4, h5, h6 {
	margin: 0;
	padding: 0;
}

a {
	color: var(--black);
}

a:hover,
a:active,
a:focus {
	color: var(--blue);
}

p {
	margin-top: 0;
	margin-bottom: var(--space-s);
	/* Unitless: a rem line-height does not follow a fluid font-size. */
	line-height: 1.6;
	text-wrap: pretty;
}

section {
	display: -ms-Flexbox;
	-ms-box-orient: horizontal;
	display: -moz-flex;
	display: -ms-flex;
	display: flex;
	flex-flow: row wrap;
}

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

/** Loader Wrapper ********************************************************/

/* The curtain. One set of keyframes serves every page: --loader-accent is the
   only thing a project page changes.

   The ground swaps on the beat of the bounce. It starts white with the
   wordmark in the accent, and every time the dot lands the two change places.
   The version this replaces pulsed on its own 4.8s cycle, unrelated to
   anything on screen, which read as a nervous tic; tied to the landing it
   reads as the dot knocking the colour through.

   The type's colour is animated and the dot's is currentColor, so the dot
   changes with the type for free and the two can never drift apart. */

/* Both of these are registered so they can be animated or transitioned. An
   unregistered custom property has no type and therefore no interpolation:
   --loader-ground would not animate in the keyframes below, and --iris-r would
   snap straight to open with no reveal to look at. The failure is silent in
   both cases. */

@property --loader-ground {
	syntax: "<color>";
	inherits: true;
	initial-value: #FFFFFF;
}

@property --iris-r {
	syntax: "<percentage>";
	inherits: false;
	initial-value: 0%;
}

.loader {
	--loader-accent: var(--blue);
	/* The surface the dot bounces on, named apart from background-color so the
	   dot's shadow can be defined as this colour with the light taken out of
	   it. The keyframes move it and the shadow follows for free. */
	--loader-ground: var(--surface);
	--dot-size: 0.7rem;
	/* Where the full stop would have been. The dot replaces a character, so it
	   has to be set like one: Poppins Bold opens its period 0.039em past the
	   advance of the letter before it, and drops the round shape 0.009em below
	   the baseline -- the same overshoot it gives the 'e', which is why a
	   period looks level with one and a dot parked on the baseline does not.
	   Both are em, so they track the wordmark through its clamp.

	   The ink of that period is 0.206em wide at any size, and --dot-size is
	   0.7rem, which is within a tenth of a pixel of it at the wordmark's full
	   size. So the dot is already a period; it was only ever in the wrong
	   place. The pair of offsets below put it back, and "henree" and the dot
	   read as one word rather than a word and a bullet beside it. */
	--dot-bearing: 0.039em;
	--dot-overshoot: 0.009em;
	--dot-shadow-h: 5px;
	/* Beat 3's geometry. loader.js overwrites the two coordinates with the
	   dot's measured centre just before the iris opens; the fallbacks are the
	   middle of the screen, for a curtain whose dot has gone missing. */
	--iris-x: 50%;
	--iris-y: 50%;
	--iris-r: 0%;
	--iris-feather: 1.25;
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	position: fixed;
	inset: 0;
	z-index: 100;
	background-color: var(--loader-ground);
	color: var(--loader-accent);
	font-size: var(--text-4xl);
	font-weight: 700;
	font-family: var(--poppins);
	letter-spacing: 0.2rem;
	overflow: hidden;
	/* What grows is the hole, not the curtain. The transparent stop is the
	   opening and the black stop is the far side of its soft edge; both sit at
	   0% while the curtain waits, so there is no smudge around the dot, and
	   the feather widens with the hole, which is what makes the edge read as
	   spreading rather than as a ring travelling outward. The gradient sizes
	   itself farthest-corner, so 100% reaches the corner furthest from the dot
	   and nothing is left of the curtain wherever the dot happened to be. */
	-webkit-mask-image: radial-gradient(circle at var(--iris-x) var(--iris-y),
		transparent var(--iris-r),
		#000 calc(var(--iris-r) * var(--iris-feather)));
	mask-image: radial-gradient(circle at var(--iris-x) var(--iris-y),
		transparent var(--iris-r),
		#000 calc(var(--iris-r) * var(--iris-feather)));
}

/* The wait. Carried by a class rather than declared on .loader so beat 2 can
   end it, though ending it is not quite enough on its own: an animation
   outranks every declaration, including an inline one, so the curtain's live
   colours have to be written inline *and* the animation taken away before they
   mean anything. Dropping the class alone falls back to the base rule above --
   white ground, accent type -- which is only half the cycle, and quite possibly
   not the half the exit landed on. */

.loader.is-waiting {
	-webkit-animation: loader-ground calc(var(--duration-jump) * 2) var(--ease-house) infinite;
	        animation: loader-ground calc(var(--duration-jump) * 2) var(--ease-house) infinite;
}

/* Two bounces to the cycle, because a swap and a swap back is what makes it a
   cycle. Each change *completes* on a landing rather than starting there, so
   the colour is already different by the moment of impact, and both ends of
   the cycle are left on a hold so the loop is seamless. Starting the ramp at
   the landing instead cannot loop -- the wrap from 100% to 0% is
   instantaneous, so one of the two swaps would be a hard cut and the other a
   fade.

   The 3% before each landing is 72ms, and the ease-in is doing more work than
   the 72ms is. The swap has to read as the landing causing it, and a window
   measured in time is a poor proxy for that, because where the dot actually is
   during that window depends entirely on the shape of the fall: the same
   window ran from 13px up under the old cubic and from 17px up -- over half
   the arc -- once the fall became a quart, which is what made the colour look
   like it was changing at the apex. Easing the swap in rather than in-out puts
   the change where the dot is instead of spreading it evenly through the
   window. The colour is a tenth of the way over while the dot is still 8px up,
   and goes the rest of the way in the last handful of milliseconds, under the
   dot's own height. Retune the fall again and this wants checking again --
   it is the arc these numbers are keyed to, not the clock. */

@-webkit-keyframes loader-ground {
	0%, 47% {
		--loader-ground: var(--surface);
		color: var(--loader-accent);
		-webkit-animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);
		        animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);   /* --ease-fall, ease-in cubic */
	}
	50%, 97% {
		--loader-ground: var(--loader-accent);
		color: var(--surface);
		-webkit-animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);
		        animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);   /* --ease-fall, ease-in cubic */
	}
	100% {
		--loader-ground: var(--surface);
		color: var(--loader-accent);
	}
}

@keyframes loader-ground {
	0%, 47% {
		--loader-ground: var(--surface);
		color: var(--loader-accent);
		-webkit-animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);
		        animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);   /* --ease-fall, ease-in cubic */
	}
	50%, 97% {
		--loader-ground: var(--loader-accent);
		color: var(--surface);
		-webkit-animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);
		        animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0);   /* --ease-fall, ease-in cubic */
	}
	100% {
		--loader-ground: var(--surface);
		color: var(--loader-accent);
	}
}

/* The exit, in three beats -- see public/js/loader.js, which times them.
   1. The dot finishes the bounce it is in and lands. Nothing here; the script
      waits out the remainder of the cycle so the curtain never leaves with the
      dot stranded in mid-air.
   2. The swapping stops -- after the landing has had its swap. A landing means
      "change colour" for the whole of the wait, and the last one is not made
      an exception of; the curtain keeps whatever that final knock gave it and
      holds it for a beat. So the colour the exit wears is decided by which
      landing the page happened to be ready for, and there is no third colour
      and no drain back to a resting state: the curtain leaves on the accent or
      on white, one bounce apart, nothing in between.

      Both readings are worth having. On the accent the type and the dot are
      white, so the hole opens *at* a white dot onto a white page and the dot is
      less covered than continuous with what arrives. On white the dot is the
      accent, and the hole eats it.
   3. The iris opens at the dot the bounce just landed and spreads outward
      past the edges of the screen. It is aimed rather than centred, which is
      the whole point: the mark the wait was built around is where the page
      comes through. This is the beat with the time in it -- beat 2 only holds
      -- so the opening is unhurried and the page arrives as something
      uncovered rather than something cut to. */

/* There is deliberately no colour transition on this beat. The swap belongs to
   the keyframes, already eased into the impact, and the curtain simply keeps
   its result -- so beat 2 has nothing to animate and only has to stop things
   moving. The version this replaces faded the ground to white here, which
   needed the live colours frozen inline against a forced reflow to give the
   transition a frame to start from. None of that is needed to leave a colour
   where it already is.

   The dot is paused rather than stopped, and loader.js then seeks it onto the
   floor. The order of those two matters -- see the note on ground() there. */

.loader.is-landed .loader-dot,
.loader.is-landed .loader-dot-shadow {
	-webkit-animation-play-state: paused;
	        animation-play-state: paused;
}

.loader.is-done {
	--iris-r: 100%;
	visibility: hidden;
	pointer-events: none;
	transition:
		--iris-r var(--duration-iris) var(--ease-iris),
		visibility 0s linear var(--duration-iris);
}

/* One flex item, so the text and the dot lay out as inline boxes on a shared
   baseline. As two items they are blockified and align-items centres the dot
   in the line instead. */

.loader-mark {
	white-space: nowrap;
}

/* The dot's box, exactly: as wide and as tall as the dot, sitting on the text
   baseline like the full stop it is. An inline-block with no in-flow content
   takes its baseline from its bottom margin edge, so this puts the bottom of
   the dot on the bottom of the type.

   Everything inside is positioned against the box, so the box does not move
   when the dot does -- which is what makes the dot's resting centre a single
   measurement rather than a reconstruction from line boxes and vertical-align,
   and what lets beat 3 aim at it. */

.loader-period {
	position: relative;
	display: inline-block;
	width: var(--dot-size);
	height: var(--dot-size);
	margin-left: var(--dot-bearing);
	vertical-align: baseline;
	/* Offset rather than margin: it moves the dot without moving the baseline
	   it is measured against, and the rect beat 3 aims at moves with it. */
	top: var(--dot-overshoot);
}

.loader-dot,
.loader-dot-shadow {
	position: absolute;
	left: 50%;
	border-radius: var(--radius-full);
}

.loader-dot {
	inset-block: 0;
	width: var(--dot-size);
	margin-left: calc(var(--dot-size) / -2);
	background-color: currentColor;
	-webkit-animation: loader-jump var(--duration-jump) infinite;
	        animation: loader-jump var(--duration-jump) infinite;
}

/* Centred on the baseline, which is the surface the dot rests on: half of it
   sits above the line and half below, the way a contact shadow does. It is
   wider than the dot's box and hangs below it, so it overflows .loader-period
   on every side -- deliberately. The box is sized to the dot because the dot
   is the thing that gets measured; the shadow is free to fall where it must,
   and nothing between here and .loader clips it.

   A shadow is the ground with the light taken out of it, not a fixed ink. Ink
   at any workable opacity is invisible on PokeDoke's navy and heavy on the
   pale blue; mixing the ground itself toward black is the same relative
   darkening whatever the accent is, and it follows the ground's swaps without
   a second set of keyframes. It is declared before the dot in the markup so it
   paints behind it without needing a z-index. */

.loader-dot-shadow {
	bottom: calc(var(--dot-shadow-h) / -2);
	width: calc(var(--dot-size) * 12 / 7);
	height: var(--dot-shadow-h);
	margin-left: calc(var(--dot-size) * -6 / 7);
	background-color: color-mix(in srgb, var(--loader-ground) 62%, #000);
	opacity: 0.5;
	-webkit-animation: loader-shadow var(--duration-jump) infinite;
	        animation: loader-shadow var(--duration-jump) infinite;
}

/* Gravity, not a linear slide: it leaves the ground fast and decelerates to the
   apex, then accelerates back down. The per-keyframe timing functions are what
   make that readable -- one curve across the whole cycle cannot do both.

   The two halves are not the same curve, and the difference is the weight. The
   rise is a cubic: it is watched, and the shape of it is already right. The
   fall is a quart, one power steeper, and it is felt rather than watched --
   the dot holds near the apex through a third of the drop and then arrives
   with most of its 2rem spent in the last few frames. The exponent is the only
   knob here; quint is one too far, and lands the dot hard enough that the
   missing squash starts to show.

   The apex sits past the half way mark for the same reason: the fall covers
   the same ground in 48% of the cycle that the rise took 52% to cover, which
   is a difference nobody can see on the way up and a harder landing on the way
   down. A real toss divides its time evenly. This one is not a real toss.

   The float at the apex is what falls out of the two curves meeting, not a
   hold -- so it costs no time of its own, and it cannot drift out of step with
   the ground cycle, which is keyed to the landings.

   The beziers are written out rather than pulled from :root because a keyframe
   cannot read a custom property here. `animation-timing-function: var(--x)`
   parses, and getKeyframes() then reports the segment as plain `ease` -- which
   is an ease-OUT, so the dot floats down and lands like a feather, and the
   failure looks like a design decision rather than a bug. */

@-webkit-keyframes loader-jump {
	0% {
		translate: 0 0;
		-webkit-animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
		        animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);   /* --ease-catch, ease-out cubic */
	}
	52% {
		translate: 0 -2rem;
		-webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);
		        animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);    /* ease-in quart -- see above */
	}
	100% {
		translate: 0 0;
	}
}

@keyframes loader-jump {
	0% {
		translate: 0 0;
		-webkit-animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
		        animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);   /* --ease-catch, ease-out cubic */
	}
	52% {
		translate: 0 -2rem;
		-webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);
		        animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);    /* ease-in quart -- see above */
	}
	100% {
		translate: 0 0;
	}
}

/* In phase with the jump, and the right way round: tight and faint while the
   dot is at its apex, wide and darker when it lands. The shadow it replaces
   ran on its own 600ms beat against a 2400ms jump, which put it at its widest
   exactly when the dot was highest -- backwards, and the reason the bounce
   never quite read. */

@-webkit-keyframes loader-shadow {
	0% {
		scale: 1 1;
		opacity: 0.5;
		-webkit-animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
		        animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);   /* --ease-catch, ease-out cubic */
	}
	52% {
		scale: 0.42 1;
		opacity: 0.1;
		-webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);
		        animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);    /* ease-in quart -- see above */
	}
	100% {
		scale: 1 1;
		opacity: 0.5;
	}
}

@keyframes loader-shadow {
	0% {
		scale: 1 1;
		opacity: 0.5;
		-webkit-animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
		        animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);   /* --ease-catch, ease-out cubic */
	}
	52% {
		scale: 0.42 1;
		opacity: 0.1;
		-webkit-animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);
		        animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0);    /* ease-in quart -- see above */
	}
	100% {
		scale: 1 1;
		opacity: 0.5;
	}
}

/** Loader Wrapper End ********************************************************/

/** Cross Page Styling ********************************************************/

.bold {
	font-weight: 600;
}

.blue {
	color: var(--blue);
}

.green {
	color: var(--green);
}

.pink {
	color: var(--pink);
}

.yellow {
	color: var(--yellow);
}

/* The swinging sign.
   Two rotations compose on the one element: `rotate` carries the swing,
   `transform` carries the hold. They are independent properties, so the hold
   never fights the keyframes.

   The whole interaction turns on one detail: hovering sets `animation: none`,
   so letting go re-applies `swing`, which restarts from its 0% frame. That
   frame is 0deg -- exactly the upright angle the hover was holding -- so the
   release is seamless and the sign swings down into its rest angle again.

   The pivot sits 6px below the top edge and 36% across rather than 50%, so the
   frame hangs from near its top and a little left of centre -- the way a hook
   is never quite where you meant to put it. The longer right side then sweeps
   the wider arc and carries more weight into the swing, which is what stops it
   reading as a box being rotated about its middle.

   Padding is 8px/6px against an always present 2px transparent border, which
   keeps the outer box the size the surrounding heading was laid out for. */

.name-tilted {
	font-weight: bold;
	/* Seven taps in a row on a touch screen is a double-tap three and a half
	   times over, and the viewport is zoomable, so without this the browser
	   spent them zooming the heading instead of loosening the sign. Panning and
	   pinching are untouched; only the double-tap gesture goes. A fallen sign
	   takes this further still -- see .is-fallen, which needs none at all so a
	   drag cannot turn into a scroll. */
	touch-action: manipulation;
	position: absolute;
	padding: 8px 6px;
	border: 2px solid transparent;
	margin: -0.7rem 0.25rem auto;
	color: #FFFFFF;
	--rest: 5deg;
	--amp: 1;
	transform-origin: 36% 6px;
	rotate: var(--rest);
	box-shadow: var(--shadow-sign-rest);
	-webkit-animation: swing var(--duration-swing) var(--ease-swing) 1 backwards;
	        animation: swing var(--duration-swing) var(--ease-swing) 1 backwards;
	/* Colour is the one thing that follows the pointer rather than the beat: the
	   fill comes back as soon as the hover ends, so the sign is never left white
	   while it waits to swing. Short, and on the base rule so it runs both ways. */
	transition:
		background-color var(--duration-quick) ease-out,
		color var(--duration-quick) ease-out,
		border-color var(--duration-quick) ease-out;
}

/* Held: straightened and pressed back against the wall. A pointer on the sign
   holds it, and so does .is-held, which sign.js uses to keep holding it for a
   beat after the pointer leaves and to park a sign it has just rehung. The two
   resolve to the same declarations, so the handover between them changes no
   computed value and nothing transitions across it.

   Both are fenced off a fallen sign. Nothing here is meaningful once the frame
   is on the floor -- it is not against a wall to be pressed to, the transform
   is being written inline every frame anyway, and .is-fallen has already cut
   the animation -- but the plumb shadow would still land, on a frame lying flat
   on the carpet. The fence on the next rule is the one that matters. */

.name-tilted:not(.is-fallen).is-held {
	-webkit-animation: none;
	        animation: none;
	transform: rotate(calc(-1 * var(--rest)));
	box-shadow: var(--shadow-sign-plumb);
}

/* A hand levelling the frame. sign.js runs the same gesture, on the same curve
   and with no oscillation either way, on a sign that has just been put back --
   but from its own FIX_SPEEDUP, and over twice as long. See --duration-catch.

   .is-held carries the transition as well as :hover, and sign.js adds it when
   the pointer ARRIVES rather than when it leaves -- the browser settles the
   :hover change during hit-testing and pointerleave lands a frame later, and in
   that frame neither rule would match, so the transform would fall back to none
   and the correction would restart from the resting angle.

   Only a hanging sign eases, and BOTH selectors have to say so. A fallen one
   has its transform rewritten every frame by the physics loop; ease that and
   the browser stops being told where the sign is and starts interpolating
   towards where it was last told, 640ms behind. A drag trails the cursor by
   most of a second, and a straight drop comes out as a sway, because what is
   interpolated is the decomposed matrix -- translate and rotate together -- so
   a falling frame that is also turning is carried along a curve rather than
   down. This fence was on :hover alone for a while, and .is-held leaks past
   :hover: sign.js adds it when the pointer arrives and release() will only take
   it off a sign that is still hanging, so a sign knocked off its hook under the
   pointer kept it for good. drop() now lets go of it as well; this is the half
   of the fix that does not depend on a class being tidied up correctly.

   Letting go stays instantaneous -- dropping .is-held takes the transition with
   it, and the swing carries the return -- and rehang() suppresses this for one
   frame when it applies .is-held over a fallen sign's translate3d, which would
   otherwise ease the sign back across the page. */

.name-tilted:not(.is-fallen).is-held {
	transition:
		transform var(--duration-catch) var(--ease-catch),
		box-shadow var(--duration-catch) var(--ease-catch),
		background-color var(--duration-quick) ease-out,
		color var(--duration-quick) ease-out,
		border-color var(--duration-quick) ease-out;
}

#tilted-blue {
	background-color: var(--blue);
}

#tilted-blue.is-dragging {
	color: var(--blue);
	background: #FFFFFF;
	border-color: var(--blue);
}

#tilted-pink {
	background-color: var(--pink);
}

#tilted-pink.is-dragging {
	color: #EF767A;
	background: #FFFFFF;
	border-color: var(--pink);
}

/* Every :hover the sign has, fenced behind a pointer that can actually hover.
   A touch screen cannot, and does not say so by simply never matching: iOS and
   iPadOS apply :hover on tap and hold it until something else is tapped. Every
   rule above would latch -- and the first of them carries `animation: none`, so
   one tap on a phone stopped the sign swinging for good and left it standing
   straight, which is a fair description of "the sign does not work on mobile".
   The colours latched with it.

   None of this is lost on touch. sign.js adds .is-held when the pointer
   ARRIVES, which a tap does, and takes it off a beat after it leaves -- so a
   tap gets the hold and then gives it back, and a mouse gets the same thing
   one event earlier than :hover would have given it. The duplication below is
   the price of the fence, and it is only three declarations twice. */

@media (hover: hover) {
	.name-tilted:not(.is-fallen):hover {
		-webkit-animation: none;
		        animation: none;
		transform: rotate(calc(-1 * var(--rest)));
		box-shadow: var(--shadow-sign-plumb);
	}
	.name-tilted:not(.is-fallen):hover {
		transition:
			transform var(--duration-catch) var(--ease-catch),
			box-shadow var(--duration-catch) var(--ease-catch),
			background-color var(--duration-quick) ease-out,
			color var(--duration-quick) ease-out,
			border-color var(--duration-quick) ease-out;
	}
	#tilted-blue:hover {
		color: var(--blue);
		background: #FFFFFF;
		border-color: var(--blue);
	}
	#tilted-pink:hover {
		color: #EF767A;
		background: #FFFFFF;
		border-color: var(--pink);
	}
}

/* Released from upright, the frame falls past its rest angle and oscillates
   about it, each arc shorter than the last: deviations from the 5deg rest of
   -5, +3, -1.8, +1.1, -0.65, +0.4, -0.15, 0.

   The keyframes are evenly spaced, 12-13% apart, because a pendulum's period
   does not change as it loses energy -- only the distance it covers does, and
   that is what the eye reads as slowing down. Spacing them evenly keeps the
   late arcs from bunching up against the end of the animation.

   ease-in-out between each pair puts the slow moment at every turning point,
   where velocity is zero, and the fast moment where it passes through rest.
   The shadow rides the same keyframes, so the throw lengthens on the way out
   and draws back in as the frame settles. */

@-webkit-keyframes swing {
	0% {
		rotate: 0deg;
		box-shadow: var(--shadow-sign-plumb);
	}
	13% {
		rotate: calc(var(--rest) + 3deg * var(--amp));
		box-shadow: var(--shadow-sign-swing);
	}
	26% {
		rotate: calc(var(--rest) - 1.8deg * var(--amp));
	}
	39% {
		rotate: calc(var(--rest) + 1.1deg * var(--amp));
		box-shadow: var(--shadow-sign-rest);
	}
	52% {
		rotate: calc(var(--rest) - 0.65deg * var(--amp));
	}
	64% {
		rotate: calc(var(--rest) + 0.4deg * var(--amp));
	}
	76% {
		rotate: calc(var(--rest) - 0.15deg * var(--amp));
	}
	88% {
		rotate: calc(var(--rest) + 0.15deg * var(--amp));
	}
	100% {
		rotate: var(--rest);
		box-shadow: var(--shadow-sign-rest);
	}
}

@keyframes swing {
	0% {
		rotate: 0deg;
		box-shadow: var(--shadow-sign-plumb);
	}
	13% {
		rotate: calc(var(--rest) + 3deg * var(--amp));
		box-shadow: var(--shadow-sign-swing);
	}
	26% {
		rotate: calc(var(--rest) - 1.8deg * var(--amp));
	}
	39% {
		rotate: calc(var(--rest) + 1.1deg * var(--amp));
		box-shadow: var(--shadow-sign-rest);
	}
	52% {
		rotate: calc(var(--rest) - 0.65deg * var(--amp));
	}
	64% {
		rotate: calc(var(--rest) + 0.4deg * var(--amp));
	}
	76% {
		rotate: calc(var(--rest) - 0.15deg * var(--amp));
	}
	88% {
		rotate: calc(var(--rest) + 0.15deg * var(--amp));
	}
	100% {
		rotate: var(--rest);
		box-shadow: var(--shadow-sign-rest);
	}
}

/* Easter egg: knocking the sign off its hook (public/js/sign.js).
   Seven clicks loosen it -- each one yanks the frame down and leaves it hanging
   more crooked, swinging wider -- and the seventh lets go. --rest and --amp
   above are what the loosening moves -- sign.js walks --rest from 5deg to
   17deg and --amp from 1 to 2.5 -- so a looser sign reuses the same keyframes
   rather than needing any of its own.
   Once it falls, .is-fallen hands the transform to JS and CSS stops touching
   it. The slot is an inert copy left behind in the heading, so the hook's
   position is always readable no matter how the page has scrolled or reflowed. */

/* The yank rides `translate`, which nothing else in the component uses, so it
   composes over both the swing and the hover instead of fighting either.
   It sits after the :hover rule so it survives `animation: none` there. */

.name-tilted.is-yanking {
	-webkit-animation: yank 170ms ease-out 1;
	        animation: yank 170ms ease-out 1;
}

@-webkit-keyframes yank {
	0% {
		translate: 0 0;
	}
	45% {
		translate: 0 var(--yank, 2px);
	}
	100% {
		translate: 0 0;
	}
}

@keyframes yank {
	0% {
		translate: 0 0;
	}
	45% {
		translate: 0 var(--yank, 2px);
	}
	100% {
		translate: 0 0;
	}
}

.name-tilted.is-fallen {
	position: fixed;
	left: 0;
	top: 0;
	margin: 0;
	z-index: 90;
	-webkit-animation: none;
	        animation: none;
	rotate: 0deg;
	translate: 0 0;
	transform-origin: 50% 50%;
	touch-action: none;
	cursor: default;
	will-change: transform;
}

/* Catchable in the air as well as off the floor, but not straight away -- see
   CATCH_DELAY_MS. .is-settled carried this when the landing was the gate; the
   gate is a clock now, and this class is the cursor telling you it has run. */

.name-tilted.is-fallen.is-catchable {
	cursor: -webkit-grab;
	cursor: grab;
}

.name-tilted.is-dragging {
	cursor: -webkit-grabbing;
	cursor: grabbing;
}

.sign-slot {
	visibility: hidden;
	/* Transparent as well as hidden, so the outline fades up into its faint
	   state when a drag starts. Left at the initial 1 it would flash on at full
	   strength -- the "let go here" reading -- and then dim to the hint. */
	opacity: 0;
	pointer-events: none;
	-webkit-animation: none !important;
	        animation: none !important;
	rotate: 0deg !important;
	translate: 0 0 !important;
	transform: none !important;
	box-shadow: none !important;
}

/* Shown only while a fallen sign is dragged within reach of its hook. */

/* The hook, while something is looking for it. Hidden the rest of the time --
   an empty dashed box in the middle of a heading is a bug, not a hint -- and
   shown for the whole of a drag rather than only inside SNAP_RADIUS.

   Two strengths, because the outline is being asked to answer two different
   questions. Faint says where this goes; full says letting go now would put it
   there. One outline that only ever appeared at full strength had to serve as
   both, which made the answer to the first question arrive at the same moment
   as the answer to the second, and too late to steer by. */

.sign-slot.is-hinting,
.sign-slot.is-target {
	visibility: visible;
	color: transparent !important;
	background: none !important;
	border: 2px dashed var(--blue);
	transition: opacity var(--duration-quick) ease-out;
}

.sign-slot.is-hinting {
	opacity: 0.32;
}

/* After .is-hinting, so it wins while both are on -- they are the same
   specificity and the drag carries both. */

.sign-slot.is-target {
	opacity: 1;
}

.bold-highlight {
	line-height: 1.3em;
	text-decoration: none;
	font-weight: 600;
	-webkit-animation: all 800ms;
}

.bold-highlight.blue-highlight:hover,
.bold-highlight.blue-highlight:focus {
	-webkit-animation: background-blue 500ms 1 ease-in-out forwards;
	        animation: background-blue 500ms 1 ease-in-out forwards;
}

.bold-highlight.blue-bg::-moz-selection {
	background: var(--blue);
	color: #FFFFFF;
}

.bold-highlight.blue-bg::selection {
	background: var(--blue);
	color: #FFFFFF;
}

.bold-highlight.green-highlight:hover,
.bold-highlight.green-highlight:focus {
	-webkit-animation: background-green 500ms 1 ease-in-out forwards;
	        animation: background-green 500ms 1 ease-in-out forwards;
}

.bold-highlight.green-highlight::-moz-selection {
	background: var(--green);
	color: #FFFFFF;
}

.bold-highlight.green-highlight::selection {
	background: var(--green);
	color: #FFFFFF;
}

.bold-highlight.pink-highlight:hover,
.bold-highlight.pink-highlight:focus {
	-webkit-animation: background-pink 500ms 1 ease-in-out forwards;
	        animation: background-pink 500ms 1 ease-in-out forwards;
}

.bold-highlight.pink-highlight::-moz-selection {
	background: var(--pink);
	color: #FFFFFF;
}

.bold-highlight.pink-highlight::selection {
	background: var(--pink);
	color: #FFFFFF;
}

.bold-highlight.yellow-highlight:hover,
.bold-highlight.yellow-highlight:focus {
	-webkit-animation: background-yellow 500ms 1 ease-in-out forwards;
	        animation: background-yellow 500ms 1 ease-in-out forwards;
}

.bold-highlight.yellow-highlight::-moz-selection {
	background: var(--yellow);
	color: #FFFFFF;
}

.bold-highlight.yellow-highlight::selection {
	background: var(--yellow);
	color: #FFFFFF;
}

@-webkit-keyframes background-blue {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--blue);
		color: #FFFFFF;
	}
}

@keyframes background-blue {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--blue);
		color: #FFFFFF;
	}
}

@-webkit-keyframes background-green {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--green);
		color: #FFFFFF;
	}
}

@keyframes background-green {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--green);
		color: #FFFFFF;
	}
}

@-webkit-keyframes background-pink {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--pink);
		color: #FFFFFF;
	}
}

@keyframes background-pink {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--pink);
		color: #FFFFFF;
	}
}

@-webkit-keyframes background-yellow {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--yellow);
		color: #FFFFFF;
	}
}

@keyframes background-yellow {
	from {
		text-decoration: none;
	}
	to {
		text-decoration: none;
		background: var(--yellow);
		color: #FFFFFF;
	}
}

.header {
	font-family: var(--poppins);
	color: var(--black);
	display: flex;
	justify-content: left;
	align-items: center;
	text-align: left;
	font-size: var(--text-lg);
	font-weight: 600;
}

.header::before {
	content: "";
	color: var(--blue);
	border-top: 0.15rem solid;
	margin: 0 0.5rem 0 0;
	width: 2.5rem;
	height: 1px;
}

.fade-in {
	-webkit-animation-name: fade-in;
	        animation-name: fade-in;
	-webkit-animation-duration: 1250ms;
	        animation-duration: 1250ms;
}

@-webkit-keyframes fade-in {
	from {
	  opacity: 0;
	}
	to {
	  opacity: 1;
	}
}

@keyframes fade-in {
	from {
	  opacity: 0;
	}
	to {
	  opacity: 1;
	}
}

/** Cross Page Styling End ********************************************************/

/** Navigation Bar ********************************************************/

#navbar {
	position: fixed;
	width: 100%;
	display: flex;
	align-items: center;
	justify-content: space-between;
	font-family: var(--poppins);
	z-index: 10;
	min-height: var(--header-height);
	padding-inline: var(--bleed-pad);
}

.scroll-background {
	-webkit-animation: scroll-bg-fadeIn 750ms ease-in-out forwards;
	        animation: scroll-bg-fadeIn 750ms ease-in-out forwards;
}

@-webkit-keyframes scroll-bg-fadeIn {
	from {
		background-color: rgba(256, 256, 256, 0);
	}
	to {
		background-color: rgba(256, 256, 256, 0.85);
	}
}

@keyframes scroll-bg-fadeIn {
	from {
		background-color: rgba(256, 256, 256, 0);
	}
	to {
		background-color: rgba(256, 256, 256, 0.85);
	}
}

.navbar-brand,
.navbar-brand > span,
.navbar-nav > li > a,
.copyright,
.name-tilted {
	-moz-user-select: none;
	-webkit-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

#accessibility-brand {
	display: none;
	visibility: hidden;
}

.navbar-brand {
	font-size: var(--text-xl);
	font-weight: 600;
	text-decoration: none;
	margin: 0;
	padding: 0;
	transition: all 500ms ease-in-out;
}

.navbar-brand::after {
	position: relative;
	content: ".";
	left: -0.3rem;
}

#nav-brand.henree:hover::after {
	content: "enree.";
	-webkit-animation: fadeIn 1000ms ease-in-out;
	animation: fadeIn 1000ms ease-in-out;
}

#nav-brand:hover::after {
	color: var(--black);
}

@-webkit-keyframes fadeIn {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}

@keyframes fadeIn {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}

nav a {
	color: var(--black);
	font-size: var(--text-md);
	/* margin-block, not a margin shorthand: the `auto` inline margins this used
	   to carry centred any nav link that had nothing overriding them. */
	margin-block: 0.25em;
}

/* Change all navbar hover color */

nav a:hover,
nav a:focus,
nav a.active {
	font-weight: 600;
	cursor: pointer;
}

#nav-brand.default:hover,
#nav-brand.default:focus,
#nav-brand.default.active {
	color: var(--blue);
}

#nav-brand.about:hover,
#nav-brand.about:focus,
#nav-brand.about.active {
	color: var(--blue);
}

#portfolio-link:hover,
#portfolio-link:focus,
#portfolio-link.active {
	color: var(--blue);
}

#portfolio-link:hover:before,
#portfolio-link.active:before {
	background: var(--blue);
}

#about-link:hover,
#about-link:focus,
#about-link.active {
	color: var(--blue);
}

#about-link:hover:before,
#about-link.active:before {
	background: var(--blue);
}

.nav-link {
	color: var(--black);
	text-decoration: none;
	position: relative;
	-webkit-animation: nav-expand 400ms ease-in-out;
	        animation: nav-expand 400ms ease-in-out;
}

.nav-link:before {
	content: "";
	position: absolute;
	width: 100%;
	height: 0.15rem;
	bottom: -3px;
	left: 0;
	visibility: hidden;
	border-radius: 10px;
	transform: scaleX(0);
	transition: var(--animate);
}

.nav-link.active:before {
	visibility: visible;
	transform: scaleX(0.3);
}

@-webkit-keyframes nav-expand {
	to {
		transform: scaleX(1);
	}
}

@keyframes nav-expand {
	to {
		transform: scaleX(1);
	}
}

.active:hover:before,
.nav-link:hover:before,
.nav-link:focus:before {
	visibility: visible;
	transform: scaleX(1);
}

.navbar-nav {
	display: flex;
	flex-direction: row;
	margin: 0;
	list-style: none;
	-webkit-padding-start: 0;
	        padding-inline-start: 0;
}

.navbar-nav > li {
	margin: auto clamp(0.7rem, 2vw, 1.2rem);
}

.navbar-nav > li > a {
	padding: 0;
}

.navbar-nav > li:last-child {
	margin-right: 0 !important;
}

.project-navbar a,
.project-navbar #nav-brand:hover::after {
	color: #FFFFFF;
}

.project-navbar #nav-brand:hover::after {
	color: #FFFFFF;
}

.project-navbar.scroll-background a,
.project-navbar.scroll-background #nav-brand:hover::after {
	color: var(--black);
}

/** Navigation Bar End ********************************************************/

/** Footer ********************************************************/

.footer {
	display: flex;
	flex-direction: column;
	font-size: var(--text-base);
	width: 100%;
	max-width: var(--container);
	margin-inline: auto;
	padding-inline: var(--gutter);
	margin-block: var(--space-xl) var(--space-l);
	align-items: center;
}

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

.footer-copyright {
	display: flex;
	font-size: var(--text-xs);
}

.footer-inner {
	display: flex;
	flex-direction: column;
	align-items: center;
}

.footer-social {
	display: flex;
	margin: var(--space-m) 0 var(--space-l);
	flex-wrap: wrap;
	gap: var(--space-m);
}

.footer-social .social-icon {
	padding: 6px 6px;
	font-size: var(--text-xl);
	/* inline-flex, not inline: an inline svg sits in a line box and inherits its
	   leading, which added 6px of height the webfont glyph never had. A flex
	   box wraps the glyph exactly. */
	display: inline-flex;
	align-items: center;
	line-height: 1;
}

/* Sized and coloured the way the webfont glyph was. height: 1em keeps
   font-size in charge; width: auto is the important half -- the glyphs are not
   square and the font never drew them square. Font Awesome ships a width class
   per aspect ratio (fa-w-14 and friends) for exactly this; letting the viewBox
   set the width gets the same answer without four more rules, and gets it right
   for any icon added later. currentColor keeps every colour rule below working
   on `color`, as they did when these were text. */

.social-glyph {
	display: block;
	height: 1em;
	width: auto;
	fill: currentColor;
}

/* The envelope opens on hover. This was jQuery swapping fa-envelope for
   fa-envelope-open; both glyphs are in the markup now and CSS picks one, which
   costs a few hundred bytes of path data and saves a script. Behind
   (hover: hover) because a touch screen latches :hover -- the sign taught us
   that one -- and an envelope stuck open is not a state anything can leave. */

.icon-email .glyph-open {
	display: none;
}

.footer-social .social-icon.icon-linkedin {
	padding: 5px 6px !important;
}

.footer-social .social-icon.icon-resume {
	padding: 5px 8px !important;
}

.footer-social a {
	display: inline-block;
	font-size: var(--text-base);
	height: 40px;
	width: 40px;
	position: relative;
}

.footer-social a::after {
	content: attr(data-text);
	position: absolute;
	padding: 0.1rem 0.6rem;
	border-radius: 10px;
	font-size: var(--text-xs);
	text-align: center;
	z-index: -1;
}

.footer-social a:hover::after {
	display: unset;
}

.footer-social a:first-child {
	margin-left: 0;
}

.footer-social a:last-child {
	margin-right: 0;
}

/* Social Media Buttons Animations */

.social-icon {
	transition: all 500ms ease-in-out;
}

.resume .social-icon {
	--resume-color: #9F9CAB;
	color: var(--resume-color);
	background: none;
	border-radius: 5px;
	position: relative;
}

.email .social-icon {
	--email-color: #779ECB;
	color: var(--email-color);
	background: none;
	border-radius: 5px;
}

.linkedin .social-icon {
	--linkedin-color: #2867B2;
	color: var(--linkedin-color);
	background: none;
	border-radius: 5px;
	height: -webkit-max-content;
	height: -moz-max-content;
	height: max-content;
}

.dribbble .social-icon {
	--dribbble-color: #EA4C89;
	color: var(--dribbble-color);
	background: none;
	border-radius: 5px;
	height: -webkit-max-content;
	height: -moz-max-content;
	height: max-content;
}

.resume:hover .social-icon {
	--resume-color: #9F9CAB;
	color: #FFFFFF;
	background: var(--resume-color);
	transform: translate3d(0, -10px, 0);
}

.email:hover .social-icon {
	--email-color: #779ECB;
	color: #FFFFFF;
	background: var(--email-color);
	transform: translate3d(0, -10px, 0);
}

.linkedin:hover .social-icon {
	--linkedin-color: #2867B2;
	color: #FFFFFF;
	background: var(--linkedin-color);
	transform: translate3d(0, -10px, 0);
}

.dribbble:hover .social-icon {
	--dribbble-color: #EA4C89;
	color: #FFFFFF;
	background: var(--dribbble-color);
	transform: translate3d(0, -10px, 0);
}

@media (hover: hover) {
	.email:hover .icon-email .glyph-closed {
		display: none;
	}
	.email:hover .icon-email .glyph-open {
		display: inline-block;
	}
}

.footer-social a::after {
	bottom: -1rem;
	-webkit-animation: shift-down 750ms 1 ease-in-out;
	        animation: shift-down 750ms 1 ease-in-out;
}

.resume::after {
	transition: all 500ms ease-in-out;
	--resume-color: #9F9CAB;
	left: -0.925rem;
	color: #FFFFFF;
	background: var(--resume-color);
}

.email::after {
	--email-color: #779ECB;
	left: -0.375rem;
	color: #FFFFFF;
	background: var(--email-color);
}

.linkedin::after {
	--linkedin-color: #2867B2;
	left: -1.075rem;
	color: #FFFFFF;
	background: var(--linkedin-color);
}

.dribbble::after {
	--dribbble-color: #EA4C89;
	left: -1.05rem;
	color: #FFFFFF;
	background: var(--dribbble-color);
}

@-webkit-keyframes shift-down {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}

@keyframes shift-down {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}

/** Footer End ********************************************************/

/* Two breakpoints, both in rem so they move with the reader's font size, and
   both placed where this layout actually changes shape rather than at a device
   width. Everything that used to need 1200px, 1900px and 2400px breakpoints is
   handled by clamp() and the capped container instead. */

/* Side by side: ~768px */

@media (min-width: 48rem) {
	.mobile-break {
		display: none;
	}
}

/* Roomy: ~1024px */

@media (min-width: 64rem) {
	.footer {
		flex-direction: row;
	}
	.footer-copyright {
		flex: 1;
		width: 100%;
		margin-bottom: 0.525rem;
		flex-direction: row-reverse;
		align-self: flex-end;
	}
	.footer-social {
		display: flex;
		width: 100%;
		margin: var(--space-s) auto 0;
		justify-content: left !important;
	}
	.footer-social a::after {
		display: none;
		bottom: -0.8rem;
	}
	.social-buttons::after {
		bottom: -1rem;
	}
}

/* Remove all animations, transitions and smooth scroll for people that prefer not to see them */

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

/* Remove all hover animations */

@media (hover: none) {
	.footer-social a::after {
		display: inherit !important;
	}
}

/** Project Styling **********************************************************/

.header {
	color: var(--project-color) !important;
}

.project-container {
	margin-top: 1.5rem !important;
	margin-bottom: 1.5rem !important;
	display: block;
	flex-direction: row;
	justify-content: space-between;
	flex-wrap: wrap;
}

.project-section {
	margin: 0.75rem 0 1.75rem;
}

.project-content {
	margin: 1rem 0;
}

.project-section .header {
	margin: 0.5rem 0;
}

/* Full bleed, but its contents line up with the content column: --bleed-pad
   resolves the percentage against the hero's own width, which is the viewport. */

.project-hero {
	display: flex;
	flex-direction: column-reverse;
	gap: var(--space-l);
	margin: 0;
	padding-block: calc(var(--header-height) + var(--space-l)) var(--space-l);
	padding-inline: var(--bleed-pad);
	background-color: var(--project-color);
}

.project-intro {
	display: flex;
	flex-direction: column;
	flex: 1;
	color: #FFFFFF;
	justify-content: flex-start;
}

.project-title {
	justify-content: flex-start;
	font-size: var(--text-2xl);
	font-family: var(--poppins);
	font-weight: 600;
	margin: calc(0.5rem + 0.5vw) 0 calc(0.2rem + 0.1vw);
}

.project-card-tags {
	display: flex;
	flex-wrap: wrap;
	margin-bottom: 0.125rem;
}

.project-card-tag {
	font-size: var(--text-2xs);
	width: -webkit-fit-content;
	width: -moz-fit-content;
	width: fit-content;
	box-sizing: border-box;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
	padding: 0.3rem 0.6rem;
	border: 2px solid #FFFFFF;
	border-radius: 12px;
	margin-top: 0.3rem;
	margin-right: 0.5rem;
}

.project-card-tags:first-child,
.project-card-tags:only-child {
	margin-left: 0px;
}

.project-card-tag:last-child {
	margin-right: 0px;
}

.project-desc {
	font-size: var(--text-md);
	justify-content: flex-end;
	margin: calc(0.5rem + 0.6vw) 0 calc(0.3rem + 0.6vw);
}

.project-cover {
	justify-content: center;
	align-items: center;
	text-align: center;
	flex: 3;
}

.project-cover > img {
	border-radius: 5px;
	width: 100%;
}

.overview {
	display: flex;
	flex-direction: column-reverse;
	justify-content: space-between;
	margin-top: 0rem !important;
}

.overview .left {
	display: flex;
	flex-direction: column;
	flex: 2;
	margin: 0.5rem 0 0;
}

.overview p:last-of-type {
	margin-bottom: 0rem;
}

.overview .right {
	display: flex;
	flex-direction: row;
	flex: 1;
	justify-content: flex-start;
	margin: 0 0 0.5rem 0;
}

.project-header::before {
	content: "";
	width: 1px;
	height: 2.5rem;
	border-left: calc(0.25rem + 0.1vw) solid var(--project-color);
	margin-right: 0.75rem;
}

.analysis .project-header::before {
	height: 4rem;
}

.project-header {
	font-family: var(--poppins);
	color: var(--project-color);
	display: flex;
	justify-content: left;
	align-items: center;
	text-align: left;
	font-size: var(--text-lg);
	font-weight: 600;
	margin-bottom: var(--space-2xs);
}

.project-subheader {
	font-family: var(--poppins);
	color: var(--black);
	display: flex;
	justify-content: left;
	align-items: center;
	text-align: left;
	font-size: var(--text-sm);
	font-weight: 600;
	margin-bottom: var(--space-2xs);
}

.outer-quote {
	display: flex;
	justify-content: left;
	align-items: center;
	text-align: left;
	margin: 1rem 0 0;
}

.outer-quote::before {
	content: "";
	color: var(--blue);
	border-left: 0.2rem solid;
	margin: 0 1rem 0 0;
	width: 1px;
	height: 5rem;
}

.quote {
	font-style: italic;
	margin-bottom: 0.2rem;
}

.quote-person {
	font-family: var(--poppins);
	color: var(--black);
	display: flex;
	justify-content: left;
	align-items: center;
	text-align: left;
}

.quote-person::before {
	content: "";
	color: var(--black);
	border-top: 0.1rem solid;
	margin: 0 0.5rem 0 0;
	width: 1.75rem;
	height: 1px;
}

.project-user-group {
	display: flex;
	flex-direction: column;
}

.project-user-group {
	margin: 0.5rem 0 1.5rem 0;
	flex-basis: calc(100%);
}

.project-user-img,
.project-user-img img {
	width: 8rem;
	height: 8rem;
	border-radius: 100px;
	margin-top: 0.1rem;
	margin-bottom: 0.75rem;
}

.project-content-header {
	color: var(--project-color);
	margin: 1rem 0 0 0;
	font-weight: bold;
}

.project-content-desc {
	font-size: var(--text-base);
	line-height: 1.6;
	margin: var(--space-3xs) 0 0;
}

.project-content-images {
	margin: 1rem 0;
	display: flex;
	flex-direction: row;
	justify-content: space-evenly;
	flex-wrap: wrap;
}

.project-content-images.web .project-image-container .project-content-img img {
	max-width: 100%;
	height: auto;
}

.project-image-container {
	margin-top: 1rem;
	text-align: center;
}

.project-image-container p {
	font-size: var(--text-xs);
	font-weight: 600;
}

.project-content-img > img {
	max-height: 23rem;
	max-width: 100%;
	background-size: cover;
	background-repeat: no-repeat;
	position: relative;
}

.project-content-ui-img > img {
	max-width: 100%;
	background-size: cover;
	background-repeat: no-repeat;
	position: relative;
}

.project-demo {
	margin: 1rem 0 2rem;
	max-width: 40rem;
}

.project-disclaimer {
	font-size: var(--text-xs);
	color: var(--grey);
	font-weight: bold;
	text-align: left;
	margin-bottom: 0.5rem;
}

.conclusion {
	margin-bottom: 2.5rem;
}

.return-top-link {
	transition: all 750ms ease-in-out;
	position: fixed;
	bottom: 0;
	right: 0;
	display: inline-flex;
	cursor: pointer;
	align-items: center;
	justify-content: center;
	margin: 0 var(--gutter) var(--gutter) 0;
	border-radius: 50%;
	padding: 0.25em;
	width: 2.75rem;
	height: 2.75rem;
	background-color: rgba(47, 46, 56, 0.8);
}

.return-top-link.show {
	visibility: visible;
	opacity: 1;
}

.return-top-link.hide {
	visibility: hidden;
	opacity: 0;
}

.return-top-link svg {
	fill: #FFFFFF;
	width: 1.1rem;
	height: 1.3rem;
}

.return-top-link:hover {
	background-color: rgba(95, 171, 230, 0.95);
}

.return-top-link:hover svg {
	fill: #FFFFFF;
}

.screen-reader-text {
	position: absolute;
	-webkit-clip-path: inset(50%);
	        clip-path: inset(50%);
	margin: -1px;
	border: 0;
	padding: 0;
	width: 1px;
	height: 1px;
	overflow: hidden;
	word-wrap: normal !important;
	clip: rect(1px, 1px, 1px, 1px);
}

.screen-reader-text:focus {
	display: block;
	top: 5px;
	left: 5px;
	z-index: 100000;
	-webkit-clip-path: none;
	        clip-path: none;
	background-color: #FFFFFF;
	padding: 15px 23px 14px;
	width: auto;
	height: auto;
	text-decoration: none;
	line-height: normal;
	color: var(--black);
	font-size: 1em;
	clip: auto !important;
}

/** Project Styling End **********************************************************/

/** Image Modal Styling **********************************************************/

.modal {
	display: none;
	position: fixed;
	z-index: 99;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: auto;
	background-color: rgb(0,0,0);
	background-color: rgba(0,0,0,0.9);
}

#modal-content {
	display: flex;
	margin: auto auto 0;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	width: 100%;
	height: 100%;
	max-width: 100%;
	max-height: 100%;
}

#modal-img {
	padding: 0 5% 0;
	max-width: 90%;
	max-height: 90%;
}

#modal-caption {
	text-align: center;
	color: #FFFFFF;
	margin: 0.75rem auto 0;
}

@-webkit-keyframes zoom {
	from {
		transform: scale(0.3);
	}
	to {
		transform: scale(1)
	}
}

@keyframes zoom {
	from {
		transform: scale(0.3);
	}
	to {
		transform: scale(1)
	}
}

@-webkit-keyframes zoom-out {
	from {
		transform: scale(1);
	}
	to {
		transform: scale(0);
	}
}

@keyframes zoom-out {
	from {
		transform: scale(1);
	}
	to {
		transform: scale(0);
	}
}

/* Add Animation */

#modal-content {
	-webkit-animation-name: zoom;
	-webkit-animation-duration: 0.6s;
	animation-name: zoom;
	animation-duration: 0.6s;
}

.out {
	-webkit-animation-name: zoom-out;
	        animation-name: zoom-out;
	-webkit-animation-duration: 0.6s;
	        animation-duration: 0.6s;
}

/** Image Modal Styling End **********************************************************/

/** Loader Styling (Override) **********************************************************/

/* The whole override: the curtain's keyframes read --loader-accent, so a case
   study only has to name its colour. This used to be two full copies of the
   jump and ground keyframes, which had to be kept in step with the originals
   by hand -- and were not. */

.loader {
	--loader-accent: var(--project-color);
}

/** Loader Styling (Override)End **********************************************************/

/* Side by side: ~768px */

@media (min-width: 48rem) {
	.project-hero {
		flex-direction: row;
	}
	.project-intro {
		flex-direction: column;
		flex: 2;
	}
	.project-cover {
		margin-block: auto;
		text-align: right;
	}
	.project-title {
		margin: 0 0 calc(0.2rem + 0.1vw);
	}
	.overview {
		flex-direction: row;
	}
	.overview .left {
		margin: 0 1.5rem 0 0;
	}
	.overview .right {
		margin: 0 0 0 1.5rem;
	}
	.project-content {
		width: 90%;
	}
	.project-content-images {
		flex-direction: row;
	}
	.analysis .project-header::before {
		height: 2.5rem;
	}
	.project-content-images.web .project-image-container .project-content-img img {
		max-width: 100%;
		max-height: 23rem;
	}
}

/* Roomy: ~1024px */

@media (min-width: 64rem) {
	.project-content {
		width: var(--measure);
	}
	.return-top-link {
		margin: 0 4rem 1.5rem 0;
	}
	.project-content-ui-img > img {
		max-width: 80%;
	}
}

:root {
	--project-color: var(--project-spotify);
}