/* ==========================================================================
   mohamedgharkan.com

   Visual language adapted from the "Ober" WordPress theme by bslthemes,
   under Mohamed's ThemeForest licence. Rebuilt as plain CSS.

   One stylesheet serves both languages: every directional rule uses CSS
   logical properties (inline-start / inline-end), so the Arabic RTL layout
   is the same layout resolved the other way round, not a mirrored copy.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
	--bg:          #F7F5F2;
	--bg-alt:      #EBE7E0;
	--ink:         #1C2528;
	--body:        #3C4649;
	--accent:      #FF8059;
	/* The bright orange is Mo's colour, but on a cream page it only reaches
	   2.3:1 — below AA for text of any size. Fills, rules and shapes keep
	   it; anything that has to be *read* uses the deeper one, which clears
	   4.5:1 on all three light surfaces. In dark mode the bright orange
	   already clears 6:1, so there it is the same colour. */
	--accent-text: #B03C16;
	/* Dark ink on the orange fill: 6.3:1, where white would be 2.5:1. */
	--accent-ink:  #1C2528;
	--rule:        rgba(28, 37, 40, 0.18);
	--card:        #FFFFFF;
	--field:       #FFFFFF;
	--shadow:      0 18px 50px -24px rgba(28, 37, 40, 0.28);

	/* Hero shape palette — taken from the theme's own hero SVGs */
	--shape-orange: #FF8059;
	--shape-teal:   #3AAFC9;
	--shape-pale:   #B9D1E4;
	--shape-violet: #676CDB;
	--shape-yellow: #FFD041;

	--font-body:       "Rubik", system-ui, -apple-system, sans-serif;
	--font-display:    "Sorts Mill Goudy", Georgia, serif;
	/* Overridden per page in <head>; this is the fallback if that is missing. */
	--font-display-ar: "El Messiri", "IBM Plex Sans Arabic", system-ui, sans-serif;
	--font-body-ar:    "IBM Plex Sans Arabic", system-ui, sans-serif;

	/* Milestones are printed in two inks. Orange is the constant; the other
	   end swaps with the theme, so the photographs sit on their background
	   in either one. */
	--duo-shadow: #1C2528;
	--duo-light:  #FF8059;
	/* Project marks are shown without their colours until the pointer is on
	   them. Grey rather than a flat silhouette: more than one of these marks
	   carries pale lettering inside a solid shape, and painting the whole
	   thing one ink would swallow the words — Mufakiru's strapline sits in a
	   filled bar and would have become a black brick. */
	--logo-mono:  grayscale(1) contrast(1.2) brightness(0.34);
	/* A mark painted through a mask is one solid fill, so it cannot be run
	   through the filter above and land where real artwork lands: artwork
	   carries a spread of tones that the filter compresses, a flat fill
	   carries one that it pushes to the end of the range. In dark mode the
	   ink came out near-white against neighbours sitting at a soft grey.
	   This is that grey, measured off them, and it is already composited —
	   no filter and no opacity on top of it. */
	--mark-ink:   #4F4F4F;

	--wrap:    1240px;
	--gutter:  clamp(20px, 5vw, 60px);
	--ease:    cubic-bezier(0.3, 0, 0.3, 1);
}

:root[data-theme="dark"] {
	--bg:         #1A1F26;
	--bg-alt:     #14181E;
	--ink:        #F4F2EF;
	--body:       #A8B0B8;
	--rule:       rgba(244, 242, 239, 0.18);
	--card:       #222830;
	--field:      #222830;
	--shadow:     0 18px 50px -24px rgba(0, 0, 0, 0.6);
	--accent-text: var(--accent);
	/* The same two-ink idea, warmed and lifted: on a near-black page the
	   light pair has to be the brighter of the two, or the photographs sink
	   into the background at their edges. Deep coffee to pale orange. */
	--duo-shadow: #4A2A1C;
	--duo-light:  #FFC9A8;
	--logo-mono:  grayscale(1) contrast(1.1) invert(1) brightness(1.6);
	--mark-ink:   #858585;
}

/* The Arabic page runs on its own two families — El Messiri for display,
   IBM Plex Sans Arabic for everything else. Rubik and the English serif are
   not loaded there at all. Arabic needs more leading and a slightly larger
   body size than Latin to stay comfortable. */
html[lang="ar"] {
	--font-display: var(--font-display-ar);
	--font-body:    var(--font-body-ar);
}
html[lang="ar"] body {
	font-size: clamp(17px, 1.2vw, 20px);
	line-height: 1.9;
}


/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

html {
	-webkit-text-size-adjust: 100%;
	scroll-behavior: smooth;
}

/* Lenis drives scrolling when it is active; let it own the scroll position. */
html.lenis { scroll-behavior: auto; }
html.lenis body { height: auto; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }

body {
	margin: 0;
	background: var(--bg);
	color: var(--body);
	font-family: var(--font-body);
	font-weight: 300;
	font-size: clamp(16px, 1.15vw, 20px);
	line-height: 1.7;
	-webkit-font-smoothing: antialiased;
	transition: background 0.7s var(--ease), color 0.7s var(--ease);
	overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

img { max-width: 100%; height: auto; display: block; }

/* Any rule that sets `display` beats the hidden attribute's own
   display:none, and several here do — .btn, for one. This says plainly
   that hidden means hidden, whatever else an element is. */
[hidden] { display: none !important; }

a {
	color: var(--accent-text);
	text-decoration: none;
	transition: color 0.4s var(--ease), opacity 0.4s var(--ease);
}
/* A link in running text underlines on hover, in the accent. Anything
   that is really a control — a button, a menu item, an icon — opts out. */
a:hover {
	text-decoration: underline;
	text-decoration-color: var(--accent);
	text-decoration-thickness: 2px;
	text-underline-offset: 0.22em;
}
.btn:hover,
.social-links a:hover,
.lang-switch:hover,
.icon-btn:hover,
.skip-link:hover,
.site-footer a:hover { text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4 {
	color: var(--ink);
	font-weight: 400;
	line-height: 1.25;
	margin: 0 0 0.6em;
}

p { margin: 0 0 1.4em; }
p:last-child { margin-bottom: 0; }

/* Keeps a single word from being left alone on the last line.
   Ignored by browsers that don't support it. */
p, .about-points li, .cta-list li, .cv-list p { text-wrap: pretty; }

::selection { background: var(--accent); color: var(--accent-ink); }

:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 3px;
	border-radius: 2px;
}

.visually-hidden {
	position: absolute;
	width: 1px; height: 1px;
	margin: -1px; padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.skip-link {
	position: absolute;
	inset-inline-start: var(--gutter);
	top: -100px;
	z-index: 200;
	background: var(--accent);
	color: var(--accent-ink);
	padding: 12px 22px;
	transition: top 0.3s var(--ease);
}
.skip-link:focus { top: 16px; text-decoration: none; }

.wrap {
	max-width: var(--wrap);
	margin-inline: auto;
	padding-inline: var(--gutter);
}


/* --------------------------------------------------------------------------
   3. Header
   -------------------------------------------------------------------------- */

.site-header {
	position: fixed;
	inset-block-start: 0;
	inset-inline: 0;
	z-index: 90;
	padding-block: 28px;
	transition: background 0.5s var(--ease), padding 0.5s var(--ease),
	            box-shadow 0.5s var(--ease);
}

.site-header.is-stuck {
	padding-block: 16px;
	background: color-mix(in srgb, var(--bg) 88%, transparent);
	backdrop-filter: blur(14px);
	box-shadow: 0 1px 0 var(--rule);
}

.site-header .wrap {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 20px;
}

.wordmark {
	font-family: var(--font-display);
	font-size: clamp(20px, 2vw, 28px);
	color: var(--ink);
	line-height: 1;
	white-space: nowrap;
}
.wordmark:hover { text-decoration: none; opacity: 0.7; }

html[lang="ar"] .wordmark { font-size: clamp(22px, 2.3vw, 32px); }

.header-tools {
	display: flex;
	align-items: center;
	gap: clamp(10px, 1.6vw, 22px);
}

.icon-btn {
	appearance: none;
	background: none;
	border: 0;
	padding: 8px;
	margin: 0;
	cursor: pointer;
	color: var(--ink);
	line-height: 0;
	border-radius: 50%;
	transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.icon-btn:hover { opacity: 0.6; }
.icon-btn svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.6; }

/* Sun / moon crossfade */
.icon-btn .i-sun { display: none; }
:root[data-theme="dark"] .icon-btn .i-sun  { display: block; }
:root[data-theme="dark"] .icon-btn .i-moon { display: none; }

.lang-switch {
	font-size: 14px;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ink);
	padding: 8px 12px;
	border: 1px solid var(--rule);
	border-radius: 100px;
	line-height: 1;
	font-weight: 400;
}
.lang-switch:hover {
	text-decoration: none;
	background: var(--ink);
	color: var(--bg);
	border-color: var(--ink);
}

/* Hamburger */
.menu-toggle { width: 38px; height: 38px; position: relative; }
.menu-toggle span {
	position: absolute;
	inset-inline: 9px;
	height: 1.5px;
	background: var(--ink);
	transition: transform 0.45s var(--ease), opacity 0.3s var(--ease);
}
.menu-toggle span:nth-child(1) { top: 13px; }
.menu-toggle span:nth-child(2) { top: 19px; }
.menu-toggle span:nth-child(3) { top: 25px; }
.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }


/* --------------------------------------------------------------------------
   4. Fullscreen menu
   -------------------------------------------------------------------------- */

.fullscreen-menu {
	position: fixed;
	inset: 0;
	z-index: 80;
	background: var(--bg-alt);
	display: grid;
	place-items: center;
	opacity: 0;
	visibility: hidden;
	transition: opacity 0.6s var(--ease), visibility 0.6s var(--ease);
}
.fullscreen-menu.is-open { opacity: 1; visibility: visible; }

