/*
Theme Name:   Jonas Lindeberg
Theme URI:    https://jonaslindeberg.com
Description:  Designtokens, patterns och innehålls-CSS för jonaslindeberg.com — hero och alla animeringar ligger i pluginet jl-experience. Innehållet är vanliga block och överlever både temabyte och saknat plugin. Template-raden nedan visar att det är ett child-tema; det behöver inte upprepas i namnet.
Author:       Jonas Lindeberg
Author URI:   https://jonaslindeberg.com
Template:     twentytwentyfive
Version:      1.21.0
Requires at least: 6.7
Requires PHP: 8.0
License:      GNU General Public License v2 or later
License URI:  https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:  jonaslindeberg
*/

/*	IMPORTANT: bump "Version" in the theme header above when a pattern is added,
	removed, or has its header changed. WP_Theme caches the theme's pattern list
	in the site transient "wp_theme_files_patterns-*" (30 min) and invalidates it
	ONLY when Version changes. Without a bump the new pattern is never
	registered, and every template referencing it renders nothing at all —
	silently, no error. A changed Title or Description fails more quietly still:
	the pattern works, the editor just shows the old label until the transient
	expires. */

/*	Ported from CraftHemsida/web/assets/style.css — THE CONTENT PART.
	Sky, parallax, day/night, seasons, eyes, drips and bats live in the
	jl-hero plugin (css/jl-hero.css). */

/*	The source's own ":root" variables are gone. They were a second token system
	beside theme.json — the same colours, two definitions, and the editor only
	saw one of them. Six of the eight had also been unused since the port; the
	two that were used now point at the theme's own tokens:
	--divider-color → --wp--custom--rule--shadow,
	--anfang        → --wp--preset--font-family--anfang. */

/*	Neutralise WP's blockGap/layout margins (":root :where(.is-layout-flow) > *").
	The source CSS handles all vertical rhythm itself through padding/margin.
	Placed FIRST so every later rule in this file wins over it.

	.entry-content covers the page content (the front page); .wp-site-blocks is
	required for template-rendered views (blog, post, archive) where the blocks
	sit directly in the site wrapper and therefore never pass .entry-content.

	.is-layout-constrained has to be included: WP writes the same blockGap rules
	for constrained as for flow, and #welcome/#related are constrained these days
	(that is where the content's column width comes from). Without the lines
	below, the 24px gaps between heading area, body text and navigation return. */
:root :where(.entry-content .is-layout-flow) > *,
:root :where(.entry-content .is-layout-constrained) > *,
:root :where(.entry-content) > *,
:root :where(.wp-site-blocks .is-layout-flow) > *,
:root :where(.wp-site-blocks .is-layout-constrained) > *,
:root :where(.wp-site-blocks) > * {
	margin-block-start: 0;
	margin-block-end: 0;
	}

/*	Default block styling that collides with the source design */
.entry-content figure.wp-block-image {
	margin: 0;
	}
.wp-block-columns.columns {
	align-items: center !important; /* core sets normal !important */
	gap: 0; /* live has no column gaps */
	}
/*	The source's "section { width: inherit }" inherits 100% through the live
	DOM's chain of sections; WP's column div has no width value → inherit gives
	auto → #social/#gaming shrink and go left-aligned. Recreate the chain: */
footer .wp-block-column {
	width: 100%;
	}
/*	The image block's figure/a wrappers must be transparent boxes so the image
	behaves like the source's a > img (core lets figure shrink-wrap to the
	image's natural width, which throws off percentage measurements): */
article.portfolio .project figure.wp-block-post-featured-image,
article.portfolio .project figure.wp-block-post-featured-image a,
.columns > .wp-block-column > figure.wp-block-image,
.columns > .wp-block-column > figure.wp-block-image a {
	display: block;
	width: 100%;
	}

html {
	background: var(--wp--preset--color--base);
	}

/*	The hero is a template part (#75), and core/template-part always wraps its
	content in an element of its own. Around the hero that wrapper must not
	exist in layout: the hero block's own wrapper is already display:contents
	so its layers behave as the direct flow children the ported CSS was written
	for, and the sky, sun and back trees sit on NEGATIVE z-indexes that die the
	moment any ancestor creates a stacking context (see the next rule).
	display:contents removes the box and creates no stacking context - the box
	tree is exactly what it was when the hero was a pattern. */
.wp-block-template-part.jl-hero-part {
	display: contents;
	}

/*	The main landmark, which no view had (#113). Screen readers navigate by
	landmarks, and a "skip to content" link means little without one.

	display:contents for the same reason as the hero part above: the sections
	inside are the site's own layout, and a real box here would put WordPress's
	flow layout between them - blockGap on every child boundary. The element
	keeps its landmark role in the accessibility tree; that it does is measured
	rather than assumed, and wants re-measuring in Safari before it is
	trusted. */
main.jl-main {
	display: contents;
	}

/*	The hero's fallback: the front forest layer, shown only when the experience
	plugin is not running (#75). It belongs to the THEME rather than the plugin
	for exactly that reason - these rules have to exist when the plugin does not.

	display:block, because an inline image sits on the text baseline and leaves
	room under it for descenders: 7px of white seam between the forest's dark
	ground and the dark section below, which is the one join this fallback exists
	to make. width:100% so the ground band reaches both edges on screens wider
	than the artwork's 2436px.

	With the plugin running this rule loses: its
	".wp-block-jl-hero-hero .jl-hero__fallback { display: none }" is (0,2,0)
	against this (0,1,0), so the fallback stays hidden - and, being lazy, is
	never downloaded. */
.jl-hero__fallback {
	display: block;
	width: 100%;
	height: auto;
	}

/*	HOW FAINT THINGS ARE ALLOWED TO BE (#113).

	The site says "less important" with opacity, and the old values - .125, .25,
	.5 - were chosen against the dark sections, where they hold up: light text
	at .5 on the dark surface measures 4.6:1. On the LIGHT surfaces the same
	numbers collapse, because the ink is black and the ground is nearly white:

		portfolio card titles   .25  ->  1.71:1   (22px text)
		"Skapat för:"           .5   ->  3.30:1
		footer icons            .25  ->  1.83:1   (pure black artwork, measured
		the MAWNSTR logo        .125 ->  1.33:1    off the canvas)

	The tokens live in theme.json (settings.custom.dim) so the editor sees them
	too, and each is the measured minimum for what it carries, rounded up:

		--dim--text  .62  text that must still be read      >= 4.5:1
		--dim--ui    .45  icons and controls                >= 3:1
		--dim--rule  .4   separators; decoration, so no rule applies - this one
		                  is taste, chosen to stay visible on a dim screen

	Deliberately NOT changed: the byline in a post header and the pinned menu,
	both of which sit on the dark surface at .5 and already pass. The same
	number means different things on the two grounds, which is the whole
	reason this comment exists. */

