/*
 * Brutalist minimalist.
 *
 * Black on white, one typeface, and rules. No colour beyond a muted grey for
 * secondary text, no radii, no shadows, no gradients, nothing that moves. The
 * hierarchy is carried entirely by size, weight and where a line is drawn.
 *
 * The markup underneath is the same semantic HTML every other document theme
 * renders. Where the design asks for text the content does not contain - a CV
 * date printed as "2021-2022" - it comes from an attribute the shared markup
 * already carries (`data-year`, `data-count`, `data-page`). CSS can print an
 * attribute; it cannot reformat a sentence.
 *
 * No webfont: Helvetica and Arial are already on the machine, which is the
 * cheapest possible way to be this plain.
 *
 * Plain CSS, served straight from public/. No build step. Edit and reload.
 */

:root {
	--ink: #000;
	--paper: #fff;
	--muted: #555;
	/* The one tone between paper and ink: what a row is filled with when the
      pointer is on it, and nothing else. */
	--wash: #f0f0f0;

	--rule-major: 4px solid var(--ink);
	--rule-minor: 1px solid var(--ink);

	--measure: 62ch;
}

/* ==========================================================================
   Page
   ========================================================================== */

html {
	background: var(--paper);
	color: var(--ink);
	font-family: Helvetica, Arial, sans-serif;
	font-size: 20px;
	line-height: 1.45;
	-webkit-text-size-adjust: 100%;
	border: var(--rule-major);
	border-width: 8px;
	min-height: 100%;
	box-sizing: border-box;
}

body {
	margin: 0 auto;
	padding: 28px 24px 40px;
	max-width: var(--measure);
}

/* The rules between chrome and content are drawn as borders here, so the
   horizontal rules themselves have nothing left to do. */
hr {
	display: none;
}

/* Black on white extends to the photographs. Every image on the site, the
   gallery and the About page alike, is drained of colour here rather than in
   the file, so the same asset stays in colour for every other theme. */
img {
	filter: grayscale(1);
	transition: filter 75ms linear;
}

img:hover {
	filter: none;
}

/* ==========================================================================
   Navigation
   ========================================================================== */

body > header {
	border-bottom: var(--rule-major);
	padding-bottom: 8px;
}

nav ul {
	display: flex;
	flex-wrap: wrap;
	gap: 18px;
	list-style: none;
	margin: 0;
	padding: 0;
}

nav li {
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.05em;
	text-transform: uppercase;
}

nav a {
	color: var(--ink);
	text-decoration: none;
}

nav a:hover,
nav a:focus-visible {
	text-decoration: underline 2px;
}

nav strong {
	font-weight: 700;
	text-decoration: underline 2px;
}

/* ==========================================================================
   Type
   ========================================================================== */

h1 {
	/* Fluid, because the longest title here is one unbreakable word:
      "PHOTOGRAPHY" set at the full size is wider than a phone, and a heading
      is the one thing on the page that cannot wrap its way out of trouble. */
	font-size: clamp(30px, 10.5vw, 61.5px);
	line-height: 0.95;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	margin: 26px 0 10px;
}

main > header p {
	font-size: 15.5px;
	color: var(--muted);
	margin: 0;
}

h2 {
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	border-bottom: var(--rule-major);
	padding-bottom: 6px;
	margin: 26px 0 0;
}

h3 {
	font-size: 18px;
	margin: 0;
}

p {
	margin: 12px 0;
}

/* The intro sets to a shorter line than the page, which is the one piece of
   running prose long enough to need it. */
[data-page="/"] main > p {
	max-width: 38ch;
}

a {
	color: var(--ink);
	text-decoration: underline;
}

a:hover,
a:focus-visible {
	text-decoration: underline 2px;
}

small {
	font-size: 16px;
	color: var(--muted);
}

del {
	text-decoration-thickness: 6px;
}

abbr {
	text-decoration-color: var(--muted);
}

/* ==========================================================================
   Home: the index list
   Full-width rows, label left and count right, hairlines between and a heavy
   rule closing the group top and bottom.
   ========================================================================== */

/* No top rule: the "Have a look at" heading above already draws one, and two
   of them 26px apart reads as a mistake rather than as a frame. */
.section-links {
	list-style: none;
	margin: 0 0 18px;
	padding: 0;
	border-bottom: var(--rule-major);
}

.section-links li {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	padding: 11px 0;
	/* The sentence is replaced by the row's own wording, so it is hidden rather
      than deleted: it is still there for the themes that want it. */
	font-size: 0;
}

.section-links li + li {
	border-top: var(--rule-minor);
}

.section-links li a {
	font-size: 18px;
	font-weight: 700;
	text-decoration: none;
}

.section-links li a:hover,
.section-links li a:focus-visible {
	text-decoration: underline 2px;
}