.fullscreen-menu ul {
	list-style: none;
	margin: 0;
	padding: 0;
	text-align: center;
}
.fullscreen-menu li {
	opacity: 0;
	transform: translateY(26px);
	transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.fullscreen-menu.is-open li { opacity: 1; transform: none; }
.fullscreen-menu.is-open li:nth-child(1) { transition-delay: 0.10s; }
.fullscreen-menu.is-open li:nth-child(2) { transition-delay: 0.16s; }
.fullscreen-menu.is-open li:nth-child(3) { transition-delay: 0.22s; }
.fullscreen-menu.is-open li:nth-child(4) { transition-delay: 0.28s; }
.fullscreen-menu.is-open li:nth-child(5) { transition-delay: 0.34s; }

.fullscreen-menu a {
	font-family: var(--font-display);
	font-size: clamp(34px, 6vw, 68px);
	line-height: 1.5;
	color: var(--ink);
	display: block;
	padding: 2px 0;
}
.fullscreen-menu a:hover { text-decoration: none; color: var(--accent-text); }
.fullscreen-menu a[aria-current] {
	color: var(--accent-text);
	text-decoration: underline;
	text-decoration-color: var(--accent);
	text-decoration-thickness: 2px;
	text-underline-offset: 0.18em;
}

/* Arabic needs more leading at display sizes so marks and descenders
   on one line clear the line above. */
html[lang="ar"] .fullscreen-menu a { line-height: 1.85; }


/* --------------------------------------------------------------------------
   5. Sections & the big fading titles
   -------------------------------------------------------------------------- */

/* Each section carries the colour of its own shape, so anything inside
   that wants a tint — the expertise chips, for one — can just ask for it. */
#about       { --section-accent: var(--shape-teal); }
#resume      { --section-accent: var(--shape-yellow); }
#works       { --section-accent: var(--shape-violet); }
#articles    { --section-accent: var(--shape-teal); }
#posts       { --section-accent: var(--shape-yellow); }
#collaborate { --section-accent: var(--shape-orange); }

/* `clip`, not `hidden`. They clip identically, and they are not the same
   thing: `hidden` makes the section a scroll container, and `position:
   sticky` inside a scroll container sticks to that — which here means not
   sticking at all. `clip` clips without making one, so the decorative shape
   still cannot widen the page and the panel beside the projects can travel
   with the reader. (`clip-path` would also keep sticky alive, but it does
   not stop the shape adding to the document's width, which it did: 655px of
   scroll on a 375px phone.) */
.section {
	padding-block: clamp(70px, 10vw, 150px);
	position: relative;
	overflow: clip;
}
.section > .wrap { position: relative; z-index: 1; }

/* One quiet shape per section, at the outer edge and behind everything
   else. They sit in the gutter and hang off the page; the section clips
   what hangs over, so nothing here can widen the document. --sy is the
   scroll drift, written from main.js. */
.section-shape {
	position: absolute;
	z-index: 0;
	line-height: 0;
	pointer-events: none;
	--sy: 0px;
	translate: 0 var(--sy);
	will-change: translate;
	opacity: .9;
}
.section-shape svg { display: block; width: 100%; height: auto; }

.section-shape--about       { width: clamp(84px, 11vw, 190px); inset-block-start: 34%; inset-inline-end: -3%; }
.section-shape--resume      { width: clamp(96px, 13vw, 215px); inset-block-start: 30%; inset-inline-start: -4%; }
.section-shape--works       { width: clamp(90px, 12vw, 205px); inset-block-start: 26%; inset-inline-end: -2%; }
.section-shape--articles    { width: clamp(80px, 10vw, 175px); inset-block-end: 12%;  inset-inline-end: -3%; }
.section-shape--posts       { width: clamp(92px, 12vw, 200px); inset-block-start: 62%; inset-inline-start: -5%; }
.section-shape--collaborate { width: clamp(130px, 18vw, 300px); inset-block-end: -12%; inset-inline-start: -8%; }

/* Below the two-column breakpoint the text runs the full width of the
   page, so there is no margin left to sit in. The shapes drop into the
   band of padding at the foot of each section instead, where they are
   under nothing at all. */
@media (max-width: 860px) {
	.section-shape {
		width: clamp(74px, 23vw, 120px);
		inset-block-start: auto;
		inset-block-end: -16px;
	}
	.section-shape--about       { inset-inline-end: -10%; }
	.section-shape--resume      { inset-inline-start: -12%; }
	.section-shape--works       { inset-inline-end: -9%; }
	.section-shape--articles    { inset-inline-end: -11%; }
	.section-shape--posts       { inset-inline-start: -14%; }
	.section-shape--collaborate { width: clamp(110px, 32vw, 170px); inset-inline-start: -18%; inset-block-end: -8%; }
}

.section--alt { background: var(--bg-alt); }

/* The oversized serif heading whose tail fades out.
   A mask on the whole word — never per-letter, which would break
   Arabic letter joining. */
.section-title {
	font-family: var(--font-display);
	font-size: clamp(54px, 11vw, 150px);
	line-height: 1;
	color: var(--ink);
	margin: 0 0 clamp(30px, 5vw, 60px);
	white-space: nowrap;
	overflow: hidden;
	/* Hug the text so the fade falls across the word itself rather than
	   across empty space beside it. */
	width: fit-content;
	max-width: 100%;
}

/* Only the last quarter or so of the word fades, so every word in a
   multi-word title stays readable — and the mask is on the word alone,
   never on the h2, so the full stop after it keeps its full colour. */
html:not([lang="ar"]) .section-title .title-text {
	display: inline-block;
	-webkit-mask-image: linear-gradient(to right, #000 74%, transparent 100%);
	        mask-image: linear-gradient(to right, #000 74%, transparent 100%);
}
html:not([lang="ar"])[dir="rtl"] .section-title .title-text {
	-webkit-mask-image: linear-gradient(to left, #000 74%, transparent 100%);
	        mask-image: linear-gradient(to left, #000 74%, transparent 100%);
}

/* Arabic gets no fade at all: thinning the tail of a word damages the
   letterforms. Instead the whole title starts faint and fills with ink from
   right to left as the section arrives, driven by --fill from main.js. The
   gradient is painted on the text itself, so the string is never split. */
html[lang="ar"] .section-title {
	line-height: 1.35;
	padding-block-end: 0.08em;
	font-weight: 600;
	overflow: visible;
	white-space: normal;
	-webkit-mask-image: none;
	        mask-image: none;
	position: relative;
	--fill: 0%;
}

html[lang="ar"] .title-text {
	background-image: linear-gradient(
		to left,
		var(--ink) var(--fill),
		color-mix(in srgb, var(--ink) 18%, transparent) var(--fill));
	-webkit-background-clip: text;
	        background-clip: text;
	color: transparent;
}
/* Registered so the value is a real percentage rather than a raw token;
   main.js rewrites it every frame, so it needs no transition of its own. */
@property --fill {
	syntax: "<percentage>";
	inherits: true;
	initial-value: 0%;
}
/* If background-clip:text is unavailable the title simply stays solid ink
   rather than vanishing. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
	html[lang="ar"] .title-text { color: var(--ink); background-image: none; }
}

/* Decoration at display size, hidden from screen readers: Mo's own
   orange, not the reading one. */
.title-dot { color: var(--accent); }

/* The faint English word behind the Arabic one. Off unless site.json asks
   for it; it is decoration, so it is hidden from screen readers. */
.title-ghost {
	position: absolute;
	inset-block-start: 50%;
	inset-inline-start: 0;
	transform: translateY(-50%);
	font-family: "Sorts Mill Goudy", Georgia, serif;
	font-size: 1.5em;
	line-height: 1;
	color: var(--ink);
	opacity: 0.06;
	white-space: nowrap;
	pointer-events: none;
	z-index: -1;
}

/* Two-column frame: thin vertical rule with metadata outside it. */
.framed {
	display: grid;
	grid-template-columns: minmax(150px, 22%) 1fr;
	gap: clamp(24px, 4vw, 60px);
	align-items: start;
}
.framed > .framed-aside {
	text-align: end;
	padding-block: 10px;
	/* These labels carry a short sentence now, not two words, so they are set
	   small and quiet: a caption beside the column, not a second heading. */
	font-size: 0.8em;
	line-height: 1.6;
	color: var(--body);
}
/* English hangs the label off the rule it sits beside. Arabic cannot do the
   same: end-aligned there means left-aligned, so a label that wrapped left
   its second line hanging away from the column it belongs to, with ragged
   space on the reading edge. In Arabic it is aligned to the start instead —
   flush with the right-hand edge the eye begins from — and the two lines are
   evened out so the first is never left long and the second a single word. */
html[lang="ar"] .framed > .framed-aside {
	text-align: start;
	text-wrap: balance;
}
.framed > .framed-main {
	border-inline-start: 1px solid var(--rule);
	padding-inline-start: clamp(24px, 4vw, 60px);
	padding-block: 10px;
}

.lede { font-size: 1.05em; max-width: 56ch; }

/* How the work runs, start to finish: one line of stages. Set a little
   wider and looser than body copy so the arrows have room to breathe. */
.workflow {
	font-size: 0.85em;
	line-height: 2;
	max-width: 62ch;
	margin: 0;
	color: var(--body);
}


/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */

.hero {
	padding-block-start: clamp(118px, 14vh, 170px);
	padding-block-end: clamp(50px, 7vw, 90px);
	position: relative;
	overflow: hidden;
}

.hero-grid {
	display: grid;
	grid-template-columns: 1fr minmax(300px, 52%);
	gap: clamp(20px, 4vw, 50px);
	align-items: center;
}

/* Sized so three roles settle on two lines at desktop rather than three,
   without crowding the name below. */
.hero-role {
	font-size: clamp(14px, 1.3vw, 19px);
	text-transform: uppercase;
	/* Tighter than the rest of the small caps on the page: three roles of
	   spaced-out uppercase would otherwise run to three lines. */
	letter-spacing: 0.055em;
	line-height: 1.5;
	font-weight: 400;
	color: var(--body);
	margin: 0;
	/* Wide enough that three roles settle on two lines at desktop; the
	   grid column is the real constraint. */
	max-width: 34em;
}
html[lang="ar"] .hero-role {
	text-transform: none;
	letter-spacing: 0;
	font-size: clamp(15px, 1.4vw, 20px);
}

/* Each title stays in one piece; a line that has to wrap breaks at a
   bullet instead. The bullets are the accent colour. */
.role-part { white-space: nowrap; }
.role-sep  { padding-inline: 0.2em; color: var(--accent-text); }

/* Two ways of setting the three roles, chosen in data/site.json. Both
   share one rule: a bullet never ends a line.

   "stacked" — each role on its own line, no bullets at all. */
[data-role="stacked"] .hero-role {
	display: flex;
	flex-direction: column;
	align-items: start;
	gap: 0.12em;
}
[data-role="stacked"] .role-sep { display: none; }

/* "inline" — one line with bullets between the roles. It starts from the
   stacked setting and only goes to one line where the whole line actually
   fits the column, which on a desktop is Arabic but not English: the three
   English titles measure about 790px against a 512px column, and would
   need roughly 10px type to fit. Everything else keeps the stacked
   setting, so a bullet can never end up dangling at a line end. */
[data-role="inline"] .hero-role {
	display: flex;
	flex-direction: column;
	align-items: start;
	gap: 0.12em;
}
[data-role="inline"] .role-sep { display: none; }

@media (min-width: 1101px) {
	html[lang="ar"] [data-role="inline"] .hero-role {
		display: block;
		white-space: nowrap;
		font-size: clamp(15px, 1.35vw, 20px);
	}
	html[lang="ar"] [data-role="inline"] .role-sep { display: inline; }
}

.hero-name {
	font-family: var(--font-display);
	font-size: clamp(52px, 9.2vw, 118px);
	line-height: 0.95;
	color: var(--ink);
	margin: 0.08em 0 0.3em;
	font-weight: 400;
}
html[lang="ar"] .hero-name {
	line-height: 1.3;
	font-size: clamp(40px, 7.4vw, 96px);
}

.hero-intro {
	border-inline-start: 2px solid var(--accent);
	padding-inline-start: clamp(20px, 2.6vw, 40px);
	margin-inline-start: clamp(0px, 6vw, 100px);
	max-width: 460px;
	font-size: clamp(16px, 1.4vw, 21px);
	color: var(--body);
}
/* The tagline is the one line of prose in the hero: full body ink, not the
   quieter grey used for supporting text elsewhere. */
.hero-intro p { color: var(--ink); }

/* In Arabic the name above is a drawing that spans the whole column, so the
   tagline is given the same measure and starts at the same edge. It then
   settles into two balanced lines instead of three short ones. */
html[lang="ar"] .hero-intro {
	margin-inline-start: 0;
	max-width: min(100%, 620px);
}

/* Evens out the line lengths so the last line is never a single orphaned
   word. Browsers without support just wrap normally. */
.hero-intro p { text-wrap: balance; }

/* The second hero line: quieter than the tagline it follows. */
.hero-support {
	font-size: 0.82em;
	color: var(--body);
	margin-block-start: 0.7em;
}

/* Tighter than a section's button row — it sits inside the hero column. */
.btn-row--hero { margin-block-start: 26px; gap: 12px; }

.social-links {
	display: flex;
	gap: 18px;
	margin-block-start: 26px;
}
.social-links a {
	color: var(--ink);
	line-height: 0;
	padding: 4px;
	transition: color 0.4s var(--ease), transform 0.4s var(--ease);
}
.social-links a:hover { color: var(--accent-text); transform: translateY(-3px); }
.social-links svg { width: 20px; height: 20px; fill: currentColor; }

/* Facts row under the hero */
.hero-facts {
	list-style: none;
	margin: clamp(40px, 6vw, 72px) 0 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 20px;
}
.hero-facts li { font-size: clamp(14px, 1.1vw, 19px); }
.hero-facts li:nth-child(2) { text-align: center; }
.hero-facts li:nth-child(3) { text-align: end; }
/* The figure itself carries the colour; the words around it do not. */
.num { color: var(--accent-text); }

.hero-facts strong {
	display: block;
	color: var(--ink);
	font-weight: 500;
}

/* --- Portrait + geometric shapes ------------------------------------------ */

.hero-figure {
	position: relative;
	/* Roughly a quarter wider than it was, so the portrait holds its own
	   beside the name. It stays inside the content column: pushed into the
	   outer gutter it ran off the side of the screen. */
	width: 100%;
	max-width: 700px;
	margin: 0;
	margin-inline-start: auto;
}

/* The portraits and the shapes are one fixed picture, built with physical
   left/right on purpose: the composition should look identical in Arabic
   and English rather than mirroring with the text. */
.hero-stage {
	position: relative;
	aspect-ratio: 749 / 918;
	width: 100%;
	direction: ltr;
}

/* The cast: a box the size of the stage that clips at its floor, so the
   portrait can rise in from underneath at full opacity. */
.hero-cast {
	position: absolute;
	inset: 0;
	overflow: hidden;
	z-index: 5;
}

/* Two cut-outs stacked on one canvas. Both are fully opaque at every
   moment: the incoming one is wiped in on top of the outgoing one, which
   only drops away once it is completely hidden behind it. No opacity is
   ever in play between them, so the two poses are never both visible and
   neither is ever seen through. */
.hero-portrait {
	position: absolute;
	bottom: 0;
	left: 50%;
	/* --pdy carries him down with the shapes he stands in front of as the
	   hero scrolls away; if he stayed put while they sank, the cut edge of
	   the photo would come out from behind them. */
	--pdy: 0px;
	transform: translateX(-50%) translateY(var(--pdy));
	transition: transform 1s var(--ease);
	height: 100%;
	width: auto;
	max-width: none;
	z-index: 1;
	opacity: 0;
	/* The cut-out ends in a straight line at the waist, and the two front
	   domes leave a gap between them where that line would show. Softening
	   the last few per cent of the image means there is no edge to see,
	   whatever the width or the pose: he fades into the composition rather
	   than stopping in it. */
	-webkit-mask-image: linear-gradient(to bottom, #000 86%, rgba(0,0,0,0.5) 94%, transparent 99%);
	        mask-image: linear-gradient(to bottom, #000 86%, rgba(0,0,0,0.5) 94%, transparent 99%);
	/* ...and the image is cut off at the domes' baseline as well, so not
	   even a ghost of it can appear below the line they stand on. The depth
	   of that cut is a variable because each composition sets its own, and
	   the wipe has to finish on exactly the value in force. */
	--cut: 1%;
	clip-path: inset(0 0 var(--cut) 0);
}
.hero-portrait.is-active { opacity: 1; z-index: 2; }
/* the one being replaced: still solid, just underneath */
.hero-portrait.is-fading { z-index: 1; }
.hero-portrait.is-fading.is-wiping-out {
	animation: portrait-unwipe 0.7s cubic-bezier(.65, 0, .35, 1) forwards;
}

/* The change of pose: the new portrait is uncovered from the floor upwards
   over the old one, both of them opaque throughout. A cross-fade let the
   two poses show through each other half-way — a double exposure — which
   is what this replaces. The travelling edge is the only thing that moves;
   neither figure shifts, so the head stays exactly where it was. */
.hero-portrait.is-wiping { animation: portrait-wipe 0.7s cubic-bezier(.65, 0, .35, 1); }
/* The new pose starts at zero height sitting ON the cut line, not at the
   very bottom of the frame: the old pose is taken away from that same line
   upwards, so starting anywhere else leaves the two edges a percent or two
   apart and a sliver of background shows between them as they travel. */
@keyframes portrait-wipe {
	from { clip-path: inset(calc(100% - var(--cut)) 0 var(--cut) 0); }
	to   { clip-path: inset(0 0 var(--cut) 0); }
}
/* The old pose is taken away behind the same travelling edge, on the same
   curve. Without this it simply waited underneath, and wherever the new
   silhouette was the narrower of the two — a shoulder, the line of a
   jacket — the old one stood out around it for the length of the wipe. Now
   the edge divides the picture: below it the new pose, above it the old,
   and never any part of both. It holds its end state until the class comes
   off, so there is no frame in which it flashes back. */
@keyframes portrait-unwipe {
	from { clip-path: inset(0 0 var(--cut) 0); }
	to   { clip-path: inset(0 0 100% 0); }
}

/* He rises in from under the floor once the shapes have landed — position
   only, never opacity, so the orange disc is never seen through him. The
   cast is what hides him until he clears it. It animates `translate`
   rather than another transform, so the one-off entrance and the running
   drift stay out of each other's way, and it fills backwards rather than
   both ways: the moment it ends he goes back to taking his opacity from
   .is-active, which is what the crossfade needs. is-entering is a one-shot
   class, so a later change of portrait is a crossfade and not another
   rise. */
html.js .hero-stage:not(.is-in) .hero-portrait { translate: 0 101%; }
html.js .hero-stage.is-entering .hero-portrait.is-active {
	animation: portrait-rise 1.15s cubic-bezier(.22, 1, .36, 1) .45s backwards;
}
@keyframes portrait-rise {
	from { translate: 0 101%; }
	to   { translate: 0 0; }
}

.hero-stage .shape {
	position: absolute;
	bottom: 0;
	left: 0;
	line-height: 0;
	will-change: transform, opacity;
}

/* Layer 1 of 3 — the entrance. Each shape arrives small and low and
   springs very slightly past its resting size, one after the next. The
   delay is set per shape in the stagger block below. */
html.js .hero-stage .shape {
	opacity: 0;
	transform: translateY(22px) scale(0.84);
	transition: opacity .45s var(--ease), transform .85s cubic-bezier(.34, 1.42, .64, 1);
}
html.js .hero-stage.is-in .shape {
	opacity: 1;
	transform: none;
	transition-delay: var(--in-delay, 0ms);
}

/* Layer 2 — the mouse. A few pixels against the pointer, further for the
   shapes meant to read as nearer. Short transition so it follows the hand
   rather than trailing it. */
.hero-stage .shape-in {
	display: block;
	--mx: 0px; --my: 0px;
	transform: translate3d(var(--mx), var(--my), 0);
	transition: transform .4s cubic-bezier(.22, 1, .36, 1);
}

/* Layer 3 — the pose the shapes take when the portrait changes, and the
   drift as the hero scrolls away. Composed in one transform from four
   custom properties so neither effect can erase the other, and scaled
   from the foot of the shape so anything standing on the floor stays on
   it. */
.hero-stage .shape svg {
	--dx: 0px; --dy: 0px; --rot: 0deg; --sc: 1;
	transform: translate3d(var(--dx), var(--dy), 0) rotate(var(--rot)) scale(var(--sc));
	transform-origin: 50% 100%;
	transition: transform 1s var(--ease);
}

/* The stagger. The two that stand in front come first, because they are
   the ground he lands on — while they are still half transparent the cut
   edge of the photo shows straight through them. */
.hero-stage .shape-2, .hero-stage .shape-b4 { --in-delay:   0ms; }
.hero-stage .shape-3, .hero-stage .shape-b5 { --in-delay:  80ms; }
.hero-stage .shape-1, .hero-stage .shape-b1 { --in-delay: 160ms; }
.hero-stage .shape-4, .hero-stage .shape-b2 { --in-delay: 240ms; }
.hero-stage .shape-5, .hero-stage .shape-b3 { --in-delay: 320ms; }

/* display:block, not inline: an inline SVG sits on a text baseline and
   leaves a stray pixel or two of daylight under the shape, which is
   exactly where the bottom of the photo is supposed to be hidden. */
.hero-stage .shape svg { display: block; width: 100%; height: auto; }

/* Offsets are percentages of the stage, so the whole composition scales
   with the viewport. Shapes 1, 2 and 6 sit in FRONT of the portrait
   (z-index above 5) and cut it off around the waist — that is what makes
   Mo read as emerging from the shapes rather than standing beside them. */
/* The theme's own offsets, as percentages of the 749x918 stage so the
   whole composition scales with the viewport. The orange arc is the
   centrepiece behind the torso; the yellow and violet quarter-circles sit
   behind the shoulder; the teal and pale-blue domes come forward (z-index
   above the portrait's 5) and cut him off at the waist. */
.hero-stage .shape-1 { width: 100%;  bottom: 0;     left: 0;                 z-index: 2; }
.hero-stage .shape-4 { width: 16.2%; bottom: 29.4%; left: auto; right: 1.3%;  z-index: 3; }
.hero-stage .shape-5 { width: 31%;   bottom: 32.7%; left: auto; right: 6%;    z-index: 4; }
.hero-stage .shape-2 { width: 55.5%; bottom: 1%;    left: -10%;               z-index: 8; }
.hero-stage .shape-3 { width: 55.5%; bottom: 1%;    left: auto; right: -10%;  z-index: 9; }

/* Each portrait change nudges the composition, never enough to look like a
   slider changing slides.

   The two front domes are the exception: they never rotate, and they never
   move apart. Their flat bases are the line Mo stands on, so they only
   scale — and they scale from that base (transform-origin 50% 100%), which
   leaves both bases on exactly the same horizontal line. */
.hero-stage[data-pose="1"] .shape-1 svg { --rot:  2deg;  --sc: 1.02; }
.hero-stage[data-pose="1"] .shape-4 svg { --rot: -8deg;  --sc: 1.08; }
.hero-stage[data-pose="1"] .shape-5 svg { --rot:  6deg;  --sc: 1.05; }
.hero-stage[data-pose="1"] .shape-2 svg,
.hero-stage[data-pose="1"] .shape-3 svg { --rot: 0deg;   --sc: 1.03; }

/* A third pose, for a third portrait: it leans the other way from the
   second rather than returning to the first, so three changes in a row
   never repeat an arrangement. */
.hero-stage[data-pose="2"] .shape-1 svg { --rot: -3deg;  --sc: 1.05; }
.hero-stage[data-pose="2"] .shape-4 svg { --rot:  6deg;  --sc: 1.04; }
.hero-stage[data-pose="2"] .shape-5 svg { --rot: -9deg;  --sc: 1.10; }
.hero-stage[data-pose="2"] .shape-2 svg,
.hero-stage[data-pose="2"] .shape-3 svg { --rot: 0deg;   --sc: 1.05; }

/* --- The three hero compositions ------------------------------------------
   Set with "heroStyle" in data/site.json. The offsets above are shared by
   the first two; only the stacking order tells them apart. */

/* 1. Faithful Ober — the 2022 site: every shape sits behind the portrait,
      so he stands in front of the composition rather than inside it. */
/* With nothing in front of him, a softened edge turns the bottom of the
   photo into a ghost over the shapes. These two compositions put solid
   colour under the whole of the cut line instead, and end the photo on it. */
.hero-stage[data-hero="ober"] .hero-portrait,
.hero-stage[data-hero="bauhaus"] .hero-portrait {
	-webkit-mask-image: none;
	        mask-image: none;
	--cut: 2%;
}

.hero-stage[data-hero="ober"] .shape-2 { z-index: 3; }
.hero-stage[data-hero="ober"] .shape-3 { z-index: 4; }

/* 3. Bauhaus collage — its own set of shapes, so it needs its own offsets.

      The orange disc is the sun the head reads against: it is the largest
      thing on the stage and the only one that touches the top of it. The
      teal quarter and the pale arch come forward and overlap each other
      around the middle, which is what hides the bottom of the photo —
      between them they cover its whole width (it spans 3%-97% of the
      stage) with a few per cent to spare, and since both are anchored to
      the stage floor and only ever scale up from it, the pose can widen
      that overlap but never open a gap in it. The pair overshoot the floor
      by a pixel, because a stage height of 350.5px rounds their feet onto
      a different device row from the photo's and leaves a hairline. */
.hero-stage[data-hero="bauhaus"] .shape-b1 { width: 80%; bottom: 35%; left: 10%;              z-index: 1; }
.hero-stage[data-hero="bauhaus"] .shape-b2 { width: 24%; bottom: 72%; left: auto; right: 4%;  z-index: 3; }
.hero-stage[data-hero="bauhaus"] .shape-b3 { width: 6.5%; bottom: 62%; left: 1%;              z-index: 3; }
.hero-stage[data-hero="bauhaus"] .shape-b4 { width: 64%; bottom: -1px; left: auto; right: -3%; z-index: 7; }
.hero-stage[data-hero="bauhaus"] .shape-b5 { width: 50%; bottom: -1px; left: -5%;              z-index: 8; }

/* The two shapes that stand in front never rotate, for the same reason the
   domes in the other compositions do not: their feet are the line he
   stands on. */
.hero-stage[data-hero="bauhaus"][data-pose="1"] .shape-b1 svg { --rot: 0deg;  --sc: 1.03; }
.hero-stage[data-hero="bauhaus"][data-pose="1"] .shape-b2 svg { --rot: -7deg; --sc: 1.06; }
.hero-stage[data-hero="bauhaus"][data-pose="1"] .shape-b3 svg { --rot: 0deg;  --sc: 1.18; }
.hero-stage[data-hero="bauhaus"][data-pose="1"] .shape-b4 svg { --rot: 0deg;  --sc: 1.02; }
.hero-stage[data-hero="bauhaus"][data-pose="1"] .shape-b5 svg { --rot: 0deg;  --sc: 1.03; }

.hero-stage[data-hero="bauhaus"][data-pose="2"] .shape-b1 svg { --rot: 0deg;  --sc: 1.06; }
.hero-stage[data-hero="bauhaus"][data-pose="2"] .shape-b2 svg { --rot: 5deg;  --sc: 1.03; }
.hero-stage[data-hero="bauhaus"][data-pose="2"] .shape-b3 svg { --rot: 0deg;  --sc: 0.88; }
.hero-stage[data-hero="bauhaus"][data-pose="2"] .shape-b4 svg { --rot: 0deg;  --sc: 1.04; }
.hero-stage[data-hero="bauhaus"][data-pose="2"] .shape-b5 svg { --rot: 0deg;  --sc: 1.05; }


/* --------------------------------------------------------------------------
   7. Buttons
   -------------------------------------------------------------------------- */

.btn {
	display: inline-block;
	font-family: var(--font-body);
	font-size: 14px;
	font-weight: 500;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	padding: 16px 34px;
	border-radius: 100px;
	border: 1px solid var(--accent);
	color: var(--accent-text);
	background: none;
	cursor: pointer;
	transition: background 0.45s var(--ease), color 0.45s var(--ease);
}
.btn:hover { background: var(--accent); color: var(--accent-ink); text-decoration: none; }
.btn--solid { background: var(--accent); color: var(--accent-ink); }
.btn--solid:hover { background: var(--ink); border-color: var(--ink); }

html[lang="ar"] .btn { text-transform: none; letter-spacing: 0; font-size: 16px; }

.btn-row { display: flex; flex-wrap: wrap; gap: 14px; margin-block-start: 34px; }


/* --------------------------------------------------------------------------
   8. About
   -------------------------------------------------------------------------- */

.about-points { list-style: none; margin: 0; padding: 0; }
.about-points li {
	position: relative;
	padding-inline-start: 28px;
	margin-block-end: 1.1em;
}
.about-points li::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 0.62em;
	width: 8px; height: 8px;
	background: var(--accent);
}


/* --------------------------------------------------------------------------
   9. Resume — skills, services, timeline
   -------------------------------------------------------------------------- */

.block-title {
	font-size: clamp(18px, 1.6vw, 24px);
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--ink);
	font-weight: 500;
	margin: clamp(44px, 6vw, 70px) 0 28px;
}
html[lang="ar"] .block-title { letter-spacing: 0; text-transform: none; }

/* Areas of expertise: plain chips, no self-scoring */
.expertise {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
}
.expertise li {
	font-size: 0.8em;
	line-height: 1.5;
	color: var(--ink);
	border: 1px solid var(--rule);
	border-radius: 100px;
	padding: 8px 18px;
	transition: border-color 0.4s var(--ease), background 0.4s var(--ease), color 0.4s var(--ease);
}
.expertise li:hover {
	border-color: color-mix(in srgb, var(--section-accent, var(--accent)) 55%, transparent);
	background: color-mix(in srgb, var(--section-accent, var(--accent)) 18%, transparent);
	color: var(--ink);
}

/* Education / Experience columns */
.cv-columns {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: clamp(24px, 4vw, 56px);
}
.cv-list { list-style: none; margin: 0; padding: 0; }
.cv-list li { margin-block-end: 30px; }
.cv-date {
	font-size: 0.72em;
	letter-spacing: 0.08em;
	color: var(--accent-text);
	text-transform: uppercase;
	display: block;
	margin-block-end: 4px;
}
html[lang="ar"] .cv-date { letter-spacing: 0; text-transform: none; }
.cv-role { color: var(--ink); font-weight: 500; font-size: 1em; margin: 0 0 2px; }
.cv-org  { font-size: 0.85em; margin: 0 0 8px; }
/* A company's Latin name is not broken across lines. Split, the brackets
   around it come apart — and in an Arabic line the closing one is mirrored,
   so it reads as a second opening bracket at the start of the next line.
   Kept whole, the name simply moves down together. Measured at every width
   from 360 to 1920: the longest of them, "(Communication Legal
   Translation)", is at most 251px against a column no narrower than 253. */
.cv-org bdi { white-space: nowrap; }

/* A mark beside the entry it belongs to. ATHAR's is drawn for a dark
   ground, so it is given one: a small rounded badge that carries its own
   background with it instead of sitting on the page's. */
.cv-logo { margin: 0 0 10px; line-height: 0; }
.cv-logo img { height: 40px; width: auto; object-fit: contain; }
.cv-logo--badge img {
	height: 50px;
	width: 50px;
	/* The whole mark, not a square cut out of it: cropping to fill sliced
	   the company's name in half. The badge carries the artwork's own dark
	   ground, so the letters still read the way they were drawn. */
	object-fit: contain;
	background: #0E1527;
	padding: 4px;
	border-radius: 12px;
	box-shadow: 0 2px 12px -5px rgba(28, 37, 40, 0.55);
}
.cv-list p.cv-note { font-size: 0.8em; margin: 0; }

/* The run of titles held at one employer. A rule in the section colour
   marks it as a note about the entry above, not a new entry. */
.cv-prog {
	font-size: 0.78em;
	margin: 10px 0 0;
	padding-inline-start: 12px;
	border-inline-start: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
	color: var(--body);
}


/* --------------------------------------------------------------------------
   10. Works
   -------------------------------------------------------------------------- */

.filters {
	display: flex;
	flex-wrap: wrap;
	gap: clamp(14px, 2.4vw, 32px);
	margin-block-end: clamp(30px, 5vw, 56px);
	padding: 0;
	list-style: none;
}
.filter {
	appearance: none;
	background: none;
	border: 0;
	padding: 4px 0;
	font-family: inherit;
	font-size: 0.8em;
	font-weight: 500;
	color: var(--body);
	cursor: pointer;
	position: relative;
	transition: color 0.4s var(--ease);
}
.filter::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-end: 0;
	height: 1.5px;
	background: var(--accent);
	transform: scaleX(0);
	transition: transform 0.4s var(--ease);
}
.filter[aria-pressed="true"] { color: var(--ink); }
.filter[aria-pressed="true"]::after { transform: scaleX(1); }
.filter:hover { color: var(--ink); }

/* --------------------------------------------------------------------------
   10a. The panel beside the projects
   -------------------------------------------------------------------------- */

/* It stays with the reader for the length of the list rather than scrolling
   away after one line. `align-self: start` is what lets it: a grid item
   stretched to the full row height has nothing to be sticky inside. */
.framed--works > .works-aside {
	position: sticky;
	inset-block-start: clamp(96px, 12vh, 132px);
	align-self: start;
	text-align: start;
	padding-block: 0;
}
.works-panel { display: flex; flex-direction: column; align-items: flex-start; }

/* One shape from the hero's own set, answering the card being read. It
   changes shape by rounding different corners, which is a cheap thing for a
   browser to move between, and takes a new colour at the same time. Logical
   corners, so the quarter turns the right way round in Arabic. */
.works-shape {
	display: block;
	width: clamp(42px, 4.4vw, 62px);
	aspect-ratio: 1;
	background: var(--shape-orange);
	border-radius: 50%;
	transition: border-start-start-radius 0.6s var(--ease),
	            border-start-end-radius 0.6s var(--ease),
	            border-end-end-radius 0.6s var(--ease),
	            border-end-start-radius 0.6s var(--ease),
	            background-color 0.6s var(--ease);
}
.works-shape[data-shape="circle"]  { border-radius: 50%; }
.works-shape[data-shape="half"]    { border-radius: 100% 100% 0 0 / 100% 100% 0 0; }
.works-shape[data-shape="quarter"] {
	border-radius: 0;
	border-start-start-radius: 100%;
}

/* Where the reader is in the list. The figure is the loud part; the total is
   there to say how much is left, not to be read first. */
.works-count {
	font-family: var(--font-display);
	font-size: clamp(34px, 4.4vw, 58px);
	line-height: 1;
	color: var(--ink);
	margin: clamp(16px, 2vw, 24px) 0 0;
	display: flex;
	align-items: baseline;
	gap: 0.12em;
	font-variant-numeric: tabular-nums;
	/* The figures are Latin either way: without this the slash lands on the
	   wrong side of them on the Arabic page. */
	direction: ltr;
}
.works-count-now { transition: opacity 0.3s var(--ease); }
.works-count-of  { font-size: 0.5em; color: var(--body); opacity: 0.5; }
.works-count-all { font-size: 0.5em; color: var(--body); opacity: 0.7; }

.works-note {
	font-size: 0.8em;
	line-height: 1.6;
	color: var(--body);
	margin: 10px 0 clamp(22px, 3vw, 34px);
	text-wrap: balance;
}

/* Stacked, one per line, because the column is narrow and a wrapped tab
   reads as two. */
.framed--works .filters {
	flex-direction: column;
	align-items: flex-start;
	gap: clamp(9px, 1.2vw, 14px);
	margin-block-end: 0;
}
.framed--works .filter { text-align: start; }

.works { display: grid; gap: clamp(30px, 5vw, 70px); }

.work {
	display: grid;
	gap: clamp(20px, 4vw, 56px);
	align-items: center;
	background: none;
	border: 0;
	padding: 0;
	margin: 0;
	font: inherit;
	color: inherit;
	text-align: start;
	cursor: pointer;
}
.work[hidden] { display: none; }

.work--image { grid-template-columns: 1fr 1fr; }

/* A project with a cover but no description yet: it reads as a finished
   card, so nothing about it should invite a click. */
.work--static { cursor: default; }
.work--static:hover .work-media img { transform: none; }
.work--static:hover .work-title { color: var(--ink); }

/* A project without a cover image: a compact text row, title against a
   short description. Deliberate on its own, and a quiet companion to the
   image rows rather than a gap where a picture should be. */
.work--text {
	grid-template-columns: minmax(180px, 34%) 1fr;
	gap: clamp(14px, 3vw, 40px);
	align-items: start;
	border-block-start: 1px solid var(--rule);
	padding-block-start: clamp(18px, 2.4vw, 28px);
	transition: border-color 0.5s var(--ease);
}
.work--text:hover { border-block-start-color: var(--accent); }
.work--text .work-title { font-size: clamp(21px, 2vw, 29px); margin-block-end: 4px; }
.work--text .work-cats { color: var(--accent-text); margin-block-end: 0.5em; }
.work-line { font-size: 0.85em; margin: 0; max-width: 56ch; }
.works { gap: clamp(26px, 4vw, 56px); }

/* The cover takes the hero's shape language: three corners softened, and
   the fourth a quarter-circle on the corner facing away from the text, so
   it changes sides down the list with the rows. 30% of the width and 40% of
   the height are the same number of pixels on a 4:3 box, which is what
   makes that corner a true quarter circle rather than an ellipse.

   Logical corners throughout, so the whole thing mirrors in Arabic without
   a second set of rules. */
.work-media {
	overflow: hidden;
	background: var(--bg-alt);
	aspect-ratio: 4 / 3;
	--corner: 24px;
	--sweep: 30% 40%;
	border-radius: var(--corner);
	transition: border-start-start-radius 0.6s var(--ease),
	            border-start-end-radius 0.6s var(--ease),
	            border-end-end-radius 0.6s var(--ease),
	            border-end-start-radius 0.6s var(--ease);
}

/* Media at the inline-start side: the sweep is on that side. */
.work--image .work-media { border-start-start-radius: var(--sweep); }
/* Media at the inline-end side — the same selector that puts it there. */
.work--image:nth-of-type(even) .work-media {
	border-start-start-radius: var(--corner);
	border-start-end-radius: var(--sweep);
}

/* Looking at one sends the sweep down to the other end of the same outer
   edge. It never crosses to the side the text is on. Movement only where
   movement is wanted: with reduced motion the corner simply stays put,
   which is better than having it jump. */
@media (prefers-reduced-motion: no-preference) {
	.work--image:not(:nth-of-type(even)):hover .work-media,
	.work--image:not(:nth-of-type(even)):focus-within .work-media {
		border-start-start-radius: var(--corner);
		border-end-start-radius: var(--sweep);
	}
	.work--image:nth-of-type(even):hover .work-media,
	.work--image:nth-of-type(even):focus-within .work-media {
		border-start-end-radius: var(--corner);
		border-end-end-radius: var(--sweep);
	}
}
.work-media img {
	width: 100%; height: 100%;
	object-fit: cover;
	transition: transform 0.9s var(--ease), filter 0.6s var(--ease);
}
.work:hover .work-media img { transform: scale(1.05); }

.work-title {
	font-family: var(--font-display);
	font-size: clamp(24px, 2.6vw, 38px);
	color: var(--ink);
	margin: 0 0 10px;
	transition: color 0.4s var(--ease);
}
.work:hover .work-title { color: var(--accent-text); }
.work-sub {
	font-size: 0.8em;
	margin: -4px 0 10px;
	color: var(--body);
	letter-spacing: 0.02em;
}
.work-cats { font-size: 0.8em; margin: 0; }

/* Where the work was done. Deliberately plain: a fact about the setting,
   not a logo or a client credit. */
.work-context {
	font-size: 0.72em;
	letter-spacing: 0.02em;
	color: var(--body);
	margin: 8px 0 0;
}
.work-year {
	font-size: 0.75em;
	letter-spacing: 0.08em;
	color: var(--accent-text);
	margin: -2px 0 8px;
}
.work-context + .work-line { margin-block-start: 0.7em; }
.work-link {
	display: inline-block;
	font-size: 0.78em;
	color: var(--accent-text);
	margin-block-start: 12px;
}
.work-cta {
	font-size: 0.75em;
	color: var(--accent-text);
	margin-block-start: 14px;
	display: inline-block;
}

/* Alternate the image side down the list */
.work--image:nth-of-type(even) .work-media { order: 2; }

/* A project's own mark, under its title. Decorative — the name is set as
   text directly above it — so it is small, and it is allowed to keep its
   own colours, which is the whole reason it is there. */
.work-logo { margin: 12px 0 0; line-height: 0; }
/* Large enough to read the wordmark inside it, which at 44px it was not. */
.work-logo img { height: 64px; width: auto; max-width: 78%; object-fit: contain; }
/* Two drawings of the same mark, one per background; only ever one shown.
   Scoped to the marks that actually have both. Written as a bare
   .on-light/.on-dark pair it hid the one drawing a mark had whenever it had
   no second one — which is how the ECSSR icon vanished from the dark page
   altogether. */
.work-logo--themed .on-dark { display: none; }
:root[data-theme="dark"] .work-logo--themed .on-light { display: none; }
:root[data-theme="dark"] .work-logo--themed .on-dark { display: block; }
.work-logo--icon img {
	height: 56px;
	width: 56px;
	/* The same three-and-one corners as a cover, at the size of an icon, so
	   the two cards that carry a mark instead of a photograph are not the
	   only square things in the section. */
	border-radius: 13px;
	border-start-start-radius: 45%;
	object-fit: cover;
	box-shadow: 0 2px 10px -4px rgba(28, 37, 40, 0.5);
}
.work-logo + .work-cats { margin-block-start: 12px; }

/* --------------------------------------------------------------------------
   10b. The projects strip
   -------------------------------------------------------------------------- */

/* Marks of the projects worked on, under the work itself. They are a record
   of where the work was done — never a client list — so the heading says so
   and nothing in here is a link.

   The row is given room: it is the quietest thing on the page and the only
   thing that makes it read as deliberate rather than as leftovers is the
   space around it. */
.logo-strip {
	margin-block-start: clamp(52px, 7vw, 92px);
	padding-block: clamp(34px, 4.5vw, 58px) clamp(10px, 2vw, 24px);
	border-block-start: 1px solid var(--rule);
	text-align: center;
}
.logo-strip-title {
	font-family: inherit;
	font-size: 0.75em;
	font-weight: 400;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--body);
	margin: 0 0 clamp(30px, 4vw, 50px);
}
html[lang="ar"] .logo-strip-title { letter-spacing: 0; text-transform: none; font-size: 0.8em; }

.logo-strip ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: clamp(34px, 5.5vw, 80px);
}
.logo-strip li {
	border-radius: 12px;
	padding: 8px 12px;
	transition: background 0.4s var(--ease);
}

/* --mark-h is written per mark by the template: the height that puts this
   mark's area where a square one's would be, so a very wide wordmark and a
   very tall one read as the same size. --mark-unit is the one place the
   whole row is scaled. */
.logo-strip img {
	--mark-unit: 1px;
	display: block;
	height: calc(var(--mark-h, 50) * var(--mark-unit));
	width: auto;
	object-fit: contain;
	opacity: 0.88;
	filter: var(--logo-mono);
	transition: opacity 0.4s var(--ease), filter 0.4s var(--ease);
}

/* A mark drawn in one flat colour is painted through its own shape rather
   than shown as a picture. That lets it be the same quiet grey as every
   other mark in the row — the ink and the shared filter, exactly as its
   neighbours get them — and take a colour of its own only on the way past,
   which a picture of a fixed colour could not do on both backgrounds. */
.logo-strip .mark--masked {
	--mark-unit: 1px;
	display: block;
	height: calc(var(--mark-h, 50) * var(--mark-unit));
	aspect-ratio: var(--mark-ratio, 1);
	background: var(--mark-ink);
	-webkit-mask: var(--mask) center / contain no-repeat;
	        mask: var(--mask) center / contain no-repeat;
	transition: background-color 0.4s var(--ease);
}
@media (hover: hover) {
	.logo-strip li:hover .mark--masked { background: var(--mark-hover, var(--shape-violet)); }
}
.logo-strip li:focus-within .mark--masked { background: var(--mark-hover, var(--shape-violet)); }
/* It carries its own colour on hover, so it needs no plate behind it. */
:root[data-theme="dark"] .logo-strip li:has(.mark--masked):hover,
:root[data-theme="dark"] .logo-strip li:has(.mark--masked):focus-within { background: none; }

@media (hover: hover) {
	.logo-strip li:hover img { opacity: 1; filter: none; }
}
.logo-strip li:focus-within img { opacity: 1; filter: none; }
/* In dark mode every other mark is dark artwork, so showing its own colours
   needs something to show them against: the plate appears only for the mark
   being looked at, and only there. */
:root[data-theme="dark"] .logo-strip li:focus-within { background: #F7F5F2; }
@media (hover: hover) {
	:root[data-theme="dark"] .logo-strip li:hover { background: #F7F5F2; }
}

/* --------------------------------------------------------------------------
   10c. Milestones — glass bubbles

   Memories rather than records: photographs with no names, no years and
   nothing to open, drifting round the title in the hero's own shapes and
   colours. Every position and size is a percentage of the stage, and the
   stage keeps one aspect ratio, so the whole cluster scales together and
   the clearances between bubbles hold at any width.
   -------------------------------------------------------------------------- */

.milestones-section { padding-block: clamp(50px, 8vw, 110px); }

.bubbles {
	position: relative;
	aspect-ratio: 1120 / 760;
	max-width: 1120px;
	margin-inline: auto;
}

/* The title holds the middle; the bubbles are solved to stay off it. */
.bubbles-heart {
	position: absolute;
	/* Physical, not logical. Centring has no direction, and the logical
	   form was wrong in Arabic: `inset-inline: 50%` sets both edges, and an
	   over-constrained box in a right-to-left context drops `left` and
	   keeps `right`, so the half-width translate then pulled it a whole
	   width off centre and the bubbles sat on the word. */
	left: 50%;
	top: 50%;
	translate: -50% -50%;
	width: min(500px, 76%);
	text-align: center;
	z-index: 3;
}
/* Smaller than a section title elsewhere, and deliberately so: here it has
   to live inside a space the bubbles are solved around, and "Milestones" is
   a long word that cannot wrap. At the usual size its ink ran 699px wide in
   a 1120px stage, which left nowhere for eight bubbles to stand. */
.bubbles-heart .section-title {
	margin: 0 auto;
	white-space: normal;
	font-size: clamp(34px, 5.4vw, 78px);
	/* Elsewhere the heading hugs its text and sits at the start of the
	   column, which is right there and wrong here: `width: fit-content` with
	   no auto margins left the word off the stage's centre line while the
	   sentence under it was on it — 128px apart in Arabic. */
	overflow: visible;
}

/* The full stop is decoration, and decoration should not drag the word it
   follows off centre. A zero-width box still paints its glyph, so the stop
   hangs past the end of the word — the far side of it in Arabic, which is
   where it already sat — and the word itself is what gets centred. */
.bubbles-heart .title-dot {
	display: inline-block;
	width: 0;
}
.bubbles-line {
	font-size: 0.85em;
	color: var(--body);
	margin: clamp(8px, 1.2vw, 16px) 0 0;
}

.bubbles-field { list-style: none; margin: 0; padding: 0; }

.bubble {
	position: absolute;
	inset-inline-start: var(--x);
	inset-block-start: var(--y);
	width: var(--s);
	aspect-ratio: 1;
	/* Three things move a bubble and none of them may overwrite another:
	   `margin` centres it on its point, `translate` carries the pointer and
	   the scroll, `transform` carries the float, `scale` carries the lift. */
	/* Both margins are a percentage of the stage's WIDTH — that is what a
	   percentage margin resolves against, whichever edge it is on — and the
	   bubble is square, so half its width is half its height. */
	margin-inline-start: calc(var(--s) / -2);
	margin-block-start: calc(var(--s) / -2);
	translate: var(--px, 0px) var(--py, 0px);
	scale: 1;
	transition: scale 0.5s var(--ease), translate 0.2s linear;
	animation: bubble-float var(--float, 9s) var(--delay, 0s) ease-in-out infinite;
}
@keyframes bubble-float {
	0%,  100% { transform: translate(0, 0) rotate(0deg); }
	25%       { transform: translate(2%, -3.2%) rotate(0.6deg); }
	50%       { transform: translate(-1.4%, -5%) rotate(-0.5deg); }
	75%       { transform: translate(-2.4%, -2%) rotate(0.3deg); }
}

/* The three shapes, all drawn whole and never flipped. The quarter and the
   blob use logical corners so they turn the right way round in Arabic. */
.bubble--circle .bubble-media,
.bubble--circle .bubble-rim,
.bubble--circle .bubble-glow { border-radius: 50%; }

.bubble--quarter .bubble-media,
.bubble--quarter .bubble-rim,
.bubble--quarter .bubble-glow {
	border-radius: 0;
	border-end-end-radius: 100%;
}
/* A straight edge does not catch light the way a curved one does, so the
   glass read as a hairline on this one and it looked like a photograph that
   had been cropped rather than a bubble. Its rim is drawn harder and its
   glow sits closer and brighter. */
.bubble--quarter .bubble-rim {
	box-shadow:
		inset 0 2px 1px -1px rgba(255, 255, 255, 0.85),
		inset 0 0 0 1.5px rgba(255, 255, 255, 0.42),
		inset 0 -14px 22px -14px rgba(28, 37, 40, 0.42),
		0 0 0 1.5px color-mix(in srgb, var(--glow) 62%, transparent),
		0 20px 40px -20px color-mix(in srgb, var(--glow) 80%, transparent);
}
:root[data-theme="dark"] .bubble--quarter .bubble-rim {
	box-shadow:
		inset 0 2px 1px -1px rgba(255, 255, 255, 0.5),
		inset 0 0 0 1.5px rgba(255, 255, 255, 0.24),
		inset 0 -14px 22px -14px rgba(0, 0, 0, 0.55),
		0 0 0 1.5px color-mix(in srgb, var(--glow) 70%, transparent),
		0 20px 44px -18px color-mix(in srgb, var(--glow) 60%, transparent);
}
.bubble--quarter .bubble-glow { inset: -11%; opacity: 0.95; }

.bubble--blob .bubble-media,
.bubble--blob .bubble-rim,
.bubble--blob .bubble-glow {
	/* Looser than a circle on purpose — at 58/42 it was still reading as
	   one. The two axes pull in different directions, which is what keeps
	   it from looking like a squashed oval. */
	border-radius: 64% 36% 52% 48% / 38% 58% 42% 62%;
}

/* The light a bubble sits in. Outside the glass, behind everything. */
.bubble-glow {
	position: absolute;
	inset: -16%;
	background: radial-gradient(circle at 50% 42%,
		color-mix(in srgb, var(--glow) 55%, transparent),
		color-mix(in srgb, var(--glow) 12%, transparent) 58%, transparent 72%);
	filter: blur(16px);
	opacity: 0.75;
	transition: opacity 0.5s var(--ease);
	z-index: 0;
}

.bubble-media {
	position: absolute;
	inset: 0;
	overflow: hidden;
	z-index: 1;
}
/* Softened: the colour taken down, the contrast eased, then pulled toward
   the colour the bubble glows in — a memory, not a document. */
.bubble-media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	filter: saturate(0.42) contrast(0.88) brightness(1.06);
	transition: filter 0.5s var(--ease), opacity 0.9s var(--ease);
}
.bubble-tint {
	position: absolute;
	inset: 0;
	background: var(--glow);
	mix-blend-mode: color;
	opacity: 0.3;
	/* The photograph is filtered, so it makes a stacking context of its own
	   and would otherwise paint over this. */
	z-index: 2;
	transition: opacity 0.5s var(--ease);
}

/* The glass itself: a lit edge at the top, a thin rim all the way round, a
   tinted ring just outside it and the shadow it casts. */
.bubble-rim {
	position: absolute;
	inset: 0;
	z-index: 3;
	pointer-events: none;
	box-shadow:
		inset 0 2px 1px -1px rgba(255, 255, 255, 0.75),
		inset 0 0 0 1px rgba(255, 255, 255, 0.3),
		inset 0 -10px 18px -14px rgba(28, 37, 40, 0.5),
		0 0 0 1px color-mix(in srgb, var(--glow) 34%, transparent),
		0 22px 44px -22px color-mix(in srgb, var(--glow) 65%, transparent);
	transition: box-shadow 0.5s var(--ease);
}
:root[data-theme="dark"] .bubble-rim {
	box-shadow:
		inset 0 2px 1px -1px rgba(255, 255, 255, 0.45),
		inset 0 0 0 1px rgba(255, 255, 255, 0.16),
		inset 0 -10px 18px -14px rgba(0, 0, 0, 0.6),
		0 0 0 1px color-mix(in srgb, var(--glow) 42%, transparent),
		0 22px 48px -20px color-mix(in srgb, var(--glow) 45%, transparent);
}

/* Looking at one brings it a little closer and a little clearer. */
@media (hover: hover) {
	.bubble:hover { scale: 1.045; z-index: 4; }
	.bubble:hover .bubble-media img { filter: saturate(0.85) contrast(0.96) brightness(1.02); }
	.bubble:hover .bubble-tint { opacity: 0.16; }
	.bubble:hover .bubble-glow { opacity: 1; }
}
.bubble:focus-within { scale: 1.045; z-index: 4; }
.bubble:focus-within .bubble-media img { filter: saturate(0.85) contrast(0.96) brightness(1.02); }
.bubble:focus-within .bubble-tint { opacity: 0.16; }

/* The photograph being replaced, under the one arriving. */
.bubble-media img.is-leaving { position: absolute; inset: 0; opacity: 0; }
.bubble-media img.is-arriving { opacity: 0; }

/* Loose marks, as in the hero: not photographs, not bubbles, the air
   between them. */
.mote {
	position: absolute;
	border-radius: 50%;
	pointer-events: none;
	z-index: 0;
	animation: bubble-float 13s ease-in-out infinite;
}
.mote--1 { width: 14px; aspect-ratio: 1; background: var(--shape-yellow); inset-inline-start: 7%;  inset-block-start: 14%; }
.mote--2 { width: 9px;  aspect-ratio: 1; background: var(--shape-orange); inset-inline-start: 93%; inset-block-start: 36%; animation-delay: -4s; }
.mote--3 { width: 46px; aspect-ratio: 1; border: 1.5px solid color-mix(in srgb, var(--shape-violet) 55%, transparent); background: none; inset-inline-start: 40%; inset-block-start: 3%; animation-delay: -7s; }
.mote--4 { width: 28px; aspect-ratio: 1; border: 1.5px solid color-mix(in srgb, var(--shape-teal) 55%, transparent); background: none; inset-inline-start: 62%; inset-block-start: 93%; animation-delay: -2s; }

/* --------------------------------------------------------------------------
   10d. Articles and posts

   The reading page is the one place on this site where the type has a job
   other than looking well: somebody has to get to the end of it. So the
   measure is short, the leading is long, and Arabic gets more of both than
   English because its letterforms need the room.
   -------------------------------------------------------------------------- */

/* The same three-soft-corners-and-a-sweep as the project covers, so a
   picture inside an article belongs to the same page as one beside a
   project. The sweep is given per picture, because the two numbers are
   only equal in pixels — a true quarter circle — at one aspect ratio. */
.curved {
	--corner: 24px;
	overflow: hidden;
	border-radius: var(--corner);
	border-start-start-radius: var(--sweep, 30% 40%);
}

/* --- the reading page ----------------------------------------------------- */

/* These pages open at the top of the document rather than below a hero, so
   they have to make their own room for the fixed header — at a phone's
   width the section's own padding is about half what the header needs. */
.article,
.article-index { padding-block-start: clamp(116px, 15vh, 170px); }

.article > .wrap,
.article-index > .wrap { max-width: 980px; }

.article-kicker {
	font-size: 0.78em;
	letter-spacing: 0.02em;
	margin: 0 0 clamp(14px, 2vw, 22px);
}
.article-kicker a { color: var(--accent-text); }

.article-title {
	font-family: var(--font-display);
	font-size: clamp(31px, 4.6vw, 58px);
	line-height: 1.18;
	color: var(--ink);
	margin: 0;
	max-width: 22ch;
}
html[lang="ar"] .article-title { line-height: 1.4; font-weight: 600; }
.article-title--index { max-width: none; }

.article-date {
	font-size: 0.78em;
	letter-spacing: 0.06em;
	color: var(--accent-text);
	margin: clamp(12px, 1.6vw, 18px) 0 0;
}
.article-summary {
	font-size: 1.05em;
	line-height: 1.75;
	color: var(--body);
	max-width: 56ch;
	margin: clamp(14px, 2vw, 22px) 0 0;
}

.article-cover {
	margin: clamp(30px, 4.5vw, 54px) 0 0;
	--corner: 24px;
	--sweep: 22.5% 40%;   /* equal in pixels on a 16:9 picture */
	overflow: hidden;
	border-radius: var(--corner);
	border-start-start-radius: var(--sweep);
	background: var(--bg-alt);
}
.article-cover img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; }

/* --- the article itself --------------------------------------------------- */

.prose {
	margin-block-start: clamp(34px, 5vw, 60px);
	/* Short enough that the eye finds the next line without hunting. */
	max-width: 68ch;
	font-size: 1.02em;
	line-height: 1.8;
	color: var(--body);
}
html[lang="ar"] .prose { line-height: 1.95; font-size: 1.05em; }

.prose > :first-child { margin-block-start: 0; }
.prose p { margin: 0 0 1.35em; }
.prose h2,
.prose h3,
.prose h4 {
	font-family: var(--font-display);
	color: var(--ink);
	line-height: 1.3;
	margin: 1.9em 0 0.6em;
}
html[lang="ar"] .prose h2,
html[lang="ar"] .prose h3,
html[lang="ar"] .prose h4 { line-height: 1.5; font-weight: 600; }
.prose h2 { font-size: clamp(23px, 2.6vw, 33px); }
.prose h3 { font-size: clamp(20px, 2.1vw, 26px); }
.prose h4 { font-size: 1.05em; }

.prose a {
	color: var(--accent-text);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.22em;
	transition: color 0.3s var(--ease);
}
.prose a:hover { color: var(--ink); }

.prose ul,
.prose ol { margin: 0 0 1.35em; padding-inline-start: 1.5em; }
.prose li { margin-block-end: 0.55em; }
.prose ul { list-style: disc; }
.prose ol { list-style: decimal; }

.prose blockquote {
	margin: 1.9em 0;
	padding-inline-start: clamp(14px, 1.8vw, 22px);
	border-inline-start: 2px solid var(--accent);
	color: var(--ink);
	font-size: 1.05em;
}
.prose blockquote p { margin: 0; }

.prose hr {
	border: 0;
	border-block-start: 1px solid var(--rule);
	margin: 2.4em 0;
}

/* Code is written left to right whatever the page is, and it is the one
   place a line may run off the side rather than wrap. */
.prose code {
	font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
	font-size: 0.86em;
	direction: ltr;
	unicode-bidi: isolate;
	background: color-mix(in srgb, var(--ink) 8%, transparent);
	padding: 0.14em 0.4em;
	border-radius: 5px;
}
.prose pre {
	direction: ltr;
	text-align: left;
	overflow-x: auto;
	background: color-mix(in srgb, var(--ink) 7%, transparent);
	padding: clamp(14px, 2vw, 22px);
	border-radius: 14px;
	margin: 1.7em 0;
	line-height: 1.6;
}
.prose pre code { background: none; padding: 0; font-size: 0.84em; }

.prose-figure { margin: 1.9em 0; }
.prose-figure img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 20px;
	border-start-start-radius: 22% 32%;
}
.prose-figure figcaption {
	font-size: 0.78em;
	color: var(--body);
	margin-block-start: 10px;
}

.article-foot { margin-block-start: clamp(40px, 6vw, 72px); }

/* --- a list of articles ---------------------------------------------------
   Used twice: the three newest on the home page and the whole list on its
   own page. One row, one link — a title and a separate "read more" would
   be two targets for one thing. */

.article-list {
	list-style: none;
	margin: clamp(26px, 4vw, 44px) 0 0;
	padding: 0;
	display: grid;
	gap: clamp(22px, 3vw, 36px);
}
.article-card a {
	display: grid;
	grid-template-columns: 260px 1fr;
	gap: clamp(18px, 2.6vw, 32px);
	align-items: start;
	color: inherit;
}
.article-card:not(:has(.article-card-media)) a { grid-template-columns: 1fr; }

.article-card-media {
	display: block;
	--corner: 16px;
	--sweep: 22.5% 40%;   /* equal in pixels on a 16:9 picture */
	overflow: hidden;
	border-radius: var(--corner);
	border-start-start-radius: var(--sweep);
	background: var(--bg-alt);
}
.article-card-media img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; }