/*	CRITICAL: body must NEVER have its own background. html targets the canvas;
	a background on body is painted on top of the hero's negative z-index layers
	(sky, sun, back trees) and puts out the whole parallax. */
body {
	background: transparent;
	}

html, body {
	margin: 0;
	padding: 0;
	width: 100vw;
	height: auto;
	font-family: 'Source Sans Pro', sans-serif;
	font-size: 1.4vw;
	overflow-x: hidden;
	}

/*	The skip link is a paragraph block (<p class="skip-link"><a>…) —
	:focus-within on the p fires when the link inside takes focus. */
p.skip-link {
	position: absolute;
	left: -9999px;
	top: auto;
	width: 1px;
	height: 1px;
	padding: 0;
	overflow: hidden;
	z-index: 100001;
	}
p.skip-link:focus-within {
	position: fixed;
	left: 1rem;
	top: 1rem;
	width: auto;
	height: auto;
	overflow: visible;
	}
	p.skip-link:focus-within a {
		display: inline-block;
		padding: 0.75rem 1rem;
		background: var(--wp--preset--color--base);
		color: var(--wp--preset--color--contrast);
		border: 2px solid var(--wp--preset--color--contrast);
		border-radius: 0.25rem;
		text-decoration: none;
		}
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
	}

/*	—————————————————————
	Text
	*/
	/*	THE TYPE SCALE IS NOT HERE. h1, h2 and paragraphs live in theme.json
		under styles.elements and styles.blocks."core/paragraph" respectively —
		WordPress generates the selector "p" for the paragraph block, so the
		rule hits every paragraph, not only block-rendered ones.

		The reason is parity: functions.php loads style.css on the front end
		only (add_editor_style is missing on purpose — the file is written
		against a DOM the editor does not have). theme.json applies on BOTH
		sides, so whatever moves there is also what the editor shows and what
		Jonas can change in the Styles panel.

		Mind the specificity: theme.json writes ":root :where(p)" (0,1,0), not
		"p" (0,0,1). Rules that used to win by being more specific can therefore
		lose — "footer p" got a ":root" prefix for exactly that reason. See trap
		15 in the porting guide.

		em does not exist as a theme.json element and stays here. */
	em {
		font-style: normal;
		text-transform: uppercase;
		font-size: .75em;
		opacity: var(--wp--custom--dim--text);
		}

	/*	The link colours live in theme.json (styles.elements.link with :hover and
		:active). The transition does not — theme.json has no concept for
		transition, so that part stays here. */
	a:link, a:visited {
		transition: .1s all ease-in-out;
		}
			/*	Link colour in dark sections. The hook is the class WordPress
				itself prints for the palette colour
				(has-dark-surface-background-color), no longer a bespoke .dark
				class — the section's colour is set by block attributes and this
				class comes along automatically. */
			.has-dark-surface-background-color p a:link,
			.has-dark-surface-background-color p a:visited {
				color: white;
				}
				.has-dark-surface-background-color p a:hover,
				.has-dark-surface-background-color p a:active {
					color: var(--wp--preset--color--accent-blue);
					}
img {
	max-width: 100%;
	}

/*	The separator's COLOUR lives in theme.json (styles.blocks."core/separator"):
	the two 1px edges and their colours, which in turn point at
	settings.custom.rule. What stays here is the width and the opacity —
	WordPress has no block concept for either, and a separator can be neither
	50% wide nor translucent from the sidebar. The specificity matches core's
	own ":not()" variants.

	The generic "hr" rule is gone: every hr on the site is a separator block,
	and core's ".wp-block-separator" (0,1,0) beat the element selector (0,0,1)
	anyway — the rule looked like it applied while doing nothing. */
	hr.wp-block-separator:not(.is-style-wide):not(.is-style-dots) {
		width: 50%;
		height: auto;
		margin: 0 auto;
		background: none;
		opacity: var(--wp--custom--dim--rule);
		}
	hr.wp-block-separator.clear:not(.is-style-wide):not(.is-style-dots) {
		clear: both;
		width: 100%;
		height: 0;
		border: none;
		padding: 0;
		margin: 0;
		opacity: 0;
		}

section {
	width: inherit;
	float: left;
	}
	section:after {
		clear: both;
		}

.top, .right, .bottom, .left {
	position: absolute;
	width: 100%;
	text-align: center;
	}
	.top {
		top: 0;
		}
	.right {
		right: 0;
		}
	.bottom {
		bottom: 0;
		}
	.left {
		left: 0;
		}

/*	—————————————————————
	Content sections (full width even without the plugin)
	*/
	/*	The front page's content IS the page since #70: the sections live in the
		page's post_content, not in the template. core/post-content wraps them in
		div.entry-content, and core's own rule gives it "display: flow-root" — a
		block formatting context, which by definition does not sit under a float
		but BESIDE it. The hero is floated and 100vw wide, so the wrapper was
		squeezed to 0px and the whole page became 21px columns far out to the
		right.

		"display: contents" removes the box from the layout entirely: the
		sections behave again like the direct children of .wp-site-blocks they
		were before #70, and the float chain the entire ported design rests on is
		untouched. Front page only — the subpages need the wrapper (see
		".entry-content figure.wp-block-image" above). Principle 9.5 in
		PORTNINGSGUIDE, in practice: a wrapper breaks the computation chain. */
	.home .entry-content {
		display: contents;
		}
	.group.content {
		position: relative;
		display: block;
		margin: 0 auto;
		width: calc(100vw + 2px);
		margin-left: -1px;
		min-height: 50vw;
		height: auto;
		float: left;
		overflow: hidden;
		}
		div#welcome {
			position: relative;
			top: 0;
			height: inherit;
			}
	/*	The sections' background and text colour are BLOCK ATTRIBUTES
		(backgroundColor/textColor from the theme palette), not the classes .dark
		and .light. That colours the section correctly in the editor too, and
		content dropped into it inherits the text colour without anyone needing
		to know a CSS class existed. */

