/*
Theme Name: Atelier
Theme URI: https://example.com/atelier
Author: Andreas Noussas
Description: A minimal architectural portfolio theme. Full-bleed auto-scrolling image marquee on the homepage, a typographic project index with hover previews, and quiet, image-first project pages. Black on white, one type size, no page builder.
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: atelier
Tags: portfolio, minimal, custom-colors, translation-ready, block-styles
*/

/* ==========================================================================
   1. Tokens
   ========================================================================== */

:root {
	--bg: #ffffff;
	--fg: #000000;

	/*
	 * Page inset. Tight by design, but text sitting hard against the window edge
	 * is uncomfortable to read, so it scales with the window: ~16px on a phone,
	 * ~23px on a laptop, ~40px on a wide screen. Everything that aligns to the
	 * left margin — site header, project title, description — uses this, so they
	 * stay on one line as it grows.
	 */
	--pad: clamp(1rem, 1.8vw, 2.5rem);
	--gutter: 1.4rem;

	/* Helvetica Neue first, Arial as the universal fallback. Both carry full
	   Greek coverage, and neither needs downloading. */
	--font: "Helvetica Neue", Helvetica, Arial, "Liberation Sans", "Nimbus Sans", sans-serif;

	--fs: clamp(15px, 0.35vw + 13.2px, 17.5px);
	--fs-small: 0.8em;
	--lh: 1.12;
	--tracking: -0.011em;

	--content-w: 34em;
	--wide-w: 62em;

	--dur: 180ms;
}

/* ==========================================================================
   2. Reset
   ========================================================================== */

*,
*::before,
*::after {
	box-sizing: border-box;
}

html {
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	background: var(--bg);
	color: var(--fg);
	font-family: var(--font);
	font-size: var(--fs);
	line-height: var(--lh);
	letter-spacing: var(--tracking);
	font-weight: 400;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-rendering: optimizeLegibility;
}

img,
video {
	max-width: 100%;
	height: auto;
	display: block;
}

a {
	color: inherit;
	text-decoration: none;
}

a:hover {
	text-decoration: underline;
	text-underline-offset: 0.18em;
	text-decoration-thickness: from-font;
}

h1,
h2,
h3,
h4,
p,
figure,
ul,
ol,
dl,
dd,
blockquote {
	margin: 0;
	font-size: inherit;
	font-weight: inherit;
}

button {
	font: inherit;
	color: inherit;
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
}

:focus-visible {
	outline: 1.5px solid currentColor;
	outline-offset: 2px;
}

.screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.skip-link {
	position: absolute;
	top: -100%;
	left: var(--pad);
	z-index: 1000;
	background: var(--bg);
	color: var(--fg);
	padding: 0.4em 0.6em;
}

.skip-link:focus {
	top: var(--pad);
}

/* ==========================================================================
   3. Header
   ========================================================================== */

.site-header {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 100;
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--gutter);
	padding: var(--pad);
	pointer-events: none;
}

.site-header > * {
	pointer-events: auto;
}

.site-header__title .custom-logo-link {
	display: block;
}

.site-header__title .custom-logo {
	height: 1.12em;
	width: auto;
}

.site-header__nav {
	text-align: right;
}

.site-header__nav a:not(:last-child)::after {
	content: ",";
}

.site-header__nav a + a {
	margin-left: 0.3em;
}

.site-header__nav .current-menu-item > a,
.site-header__nav a[aria-current="page"] {
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

.site-header__nav ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: inline;
}

.site-header__nav li {
	display: inline;
}

.site-header__nav li:not(:last-child)::after {
	content: ",";
}

/*
 * Over the homepage marquee the header sits on top of arbitrary photography.
 * `difference` blending makes it invert itself against whatever is behind:
 * black over pale images, white over dark ones. Toggleable in the Customizer.
 */
.site-header--blend {
	color: #fff;
	mix-blend-mode: difference;
}

/* ==========================================================================
   4. Layout
   ========================================================================== */

.site-main {
	min-height: 100vh;
}

.pad-top {
	/* Clears the fixed header: one line of text plus both paddings. */
	padding-top: calc(var(--pad) * 2 + 1.12em);
}