.article-card-text { display: block; }
.article-card-date {
	display: block;
	font-size: 0.74em;
	letter-spacing: 0.06em;
	color: var(--accent-text);
	margin-block-end: 8px;
}
.article-card-title {
	display: block;
	font-family: var(--font-display);
	font-size: clamp(21px, 2.1vw, 29px);
	line-height: 1.25;
	color: var(--ink);
	transition: color 0.35s var(--ease);
}
html[lang="ar"] .article-card-title { line-height: 1.45; }
.article-card-summary {
	display: block;
	font-size: 0.86em;
	line-height: 1.7;
	color: var(--body);
	margin-block-start: 8px;
	max-width: 62ch;
}
@media (hover: hover) {
	.article-card a:hover .article-card-title { color: var(--accent-text); }
}
.article-card a:focus-visible .article-card-title { color: var(--accent-text); }

.article-index .article-list { margin-block-start: clamp(34px, 5vw, 56px); }

/* --- posts ----------------------------------------------------------------
   Highlights from elsewhere. Each card is Mo's own line and a link out;
   nothing here is an embed, so nobody else's script runs on this page. */

.post-cards {
	list-style: none;
	margin: clamp(26px, 4vw, 44px) 0 0;
	padding: 0;
	display: grid;
	/* Capped rather than 1fr: with a single post, `1fr` stretched one card
	   across the whole column and a tall picture inside it became a wall. */
	grid-template-columns: repeat(auto-fit, minmax(230px, 330px));
	justify-content: start;
	gap: clamp(18px, 2.4vw, 30px);
}
.post-card a {
	display: flex;
	flex-direction: column;
	height: 100%;
	color: inherit;
	background: var(--card);
	border: 1px solid var(--rule);
	border-radius: 18px;
	border-start-start-radius: 34px;
	overflow: hidden;
	padding: 0 0 clamp(16px, 2vw, 22px);
	transition: border-color 0.35s var(--ease), transform 0.35s var(--ease);
}
@media (hover: hover) {
	.post-card a:hover { border-color: var(--accent); transform: translateY(-3px); }
}
.post-card a:focus-visible { border-color: var(--accent); }