/*
 * The count, where there is one. `attr()` on a missing attribute is the empty
 * string, so the rows without one print nothing rather than a stray space.
 *
 * It sits in `::before` because `::after` is the chevron below, and the two
 * are put back in reading order with `order`. The auto margin is what pushes
 * both to the right-hand edge.
 */
.section-links li::before {
	content: attr(data-count);
	order: 1;
	margin-left: auto;
	font-size: 15.5px;
	color: var(--muted);
}

.section-links li::after {
	order: 2;
	margin-left: 10px;
}

details {
	border-top: var(--rule-minor);
	margin: 18px 0;
	padding-top: 10px;
}

summary {
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	cursor: pointer;
}

kbd {
	font-family: inherit;
	font-weight: 700;
}

/* ==========================================================================
   CV
   Two columns: the years, then the position. The dates in the markup read
   "November 2021 to June 2022, part time"; this theme prints "2021-2022" and
   leaves the rest in the document for a theme that wants it.
   ========================================================================== */

.cv-entry {
	display: grid;
	grid-template-columns: 96px 1fr;
	gap: 4px 16px;
	padding: 14px 0;
}

.cv-entry + .cv-entry {
	border-top: var(--rule-minor);
}

.cv-entry h3 {
	grid-area: 1 / 2;
	font-weight: 700;
}

/* The dates paragraph, into the left column beside the title. Direct children
   only: the description below is markdown and has paragraphs of its own. */
.cv-entry > p {
	grid-area: 1 / 1;
	margin: 0;
	font-size: 13px;
	color: var(--muted);
	line-height: 1.35;
}

/* The description, as one block in the right column whatever it turned out to
   be: a sentence, several paragraphs, a list, or a mixture. */
.cv-description {
	grid-area: 2 / 2;
	font-size: 16px;
}

.cv-description > * {
	margin: 0 0 8px;
}

.cv-description > :last-child {
	margin-bottom: 0;
}

.cv-description ul {
	padding-left: 18px;
}

.cv-entry > p small,
.cv-entry > p time {
	font-size: 0;
}

.cv-entry > p time::before {
	content: attr(data-year);
	font-size: 13px;
}

/* A hyphen, and nothing after it when the position is still going. */
.cv-entry > p time:first-of-type::after {
	content: "-";
	font-size: 13px;
}

/* ==========================================================================
   Projects, albums, contact
   All the same idea: ruled rows, label first.
   ========================================================================== */

.project-list,
.album-list,
.contact-list,
.social-list {
	list-style: none;
	margin: 14px 0;
	padding: 0;
	border-top: var(--rule-major);
}

.project-list > li,
.album-list > li,
.contact-list > li,
.social-list > li {
	border-bottom: var(--rule-minor);
	padding: 11px 0;
	font-size: 16px;
}

.project-list > li a {
	font-size: 18px;
	font-weight: 700;
	text-decoration: none;
}

.project-list > li a:hover,
.project-list > li a:focus-visible {
	text-decoration: underline 2px;
}

/* A project row is its cover and its words, side by side. The cover is a
   column of its own rather than a floated image, so the description wraps
   against a straight edge however many lines it runs to, and the row keeps its
   rules whether or not there is a screenshot to show: an unset cover leaves
   the column empty and every name on the page stays on the same margin. */
.project-list > li {
	display: grid;
	grid-template-columns: 12rem auto;
	gap: 0 14px;
	padding: 0;
	/* Room for a cover worth looking at, even on a two-line description. */
	min-height: 6.5rem;
}

/* Floor to ceiling, so the picture is bounded by the same rules as the row and
   nothing pads it away from them. Cropped rather than fitted: the screenshots
   are every shape, and a column of covers all the same width reads as a column
   only if they are all the same width. */
.project-cover {
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-right: var(--rule-minor);
	filter: none;
}

/* The padding the row used to carry, moved onto the words alone. */
.project-summary {
	grid-column: 2;
	padding: 11px 0;
}

/* The one that was chosen rather than the one that is newest: deeper, so its
   cover is worth looking at, and closed off with heavy rules so it reads as
   the front-page story rather than as the first row of a list. The list draws
   its own rule above the first item, so this one only adds its own when it is
   further down. */
.project-list > li[data-featured] {
	min-height: 9.5rem;
	border-bottom: var(--rule-major);
}

.project-list > li[data-featured]:not(:first-child) {
	border-top: var(--rule-major);
}

.featured-flag {
	float: right;
	font-size: 12px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.14em;
	padding: 2px 6px;
	margin-left: 8px;
	background: var(--ink);
	color: var(--paper);
}

/* The whole row opens the project, not just the name. The name stays the only
   link in the page's structure: a pseudo-element stretched over the row takes
   the clicks, so a screen reader still hears one link per project and there is
   no script and no second copy of the address to keep in step. */
.project-list > li {
	position: relative;
}

.project-list > li a::after {
	content: "";
	position: absolute;
	inset: 0;
}