.wrap {
	padding-left: var(--pad);
	padding-right: var(--pad);
}

/* ==========================================================================
   5. Homepage marquee
   ========================================================================== */

.marquee {
	position: fixed;
	inset: 0;
	overflow: hidden;
	background: var(--bg);
	touch-action: pan-y;
	cursor: grab;
}

.marquee.is-dragging {
	cursor: grabbing;
}

/*
 * Vertical alignment runs through one variable so the whole strip stays on a
 * single line — the item box within the strip, and the image within the item
 * box, which is what matters once images are shorter than the window.
 * Set in the Customizer; flex-end (sitting on the bottom edge) is the default.
 */
.marquee__track {
	display: flex;
	align-items: var(--marquee-align, flex-end);
	height: 100%;
	width: max-content;
	will-change: transform;
}

.marquee__set {
	display: flex;
	align-items: var(--marquee-align, flex-end);
	height: 100%;
	flex: 0 0 auto;
}

.marquee__item {
	flex: 0 0 auto;
	height: var(--marquee-h, 100%);
	display: flex;
	align-items: var(--marquee-align, flex-end);
	padding-right: var(--marquee-gap, 0.75rem);
}

.marquee__item img {
	height: 100%;
	width: auto;
	max-width: none;
	object-fit: contain;
	user-select: none;
	-webkit-user-drag: none;
}

/*
 * On a phone, a landscape image at full viewport height is wider than the
 * screen, so you only ever see a slice of it. Constrain by width there and
 * let the height fall out of the aspect ratio instead.
 */
@media (max-width: 700px) {
	.marquee__item img {
		height: auto;
		max-height: 100%;
		width: auto;
		max-width: 86vw;
	}
}

/* Caption revealed on hover, bottom-left, riding the same blend trick. */
.marquee__caption {
	position: fixed;
	left: var(--pad);
	bottom: var(--pad);
	z-index: 90;
	color: #fff;
	mix-blend-mode: difference;
	opacity: 0;
	transition: opacity var(--dur) ease;
	pointer-events: none;
}

.marquee__caption.is-visible {
	opacity: 1;
}

.marquee-empty {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 100vh;
	padding: var(--pad);
	text-align: center;
}

@media (prefers-reduced-motion: reduce) {
	.marquee {
		overflow-x: auto;
		scroll-snap-type: x proximity;
	}

	.marquee__item {
		scroll-snap-align: center;
	}
}

/* ==========================================================================
   6. Project index
   ========================================================================== */

.index {
	padding-bottom: 30vh;
}

.index__row {
	display: grid;
	grid-template-columns: minmax(0, 2.2fr) minmax(0, 1.4fr) minmax(0, 1fr) 4.5em;
	gap: var(--gutter);
	padding: 0.12em 0;
	align-items: baseline;
}

/* The metadata columns stay on one line so the rows keep an even rhythm;
   only the project title is allowed to wrap. */