/* The whole picture, not a band cut from its middle — but not unbounded
   either. A tall photograph is scaled to fit the height below and sits on
   the card's own colour; nothing is cropped, and no single card runs away
   down the page. */
.post-media {
	display: block;
	margin-block-end: clamp(12px, 1.6vw, 18px);
	background: var(--bg-alt);
}
.post-media img {
	display: block;
	width: 100%;
	height: auto;
	max-height: 300px;
	object-fit: contain;
}

.post-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: clamp(16px, 2vw, 22px) clamp(16px, 2vw, 22px) 0;
}
.post-media + .post-head { padding-block-start: 0; }
.post-platform { display: inline-flex; width: 15px; height: 15px; color: var(--ink); }
.post-platform svg { width: 100%; height: 100%; fill: currentColor; }
.post-where { font-size: 0.74em; letter-spacing: 0.04em; color: var(--ink); }
.post-date { font-size: 0.72em; color: var(--body); margin-inline-start: auto; }

.post-excerpt {
	display: block;
	font-size: 0.88em;
	line-height: 1.75;
	color: var(--body);
	padding: clamp(10px, 1.4vw, 14px) clamp(16px, 2vw, 22px) 0;
}
.post-go {
	display: block;
	font-size: 0.76em;
	color: var(--accent-text);
	padding: clamp(12px, 1.6vw, 16px) clamp(16px, 2vw, 22px) 0;
	margin-block-start: auto;
}