.project-list > li:hover {
	background: var(--wash);
}

.project-list > li:hover a {
	text-decoration: underline 2px;
}

/* Reaching the row by keyboard focuses the link inside it, and the ring has to
   be drawn around the thing that responds to Enter: the whole row. */
.project-list > li:has(a:focus-visible) {
	outline: var(--rule-minor);
	outline-offset: -1px;
}

/* A phone has no room for both, so the cover stacks on top of its words.
   Space does the dividing here rather than a rule: stacked, a row is already
   two different kinds of thing, and a rule under the description with a gap
   after it closes a row while pushing the next cover away from the line that
   is supposed to introduce it. Left as air, the gap groups each cover with
   the words underneath it and nothing has to hug anything. */
@media (max-width: 600px) {
	.project-list > li {
		grid-template-columns: 1fr;
		min-height: 0;
		border-bottom: 0;
		margin-bottom: 40px;
	}

	/* The rule down the side of a cover is a column edge, and there is no
      column on a phone. */
	.project-cover {
		border-right: none;
	}

	.project-summary {
		grid-column: 1;
		padding-bottom: 0;
	}
}

.album-group {
	border-top: 0;
	margin: 18px 0;
	padding-top: 0;
}

.album-group > summary {
	border-bottom: var(--rule-major);
	padding-bottom: 6px;
	letter-spacing: 0.12em;
}

.album-group .album-list {
	border-top: 0;
}

/* "Elsewhere" draws the rule above this one, same as the home page's index. */
.social-list {
	border-top: 0;
	margin-top: 0;
}

address {
	font-style: normal;
}

.project-facts {
	display: grid;
	grid-template-columns: 96px 1fr;
	gap: 4px 16px;
	margin: 14px 0;
	border-top: var(--rule-minor);
	border-bottom: var(--rule-minor);
	padding: 11px 0;
}

.project-facts dt {
	font-size: 13px;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--muted);
}

.project-facts dd {
	margin: 0;
	font-size: 16px;
}

/* Built with, as marks and names on one line. The brand colours each skill
   carries are ignored here on purpose: this theme has two colours. */
.skill-list {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 10px;
	list-style: none;
	margin: 0;
	padding: 0;
}

.skill-list li {
	display: flex;
	align-items: center;
	gap: 5px;
	font-size: 14px;
}

.skill-logo {
	width: 15px;
	height: 15px;
	object-fit: contain;
	filter: none;
}

/* The marks on the contact page, at the size of the line they sit on. */
.social-list a {
	display: inline-flex;
	align-items: center;
	gap: 7px;
}

.social-logo {
	width: 16px;
	height: 16px;
	object-fit: contain;
	filter: none;
}

/* ==========================================================================
   The skill filter
   Seven groups of checkboxes. Set in columns they take four lines instead of a
   screen and a half, which is the whole reason the shared markup folds them
   behind a `details` the HTML theme cannot lay out. The generic `details` rule
   above already draws the hairline and sets the summary.
   ========================================================================== */

.skill-filter[open] > summary {
	margin-bottom: 12px;
}

.skill-filter form {
	columns: 15rem;
	column-gap: 24px;
}

.skill-filter fieldset {
	/* A group is never split down the middle by a column break. */
	break-inside: avoid;
	display: inline-block;
	width: 100%;
	box-sizing: border-box;
	min-width: 0;
	border: 0;
	margin: 0 0 16px;
	padding: 0;
}

/* The rule is drawn under the legend rather than around the fieldset, so the
   type name reads as a heading over its list rather than as a boxed label. */
.skill-filter legend {
	display: block;
	width: 100%;
	border-bottom: var(--rule-minor);
	padding: 0 0 5px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
}

.skill-filter ul {
	list-style: none;
	margin: 7px 0 0;
	padding: 0;
}

.skill-filter li {
	font-size: 16px;
	padding: 1px 0;
}

/*
 * The box is the theme in miniature: a 2px rule, square corners, and filled
 * solid when it is on. No tick - a black square against a white one is the
 * same distinction the rest of the page is drawn with, and it survives being
 * printed or read in greyscale, which a colour would not.
 */
.skill-filter input[type="checkbox"] {
	appearance: none;
	width: 16px;
	height: 16px;
	margin: 0 5px 0 0;
	vertical-align: -3px;
	border: 2px solid var(--ink);
	border-radius: 0;
	background: var(--paper);
	cursor: pointer;
}

.skill-filter input[type="checkbox"]:checked {
	background: var(--ink);
}

.skill-filter input[type="checkbox"]:hover {
	box-shadow: 0 0 0 2px var(--muted);
}

/* Offset, so the ring is not swallowed by the border it sits against. */
.skill-filter input[type="checkbox"]:focus-visible {
	outline: 2px solid var(--ink);
	outline-offset: 2px;
}