article {
	float: left;
	position: relative;
	top: auto;
	display: block;
	}
	/*	The text columns no longer float. WP's constrained layout centres with
		"margin-inline: auto !important", and a float ignores auto (it computes
		to 0) — the column would come out the right width but left-aligned. The
		source's "article { float: left }" stays for the portfolio, which still
		needs it. Same counter-measure as ".blog .post-card { float: none }"
		further down.

		"flow-root" replaces the block formatting context the float used to give
		for free. Without it the children's top margins collapse out through the
		column and move the whole article: the heading's 4em (of 1.5rem) is
		larger on mobile than the column's own 5em and took over the
		positioning. */
	article.introduction,
	article.post-header,
	article.entry {
		float: none;
		display: flow-root;
		}

	/*	THE COLUMN WIDTH IS NOT HERE. The content's width and centring come from
		WordPress's layout system: settings.layout.contentSize in theme.json
		(50vw) plus "layout": "constrained" on #welcome and #related. That makes
		the column visible in the block editor and changeable from the sidebar,
		instead of being a percentage only this file knows about.

		What stays here is the vertical rhythm. The top margin differs between
		desktop and mobile, and block attributes render as inline styles that no
		media query can beat — so vertical measurements have to live in CSS. The
		":root" prefix is needed to beat WP's layout reset
		":root :where(.is-layout-flow) > :first-child { margin-block-start: 0 }" (0,2,0). */
	/*	WHERE CONTENT STARTS ON THE DARK SURFACE — one value for every view.

		What the offset has to clear is the roots artwork (2_Base.png), which
		hangs from the top of the section and is sized in vw: at 100vw wide it
		is 46.2vw tall. Measured off its alpha channel, the artwork is solid for
		its top 27%, thins past half coverage at 43%, and is nearly gone by 64%.
		The token puts text at 17vw - just under the dense mass, where the roots
		have thinned - and being a vw it stays in the same place on the artwork
		at every width.

		It used to be 15em, which only looked right because the body font is
		itself 1.4vw: an offset expressed in text size doing a job measured in
		image height. That coincidence breaks the moment the type scale moves.

		Uniform, which it was not: the intro carried 15em plus 4em on its own h2
		(376px at 1280 wide) while a post header carried 15em flat (269px), so a
		post started higher than the front page on desktop - and lower on mobile,
		where the compensations ran the other way. One token, both views, and
		the h2's extra margin is gone. #113 follow-up. */
	:root article.introduction,
	:root article.post-header {
		margin-top: var(--wp--custom--dark--content-top);
		}
	/*	WHERE CONTENT ENDS ON THE DARK SURFACE — the other half, also one value.

		Below the content comes the paint splash (#group-4), whose artwork opens
		with its own dark band in vw - the plugin decides how tall. What this
		token adds is text-sized breathing room, and it is in em on purpose:
		on mobile the body font is 4vw instead of 1.4vw, so the artwork's vw air
		shrinks to a line or two of text there, while on desktop it is many.

		It sits on whatever #welcome ends with - the intro, a post's entry, a
		search or 404 header - instead of each view carrying its own number: the
		front page had 0 on desktop and 5em on mobile, a post 4em on both. Short
		views are held open by the section's 50vw minimum height regardless.
		":root #welcome" (1,2,0) beats ":root article.entry" and WP's layout
		reset ":root :where(.is-layout-constrained) > :last-child". #129. */
	:root #welcome > :last-child {
		margin-bottom: var(--wp--custom--dark--content-bottom);
		}
		:root article.introduction h2 {
			margin-top: 0;
			}
		article.introduction p {
			text-align: left;
			}

			.introduction div:nth-of-type(1) p:nth-of-type(1)::first-letter {
				display: inline-block;
				width: 1em;
				height: 1em;
				margin-right: .125em;
				font-size: 4em;
				line-height: .95em;
				text-align: center;
				float: left;
				border: 1px solid var(--wp--custom--overlay--light);
				background: rgba(255,255,255,.125);
				font-family: var(--wp--preset--font-family--anfang);
				}
			.introduction div:nth-of-type(2) {
				width: 70%;
				margin-left: calc(-25% + 1em);
				float: left;
				}
			.introduction div:nth-of-type(3) {
				width: 70%;
				margin-right: calc(-25% + 1em);
				float: right;
				}

	/*	THE GRID IS NOT HERE. Three columns come from the block attribute
		"layout": {"type":"grid","columnCount":3} on core/post-template, exactly
		like the blog listing's two columns — the same problem should have the
		same solution. The gap is blockGap 0; the source's air between cards
		comes from .project's own margins, not from the grid.

		The cards are posts since #51: the query in patterns/portfolio.php
		renders them, so a card is an <li class="wp-block-post"> where it used
		to be a hand-written <section class="web">. The heading is
		core/post-title and the image core/post-featured-image; the class
		.project remains on the group around image and tags, and still carries
		the look.

		What stays here is padding and margin, which differ between desktop and
		mobile and therefore cannot be inline styles (see the corresponding note
		at article.introduction). */
	article.portfolio {
		/*	padding, not margin: in #51 the grid moved from article.portfolio to
			the core/post-template inside it, which turned the article into an
			ordinary block box whose bottom margin collapses out through .base
			instead of making room. The space is needed: the tag row is pushed
			1.5em down on hover and otherwise ended up 21px below the section,
			where the footer (same z-index, later in the document) painted over
			it. */
		padding: 0 1em 2em;
		}
		article.portfolio .wp-block-post-template > li {
			z-index: 100;
			position: relative;
			margin: 0;
			display: flex;
			flex-flow: row wrap;
			}
			article.portfolio .project {
				z-index: 100;
				position: relative;
				}
		/*	The heading takes the whole first line of the card, so .project always
			wraps below it (#146). flex-basis 100% says so directly; it used to be
			inferred from a sum - min-width: calc(100% - 4em) plus 2em padding
			each side filling the line, and .project's margins overflowing it.
			That sum assumed content-box, but the heading is core/post-title
			(#51) and core styles .wp-block-post-title as border-box, so the
			padding sat INSIDE the min-width and 4em of the heading's font stayed
			free. From about 1600px wide that was room enough for .project's
			margins and a portrait image (max-width: 90% makes it compressible):
			Acid Masters' iOS card squeezed in beside its heading, cropped.
			box-sizing is stated here rather than inherited from core. */
		article.portfolio h3 {
			text-align: center;
			font-weight: 400;
			margin: 0;
			padding: 2em 2em 0;
			box-sizing: border-box;
			flex: 0 0 100%;
			}
		article.portfolio .tags {
			opacity: 0;
			}
		/*	The image should FIT INSIDE the card, not fill it, and the card must
			have its height BEFORE the image has loaded.

			The height is therefore definite and the width free.
			core/post-featured-image prints width and height attributes, which
			the browser translates into an aspect-ratio; with a definite height
			the width is derived from it before loading, and the card gets its
			size immediately. The images are lazy-loaded, so the alternative is
			visible: with "height: auto" (#59) every unloaded image was 0×0, the
			cards 78px tall instead of 393, and the whole grid jumped as you
			scrolled past (#68).

			Core's own rule sets "width: 100%" on the image — that has to go, or
			a portrait image (405×800) is put in the same landscape box as the
			landscape ones and cropped by the inline object-fit: cover, which
			cannot be beaten from the stylesheet without !important. With a free
			width the box takes the image's own proportions and object-fit has
			nothing to crop. */
		article.portfolio .project a img {
			width: auto;
			height: 11em;
			max-width: 90%;
			margin: 0 auto;
			display: block;
			}
		article.portfolio .wp-block-post-template > li:hover a img {
			transition: .5s opacity ease-in-out;
			}
		article.portfolio .project:hover .tags {
			opacity: 1;
			transition: .5s all ease-in-out;
			transform: translateY(1.5em);
			}
		article.portfolio .wp-block-post-template > li > .project {
			flex: 1 1 0;
			margin: 1em 1em 0;
			padding-top: 2em;
			border: 1px solid var(--wp--preset--color--card-border);
			border-radius: 1em;
			}
		article.portfolio h3 {
			opacity: var(--wp--custom--dim--text);
			}
		article.portfolio .wp-block-post-template > li:hover h3 {
			opacity: 1;
			transition: .5s opacity ease-in-out;
			}
		article.portfolio .wp-block-post-template > li:hover > .project {
			background: var(--wp--custom--overlay--neutral);
			border: 1px solid var(--wp--preset--color--card-border-hover);
			}
			/*	Grouped entries: one work in several parts.
				#acidmasters spanned two columns until #51 — the app and the
				website under ONE heading. There were two boxes, each with its
				own border; it was the heading that was shared. So it is the
				heading that is restored here: jl-portfolio marks the cards
				.jl-group-lead and .jl-group-follow (#63), and the following
				card's heading is hidden.

				visibility, not display: the height must stay, or the cards'
				images sit at different heights and the pair looks like a
				mistake.

				The card is a flex row where the heading takes the whole first
				line (flex-basis 100%, #146), so .project always starts the next
				one. That used to depend on .project's margins overflowing the
				line - which failed on large screens, where a portrait image
				squeezed in beside the heading. */
			article.portfolio .wp-block-post-template > li.jl-group-follow h3 {
				visibility: hidden;
				}
			/*	Hover applies to the whole work, not the half under the cursor. */
			article.portfolio .wp-block-post-template > li.jl-group-lead:hover + li.jl-group-follow > .project,
			article.portfolio .wp-block-post-template > li.jl-group-lead:has(+ li.jl-group-follow:hover) > .project {
				background: var(--wp--custom--overlay--neutral);
				border: 1px solid var(--wp--preset--color--card-border-hover);
				}
			article.portfolio .wp-block-post-template > li.jl-group-lead:has(+ li.jl-group-follow:hover) h3 {
				opacity: 1;
				transition: .5s opacity ease-in-out;
				}

	/*	#group-3 lacks .group and therefore inherits "section { width: inherit }"
		→ auto → shrink-to-fit on the float (PORTNINGSGUIDE §9.5). Whenever its
		content is narrower than the window, the light surface stops in
		mid-window.

		This used to cover only the templates' copy (.full), on the assumption
		that "on the front page the portfolio fills the full width anyway". It
		does on desktop, where the three-column grid is as wide as the page. It
		does not under 600px, where the grid is one column: 480px of section in
		a 560px window, a white strip down the right (#123). :not(.group) is
		exactly the set of sections with the problem - both #group-3 variants,
		and nothing else in the templates, patterns or seed. */
	section.content:not(.group) {
		width: 100vw;
		}