@media (max-width: 860px) {
	.article-card a { grid-template-columns: 128px 1fr; gap: 16px; }
	.article-card-summary { display: none; }
	.article-index .article-card-summary { display: block; }
}

/* --------------------------------------------------------------------------
   11. Work with me + contact
   -------------------------------------------------------------------------- */

.cta-panel {
	background: var(--card);
	padding: clamp(30px, 5vw, 60px);
	box-shadow: var(--shadow);
}
.cta-panel h3 {
	font-family: var(--font-display);
	font-size: clamp(26px, 3.4vw, 44px);
	margin-block-start: 0;
}
.cta-list {
	list-style: none;
	margin: 26px 0 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: 14px 26px;
}
.cta-list li {
	position: relative;
	padding-inline-start: 24px;
	font-size: 0.85em;
}
.cta-list li::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 0.6em;
	width: 7px; height: 7px;
	background: var(--accent);
}

/* The invitation panel and the ways to answer it are one section, so the
   form follows the panel directly rather than opening a second one. */
.contact-grid {
	display: grid;
	grid-template-columns: minmax(210px, 32%) 1fr;
	gap: clamp(28px, 5vw, 70px);
	align-items: start;
	margin-block-start: clamp(34px, 5vw, 64px);
}

/* In a narrow column the buttons stack and share a width, so their edges
   line up with the form beside them. */