/*
 * `appearance: none` throws away the control the operating system would have
 * drawn, and in a forced-colours mode that is the only one guaranteed to be
 * visible. Asked for one, this hands the box back.
 */
@media (forced-colors: active) {
	.skill-filter input[type="checkbox"] {
		appearance: auto;
	}
}

.skill-filter label {
	cursor: pointer;
}

/* The submit row belongs to the form, not to a column. */
.skill-filter form > p {
	column-span: all;
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px 18px;
	border-top: var(--rule-major);
	margin: 0;
	padding-top: 12px;
}

.skill-filter button {
	background: var(--ink);
	color: var(--paper);
	border: 0;
	border-radius: 0;
	padding: 9px 18px;
	font-family: inherit;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	cursor: pointer;
}

/* Inverted rather than tinted: there is no third colour to hover into. */
.skill-filter button:hover,
.skill-filter button:focus-visible {
	background: var(--paper);
	color: var(--ink);
	box-shadow: inset 0 0 0 2px var(--ink);
}

.skill-filter form > p a {
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
}

/* Nothing matched. It keeps the rules the list would have drawn, so the page
   holds its shape rather than collapsing around a single sentence. */
.project-list-empty {
	border-top: var(--rule-major);
	border-bottom: var(--rule-minor);
	margin: 14px 0;
	padding: 14px 0;
	font-size: 16px;
	color: var(--muted);
}

/* ==========================================================================
   Photographs
   Square corners, a hairline, and the caption in the same muted grey as every
   other secondary line on the site.
   ========================================================================== */

/* Columns rather than a grid, so a tall portrait next to a short landscape
   leaves no dead row underneath it. Each photograph keeps its own height and
   the next one starts where the last ended. The trade is reading order: images
   run down a column before moving across, which is how a contact sheet reads
   anyway. */
.gallery {
	columns: 13rem;
	column-gap: 16px;
	list-style: none;
	margin: 18px 0;
	padding: 0;
}

/* Every photograph here is shot 3:2, so a landscape is 2/3 of a column tall
   and a portrait 3/2. Give each one the same fixed amount of space underneath
   for its caption and the gap - call it K - and the two heights lock together:

      landscape item = 2/3 W + K
      portrait  item = 3/2 W + K

   A portrait is exactly two landscapes when 3/2 W + K = 2 (2/3 W + K), which
   solves to K = W/6. At that one value every item in a column is a whole
   number of the same unit, so a column break lands at the same height as the
   one beside it: the bottom of a portrait meets the bottom of the second
   landscape next to it, every time, at any column width.

   It was never exact before. The caption and gap came to whatever they came
   to - 54px against the 48px the arithmetic wanted, then 60px against 56.5px
   after the type went up - so the edges were always five or eight pixels out
   and it read as a near miss rather than a grid. `container-type` below is
   what makes it exact: it lets the caption be sized from the width of its own
   column rather than from a number typed in here. */
.photo {
	/* Nothing may split a photograph from its caption across a column break. */
	break-inside: avoid;
	/* inline-block keeps Chrome from breaking inside the item regardless. */
	display: inline-block;
	width: 100%;
	margin: 0 0 16px;
	/* So `cqw` below means "one percent of this column". */
	container-type: inline-size;
}

.photo figure {
	margin: 0;
}

.photo img {
	display: block;
	width: 100%;
	height: auto;
	border: var(--rule-minor);
	filter: none;
}

.photo figcaption {
	font-size: 13px;
	/* Tighter than the page, so that the longest caption - a place name, the
      pixel dimensions and the link - still comes to two lines inside the slot
      the arithmetic above allows. */
	line-height: 1.25;
	color: var(--muted);
	padding-top: 6px;
}

/* K = W/6, of which 16px is the gap under the item, so the caption takes the
   rest. Below 330px of column there is no room for two lines of caption in
   W/6, and a caption clipped in half is a worse fault than an edge a few
   pixels out - so the slot is only imposed where it fits, and a narrow column
   goes back to captions at their natural height. A single column has nothing
   to line up with anyway. */
@container (min-width: 330px) {
	.photo figcaption {
		box-sizing: border-box;
		block-size: calc(100cqw / 6 - 16px);
	}
}

/* ==========================================================================
   About
   The portrait beside the tagline, then the same ruled rows as everywhere
   else: a hairline under each track, each film, each company. Every picture
   sits in a 2px rule, square, and the captions are the same grey as the rest.
   ========================================================================== */

.about-intro {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 18px;
	align-items: start;
	margin-top: 20px;
}

/* The line, the flags and the traits as one column with one gap between them.
   They used to be three grid items sharing out whatever height the portrait
   had, which put a different amount of air above each one; now the spacing is
   this single number. */
.about-text {
	display: flex;
	flex-direction: column;
	gap: 18px;
}

.about-portrait {
	margin: 0;
	border: 2px solid var(--ink);
}