/*	—————————————————————
	The blog (templates/home.html, index.html, single.html)

	The listing is the core Query Loop block. The grid (two columns, not three —
	the blog should differ from the commissions), the cards' border and radius,
	the image format, the typography and the spacing all sit as BLOCK ATTRIBUTES
	in patterns/post-grid.php and as presets in theme.json — not here. What
	remains below is what block attributes cannot express: hover states, auto
	margins, and counter-measures against the source's own rules.
	*/
	/*	The card is an <article> and would otherwise be caught by the source's
		"article { float: left }" — the grid cell's height then collapses and the
		rows fall out of step. The cell is made a flexbox so the card fills the
		row's height; the grid layout comes from the block's layout attribute. */
	.blog .wp-block-post-template > li {
		display: flex;
		}
		/*	The card in the portfolio's idiom (#135): the title above a box, the
			box holding image, pills and excerpt. The title is dimmed and comes up
			on hover, the box gets the portfolio's overlay and border - the same
			tokens as article.portfolio above, so the two listings read as one
			family. Two columns stay (Jonas, 2026-09-13). */
		.blog .post-card {
			float: none;
			position: relative;
			flex: 1 1 auto;
			}
			.blog .post-card .wp-block-post-title {
				margin: 0;
				padding: 1em 1em .5em;
				}
			/*	Dimmed through the link's COLOUR, not the portfolio's opacity -
				same result on the page, but opacity below 1 creates a stacking
				context, which would trap the stretched link below inside the
				title: the box's image (position: relative) then painted over it
				and clicks on the image went nowhere (measured). currentColor in
				"color" is the inherited colour, the heading's. */
			.blog .wp-block-post-title a {
				text-decoration: none;
				color: color-mix(in srgb, currentColor calc(var(--wp--custom--dim--text) * 100%), transparent);
				transition: .5s color ease-in-out;
				}
				.blog .post-card:hover .wp-block-post-title a,
				.blog .post-card:focus-within .wp-block-post-title a {
					color: inherit;
					}
			/*	The whole card is the link, and there is still ONE link per post:
				the title's, stretched over the card by a pseudo-element. Wrapping
				the card in an <a> is not possible with core blocks, and a second
				link on the image would announce every post twice. */
			.blog .wp-block-post-title a::after {
				content: "";
				position: absolute;
				inset: 0;
				z-index: 1;
				}
			/*	flex-grow so the boxes in a row end at the same line, whatever the
				excerpts' lengths - the grid's rows are equally tall. */
			.blog .post-card-box {
				flex: 1 0 auto;
				transition: .5s all ease-in-out;
				}
				/*	!important only to beat the preset class the border attribute
					writes, which WordPress declares !important itself. */
				.blog .post-card:hover .post-card-box {
					background: var(--wp--custom--overlay--neutral);
					border-color: var(--wp--preset--color--card-border-hover) !important;
					}
				/*	Focus sits on the title's link, but what the pointer highlights
					is the box - so the keyboard gets the box as well. Only where :has
					exists does the title's own outline give way to it. */
				@supports selector(:has(*)) {
					.blog .post-card:has(.wp-block-post-title a:focus-visible) .post-card-box {
						outline: 2px solid currentColor;
						outline-offset: 2px;
						}
					.blog .wp-block-post-title a:focus-visible {
						outline: none;
						}
					}
			/*	Inset like the portfolio's image (90%). Unlike it, cropped to one
				format: blog images are photographs, not transparent device
				mockups, and a grid of photographs needs a common shape. */
			.blog .post-card .wp-block-post-featured-image {
				width: 90%;
				margin: 0 auto;
				}
			/*	The category pills are real links to their archives, so they sit
				above the stretched title link. */
			.blog .post-meta .wp-block-post-terms a {
				position: relative;
				z-index: 2;
				}
			/*	The shared pill row's z-index (1000, for the portfolio's hover
				tags) makes it a layer above the stretched link, which left the
				date unclickable. The category links lift themselves instead. */
			.blog .post-meta {
				z-index: auto;
				}
			/*	The pill rule was written for inline links and spans, where its
				height is ignored; the date is a block, where it is not, and with
				border-box it came out 18px against the links' 28. The weight is
				the terms' own (600, from the parent theme's post-terms style),
				which the portfolio's pills carry too. */
			.blog .post-meta .wp-block-post-date {
				box-sizing: content-box;
				font-weight: 600;
				}
		.blog .no-results {
			opacity: var(--wp--custom--dim--text);
			}

	.blog-pagination {
		clear: both;
		}