.btn-row--stack { flex-direction: column; align-items: stretch; }
.btn-row--stack .btn { text-align: center; }

.contact-points { list-style: none; margin: 0; padding: 0; }
.contact-points li { margin-block-end: 30px; }
.contact-icon {
	width: 38px; height: 38px;
	display: grid;
	place-items: center;
	background: color-mix(in srgb, var(--accent) 16%, transparent);
	margin-block-end: 12px;
}
.contact-icon svg { width: 18px; height: 18px; stroke: var(--accent); fill: none; stroke-width: 1.6; }
.contact-label { font-size: 0.72em; letter-spacing: 0.08em; text-transform: uppercase; display: block; }
html[lang="ar"] .contact-label { letter-spacing: 0; text-transform: none; }
.contact-value { color: var(--ink); font-weight: 400; }

.field { margin-block-end: 22px; }
.field label {
	display: block;
	color: var(--ink);
	font-size: 0.8em;
	font-weight: 500;
	margin-block-end: 8px;
}
.field input,
.field textarea {
	width: 100%;
	font-family: inherit;
	font-size: 0.9em;
	font-weight: 300;
	color: var(--ink);
	background: var(--field);
	border: 1px solid var(--rule);
	padding: 14px 18px;
	transition: border-color 0.4s var(--ease);
}
.field input:focus,
.field textarea:focus { border-color: var(--accent); outline: none; }
.field textarea { min-height: 150px; resize: vertical; }
/* Honeypot. Clipped rather than pushed off-screen: a negative offset
   overflows the inline axis, which in RTL drags the whole document
   out of view. */