.about-portrait img {
	filter: none;
}

.about-portrait img,
.pet-list img,
.interest-list img,
.about-outro img {
	display: block;
	width: 100%;
	height: auto;
}

.about-portrait figcaption {
	padding: 5px 7px;
	border-top: 2px solid var(--ink);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.about-tagline {
	margin: 0;
	font-size: 17px;
	line-height: 1.5;
}

/* Two flags, cut square and ruled like everything else here. The stripes are
   gradients rather than an image so they stay sharp at any size and need no
   extra request. */
.about-flags {
	display: flex;
	gap: 6px;
	margin: 0;
}

.flag {
	width: 26px;
	height: 19px;
	border: 2px solid var(--ink);
}

.trans-flag {
	background: linear-gradient(
		to bottom,
		#5bcefa 0 20%,
		#f5a9b8 20% 40%,
		#fff 40% 60%,
		#f5a9b8 60% 80%,
		#5bcefa 80%
	);
}

/* The Nordic cross, off-centre the way the real one is: the bands sit 6/22
   across and 6/16 down, so the arm to the left of the cross is the short one. */
.norway-flag {
	background:
		linear-gradient(
			to bottom,
			transparent 43.75%,
			#00205b 43.75% 56.25%,
			transparent 56.25%
		),
		linear-gradient(
			to right,
			transparent 31.82%,
			#00205b 31.82% 40.91%,
			transparent 40.91%
		),
		linear-gradient(
			to bottom,
			transparent 37.5%,
			#fff 37.5% 62.5%,
			transparent 62.5%
		),
		linear-gradient(
			to right,
			transparent 27.27%,
			#fff 27.27% 45.45%,
			transparent 45.45%
		),
		#ba0c2f;
}

/* Stacked rather than run together on one line: three short phrases set as a
   list read as three separate facts, which is what they are. */
.trait-list {
	list-style: none;
	margin: 0;
	padding: 0;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
}

.trait-list li {
	position: relative;
	padding: 3px 0 3px 15px;
	border-bottom: var(--rule-minor);
}

.trait-list li:last-child {
	border-bottom: none;
}

.trait-list li::before {
	content: "\25AA";
	position: absolute;
	left: 0;
	color: var(--ink);
}

.household p,
.side-quests p {
	font-size: 16px;
}

/* Three across on any screen with room for them, fewer when there is not.
   `auto-fit` with a `1fr` maximum is what fills the line: the columns take
   whatever is left over rather than sitting at a fixed width with a gap down
   the right-hand side. */
.pet-list {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
	gap: 16px;
	list-style: none;
	margin: 12px 0;
	padding: 0;
}

.pet-list figure,
.interest-list figure,
.about-outro figure {
	margin: 0;
}

.pet-list img,
.interest-list img,
.about-outro img {
	border: 2px solid var(--ink);
}

/* The cats are phone portraits, twice as tall as wide. Cropped to 4:5 they
   sit level with each other and do not push the rest of the page a screen
   down; the full frame is one click away in the Studio, not here. */
.pet-list img {
	aspect-ratio: 4 / 5;
	object-fit: cover;
}

.pet-list figcaption,
.couple-photo figcaption,
.about-outro figcaption {
	font-size: 13px;
	color: var(--muted);
	padding-top: 6px;
}

.pet-list figcaption strong {
	color: var(--ink);
}

.couple-photo {
	margin: 12px 0 0;
}

/* The picture alone, so the sticker is measured against the photograph and
   not the photograph plus its caption. */
.couple-photo .picture {
	position: relative;
	display: block;
	border: 2px solid var(--ink);
}

.couple-photo img {
	display: block;
	width: 100%;
	height: auto;
}

/*
 * The sticker. The face it covers is already blocked out in the file; this is
 * the theme's version of the joke, a black square a little larger than the
 * block with the caption set inside it.
 */
.face-sticker {
	/* Where the blocked-out patch actually is in the photograph, measured off
      the file rather than guessed: 58.9% across, 13.3% down, 23.3% wide. These
      are a little larger and a little further left and up, so the sticker
      covers the patch with a margin all round.

      Set the sticker position in the Studio to override all three; a new
      photograph needs new numbers, and that is the field to put them in. */
	position: absolute;
	left: var(--face-x, 57%);
	top: var(--face-y, 10%);
	width: var(--face-size, 26%);
	/* Square, because the patch is: the box would otherwise be stretched by
      whatever shape the photograph happens to be. */
	aspect-ratio: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	padding: 6%;
	background: var(--ink);
	color: var(--paper);
}

.face-sticker::after {
	content: "Imagine a beautiful face here";
	font-size: clamp(7px, 1.6vw, 11px);
	font-weight: 700;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	text-align: center;
	line-height: 1.3;
}

/* Plex. The heading, a grey line about where it came from, then rows. */
.plex-blurb {
	margin: 8px 0 0;
}

.plex-blurb small {
	font-size: 14px;
}

.plex-list ol {
	list-style: none;
	margin: 6px 0 0;
	padding: 0;
}

.plex-list li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0 6px;
	padding: 10px 0;
	border-bottom: var(--rule-minor);
	font-size: 16px;
}