/*	—————————————————————
	Single post (templates/single.html)
	*/
	/*	Its own class, NOT .introduction: the latter's
		"div:nth-of-type(2|3)" rules are the front page's three-column layout and
		hit (being descendant selectors) the inner divs of the heading area —
		the byline ended up 162px out of line. The width comes from contentSize
		just as it does for the introduction; only the top margin is written
		here. ":root" prefix for the same reason as there: WP's
		":root :where(.is-layout-flow) > :first-child" would otherwise eat the
		margin. */
	/*	The offset itself is set once, with article.introduction above - a post
		header and the intro start at the same height by construction now. */
		/*	The size comes from the block's fontSize attribute; this is only the
			weight and the line height, which the h1 does not get from a
			preset. */
		article.post-header h1,
		article.post-header .wp-block-post-title,
		article.post-header .wp-block-query-title {
			margin: 0;
			padding: 0 0 .25em;
			font-size: var(--wp--preset--font-size--x-large);
			font-weight: 600;
			line-height: 1.15em;
			}
		article.post-header .byline {
			padding-bottom: 1.5em;
			opacity: .5;
			}

	/*	The body text sits in the dark section, directly after the heading area
		(Jonas's choice, 2026-08-02). The same column as .post-header because
		both are children of the same constrained #welcome — so they inherit the
		column rather than each happening to land on the same percentage. */
	:root article.entry {
		margin-top: 2em;
		}
		article.entry h2,
		article.entry h3 {
			padding: 1.5em 0 0;
			text-align: left;
			}
		article.entry p,
		article.entry li {
			text-align: left;
			}
		article.entry ul,
		article.entry ol {
			padding-left: 1.5em;
			}
		article.entry img {
			border-radius: .5em;
			}
		article.entry blockquote {
			margin: 1em 0;
			padding-left: 1em;
			border-left: 2px solid var(--wp--custom--rule--shadow);
			font-style: italic;
			}

	.post-nav {
		clear: both;
		margin-bottom: 4em;
		font-size: .9rem;
		}

	/*	Related posts: core/latest-posts with excludeCurrentPost, so no PHP of
		our own. The same card idiom as the listing, but the block has its own
		class names (wp-block-latest-posts__*) and its own grid. */
	.related-posts.is-grid {
		margin: 0 auto 2em;
		padding: 0 1em;
		max-width: 60vw;
		list-style: none;
		}
		/*	Core's own "columns-2" rule assumes its own gap
			(width: calc(50% - 1.25em)); with the cards' 1em margins the row
			breaks and the cards stack. Set the width ourselves. */
		.related-posts.is-grid > li {
			box-sizing: border-box;
			width: calc(50% - 2em);
			display: flex;
			flex-flow: column nowrap;
			margin: 1em;
			padding: 1.5em;
			border: 1px solid var(--wp--preset--color--card-border);
			border-radius: 1em;
			}
			.related-posts.is-grid > li:hover {
				background: var(--wp--custom--overlay--neutral);
				border: 1px solid var(--wp--preset--color--card-border-hover);
				transition: .5s all ease-in-out;
				}
		.related-posts .wp-block-latest-posts__featured-image img {
			width: 100%;
			height: 9em;
			object-fit: cover;
			border-radius: .5em;
			}
		.related-posts .wp-block-latest-posts__post-title {
			display: block;
			padding: .75em 0 0;
			font-size: 1.15rem;
			line-height: 1.4rem;
			text-align: center;
			text-decoration: none;
			}
		.related-posts .wp-block-latest-posts__post-date {
			display: block;
			padding: .5em 0;
			font-size: .75rem;
			text-align: center;
			opacity: var(--wp--custom--dim--text);
			}
		.related-posts .wp-block-latest-posts__post-excerpt {
			font-size: .9rem;
			line-height: 1.4rem;
			}