.field--hp {
	position: absolute;
	width: 1px; height: 1px;
	margin: -1px; padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.form-note { font-size: 0.75em; margin-block-start: 16px; opacity: 0.8; }

.form-success {
	background: color-mix(in srgb, var(--accent) 14%, transparent);
	border-inline-start: 3px solid var(--accent);
	color: var(--ink);
	padding: 14px 18px;
	font-size: 0.85em;
	margin-block-end: 22px;
}

/* The answer after a submission, in place of the form and in the language
   the visitor was reading. It takes focus and announces itself, and Escape
   or the close button brings the form back with what was typed still in
   it — nothing is cleared unless the message actually got through. */
.form-result {
	border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
	background: color-mix(in srgb, var(--accent) 10%, transparent);
	border-radius: 18px;
	padding: clamp(26px, 4vw, 40px);
}
.form-result:focus { outline: 2px solid var(--accent); outline-offset: 4px; }
.form-result:focus:not(:focus-visible) { outline: none; }
.form-result.is-error {
	border-color: color-mix(in srgb, var(--shape-violet) 45%, transparent);
	background: color-mix(in srgb, var(--shape-violet) 10%, transparent);
}
.form-result-text {
	margin: 0;
	color: var(--ink);
	font-size: clamp(16px, 1.3vw, 19px);
	line-height: 1.7;
}
.form-result-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-block-start: 24px;
}

.btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.btn[disabled]:hover { background: none; color: var(--accent-text); }


/* --------------------------------------------------------------------------
   12. Footer
   -------------------------------------------------------------------------- */

.site-footer {
	padding-block: clamp(34px, 4vw, 52px);
	border-block-start: 1px solid var(--rule);
	font-size: 0.8em;
}
/* The mark leads, the line of copyright follows it. On a phone they stack. */
.site-footer .wrap {
	display: flex;
	flex-wrap: wrap;
	gap: 18px 28px;
	justify-content: space-between;
	align-items: center;
}
.site-footer .mark--footer { flex: 0 1 auto; }


/* --------------------------------------------------------------------------
   13. Custom cursor (pointer devices only — see main.js)
   -------------------------------------------------------------------------- */

/* Physical left/top, not inset-inline-start: on the Arabic page the logical
   property resolves to `right`, and the translate then pushes the cursor
   off the side of the screen — which is why it was invisible in Arabic.
   direction:ltr keeps the label inside the ring laid out predictably. */
.cursor-dot,
.cursor-ring {
	position: fixed;
	left: 0;
	top: 0;
	direction: ltr;
	border-radius: 999px;
	pointer-events: none;
	z-index: 999;
	opacity: 0;
	transition: opacity 0.3s var(--ease);
}
.cursor-dot {
	width: 7px; height: 7px;
	background: var(--accent);
	margin: -3.5px 0 0 -3.5px;
}
.cursor-ring {
	width: 40px; height: 40px;
	margin: -20px 0 0 -20px;
	border: 1px solid var(--accent);
	display: grid;
	place-items: center;
	font-family: var(--font-body);
	font-size: 12px;
	font-weight: 500;
	color: var(--accent-ink);
	transition: opacity 0.3s var(--ease),
	            width 0.35s var(--ease), height 0.35s var(--ease),
	            margin 0.35s var(--ease), background 0.35s var(--ease),
	            border-radius 0.35s var(--ease), border-color 0.35s var(--ease);
}
.cursor-ring span {
	opacity: 0;
	transform: scale(0.8);
	transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
}

body.cursor-ready .cursor-dot,
body.cursor-ready .cursor-ring { opacity: 1; }

/* Over a link or button: the ring swells and fills, the dot gets out of
   the way. Multiply in light and screen in dark both keep whatever is
   underneath readable through the fill. */
body.cursor-hot .cursor-ring {
	width: 70px; height: 70px;
	margin: -35px 0 0 -35px;
	background: color-mix(in srgb, var(--accent) 45%, transparent);
	border-color: transparent;
	mix-blend-mode: multiply;
}
:root[data-theme="dark"] body.cursor-hot .cursor-ring { mix-blend-mode: screen; }
body.cursor-hot .cursor-dot { opacity: 0; }

/* Over a project card the ring becomes a labelled pill. */
body.cursor-view .cursor-ring {
	width: 84px; height: 40px;
	margin: -20px 0 0 -42px;
	border-radius: 999px;
	background: var(--accent);
	border-color: transparent;
	mix-blend-mode: normal;
}
body.cursor-view .cursor-ring span { opacity: 1; transform: none; }
body.cursor-view .cursor-dot { opacity: 0; }

/* Over a text field the native caret is the useful one. */
body.cursor-text .cursor-dot,
body.cursor-text .cursor-ring { opacity: 0; }

/* Hide the system cursor only once ours is actually running, so a failure
   never leaves the page with no pointer at all. */
body.cursor-ready,
body.cursor-ready a,
body.cursor-ready button,
body.cursor-ready .work { cursor: none; }
body.cursor-ready input,
body.cursor-ready textarea { cursor: auto; }

/* The magnetic pull on primary buttons. */
.btn--solid { transition: background 0.4s var(--ease), color 0.4s var(--ease),
                          border-color 0.4s var(--ease), transform 0.3s var(--ease); }


/* --------------------------------------------------------------------------
   14. Scroll reveals
   -------------------------------------------------------------------------- */

/* Gated on .js: without scripting these elements must stay visible,
   otherwise a failed script would leave a blank page. */
.js [data-reveal] {
	opacity: 0;
	transform: translateY(34px);
	transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
}
.js [data-reveal].is-visible { opacity: 1; transform: none; }

/* Arabic animates by word or line, never by letter — splitting a word into
   letters breaks the joining forms. Both languages use the same unit here. */