.plex-list li:last-child {
	border-bottom: var(--rule-major);
}

.plex-list .artist::before {
	content: "- ";
}

.plex-list .play-count {
	font-size: 13px;
	color: var(--muted);
}

/* Every row is now a name and a count with nothing between them, so the count
   is what goes to the right edge. The album used to do this job on the music
   rows before it came off them. */
.plex-list .play-count {
	margin-left: auto;
}

.plex-empty,
.plex-note {
	font-size: 13px;
	color: var(--muted);
	margin: 10px 0 0;
}

/* Interests, as bordered chips, with the photograph held beside them rather
   than queued up among them. Ordinary flow rather than a flex row, because a
   float is the one thing that takes an element out of the line and lets the
   rest wrap around it, and flex items cannot float. */
.interest-list ul {
	list-style: none;
	margin: 12px 0 0;
	padding: 0;
}

/* The float is inside the list, so the section has to close over it or the
   next heading rides up alongside the picture. */
.interest-list::after {
	content: "";
	display: block;
	clear: both;
}

.interest-list li,
.interest-list figcaption {
	display: inline-block;
	vertical-align: top;
	margin: 0 6px 8px 0;
	border: 2px solid var(--ink);
	padding: 4px 10px;
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
}

.interest-list li:has(figure) {
	float: right;
	width: 200px;
	border: 0;
	padding: 0;
	margin: 0 0 10px 18px;
}

.interest-list figure {
	width: 200px;
}

/* Cropped to the same 4:5 as the cats. A phone portrait at its own height
   makes the section as tall as the picture and leaves the chips beside it
   sitting in a column of nothing. */
.interest-list figure img {
	aspect-ratio: 4 / 5;
	object-fit: cover;
}

.interest-list figcaption {
	display: block;
	margin: 8px 0 0;
	text-align: center;
}

/* A phone has no room beside anything: the picture goes back into the flow,
   above the chips it was standing next to. */
@media (max-width: 600px) {
	.interest-list li:has(figure) {
		float: none;
		width: 100%;
		margin: 0 0 12px;
	}

	.interest-list figure {
		width: 100%;
	}
}

.interest-list figcaption {
	display: inline-block;
	margin-top: 8px;
	box-sizing: border-box;
	width: 100%;
}

.side-quests ul {
	list-style: none;
	margin: 0;
	padding: 0;
}

.side-quests li {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	padding: 10px 0;
	border-bottom: var(--rule-minor);
	font-size: 16px;
	font-weight: 700;
}

.side-quests li:last-child {
	border-bottom: var(--rule-major);
}

.side-quests a {
	text-decoration: none;
}

.side-quests a:hover,
.side-quests a:focus-visible {
	text-decoration: underline 2px;
}

/*
 * A chevron cut from two rules rather than typed as an arrow: at 3px it is
 * the same weight the page is ruled with, which "→" never is - it arrives at
 * whatever weight the font feels like and sat there looking apologetic.
 *
 * Only on the rows that are links. A marker on a row that goes nowhere reads
 * as a link that has broken, which is what made "Gjeitsund Webutvikling" look
 * clickable when it is not.
 */
.section-links li::after,
.side-quests li:has(a)::after {
	content: "";
	align-self: center;
	width: 8px;
	height: 8px;
	border-top: 3px solid var(--ink);
	border-right: 3px solid var(--ink);
	transform: rotate(45deg);
	/* The rotated corner reaches past the box, so it needs room on the right. */
	margin-right: 4px;
}

.about-outro {
	margin-top: 26px;
}

.about-outro figure {
	width: 240px;
}

@media (max-width: 600px) {
	.about-intro {
		grid-template-columns: 1fr;
	}

	.about-portrait {
		width: 140px;
	}

	.pet-list {
		gap: 10px;
	}
}

/* ==========================================================================
   Write-ups
   ========================================================================== */

/* The contents, closed until someone wants it: a rule above and below, and the
   depth drawn as an indent so the shape of the write-up shows at a glance. */
.toc {
	/* The facts above already close with a rule, so this only draws the one
      below it: three rules within a centimetre of each other is a fence. */
	border-bottom: var(--rule-minor);
	margin: 0 0 4px;
	padding: 0 0 8px;
}

.toc > summary {
	font-size: 13px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.12em;
}

.toc ol {
	list-style: none;
	margin: 8px 0 2px;
	padding: 0;
}

.toc li {
	font-size: 15.5px;
	padding: 2px 0;
}

.toc li[data-level="2"] {
	padding-left: 18px;
}

.toc li[data-level="3"] {
	padding-left: 36px;
}