.index__location,
.index__type,
.index__year {
	min-width: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.index__row:hover {
	text-decoration: none;
}

.index__row:hover .index__title {
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

.index__year {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.index__head {
	padding-bottom: 0.5em;
	opacity: 0.4;
}

.index__head:hover .index__title {
	text-decoration: none;
}

/* Cursor-following preview, desktop pointers only. */
.index-preview {
	position: fixed;
	top: 0;
	left: 0;
	z-index: 200;
	width: 22vw;
	max-width: 320px;
	pointer-events: none;
	opacity: 0;
	transition: opacity 120ms ease;
	will-change: transform;
}

.index-preview.is-visible {
	opacity: 1;
}

.index-preview img {
	width: 100%;
	height: auto;
}

/* Narrow windows drop the metadata columns — title and year carry the list. */
@media (max-width: 760px) {
	.index__row {
		grid-template-columns: minmax(0, 1fr) 4.5em;
		row-gap: 0;
	}

	.index__location,
	.index__type {
		display: none;
	}

	.index__head {
		display: none;
	}
}

/* Touch / coarse pointers get inline thumbnails instead of hover previews. */
.index__thumb {
	display: none;
	margin: 0.4em 0 1.1em;
	max-width: 60vw;
}

@media (hover: none), (pointer: coarse) {
	.index__thumb {
		display: block;
		grid-column: 1 / -1;
	}
}

/* ==========================================================================
   7. Single project
   ========================================================================== */

/*
 * A project page is composed to fit the window: the intro, the text and the
 * navigation take the height they need, and the gallery absorbs everything
 * left over. Nothing needs scrolling to be seen — unless the description runs
 * long, in which case the page grows and scrolls rather than clipping.
 */
.project {
	display: flex;
	flex-direction: column;
	height: 100vh;
	height: 100svh;

	/* Width of the right-hand column: the details block and the description
	   below it share it, so their left edges line up. */
	--aside-w: 26em;
}

/* The text and the details never shrink; only the slideshow gives ground. If
   the description is long enough that even that is not enough, the page
   overflows and scrolls rather than squeezing the words. */
.project > .wrap {
	flex: 0 0 auto;
}

.project__intro {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	column-gap: var(--gutter);
	row-gap: 1.6em;
	padding-bottom: 1.8em;
	/* Both blocks start at the same height rather than stretching. */
	align-items: start;
}

/* The title has the row to itself, so the two columns below it line up. */
.project__title {
	grid-column: 1 / -1;
}

.project__details {
	display: grid;
	grid-template-columns: max-content minmax(0, 1fr);
	gap: 0 var(--gutter);
	align-content: start;
	max-width: var(--aside-w);
	justify-self: end;
	width: 100%;
}

.project__details dt {
	opacity: 0.4;
}

.project__details dd {
	margin: 0;
}

@media (max-width: 700px) {
	.project__intro {
		grid-template-columns: 1fr;
		gap: 1.6em;
	}

	.project__details {
		justify-self: start;
	}
}

/* --------------------------------------------------------------------------
   7a. 3D site model
   -------------------------------------------------------------------------- */

/*
 * The model is the opening frame of the gallery, so it simply fills the slide.
 * `touch-action: none` because a drag here turns the model - the browser must
 * not steal the gesture for scrolling.
 */
.model {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
}

.model__stage {
	position: relative;
	flex: 1 1 auto;
	min-height: 0;
	overflow: hidden;
	cursor: grab;
	touch-action: none;
}

.model.is-ready .model__stage:active {
	cursor: grabbing;
}

.model__stage canvas {
	display: block;
	width: 100%;
	height: 100%;
}

/* The poster carries the section until the model is up, then steps aside. */
.model__poster {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: contain;
	transition: opacity 400ms ease;
}

.model.is-ready .model__poster {
	opacity: 0;
	pointer-events: none;
}

.model.is-loading .model__stage::after {
	content: "";
	position: absolute;
	left: var(--pad);
	bottom: var(--pad);
	width: 1.4em;
	height: 1px;
	background: currentColor;
	opacity: 0.35;
	animation: atelier-load 1.1s ease-in-out infinite;
}

@keyframes atelier-load {
	0%, 100% { transform: scaleX(0.2); transform-origin: left; }
	50%      { transform: scaleX(1);   transform-origin: left; }
}

.model__hint {
	position: absolute;
	right: var(--pad);
	bottom: var(--pad);
	margin: 0;
	font-size: var(--fs-small);
	color: #fff;
	mix-blend-mode: difference;
	opacity: 0;
	transition: opacity 400ms ease;
	pointer-events: none;
}

.model.is-ready .model__hint {
	opacity: 0.75;
}

.model.is-ready.has-interacted .model__hint {
	opacity: 0;
}

.model__caption {
	margin: 0.6em 0 0;
	font-size: var(--fs-small);
	opacity: 0.5;
	flex: 0 0 auto;
}

/*
 * No WebGL, or the model failed to load: fall back to the poster. With no
 * poster either, the section removes itself rather than leaving a blank band.
 */
.model.is-unsupported .model__hint,
.model.is-failed .model__hint {
	display: none;
}

.model.is-unsupported:not(:has(.model__poster)),
.model.is-failed:not(:has(.model__poster)) {
	display: none;
}

.model.is-unsupported .model__stage,
.model.is-failed .model__stage {
	cursor: default;
}

@media (prefers-reduced-motion: reduce) {
	.model.is-loading .model__stage::after {
		animation: none;
	}
}

/* --------------------------------------------------------------------------
   7b. Project gallery — full width, one image at a time
   -------------------------------------------------------------------------- */

/*
 * The slideshow sits under the description and matches it exactly: same left
 * edge, same width. Its wrapper carries the page inset and takes whatever
 * height the rest of the page leaves over.
 */
.project__slideshow {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	margin-top: 1.2em;
}

.gallery {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	min-height: 0;
}

/* One column of the intro grid, so image edges land on the text edges. */
@media (min-width: 701px) {
	.project__slideshow .gallery {
		max-width: calc((100% - var(--gutter)) / 2);
	}
}

.gallery__stage {
	position: relative;
	flex: 1 1 auto;
	min-height: var(--gallery-h, 180px);
	overflow: hidden;
	background: var(--bg);
	cursor: pointer;
}

.gallery__ui {
	flex: 0 0 auto;
}

.gallery__slide {
	position: absolute;
	inset: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
	justify-content: center;
}

.gallery__slide--model {
	cursor: default;
}

.gallery__slide img {
	width: 100%;
	height: 100%;
	object-fit: var(--gallery-fit, contain);
	object-position: center;
}

.gallery__slide figcaption {
	position: absolute;
	left: 0.75rem;
	bottom: 0.75rem;
	font-size: var(--fs-small);
	color: #fff;
	mix-blend-mode: difference;
	max-width: 40em;
}

/*
 * No JavaScript: every slide is in the markup, so stack them rather than
 * hiding all but one. The script swaps the stage to absolute positioning.
 */
.gallery:not(.is-ready) .gallery__stage {
	height: auto;
	cursor: default;
}

.gallery:not(.is-ready) .gallery__slide {
	position: relative;
	inset: auto;
	height: var(--gallery-h, 180px);
}

.gallery:not(.is-ready) .gallery__ui {
	display: none;
}

/* Transition: fade (default) */
.gallery.is-ready .gallery__slide {
	opacity: 0;
	transition: opacity var(--gallery-dur, 700ms) ease;
	pointer-events: none;
	z-index: 0;
}

.gallery.is-ready .gallery__slide.is-current {
	opacity: 1;
	pointer-events: auto;
	z-index: 1;
}

/* Transition: slide */
.gallery.is-ready[data-transition="slide"] .gallery__slide {
	opacity: 1;
	transform: translate3d(100%, 0, 0);
	transition: transform var(--gallery-dur, 700ms) cubic-bezier(0.6, 0, 0.2, 1);
}

.gallery.is-ready[data-transition="slide"] .gallery__slide.is-current {
	transform: translate3d(0, 0, 0);
}

.gallery.is-ready[data-transition="slide"] .gallery__slide.is-leaving {
	transform: translate3d(-100%, 0, 0);
	z-index: 1;
}

.gallery.is-ready[data-transition="slide"].is-reversing .gallery__slide {
	transform: translate3d(-100%, 0, 0);
}

.gallery.is-ready[data-transition="slide"].is-reversing .gallery__slide.is-current {
	transform: translate3d(0, 0, 0);
}

.gallery.is-ready[data-transition="slide"].is-reversing .gallery__slide.is-leaving {
	transform: translate3d(100%, 0, 0);
}

/* Transition: none */
.gallery.is-ready[data-transition="none"] .gallery__slide {
	transition: none;
}

.gallery__ui {
	display: flex;
	align-items: baseline;
	gap: var(--gutter);
	padding: 0.5em 0 0;
}

.gallery__nav {
	font: inherit;
	line-height: 1;
	padding: 0.1em 0;
}

.gallery__nav:hover {
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

.gallery__counter {
	font-variant-numeric: tabular-nums;
	opacity: 0.4;
	margin-left: auto;
}

/* Stacked, the page cannot fit one screen anyway — give the gallery a definite
   height and let the page scroll normally. */
@media (max-width: 700px) {
	.project {
		height: auto;
		min-height: 100svh;
	}

	.gallery__stage,
	.gallery:not(.is-ready) .gallery__slide {
		flex: 0 0 auto;
		height: var(--gallery-h-mobile, 46vh);
		min-height: 0;
	}
}

/*
 * A short laptop window has to find the room somewhere. The site model gives it
 * up first — it is context, and the photographs are the point.
 */
@media (min-width: 701px) and (max-height: 860px) {
	.model__stage {
		min-height: 130px;
	}
}

@media (min-width: 701px) and (max-height: 680px) {
	.model__stage {
		min-height: 104px;
	}

	.project__nav {
		padding: 1em 0 0.8em;
	}
}

@media (prefers-reduced-motion: reduce) {
	.gallery.is-ready .gallery__slide {
		transition: none;
	}
}

.project__nav {
	display: flex;
	justify-content: space-between;
	gap: var(--gutter);
	padding: 1.4em 0 1.1em;
}

.project__nav span {
	opacity: 0.4;
}

/* ==========================================================================
   8. Editor content
   ========================================================================== */

.entry-content > * {
	max-width: var(--content-w);
	margin-block: 1.1em;
}

/*
 * The description occupies the left column, level with the project data on the
 * right. Its children are capped by the column rather than the page, and a
 * "full width" image simply fills that column - the gallery below is where
 * full-bleed images belong.
 */
@media (min-width: 701px) {
	.project .entry-content > * {
		max-width: 100%;
	}

	.project .entry-content > .alignfull,
	.project .entry-content > .alignwide {
		width: 100%;
		max-width: 100%;
		margin-left: 0;
		margin-right: 0;
	}
}

/*
 * WordPress generates its own layout CSS from theme.json and centres constrained
 * content with `margin-left/right: auto !important`. That is invisible in a
 * static preview but pushes the text column into the middle of the page on a
 * real install. This page wants it hard left, under the title — so override at
 * higher specificity. Deliberately aligned blocks keep their own behaviour.
 */
.project .entry-content > *:not(.alignfull):not(.alignwide):not(.aligncenter),
.page-body .entry-content > *:not(.alignfull):not(.alignwide):not(.aligncenter) {
	margin-inline: 0 !important;
}

.entry-content > :first-child {
	margin-top: 0;
}

/* Images want room; text does not. */
.entry-content > figure,
.entry-content > .wp-block-image,
.entry-content > .wp-block-gallery,
.entry-content > .wp-block-embed,
.entry-content > .wp-block-video,
.entry-content > .wp-block-columns {
	max-width: none;
	margin-block: 2.2em;
}

.entry-content > .alignwide {
	max-width: var(--wide-w);
}

.entry-content > .alignfull {
	max-width: none;
	width: calc(100% + var(--pad) * 2);
	margin-left: calc(var(--pad) * -1);
	margin-right: calc(var(--pad) * -1);
}

/* The image bleeds to the edge; its caption should still line up with the text. */
.entry-content > .alignfull figcaption,
.entry-content > .alignfull .wp-element-caption {
	padding-left: var(--pad);
	padding-right: var(--pad);
}

.entry-content h2,
.entry-content h3 {
	margin-top: 2.2em;
}

.entry-content strong {
	font-weight: 600;
}

.entry-content em {
	font-style: italic;
}

.entry-content a {
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

.entry-content ul,
.entry-content ol {
	padding-left: 1.2em;
}

.entry-content li {
	margin-bottom: 0.25em;
}

.entry-content figcaption,
.wp-element-caption {
	font-size: var(--fs-small);
	opacity: 0.5;
	margin-top: 0.5em;
	text-align: left;
}

.entry-content blockquote {
	padding-left: 1.2em;
	border-left: 1px solid currentColor;
}

.wp-block-gallery {
	gap: var(--pad) !important;
}

/* ==========================================================================
   9. Simple pages (Information, 404, fallback loop)
   ========================================================================== */

.page-body {
	padding-bottom: 30vh;
}

.page-body .entry-title {
	margin-bottom: 1.6em;
	max-width: var(--content-w);
}

.archive-list li {
	list-style: none;
}

.archive-list {
	padding: 0;
	margin: 0;
}

/* ==========================================================================
   10. Print
   ========================================================================== */

@media print {
	.site-header,
	.project__nav,
	.index-preview {
		display: none;
	}

	body {
		font-size: 10pt;
	}
}