.js .reveal-words > span {
	display: inline-block;
	opacity: 0;
	transform: translateY(26px);
	transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
.js .reveal-words.is-visible > span { opacity: 1; transform: none; }


/* --------------------------------------------------------------------------
   15. Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1024px) {
	.hero-grid { grid-template-columns: 1fr; }
	.hero-figure { max-width: 430px; margin-inline: auto; order: -1; }
	.hero-intro { margin-inline-start: 0; }
	.contact-grid { grid-template-columns: 1fr; }
}

@media (max-width: 860px) {
	.framed { grid-template-columns: 1fr; }
	.framed > .framed-aside { text-align: start; }
	.framed > .framed-main {
		border-inline-start: 0;
		padding-inline-start: 0;
		border-block-start: 1px solid var(--rule);
		padding-block-start: 24px;
	}
	/* Each title has to stay in one piece at every width, so the line
	   breaks between them instead; the type steps down to make that fit.
	   Verified down to 320px in both languages. */
	.hero-role { font-size: clamp(13px, 3.4vw, 17px); }

	.cv-columns { grid-template-columns: 1fr; }

	/* No column to sit beside, so the panel loses everything that was about
	   the column: the shape, the count and the label. What is left is the
	   one part that has to stay in reach while the list is long — the
	   filters — which pin themselves under the header and scroll sideways
	   rather than wrapping into three rows of tabs. */
	.framed--works > .works-aside {
		position: sticky;
		/* Under the header, not behind it. */
		inset-block-start: 62px;
		z-index: 5;
		/* A grid item is as wide as its content unless it is told it may be
		   narrower, and these tabs are wider than a phone. Without this the
		   bar runs past the screen and the section clips the tabs off the
		   end instead of letting them scroll to. */
		min-width: 0;
		margin-inline: calc(var(--gutter) * -1);
		padding-block: 10px;
		/* Opaque, not nearly so: at 92% the card title passing underneath
		   read as a smear through the tabs rather than as something behind
		   them. */
		background: var(--bg-alt);
		border-block-end: 1px solid var(--rule);
	}
	.works-shape,
	.works-count,
	.works-note { display: none; }
	/* The panel lays its parts out at their own width in the column; here
	   the bar has to be exactly as wide as the screen, or it sizes itself
	   to the tabs and there is nothing for overflow-x to scroll. */
	.works-panel { align-items: stretch; }
	.framed--works .filters {
		min-width: 0;
		flex-direction: row;
		flex-wrap: nowrap;
		gap: 20px;
		overflow-x: auto;
		overscroll-behavior-inline: contain;
		scrollbar-width: none;
		padding-inline: var(--gutter);
	}
	.framed--works .filters::-webkit-scrollbar { display: none; }
	.framed--works .filter { white-space: nowrap; }
	/* The main column already draws the rule under the pinned bar. */
	.framed--works > .framed-main { border-block-start: 0; padding-block-start: 10px; }

	/* Three marks, then two, centred under them. A third of the row each,
	   less the gap, is what puts the break after the third. */
	.logo-strip ul { gap: clamp(20px, 6vw, 34px) clamp(18px, 5vw, 30px); }
	.logo-strip li { flex: 0 0 calc(33.333% - clamp(18px, 5vw, 30px)); padding: 4px; }
	.logo-strip img,
	.logo-strip .mark--masked { --mark-unit: 0.74px; margin-inline: auto; max-width: 100%; }
	.work-logo img { height: 52px; }
	/* The icon is square, and the height above would otherwise squash it. */
	.work-logo--icon img { width: 52px; }

	.work--image, .work--text { grid-template-columns: 1fr; gap: 14px; }
	.work--image { gap: 18px; }
	.work--image:nth-of-type(even) .work-media { order: 0; }
	/* One column, so every cover has the text below it rather than beside
	   it; the sweep stays on the top outer corner and keeps alternating. */
	.work-media { --corner: 16px; }

	/* A tighter cluster, six of the eight. The stage is capped rather than
	   let out to the full width of a tablet: its proportions are upright,
	   so at 800px across it would be fifteen hundred tall. */
	.bubbles {
		aspect-ratio: 350 / 660;
		max-width: 430px;
	}
	.bubble {
		inset-inline-start: var(--mx);
		inset-block-start: var(--my);
		width: var(--ms);
		margin-inline-start: calc(var(--ms) / -2);
		margin-block-start: calc(var(--ms) / -2);
	}
	/* Their photographs are not lost: js/main.js rotates them through the
	   bubbles that are here. */
	.bubble[data-wide-only] { display: none; }
	.bubbles-heart { width: min(300px, 86%); }
	/* The cluster is tighter here, so the loose marks have to be told where
	   the gaps are: left where they were, two of them landed on a bubble
	   and read as a mistake rather than as air. */
	/* The only free band at this width is the one the title is in, so these
	   two live beside it rather than above or below it — pushed to the very
	   edges, because at 8% the dot came within two pixels of the Arabic
	   word and anything longer would have landed on it. */
	.mote--1 { inset-inline-start: 3%;  inset-block-start: 46%; }
	.mote--2 { inset-inline-start: 95%; inset-block-start: 55%; }
	.mote--3 { width: 34px; inset-inline-start: 84%; inset-block-start: 5%; }
	.mote--4 { width: 20px; inset-inline-start: 9%;  inset-block-start: 91%; }
}

@media (max-width: 560px) {
	.hero-facts { grid-template-columns: 1fr; gap: 14px; }
	.hero-facts li,
	.hero-facts li:nth-child(2),
	.hero-facts li:nth-child(3) { text-align: start; }
	.hero { padding-block-start: 104px; }
	/* The two front domes hang past the composition by design. On the
	   narrowest phones that runs them off the screen, so pull them in
	   rather than let the edges get clipped. */
	.hero-figure { max-width: 286px; }
	.hero-stage .shape-2 { left: -5%; }
	.hero-stage .shape-3 { right: -5%; }
	.lang-switch { padding: 7px 9px; font-size: 12px; }
	.section-title { white-space: normal; }
	html[lang="ar"] body { font-size: 16px; }
	.title-ghost { font-size: 1.2em; }
}


/* --------------------------------------------------------------------------
   15b. Arabic type corrections

   Latin small-caps tricks do not belong on Arabic: letter-spacing pulls
   joined letters apart and uppercase means nothing. This sits after every
   component rule so it wins on order, and it catches anything added later
   without each new rule needing its own Arabic exception.
   -------------------------------------------------------------------------- */

html[lang="ar"] *,
html[lang="ar"] *::before,
html[lang="ar"] *::after {
	letter-spacing: 0;
	text-transform: none;
}


/* --------------------------------------------------------------------------
   17. Mo's calligraphic marks

   Four drawings of the same name, each with one job: the taliq is the
   Arabic hero name and Arabic wordmark, the signature closes About, the
   banner frames the preloader and the plate signs the footer. Never two of
   them on one screen. They paint in currentColor, so light and dark need
   no separate files.
   -------------------------------------------------------------------------- */

.svg-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.mark { display: block; height: auto; fill: currentColor; }

/* Header wordmark (Arabic only; English keeps the typeset name). */
.wordmark--mark { display: flex; align-items: center; }
.mark--wordmark {
	height: clamp(22px, 2.2vw, var(--wordmark-size, 28px));
	width: auto;
	color: var(--ink);
	transition: color 0.4s var(--ease), opacity 0.4s var(--ease);
}

/* Hero name. One line, always: it scales with the column rather than
   wrapping, and the readable name sits beside it for screen readers. */
.hero-name--mark { margin: 0.1em 0 0.34em; line-height: 1; }
.mark--hero {
	width: 100%;
	max-width: 620px;
	height: auto;
	color: var(--ink);
}

/* The signature that closes About. Served as a file and painted through a
   mask, so it costs one cached request instead of ~22KB of markup on each
   page, and still takes the accent colour. */
.signature {
	width: min(330px, 78%);
	aspect-ratio: 3038 / 556;
	margin-block-start: clamp(26px, 3vw, 40px);
	background: var(--accent);
	/* Relative to this stylesheet, not to the page: the same rule then works
	   from "/", from "/en/", and from anywhere the site is served. */
	-webkit-mask: url("../img/logo/name-signature.svg?v=83f6fbfeec") no-repeat center / contain;
	        mask: url("../img/logo/name-signature.svg?v=83f6fbfeec") no-repeat center / contain;
}
/* Drawn in from the start edge as it arrives. Both .js and dir live on the
   <html> element, hence the combined selector. Gated on .js like the other
   reveals, so without scripting the signature is simply there. */
html.js .signature {
	clip-path: inset(0 0 0 100%);
	transition: clip-path 1.6s var(--ease);
}
html.js[dir="rtl"] .signature { clip-path: inset(0 100% 0 0); }
html.js .signature.is-visible { clip-path: inset(0 0 0 0); }

/* Footer mark. */
.mark--footer {
	height: 36px;
	width: auto;
	max-width: 100%;
	color: var(--ink);
	opacity: 0.85;
}

/* The preloader — one moment of loading, the first visit of a browser
   session only. The name writes itself in, a hairline under it fills as
   the page actually loads, and then the whole thing lifts away like a
   curtain and hands the hero its entrance.

   It is display:none until main.js decides to show it, so a page without
   scripting is never left behind a cover. */
.preloader {
	display: none;
	position: fixed;
	inset: 0;
	z-index: 1000;
	background: var(--bg);
	place-items: center;
	padding-inline: var(--gutter);
	transform: translateY(0);
}
.preloader.is-on { display: grid; }

/* The curtain. translate, not transform, so nothing here has to know what
   else might be transformed. */
.preloader.is-leaving {
	translate: 0 -100%;
	transition: translate .62s cubic-bezier(.76, 0, .24, 1);
}

.preloader-inner {
	display: grid;
	justify-items: center;
	gap: clamp(20px, 3vw, 34px);
	width: min(620px, 82vw);
}

/* The mark, written rather than faded in: Arabic from the right, English
   from the left, each in the direction the hand would take. */
.preloader-mark,
.preloader-name {
	width: 100%;
	/* Mo's own orange: WCAG 1.4.3 exempts a logotype from the contrast
	   rule, and that is what this is. The deeper orange is for running
	   text — links, labels, chip text — not for the mark. */
	color: var(--accent);
	clip-path: inset(0 100% 0 0);
}
.preloader-mark { height: auto; fill: currentColor; display: block; }
.preloader-name {
	margin: 0;
	font-family: "Cormorant Garamond", var(--font-display), serif;
	font-style: italic;
	font-weight: 600;
	font-size: clamp(42px, 8vw, 92px);
	line-height: 1.1;
	letter-spacing: 0;
	text-align: center;
	white-space: nowrap;
}
[dir="rtl"] .preloader-mark,
[dir="rtl"] .preloader-name { clip-path: inset(0 0 0 100%); }

.preloader.is-on .preloader-mark,
.preloader.is-on .preloader-name {
	animation: mark-draw 1.15s cubic-bezier(.33, 0, .2, 1) forwards;
}
@keyframes mark-draw { to { clip-path: inset(0 0 0 0); } }

/* The hairline. Its scale is written from main.js and really does follow
   the load, so it is a progress bar and not a stopwatch. */
.preloader-bar {
	width: min(260px, 60%);
	height: 2px;
	background: color-mix(in srgb, var(--accent) 22%, transparent);
	overflow: hidden;
}
.preloader-bar span {
	display: block;
	height: 100%;
	background: var(--accent);
	transform: scaleX(var(--p, 0));
	transform-origin: left center;
	transition: transform .35s var(--ease);
}
[dir="rtl"] .preloader-bar span { transform-origin: right center; }


/* --------------------------------------------------------------------------
   16. Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*,
	*::before,
	*::after {
		animation-duration: 0.001ms !important;
		transition-duration: 0.001ms !important;
	}
	[data-reveal],
	.reveal-words > span { opacity: 1 !important; transform: none !important; }
	.cursor-dot, .cursor-ring { display: none; }
	/* One portrait, held still, and shapes that stay where they are. */
	.hero-portrait { opacity: 0 !important; }
	.hero-portrait.is-active { opacity: 1 !important; }
	.hero-stage .shape { opacity: 1 !important; transform: none !important; }
	.hero-stage .shape-in { transform: none !important; }
	.section-shape { translate: none !important; }
	.hero-stage .shape svg { --rot: 0deg !important; --sc: 1 !important; transform: none !important; }
	.hero-portrait { translate: none !important; animation: none !important; }
	/* The bubbles stop moving: no float, no lean toward the pointer, no
	   drift with the scroll, and nothing to rotate. They are simply where
	   they are. */
	.bubble, .mote { animation: none !important; transform: none !important; }
	.bubble { translate: none !important; scale: 1 !important; }
	/* No preloader at all, and the signature is simply present. */
	.preloader { display: none !important; }
	.signature { clip-path: none !important; }
}