/* A write-up brings its own headings, and `#` renders as h3 with `##` under it
   as h4. Left to the browser an h4 is bolder and bigger than this theme's h3,
   which puts a subsection above the section holding it; the scale below is the
   one the page is read in. */
.write-up h3 {
	font-size: 20px;
	font-weight: 700;
	letter-spacing: -0.01em;
	border-bottom: var(--rule-minor);
	margin: 24px 0 0;
	padding-bottom: 4px;
}

.write-up h4 {
	font-size: 14px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	margin: 18px 0 0;
}

.write-up h5,
.write-up h6 {
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.08em;
	color: var(--muted);
	margin: 14px 0 0;
}

pre {
	border-left: var(--rule-major);
	padding: 8px 0 8px 14px;
	overflow-x: auto;
	font-size: 15.5px;
}

samp,
code {
	font-family: "Courier New", monospace;
}

/* The contact block on the CV. The shared list rule draws a heavy rule above
   itself, which under a heading that already draws one is two lines a
   millimetre apart. */
.cv-contact .contact-list {
	border-top: 0;
	margin-top: 0;
}

/* ==========================================================================
   Potatoes
   The struck-out word in the title is a button, and has to stop looking like
   one: same type, same colour, no chrome. What it opens is the browser's own
   popover, which arrives centred and needs only a box drawn around it.
   ========================================================================== */

.potato-trigger {
	font: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
	color: inherit;
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
}

.potato-trigger:hover del,
.potato-trigger:focus-visible del {
	background: var(--ink);
	color: var(--paper);
}

.potato-popover {
	max-width: 54ch;
	border: var(--rule-major);
	background: var(--paper);
	color: var(--ink);
	padding: 20px 22px 16px;
}

.potato-popover::backdrop {
	background: rgb(0 0 0 / 0.4);
}

.potato-popover h2 {
	margin-top: 0;
}

.potato-forms-intro {
	margin-bottom: 4px;
	font-size: 16px;
}

.potato-forms {
	margin: 0 0 16px;
	padding-left: 18px;
	font-size: 16px;
}

/* The buttons in the theme, drawn the way a rule is: a box, and the word
   inside it. */
.potato-popover > button,
.print-button {
	font: inherit;
	font-size: 13px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	color: var(--ink);
	background: var(--paper);
	border: var(--rule-minor);
	padding: 6px 12px;
	cursor: pointer;
}

.potato-popover > button:hover,
.potato-popover > button:focus-visible,
.print-button:hover,
.print-button:focus-visible {
	background: var(--ink);
	color: var(--paper);
}

.cv-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
	margin: 12px 0 0;
}

/* The download link beside it is an anchor, so it needs the two things a real
   button already has: a box to sit in, and no underline - in both resting and
   hover states, since the rule for links thickens the underline rather than
   setting one. */
.download-button {
	display: inline-block;
	text-decoration: none;
}

.download-button:hover,
.download-button:focus-visible {
	text-decoration: none;
}

.print-hint {
	font-size: 13px;
	color: var(--muted);
}

/* ==========================================================================
   Hire me
   The last thing before the footer, and the only place the site asks for
   something. A major rule above it and the footer's own rule below, so it
   reads as its own panel without a second border to draw.
   ========================================================================== */

.hire-me {
	border-top: var(--rule-major);
	margin-top: 34px;
	padding-top: 12px;
}

.hire-me h2 {
	margin-top: 0;
}

.hire-me p {
	font-size: 16px;
	max-width: 52ch;
}

.hire-me-action a {
	font-weight: 700;
}

/* ==========================================================================
   Footer
   ========================================================================== */

/* The copyright and the theme link sit on one line, divided rather than
   stacked, which is the only place this theme spends a character on decoration. */
footer {
	display: flex;
	flex-wrap: wrap;
	gap: 0 6px;
	border-top: var(--rule-major);
	margin-top: 18px;
	padding-top: 10px;
	font-size: 13px;
	color: var(--muted);
}

footer p {
	margin: 2px 0;
}

footer p + p::before {
	content: "· ";
}

footer small {
	font-size: 13px;
}

footer a {
	color: var(--muted);
}

/* ==========================================================================
   Carousel
   The screenshots on a project page, as a strip you scroll sideways. No script:
   `overflow-x` with scroll snapping is the whole mechanism, and a browser that
   has the newer carousel pseudo-elements gets dots and arrows on top of it.
   Bare, with no stylesheet at all, the same markup is a list of pictures.
   ========================================================================== */

.carousel {
	margin-bottom: 18px;
	display: flex;
	gap: 14px;
	list-style: none;
	margin: 12px 0 0;
	padding: 0 0 12px;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	overscroll-behavior-x: contain;
	border-bottom: var(--rule-minor);
}

.carousel > li {
	flex: 0 0 100%;
	scroll-snap-align: center;
}