/*	The tag row on the portfolio cards. The terms come from core/post-terms,
	which renders them as LINKS with a separator span between them — not as list
	items. The pill shape therefore sits on the link.

	"display: contents" on the post-terms blocks makes their links flex children
	of .tags, so the group's blockGap (0.5rem) becomes the gap between the pills.
	Without it the two blocks (categories and tags respectively) would have been
	two flex children and the pills would have clumped together in pairs.

	The terms should not be clickable in the portfolio view (#51). core/post-terms
	has no such setting — there is no attribute for rendering a term as text — so
	it is done here. Note: the links are still keyboard-focusable. A proper
	solution renders them as spans and belongs in the portfolio plugin, alongside
	the 301s. */
/*	The blog cards' pill row (.post-meta, #135) shares the look: date and
	categories. Its category pills stay links - see the blog section. */
.tags,
.post-meta {
	z-index: 1000;
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	margin: .5em auto;
	text-align: center;
	padding: 0;
	font-size: .75em;
	font-weight: 300;
	}
	.tags .wp-block-post-terms,
	.post-meta .wp-block-post-terms {
		display: contents;
		}
	.tags .wp-block-post-terms__separator,
	.post-meta .wp-block-post-terms__separator {
		display: none;
		}
	/*	The pill sits on the term, and the term is either a link or a span. With
		jl-portfolio active, core's <a> is swapped for <span class="jl-term">
		(#63), which is the proper solution: a term in the portfolio view should
		not be focusable and should not be announced as a link. Without the
		plugin core renders links as before, and then pointer-events closes off
		the click — worse, but not broken. Both forms must look identical. */
	.tags a,
	.tags .jl-term,
	.post-meta .wp-block-post-date,
	.post-meta .wp-block-post-terms a {
		padding: .25rem .5rem;
		height: 1rem;
		background: var(--wp--preset--color--light-surface-soft);
		line-height: 1rem;
		border: 1px solid var(--wp--preset--color--card-border-hover);
		border-radius: .25rem;
		color: rgba(0,0,0,.75);
		text-decoration: none;
		}
		.tags a {
			pointer-events: none;
			}
/*	The <footer> element is the template part's own wrapper (#74). core/template-part
	always emits one, and with area "footer" declared in theme.json the tag is
	<footer> - so the part's content starts at the group inside it, and the
	group's tagName is "div" rather than "footer" to avoid nesting two of them.
	Two <footer> elements would both match this rule: two floats, twice the
	width, a broken page. */
footer {
	position: relative;
	z-index: 100;
	width: 100vw;
	float: left;
	text-align: center;
	}
	/*	The padding sits on the group, not on the wrapper, because the group is
		what carries the gradient - as a BLOCK ATTRIBUTE, the "footer-fade"
		preset in theme.json, with "light-text" for the text (parts/footer.html).
		Padding on the wrapper instead would leave 4em of ungradiented page above
		and below the fade. */
	footer > .wp-block-group {
		padding: 4em 0;
		}
	/*	The ":root" prefix is required since the paragraph padding moved to
		theme.json: ":root :where(p)" is (0,1,0) and would otherwise beat
		"footer p" (0,0,2). */
	:root footer p {
		padding: .5em 1em;
		}
	footer hr {
		margin-top: 1em;
		margin-bottom: 1em;
		}
	footer img,
	footer .wp-block-image img {
		box-sizing: content-box; /* core sets border-box → percentages shrink with the padding */
		min-width: 5em;
		max-width: 90%; /* doubled selector: beats core's .wp-block-image img { max-width:100% } */
		padding: .5em 1em;
		}

.columns {
	width: 100%;
	display: flex;
	align-items: center;
	}
	.columns > * {
		flex: 1;
		}

	#reset:link, #reset:visited {
		z-index: 1000000;
		display: block;
		position: fixed;
		bottom: -4em;
		left: calc(50% - 1em);
		width: 2em;
		height: 2em;
		transform: rotate(-45deg);
		background: var(--wp--custom--overlay--light);
		color: white;
		line-height: .75em;
		font-size: 1.5em;
		border: 1px solid black;
		border-radius: 0 .25em 0 0;
		opacity: var(--wp--custom--dim--ui);
		box-shadow:
		0 0 .5em 0 rgba(255,255,255,1);
		}
		/*	Keyboard parity (#113): the clearer state was mouse-only, so a
			keyboard user landed on a faint arrow with no affordance. Same
			declarations for :focus-visible - grouped rather than duplicated so
			the two states cannot drift apart. */
		#reset:hover,
		#reset:focus-visible {
			opacity: 1;
			background: rgba(0,0,0,.1);
			box-shadow: inset 0 0 .25em 0 rgb(0,0,0),
			inset 0 0 1em 0 rgba(0,0,0,.5),
			0 0 .5em 0 rgba(255,255,255,1);
			border: 1px solid white;
			}
		/*	Parked off screen until ux.js pins it - and until then it must not be
			a tab stop either (#113). It used to keep its place in the tab order
			while sitting at bottom: -4em, so a keyboard user hit "Tillbaka till
			toppen" pointing at nothing they could see. visibility does both: it
			hides the element and removes it from the tab order, and it costs
			nothing when the plugin is off, since #reset is then not rendered at
			all. */
		#reset:link, #reset:visited {
			visibility: hidden;
			}
		#reset.fixed-to-bottom {
			bottom: calc(-1em - 1px);
			visibility: visible;
			}
	/*	The mawnstr logo is an image block: #links > figure > a > img */
	#links > a img,
	#links > figure a img {
		width: 50%;
		opacity: var(--wp--custom--dim--ui);
		transition: .25s opacity ease-in-out;
		}
		#links > a:hover img,
		#links > figure a:hover img,
		#links > a:focus-visible img,
		#links > figure a:focus-visible img {
			opacity: 1;
			}
		/*	Keyboard parity again (#113). The resting opacity of .125 is a
			separate question - as a link's only visible content it sits far
			under the 3:1 that WCAG 1.4.11 asks of a control, and roughly .4
			would be needed against this white ground. That is a design
			decision, not a bug fix, so it is left to Jonas. */
	/*	The icon rows are flex groups of image blocks. The gap (~4px, the
		source's tight word spacing) is now blockGap on the groups themselves in
		patterns/footer-contact.php — WP's own concept, and therefore reachable
		from the sidebar. What stays here: the figure width, which no block
		attribute expresses. The figures are given the icon width — otherwise
		they shrink-wrap to the SVG's natural width and core's max-width:100%
		clips the icon. */
	#social figure.wp-block-image,
	#gaming figure.wp-block-image {
		width: 2rem;
		}
	#gaming img, #social img {
		position: relative;
		min-width: 1rem;
		width: 2rem; /* rem, inte em: figure-wrapperns font-size ska inte skala ikonen */
		max-width: none; /* the footer rule's 90% would otherwise measure against the narrow figure */
		padding: 0;
		opacity: var(--wp--custom--dim--ui);
		transition: .25s opacity ease-in-out;
		}
		#gaming img:hover, #social img:hover {
			opacity: 1;
			}