.carousel img {
	display: block;
	width: 100%;
	height: auto;
	border: var(--rule-minor);
	filter: none;
}

/* A dot per screenshot, drawn by the browser from the items themselves.
   Everything below is ignored where these are not supported, which leaves the
   strip above working exactly as it did. */
.carousel {
	/* Dots above the strip rather than below it. Placed after, the group is
      not laid out inside the section that holds the strip and paints over
      whatever follows; before, it sits under the heading where it reads as
      part of the picture. */
	scroll-marker-group: before;
}

.carousel::scroll-marker-group {
	display: flex;
	justify-content: center;
	gap: 8px;
	padding: 10px 0 0;
}

.carousel > li::scroll-marker {
	content: "";
	width: 9px;
	height: 9px;
	border: var(--rule-minor);
	background: var(--paper);
}

.carousel > li::scroll-marker:target-current {
	background: var(--ink);
}

.carousel::scroll-button(*) {
	font: inherit;
	font-size: 15px;
	line-height: 1;
	border: var(--rule-minor);
	background: var(--paper);
	color: var(--ink);
	padding: 4px 10px;
	cursor: pointer;
}

.carousel::scroll-button(*):disabled {
	opacity: 0.35;
	cursor: default;
}

.carousel::scroll-button(left) {
	content: "←";
}

.carousel::scroll-button(right) {
	content: "→";
}

@media (prefers-reduced-motion: reduce) {
	.carousel {
		scroll-behavior: auto;
	}
}

/* ==========================================================================
   Contact form
   Labels above their fields, boxes drawn with the same rules as everything
   else, and no rounded corner in sight.
   ========================================================================== */

.contact-form form {
	max-width: 46ch;
}

.contact-form p {
	margin: 12px 0;
}

.contact-form label {
	display: block;
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	color: var(--muted);
	margin-bottom: 3px;
}

.contact-form input,
.contact-form textarea {
	font: inherit;
	font-size: 15.5px;
	width: 100%;
	box-sizing: border-box;
	color: var(--ink);
	background: var(--paper);
	border: var(--rule-minor);
	padding: 7px 9px;
}

.contact-form textarea {
	resize: vertical;
}

.contact-form input:focus-visible,
.contact-form textarea:focus-visible {
	outline: var(--rule-major);
	outline-offset: -4px;
}

.contact-form button {
	font: inherit;
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	color: var(--paper);
	background: var(--ink);
	border: var(--rule-minor);
	padding: 9px 18px;
	cursor: pointer;
}

.contact-form button:hover,
.contact-form button:focus-visible {
	color: var(--ink);
	background: var(--paper);
}

/* What happened to the last message. */
.form-outcome {
	border-left: var(--rule-major);
	padding: 4px 0 4px 12px;
	font-size: 15.5px;
}

.form-outcome[data-tone="bad"] {
	color: var(--muted);
}

/* The honeypot, off the page rather than `display: none`: something that
   fills every field is more likely to skip a field it can tell is hidden. */
.form-trap {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* ==========================================================================
   Full-size viewer
   ========================================================================== */

/* Every photograph is a link to its own full-size file. That is the whole
   feature with no script: follow it and there is the big version. With
   `viewer.js` the click opens it in a dialog instead.

   An anchor is inline and the pictures inside these are laid out as blocks, so
   the box has to become one or the picture sits on a text baseline with a
   descender's worth of gap under it. The gallery caption's "see it full size"
   carries the same class and is a phrase in a sentence, which is what the
   `:has` keeps out of this. */
.full-size:has(img) {
	display: block;
	cursor: zoom-in;
}

/* A sheet on top of the page, in the terms the rest of the theme is set in:
   the major rule around it, paper behind it, and the close as a bar across the
   bottom rather than a cross floating in a corner. */
.image-viewer {
	max-width: min(96vw, 1400px);
	max-height: 94vh;
	padding: 0;
	border: var(--rule-major);
	background: var(--paper);
	color: var(--ink);
}

.image-viewer::backdrop {
	background: rgb(0 0 0 / 0.82);
}

.image-viewer-image {
	display: block;
	/* The bar underneath is part of the dialog's own height, so the picture is
      capped at what is left of the screen after it. */
	max-width: 100%;
	max-height: calc(94vh - 3rem);
	object-fit: contain;
}

.image-viewer-close {
	display: block;
	width: 100%;
	border: 0;
	border-top: var(--rule-major);
	padding: 0.5rem;
	background: var(--paper);
	color: var(--ink);
	font: inherit;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	cursor: pointer;
}

.image-viewer-close:hover,
.image-viewer-close:focus-visible {
	background: var(--ink);
	color: var(--paper);
}

/*
 * Selected text, inverted. A theme with no rounded corners and no colour has
 * one honest way to mark a selection, and this is it.
 */
::selection {
	background: var(--ink);
	color: var(--paper);
}