.clear {
	clear: both;
	height: 0;
	border: none;
	padding: 0;
	margin: 0;
	}

.byline {
	opacity: var(--wp--custom--dim--text);
	}

.pro-tip {
	position: absolute;
	top: 2em;
	right: 2em;
	width: 10em;
	}

/*	—————————————————————
	Header/navigation (lives in the page content, above the hero)
	*/
header {
	z-index: 10000;
	position: absolute;
	top: 0;
	left: 0;
	width: calc(100vw + 1px);
	padding: .5em 0;
	background: rgba(50,50,50,0);
	}
	/*	The source has #logotype as a bare <a>, inline and therefore 0px tall in
		the flow — an empty inline link with no visible content gets no box at
		all. WP's image block wraps that same link in <figure
		class="wp-block-image"> > <a>, and core sets GLOBALLY
		".wp-block-image > figure > a { display: inline-block }". An empty
		inline-block keeps a small box of its own (line height from its own font
		metrics) even though the image inside is position:absolute — unlike an
		empty inline, which is optimised away entirely.

		Harmless at rest, but in the pinned state (.fixed-to-top) the header is a
		visible black band whose height MUST come only from nav plus the header's
		own padding. The box showed up there as surplus band height, which pushed
		the logotype into the container instead of letting it sweep out below the
		edge as it does in the source (compared against jonaslindeberg.com,
		2026-08-05: the logotype should stick out below the band).

		display:inline restores the source's behaviour exactly. line-height:0
		stays as a second safety net (it does not affect the image's own em
		measurements — those count against the image's OWN font-size, not the
		figure's). */
	header #logotype {
		line-height: 0;
		}
		header #logotype a {
			display: inline;
			}
		/*	That display:inline is deliberate (see above) and leaves the link
			with no box at all, so its focus ring landed on a 0x0 point while
			the signature it links from sits elsewhere, absolutely positioned
			(#113). The ring therefore goes on the image instead - the thing
			the eye is actually looking for. The link's own indicator is
			replaced, never simply removed. */
		header #logotype a:focus-visible {
			outline: none;
			}
			header #logotype a:focus-visible img {
				outline: 2px solid var(--wp--preset--color--accent-blue, #fff);
				outline-offset: .25em;
				opacity: 1;
				}
	header #logotype img {
		position: absolute;
		top: .25em;
		right: 2em;
		height: 5em;
		transform: rotate(-6deg);
		animation: fade-in 3s;
		animation-iteration-count: 1;
	}
	nav {
		width: 50vw;
		font-weight: 100;
		}
		nav ul,
		nav .wp-block-navigation__container {
			display: flex;
			list-style: none;
			padding: .25em .5em; /* core zeroes padding on __container (0,1,0) */
			margin: 0;
			animation: fade-in 3s;
			animation-iteration-count: 1;
			}
			/*	The button shape on the menu items. core/navigation exposes no
				border, radius or background PER item — only blockGap and
				typography — so this part cannot be expressed as a block
				attribute. The selectors are written against the navigation
				block's own classes in order to beat core's ".wp-block-navigation
				.wp-block-navigation-item__content" (0,2,0). */
			nav ul li,
			nav .wp-block-navigation-item {
				flex: 1 1 auto;
				max-width: 10em;
				text-align: center;
				border-radius: .25em;
				overflow: hidden;
				border: 1px dotted rgba(255,255,255,.1);
				}
				/*	line-height:normal beats WP's global text style (1.4 at the
					root, 28.224px), which made the pills ~3px taller than the
					source's browser normal (~45.5px) — visible as a leftover gap
					in the pinned band after the figure/display fix above. */
				nav ul li a:link, nav ul li a:visited,
				.wp-block-navigation .wp-block-navigation-item__content:link,
				.wp-block-navigation .wp-block-navigation-item__content:visited {
					display: block;
					width: 100%;
					height: 100%;
					padding: .5em 0;
					color: white;
					background: rgba(50,50,50,.5);
					opacity: .5;
					line-height: normal;
					text-decoration: none;
					border-radius: .125em;
					}
					nav ul li a:hover, nav ul li a:active,
					.wp-block-navigation .wp-block-navigation-item__content:hover,
					.wp-block-navigation .wp-block-navigation-item__content:active {
						background: var(--wp--preset--color--accent-blue);
						color: white;
						opacity: 1;
						}

.fixed-to-top {
	position: fixed;
	top: 0;
	left: 0;
	background: rgba(0,0,0,1);
	border-bottom: 1px solid var(--wp--custom--overlay--light);
	transition: .5s background ease-in-out;
	}
	.fixed-to-top > a {
		transition: 0 all ease-in;
	}
	.fixed-to-top nav ul li {
		border: 1px solid rgba(255,255,255,.5);
		}
		.fixed-to-top nav ul li:hover {
			border: 1px solid var(--wp--preset--color--accent-blue);
			}
	.fixed-to-top nav ul li a:link, .fixed-to-top nav ul li a:visited {
		background: transparent;
		opacity: 1;
		}
	.fixed-to-top nav ul li a:hover, .fixed-to-top nav ul li a:active {
		background: var(--wp--preset--color--accent-blue);
		color: white;
		}
	.fixed-to-top #logotype img {
		opacity: 1;
		transform: rotate(0deg);
		}

/*	Link effect in dark sections. The gradient underline cannot be expressed as
	a block attribute — it stays here, but the hook is now the palette colour's
	own class. */
.has-dark-surface-background-color p a:link,
.has-dark-surface-background-color p a:visited {
	background-image:
	linear-gradient(
	90deg,
	var(--wp--preset--color--accent-blue) 0%,
	rgba(150,255,0,.5) 40%,
	rgba(250,255,0,.5) 60%,
	rgba(255,0,150,.25) 80%,
	rgba(255,0,150,0) 100%);
	background-size: 0% 1em;
	background-repeat: no-repeat;
	background-position: left center;
	transition: 1s background-size ease-in-out;
	}
	article p a:hover,
	.has-dark-surface-background-color p a:hover {
		color: inherit;
		background-size: 1000% 1em;
	}

/*	Motion off (#142, #148). html.jl-motion-still is set by the accessibility
	plugin when a visitor, or their system, has switched motion off, and the
	same plugin then finishes every CSS animation and transition at once - the
	theme's entrance fades and hover effects included. Nothing is listed here:
	this used to name the fades one by one, and everything unnamed (the
	portfolio's hover slide among it) kept moving. A rule belongs here only if
	still should look different from where an animation ends. */

/*	—————————————————————
	Mobil
	*/
@media only screen and (max-width: 600px) {
	html, body {
		font-size: 4vw;
		}
	nav {
		width: 100%;
		}
		nav ul li,
		nav .wp-block-navigation-item {
			max-width: 100%;
			}
		header #logotype {
			width: 100%;
			height: 2em;
			transition: 1s all ease-in-out;
			}
		header #logotype img {
			transform: rotate(0deg);
			width: 50%;
			top: 7em;
			right: 25%;
			}
		.fixed-to-top #logotype {
			opacity: 0;
			height: 0;
			}
			.fixed-to-top #logotype img {
				animation: fade-out .5s;
				animation-iteration-count: 1;
				transform: translate(0, -20em) scale(0);
				transition: .5s all ease-in-out;
				}
	/*	Mobile column width. One line instead of four width/margin rules: the
		constrained layout reads the width from this variable, so changing it is
		enough. "html body" (0,0,2) is needed to beat WP's own "body"
		declaration (0,0,1) regardless of enqueue order. */
	html body {
		--wp--style--global--content-size: 90vw;
		}
	/*	Mobile keeps its own offset, and in em rather than vw on purpose: down
		here the body font stops following the viewport (it is a readable ~15px
		instead of 1.4vw), and what content has to clear is no longer the roots
		but the logotype, which moves down INTO the section
		(header #logotype img { top: 7em }) and is sized in em. Same value as
		the two views had between them before - 11em, the larger of the two - so
		mobile looks exactly as it did, while still being decided in one place. */
	:root {
		--wp--custom--dark--content-top: 11em;
		}
		.introduction div:nth-of-type(2), .introduction div:nth-of-type(3) {
			width: 100%;
			margin: 0;
			}
	article.portfolio {
		min-height: calc(100vw / 2.5);
		text-align: center;
		display: block;
		width: 100%;
		margin: 1em 0;
		/*	Same reason as in the desktop rule (#59): the space below the grid
			has to be padding, or it collapses out and the footer lies over the
			tag row. On mobile the tags are always visible, so the need is
			constant. */
		padding: 0 0 2em;
		margin-bottom: 7.5vh;
		/*	No overflow here, and none on the cards below (#123). The source made
			the mobile portfolio a sideways swipe carousel - row nowrap,
			overflow-x: scroll, touch scrolling - and those declarations came
			along in the port. The port stacks the cards as a one-column grid
			instead, so there was nothing to scroll, and "scroll" FORCES a
			scrollbar whether or not anything overflows. Invisible on macOS and
			phones, where scrollbars are overlays; on Windows a real bar inside
			every card, which also clipped the tag row: 442px of content in a
			423px box. */
		}
	article.portfolio .wp-block-post-template > li {
		position: relative;
		margin: 0;
		width: 100%;
		padding-bottom: 1em;
		border-radius: .25em;
		/*	A plain block, not a column flex container. Together with the fixed
			height below and the tag nudge further down, flex made the card
			shorter than its own content, so the tag row hung out of the bottom
			(#123). On mobile the tags are always shown, and the card has to
			grow around them. */
		display: block;
		}
		article.portfolio h3 {
			margin-bottom: 0;
			padding: 1.5em 1em 0;
			}
		/*	min-height only: this used to be height, min-height AND max-height
			of 50vw, which locked the box at 280px on a 560px screen while the
			image and the always-visible tag row needed 336. */
		article.portfolio .wp-block-post-template > li > .project {
			padding-top: 2em;
			min-height: 50vw;
			margin-bottom: 1em;
			}
			/*	Mobilt finns ingen hovring — taggarna ska synas hela tiden. */
			article.portfolio .tags,
			article.portfolio .project:hover .tags {
				opacity: 1;
				font-size: .75em;
				/*	none, not the translateY(.5em) this inherited from the desktop
					hover slide. A transform moves the row without moving the
					layout, so the card never grew to include it (#123). */
				transform: none;
				}
	/*	The block's grid layout gives two columns; on mobile we want one. The
		specificity (0,2,0) beats core's generated .wp-container-* rule. */
	.blog .wp-block-post-template {
		grid-template-columns: 1fr;
		}
	:root article.entry {
		margin-bottom: 2em;
		}
	.post-nav {
		margin-bottom: 2em;
		}
	.related-posts.is-grid {
		max-width: 100%;
		padding: 0;
		}
		.related-posts.is-grid > li {
			width: 100%;
			margin: 1em 0;
			}
	.top, .right, .bottom, .left {
		position: relative;
		width: 100%;
		text-align: center;
		height: inherit;
		}
		.top { top: auto; }
		.right { right: auto; }
		.bottom { bottom: auto; }
		.left { left: auto; }
	.columns {
		width: 100%;
		display: block;
		}
		.columns > * {
			width: 100%;
			float: left;
			}
	.pro-tip {
		display: none;
		}
	footer img {
		margin: auto;
		width: 50%;
		}
	}

/*	Note: prefers-reduced-motion is deliberately not handled — see the
	corresponding note in jl-hero/css/jl-hero.css. The live site does not handle
	it either. */



