/* =========================================================
   TYPOGRAPHY
   ========================================================= */

/*
 * Legal Disclaimer for Student/Education licence (Gravur, Lineto)
 *
 * Student/Education Licence - Our package includes OTF, TTF, WOFF and WOFF2 font formats.
 *
 * For expiry date please check the relevant Licence Document.
 *
 * Licence to produce and publish work within educational context.
 * Any commercial exploitation is subject to separate licencing by the client.
 * The student/educational license expires at the end of the study period.
 * An upgrade to a full licence for professional use is required after expiry.
 * The upgrade within 3 months of expiry will be available at a reduced price for the educational Licence Holder.
 */

/* Gravur Cond, all 10 styles, as one family "Gravur" with its weights:
   Thin 100, Light 300, Regular 400, Bold 700, Black 900, each upright and
   italic. Only the styles a page actually uses are downloaded (here
   Light and Regular). woff2 first, woff as a fallback for older browsers.
   (Adapted from assets/type/stylesheet.css, which names each style as its
   own family.) */

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondThin.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondThin.woff") format("woff");
  font-weight: 100;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondThinItalic.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondThinItalic.woff") format("woff");
  font-weight: 100;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondLight.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondLight.woff") format("woff");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondLightItalic.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondLightItalic.woff") format("woff");
  font-weight: 300;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondRegular.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondRegular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondItalic.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondItalic.woff") format("woff");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondBold.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondBold.woff") format("woff");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondBoldItalic.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondBoldItalic.woff") format("woff");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondBlack.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondBlack.woff") format("woff");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Gravur";
  src: url("../assets/type/GravurLLEduWeb-CondBlackItalic.woff2") format("woff2"),
       url("../assets/type/GravurLLEduWeb-CondBlackItalic.woff") format("woff");
  font-weight: 900;
  font-style: italic;
  font-display: swap;
}

:root {
  --ink: #111;
  /* One "design pixel": 1px at the 1728 × 1117 design size, growing and
     shrinking with the window, so every size below scales with it. */
  --px: min(100vw / 1728, 100vh / 1117);
  --font: "Gravur", "DIN Alternate", "DIN 1451", "Bahnschrift", "Helvetica Neue", Arial, sans-serif;
  /* brown outer glow (#5C3A21), 65% opacity, 23px — on the stack images
     (see .stack-layer) */
  --glow: 0 0 calc(23 * var(--px)) rgba(92, 58, 33, 0.65);
  /* title / nav glow: white, blended with "difference", so it reads
     black over light areas and white over dark ones */
  /* two stacked layers: a wide 50px glow plus a tighter 25px one, both
     at full opacity — stacking makes it stronger than one layer can be */
  --text-glow:
    0 0 calc(50 * var(--px)) rgba(255, 255, 255, 1),
    0 0 calc(0.5 * var(--px)) rgb(255, 255, 255);
}

body {
  font-family: var(--font);
  font-weight: 300;
  color: var(--ink);
  letter-spacing: 0.04em;
}

h1, h2, p {
  margin: 0;
  font-weight: 300;
}

/* .glow elements (title, Projects) draw their glow as a separate copy
   of the text (::before, from data-text): the copy's letters are
   invisible, only its glow shows, blended with "difference" against
   the images and background behind. The real text (the inner span)
   sits on top, unblended.
   Layers: images z-index 1 → glow 2 → text 3. Neither .glow nor its
   parents may create a stacking context (no z-index, filter, opacity…),
   or the glow could only blend with its own element, not the page. */
.glow {
  position: relative;
}
.glow::before {
  content: attr(data-text);
  content: attr(data-text) / ""; /* decorative: screen readers skip it */
  position: absolute;
  inset: 0;
  padding: inherit;
  z-index: 2;
  color: transparent;
  text-shadow: var(--text-glow);
  mix-blend-mode: difference;
  pointer-events: none;
}
.glow > span {
  position: relative;
  z-index: 3;
}

/* Hovering "Projects" or "Ames Gerould" (projects.js adds
   body.word-hover and picks a pair from its Halloween palette): the
   hovered word turns one colour, the other word its complement
   (--nav-colour / --title-colour), and each word's glow turns the
   complement of its own new text colour — i.e. the other word's colour.
   While coloured, the glow drops its "difference" blend, which would
   otherwise invert the colour.
   Glow: two layers at 57.5px and 28.75px plus a tight 14px layer at 15%
   (15% wider and stronger than the earlier 50px / 25px). */
.glow > span,
.glow::before {
  transition: color 0.3s ease, text-shadow 0.3s ease;
}
body.word-hover .nav .glow > span { color: var(--nav-colour); }
body.word-hover .title > span     { color: var(--title-colour); }
body.word-hover .nav .glow::before {
  mix-blend-mode: normal;
  text-shadow:
    0 0 calc(57.5 * var(--px)) var(--title-colour),
    0 0 calc(28.75 * var(--px)) var(--title-colour),
    0 0 calc(14 * var(--px)) color-mix(in srgb, var(--title-colour) 15%, transparent);
}
body.word-hover .title::before {
  mix-blend-mode: normal;
  text-shadow:
    0 0 calc(57.5 * var(--px)) var(--nav-colour),
    0 0 calc(28.75 * var(--px)) var(--nav-colour),
    0 0 calc(14 * var(--px)) color-mix(in srgb, var(--nav-colour) 15%, transparent);
}

/* Figma: Gravur Cond Light 175px, tracking 12.25px — 175px on the
   1117px-tall frame = 15.67vmin, 12.25 / 175 = 0.07em */
/* "Ames Gerould" and "Projects" share one size (Figma 175px + 10%, − 2%,
   − 10%, − 10%) */
:root {
  --display-size: calc(152.84 * var(--px));
}

.title {
  text-align: center;
  font-size: var(--display-size);
  line-height: normal;
  letter-spacing: 0.07em;
}

/* Figma: Gravur Cond Light 53px / 53px, tracking 1.59px — 53px on the
   1117px-tall frame = 4.745vmin. Solid #111 like the header, no blend. */
.intro {
  text-align: center;
  font-size: calc(53 * var(--px));
  line-height: 1.15; /* 1 + 0.5% + 1% + 1.5% + 2% + 2% */
  letter-spacing: 0.03em;
  color: var(--ink);
}


/* The CV / contact block: solid #111 like the header, no blend. */
.info {
  /* 15% larger than the original clamp(0.7rem, 1.875vmin, 1.875rem);
     line height and tracking are relative, so they scale with it */
  font-size: calc(24.08 * var(--px));
  line-height: 1.342; /* 1.22 + 10% — also used on mobile */
  letter-spacing: 0.03em;
  font-weight: 400;
  color: var(--ink);
}

/* headings use the same size, tracking and weight as the CV / contact text */
.info h2 {
  font-size: inherit;
  letter-spacing: inherit;
  font-weight: inherit;
}

.contact-grid address {
  font-style: normal;
}

.contact-grid a {
  color: inherit;
  text-decoration: none;
}
.contact-grid a:hover {
  text-decoration: underline;
}
/* no underline on any of the contact links */
.contact-grid a[href^="mailto:"]:hover,
.contact-grid .instagram-link:hover,
.contact-grid .phone-link:hover,
.contact-grid .address-link:hover {
  text-decoration: none;
}

/* same as .title (Figma: 175px, tracking 12.25px) */
.nav {
  font-size: var(--display-size); /* same as the title */
  line-height: normal;
  letter-spacing: 0.07em;
}
.nav a {
  display: inline-block; /* a box, so its glow copy lines up exactly */
  color: inherit;
  text-decoration: none;
}


/* =========================================================
   LAYOUT
   ========================================================= */

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

/* Cursor: the normal arrow everywhere — never the text (I-beam) cursor —
   and the hand on things you can click. */
* {
  cursor: default;
}
a,
a *,
.title > span,
.thumb,
.thumb-spread,
.thumb-spread * {
  cursor: pointer;
}

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

body {
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.title {
  padding-top: calc(22.34 * var(--px));
  position: relative;
  top: calc(-38.83 * var(--px)); /* 50 design px up, then 1% of the design height (11.17) back down */
  left: calc(17.28 * var(--px));  /* optical adjustment: 1% of the design width to the right */
  /* no z-index here: the text (span) and glow set their own, see .glow */
}

/* Side margin shared by the biography and the CV / contact block, so the
   biography's box starts exactly at the years ("2025 – present") and
   ends the same distance from the right edge. */
:root {
  --page-margin: calc(43.2 * var(--px));
}

.intro {
  width: calc(100% - 2 * var(--page-margin));
  margin: calc(22.34 * var(--px)) auto 0;
  /* shifted down 0.5% of the window height, without moving anything else */
  position: relative;
  top: calc(5.585 * var(--px));
}

/* CV column is as wide as its longest entry needs (so entries stay on
   one line when there's room); Contact takes the rest of the width,
   out to the right margin. */
.info {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: calc(69.12 * var(--px));
  padding: 0 var(--page-margin);
  margin-top: calc(44.68 * var(--px));
  /* shifted down 5% of the window height, without moving anything else */
  position: relative;
  top: calc(55.85 * var(--px));
}

.info h2 {
  margin-bottom: calc(22.34 * var(--px));
}

/* Each section is a grid whose columns its list shares (subgrid), so the
   heading can sit exactly on a column edge of the text below it. */
.cv-section,
.contact-section {
  display: grid;
  /* heading row on top; content row below it, with its content at the
     top — so the CV's first line and both contact blocks start on the same
     line (the headings are the same height in both sections) */
  grid-template-rows: auto 1fr;
  column-gap: calc(16 * var(--px));
}
.cv,
.contact-grid {
  align-self: start;
}

/* "Curriculum Vitae" lines up with "BA Visual Communication…",
   not with the years */
.cv-section {
  grid-template-columns: max-content 1fr;
  column-gap: calc(50.56 * var(--px)); /* gutter between years and entries */
}
.cv-section h2 {
  grid-column: 2;
}

.cv {
  list-style: none;
  margin: 0;
  padding: 0;
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
}
.cv li {
  display: contents;
}
.cv li span:first-child {
  white-space: nowrap;
}

/* "Contact" lines up with "xo@amesgerould.ch": both sit in the first
   column with the same 12% indent */
.contact-section {
  grid-template-columns: 1fr 1fr;
  /* whole contact block shifted right by 3% of the window width;
     everything inside moves together, so its alignments are kept */
  position: relative;
  left: calc(51.84 * var(--px));
}
.contact-section h2,
.contact-grid > div:first-child {
  grid-column: 1;
  padding-left: 12%;
}

.contact-grid {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  row-gap: calc(16 * var(--px));
  align-items: start; /* both contact blocks start on the same line */
}

.nav {
  display: flex;
  justify-content: center;
  padding: calc(22.34 * var(--px)) calc(138.24 * var(--px)) calc(22.34 * var(--px));
  /* (was shifted down 1.5% of the design height; moved back up 1.5%) */
  position: relative;
  top: 0;
  /* no z-index here: the text (span) and glow set their own, see .glow */
}

/* The image grid sits in exactly the Projects sheet's frame — same
   position, width, height and rounded corners (--sheet-… in :root, below)
   — split into two equal rows. */
.image-stack {
  position: fixed;
  left: var(--sheet-left);
  top: var(--sheet-top);
  width: var(--sheet-width);
  height: var(--sheet-height);
  z-index: 1;
  display: grid;
  grid-template-rows: repeat(2, 1fr);
  gap: 0; /* no gutter on either axis */
  pointer-events: none;
  --columns: 4;                             /* images per row */
}

/* Empty placeholders marking the two rows (used for click-outside) */
.stack-row:nth-child(1) { grid-area: 1 / 1; }
.stack-row:nth-child(2) { grid-area: 2 / 1; }

/* One layer per swipe, filling the grid's frame.
   The glow sits on the layer, not on each image, so it only traces the
   outer edge of the whole block — the seams where images touch get no
   shadow. */
.stack-layer {
  position: absolute;
  inset: 0;
  /* the sheet's rounded corners; the tiles are clipped to them (the glow,
     applied after, follows the rounded shape) */
  border-radius: var(--sheet-radius);
  overflow: hidden;
  filter: drop-shadow(var(--glow));
}

/* The layer sinking to third from the top fades out in place — the
   same quick 0.25s fade as a new layer fading in on top (.stack-img),
   in reverse — then stack.js removes it. */
.stack-layer.fading {
  opacity: 0;
  transition: opacity 0.25s ease;
}

/* Click outside the grid (or the "Ames Gerould" reset): the layer on top
   of the stack shrinks to nothing, centred on the grid, while fading out,
   in 0.2s (25% faster than the 0.25s fade); the layers under it just
   fade (.fading). */
.stack-layer.shrinking {
  opacity: 0;
  transform: scale(0);
  transform-origin: center;
  transition:
    opacity 0.2s ease,
    transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}

/* One strip per row, with an exact size: the row's images sit directly
   side by side, centred, so they always touch, whatever their
   proportions. Top-row strips sit on the middle line, bottom-row
   strips hang from it. */
.stack-strip {
  position: absolute;
  left: 0;
  right: 0;
  height: 50%;
  display: flex;
  justify-content: center;
}
.stack-strip:nth-child(1) { top: 0; }
.stack-strip:nth-child(2) { top: 50%; }

/* Each image is as tall as the row and as wide as its proportions
   allow; if a row of four would be wider than the grid, they shrink
   evenly (flex-shrink) and fit within their share of the width.
   Hidden images wait in place, fully transparent; once faded out they
   are also visibility: hidden, so the browser doesn't keep drawing them. */
.stack-img {
  flex: 0 1 auto;
  min-width: 0;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  visibility: hidden;
  /* leaving: a quick fade out in place; hide once gone */
  transition:
    opacity 0.25s ease,
    visibility 0s 0.25s;
}
.stack-strip:nth-child(1) .stack-img { object-position: center bottom; }
.stack-strip:nth-child(2) .stack-img { object-position: center top; }

/* The images fill the frame: each row is `columns` equal tiles side by
   side, every image cropped to fill its tile (top row keeps the bottom
   of its image, bottom row the top), so the grid always reaches the
   frame's edges and rounded corners. */
.stack-img {
  flex: 1 1 0;
  object-fit: cover;
}

/* Experiment — a thumbnail clicked in the Projects overlay: its tiled
   image (the top hover layer, .expanded, see stack.js) fills a band 96.5%
   of the window's width, centred. Each row's images become equal tiles
   side by side, cropped to fill them (cover) instead of keeping their
   own proportions; the row height stays the same. */
.stack-layer.expanded {
  /* larger than the usual frame and centred in the window: 96.5% of its
     width and 95% of its height (1.75% / 2.5% clear at the sides / top
     and bottom), so all four rounded corners stay in view */
  position: fixed;
  inset: 2.5vh 1.75vw;
}
/* While the single-image view is showing, every other layer of the stack
   is hidden (it comes back with the next scroll / swipe, which clears
   the view). */
body.stack-expanded .stack-layer:not(.expanded) {
  opacity: 0;
  transition: opacity 0.25s ease;
}

.stack-layer.expanded .stack-img {
  /* (equal tiles, cropped to fill, as above) inverted against the single
     image on top, and dimmed to 37.5% brightness (50%, then 25% less) */
  filter: invert(1) brightness(0.375);
}

/* ...and on top of the tiles, the same image once more: single, centred,
   as tall as the band, its width following its own proportions, in its
   original colours (the tiles behind it are the inverted version). It
   fades in with the band. */
.stack-solo {
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  width: auto;
  transform: translateX(-50%);
  /* like the Projects sheet: the brown glow around its edge, and a 55%
     white veil over the tiles everywhere around it (a hole where the
     image is) — the glow is listed first, so it's drawn over the veil.
     The band clips both to its rounded frame. */
  box-shadow:
    var(--glow),
    0 0 0 200vmax rgba(255, 255, 255, 0.55);
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}
.stack-solo.visible {
  opacity: 1;
}

/* switches an image's state without animating (see stack.js) */
.stack-img.snap { transition: none; }

/* arriving (a swipe, or hovering a thumbnail in the Projects overlay):
   a quick fade in, in place, on top of the stack */
.stack-img.visible {
  opacity: 1;
  visibility: visible;
  transition:
    opacity 0.25s ease,
    visibility 0s;
}

@media (prefers-reduced-motion: reduce) {
  .image-stack .stack-img,
  .image-stack .stack-img.visible {
    transform: none;
    opacity: 0;
    transition: opacity 0.3s ease, visibility 0s 0.3s;
  }
  .image-stack .stack-img.visible {
    opacity: 1;
    transition: opacity 0.3s ease, visibility 0s;
  }
}

/* (the mobile layout is at the end of this file) */


/* =========================================================
   BACKGROUND IMAGE
   ========================================================= */

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-color: #f4f4f4;
  background-image: url("../assets/media/Untitled-9.webp");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0.5;
}

/* =========================================================
   PROJECTS OVERLAY
   Opens on top of the main page (js/projects.js). Built from the
   Figma frame "Projects Overview" (1728 × 983): every size below is
   in Figma pixels × --u, where --u fits that frame into the window.
   While it's open, the layers stack as described further down
   (body.projects-open …).
   ========================================================= */

/* The sheet's frame, shared by the Projects sheet, the swiped image grid
   and everything shaped like them (veil hole, inverted area, title
   cut-outs), so they always match. Figma: 1107 × 820 at (311, 89),
   corners 13, bottom edge raised 16.76; then scaled by --sheet-scale
   around its own centre. */
:root {
  --u: var(--px); /* one Figma pixel = one design pixel (the same at 1728 × 1117) */
  --sheet-scale: 1.119; /* ← frame size: 1 = Figma size (+5%, +5%, +1.5%) */
  --sheet-base-w: calc(1107 * var(--u));
  --sheet-base-h: calc(820 * var(--u) - 16.76 * var(--px));
  --sheet-width: calc(var(--sheet-base-w) * var(--sheet-scale));
  --sheet-height: calc(var(--sheet-base-h) * var(--sheet-scale));
  /* the Figma position, moved by half the extra size so it stays centred */
  --sheet-left: calc((100vw - 1728 * var(--u)) / 2 + 311 * var(--u)
                     - (var(--sheet-width) - var(--sheet-base-w)) / 2);
  --sheet-top: calc((100vh - 983 * var(--u)) / 2 + 89 * var(--u)
                    - (var(--sheet-height) - var(--sheet-base-h)) / 2);
  --sheet-radius: calc(13 * var(--u));
  /* Opening / closing: a very fast fade, with the sheet and everything
     shaped like it growing from nothing at the window's centre. */
  --open-time: 0.18s;
  --open-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  --grow-origin: calc(50vw - var(--sheet-left)) calc(50vh - var(--sheet-top));
}

.projects {
  position: fixed;
  inset: 0;
  z-index: 1;
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--open-time) var(--open-ease), visibility 0s var(--open-time);
}
body.projects-open .projects {
  visibility: visible;
  opacity: 1;
  transition: opacity var(--open-time) var(--open-ease), visibility 0s;
}

/* the sheet (transparent — the veil and the inverted area around and
   behind it give it its look); grows in from the window's centre */
.projects-panel {
  position: absolute;
  left: var(--sheet-left);
  top: var(--sheet-top);
  width: var(--sheet-width);
  height: var(--sheet-height);
  background: rgba(255, 255, 255, 0); /* sheet white at 0% — the page shows through */
  border-radius: var(--sheet-radius);
  box-shadow: var(--glow); /* same brown glow as the swipe images */
  transform: scale(0);
  transform-origin: var(--grow-origin);
  transition: transform var(--open-time) var(--open-ease);
}
body.projects-open .projects-panel {
  transform: none;
}

/* The thumbnails and project list only show while the pointer is right on
   a thumbnail or a project's text (the panel has .focus, see projects.js);
   anywhere else they fade away, leaving just the image behind, seen
   through the sheet's frame and its inversion. Fading out waits 0.12s, so
   moving across the gaps between thumbnails or words doesn't blink them. */
.projects-grid,
.projects-list {
  transition: opacity 0.15s ease;
}
.projects-panel:not(.focus) :is(.projects-grid, .projects-list) {
  opacity: 0;
  transition: opacity 0.2s ease 0.12s;
}

/* The veil: 90% white everywhere outside the sheet (a hole where the
   sheet is), drawn as a huge box-shadow around an empty box of the
   sheet's shape. Its own layer, below "Ames Gerould" / "Projects", so it
   doesn't wash them out; fades in with the sheet. */
.projects-shade {
  position: fixed;
  z-index: 1;
  left: var(--sheet-left);
  top: var(--sheet-top);
  width: var(--sheet-width);
  height: var(--sheet-height);
  border-radius: var(--sheet-radius);
  box-shadow: 0 0 0 200vmax rgba(255, 255, 255, 0.9); /* ← veil strength */
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
  transition:
    opacity var(--open-time) var(--open-ease),
    visibility 0s var(--open-time);
}
/* also on the main page whenever images are stacked — except the larger
   expanded view after a thumbnail click, which reaches past the frame */
body.projects-open .projects-shade,
body:not(.stack-empty):not(.stack-expanded) .projects-shade {
  visibility: visible;
  opacity: 1;
  transition:
    opacity var(--open-time) var(--open-ease),
    visibility 0s;
}

/* While the overlay is open, the layers stack (back to front): images →
   veil (z 1) → "Ames Gerould" / "Projects" (glow z 2, letters z 3) →
   inverting area and list glow (z 4) → the sheet with its thumbnails and
   list (z 5). So the two words sit under the inversion, while the
   thumbnails and list stay on top. */
body.projects-open .projects-invert,
body.projects-open .projects-glow-layer { z-index: 4; }
body.projects-open .projects { z-index: 5; }

/* Where "Ames Gerould" and "Projects" pass under the sheet, those parts
   aren't drawn at all: the sheet's rectangle is cut out of each word's
   letters (span) and glow copy (::before) with a mask. projects.js
   measures the sheet (.sheet-probe) against each word and sets --hole-x /
   --hole-y / --hole-w / --hole-h, in that word's own coordinates. (Masking
   the letters and glow separately, rather than the whole element, keeps
   the glow blending with the page behind.) */
.sheet-probe {
  position: fixed;
  left: var(--sheet-left);
  top: var(--sheet-top);
  width: var(--sheet-width);
  height: var(--sheet-height);
  visibility: hidden;
  pointer-events: none;
}
body.projects-open :is(.title, .nav .glow)::before,
body.projects-open :is(.title, .nav .glow) > span {
  -webkit-mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
          mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
  -webkit-mask-size: 100% 100%, var(--hole-w, 0) var(--hole-h, 0);
          mask-size: 100% 100%, var(--hole-w, 0) var(--hole-h, 0);
  -webkit-mask-position: 0 0, var(--hole-x, 0) var(--hole-y, 0);
          mask-position: 0 0, var(--hole-x, 0) var(--hole-y, 0);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-composite: xor;     /* keep everything except the hole */
          mask-composite: exclude;
}

/* The overlay only catches the pointer on the sheet itself, so the two
   words underneath can still be hovered and clicked; a click anywhere
   outside the sheet closes the overlay (projects.js). */
.projects { pointer-events: none; }
.projects-panel { pointer-events: auto; }

/* Inside the sheet's shape, the images behind are inverted: a white
   rectangle with a "difference" blend, the same size and position as
   the sheet. It has to live outside .projects — that's its own layer,
   so nothing inside it can blend with the images behind — and comes
   between the image stack and the overlay in the page (all z-index 1),
   so the thumbnails and text on the sheet aren't inverted. It fades and
   grows in with the sheet. */
.projects-invert {
  position: fixed;
  z-index: 1;
  left: var(--sheet-left);
  top: var(--sheet-top);
  width: var(--sheet-width);
  height: var(--sheet-height);
  border-radius: var(--sheet-radius);
  background: #fff;
  mix-blend-mode: difference;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
  transform: scale(0);
  transform-origin: var(--grow-origin);
  transition:
    opacity var(--open-time) var(--open-ease),
    transform var(--open-time) var(--open-ease),
    visibility 0s var(--open-time);
}
body.projects-open .projects-invert {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition:
    opacity var(--open-time) var(--open-ease),
    transform var(--open-time) var(--open-ease),
    visibility 0s;
}

/* Space between the sheet's top / bottom edges and its contents: the
   project list sits at the top, the thumbnail grid at the bottom. (On
   :root, so the list's glow copy outside the sheet lines up too.) */
:root {
  --sheet-padding: calc(50 * var(--u)); /* ← adjust to tighten */
}

/* Rows of up to 10 thumbnails, 1059 wide, centred in the panel, its
   bottom edge --sheet-padding above the panel's bottom; gaps 20 across,
   40 down. Every row is centred, so a shorter last row sits in the
   middle. Thumbnails keep their own proportions and hang from the top
   of their row. */
.projects-grid {
  position: absolute;
  left: 0;
  right: 0;
  margin-inline: auto;
  bottom: var(--sheet-padding);
  width: calc(1059 * var(--u));
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: calc(40 * var(--u)) calc(20 * var(--u));
}
/* shrunk by projects.js (fitGrid) when it would run into the list:
   it scales towards its bottom edge, staying centred */
.projects-grid {
  transform-origin: center bottom;
}
.projects-grid > * {
  flex: 0 0 calc(87.9 * var(--u)); /* (1059 − 9 gaps of 20) / 10 */
}

.thumb {
  display: block;
  width: 100%;
  height: auto;
}
/* Inverted, as in Figma (where it's a "difference" blend on the white
   sheet — on pure white, invert() looks identical, and it lets the
   hover glow below stay brown instead of being inverted too). */
.thumb.invert { filter: invert(1); }
.thumb.flip { transform: rotate(180deg); }

/* Hovering a thumbnail or a project's text (projects.js): the panel gets
   .focus and a --hover-colour (from the background image's hue); the
   hovered project's thumbnails and text get .linked. Its text turns that
   colour, its thumbnails glow in it, other thumbnails fade to 25% and
   other text to 50%, and a hovered thumbnail (.hovered) grows wider
   (GROW_PX in projects.js). */
.projects-panel {
  /* --hover-colour: the highlight colour, set by projects.js.
     The glow is two stacked layers at full colour, 14.375px each
     (half the previous 28.75px radius; stacked to be ~50% more opaque
     than the single 97.5% layer, which was already nearly solid). */
  --hover-glow-filter:
    drop-shadow(0 0 calc(14.375 * var(--px)) var(--hover-colour, #000))
    drop-shadow(0 0 calc(14.375 * var(--px)) var(--hover-colour, #000));
}
.thumb,
.thumb-spread {
  transition: opacity 0.2s ease, filter 0.2s ease, scale 0.2s ease, background-color 0.2s ease;
}
.thumb.hovered,
.thumb-spread.hovered {
  position: relative;
  z-index: 1; /* above its neighbours while enlarged */
}
.projects-panel.focus :is(.thumb, .thumb-spread) {
  opacity: 0.25; /* other projects' thumbnails */
}
.projects-panel.focus .project {
  opacity: 0.5;  /* other projects' text */
}
.projects-panel.focus :is(.thumb, .thumb-spread, .project).linked {
  opacity: 1;
}
.thumb.linked,
.thumb-spread.linked { filter: var(--hover-glow-filter); }
.thumb.invert.linked { filter: invert(1) var(--hover-glow-filter); }

/* Behind the highlighted thumbnails: a background exactly the size of
   the thumbnail, in the complement of the glow colour (--hover-bg, set
   by projects.js). It shows wherever the image is transparent. Inverted
   thumbnails invert their background too, so they get the RGB inverse
   (--hover-bg-inverted), which comes out as the complement on screen. */
.thumb.linked,
.thumb-spread.linked { background-color: var(--hover-bg); }
.thumb.invert.linked { background-color: var(--hover-bg-inverted); }

/* Spreads turned 90°, showing the upper (default) or lower half of the
   page: the box is 87.9 × 110.2; inside it, a 110.2 × 87.9 window is
   rotated to fit, and the page image slides to its top or bottom. */
.thumb-spread {
  position: relative;
  aspect-ratio: 87.9 / 110.2;
}
.thumb-spread > div {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 125.4%;   /* = the box's height */
  height: 79.7%;   /* = the box's width */
  transform: translate(-50%, -50%) rotate(90deg);
  overflow: hidden;
}
.thumb-spread img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: auto;
}
.thumb-spread.lower img {
  top: auto;
  bottom: 0;
}

/* Gravur Cond Regular / 1.198, tracking 0.03em, 956 wide, centred,
   --sheet-padding below the panel's top */
.projects-list {
  position: absolute;
  /* 956 wide, centred in the panel, its lines centred too */
  left: 0;
  right: 0;
  margin: 0 auto;
  top: var(--sheet-padding); /* at the top of the sheet */
  width: calc(956 * var(--u));
  text-align: center;
  font-size: calc(28.41 * var(--u)); /* Figma 22 + 10%, − 2%, − 1%, + 10%, + 10% */
  font-weight: 400;
  line-height: 1.198;
  letter-spacing: 0.03em;
  color: #000;
}

/* one project in the list (wrapped by projects.js); its hover colour
   is set there */
.project {
  transition: color 0.15s ease, opacity 0.2s ease;
}

/* White glow around the hovered project, blending ("difference") with
   whatever is behind it. It's drawn by an invisible copy of the list
   outside the overlay (.projects-glow-layer, filled by projects.js) —
   inside the overlay nothing can blend with the images behind. The layer
   matches the sheet's box and animation, so the copy lines up with the
   real list; it sits above the inverted area and below the overlay, so
   the coloured text stays on top. Only the hovered project (.lit) glows. */
.projects-glow-layer {
  position: fixed;
  z-index: 1;
  left: var(--sheet-left);
  top: var(--sheet-top);
  width: var(--sheet-width);
  height: var(--sheet-height);
  mix-blend-mode: difference;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
  transform: scale(0);
  transform-origin: var(--grow-origin);
  transition:
    opacity var(--open-time) var(--open-ease),
    transform var(--open-time) var(--open-ease),
    visibility 0s var(--open-time);
}
body.projects-open .projects-glow-layer {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition:
    opacity var(--open-time) var(--open-ease),
    transform var(--open-time) var(--open-ease),
    visibility 0s;
}
.projects-list.projects-list-glow {
  color: transparent; /* only the glow shows */
}
.projects-list-glow span {
  transition: text-shadow 0.2s ease;
}
.projects-list-glow span.lit {
  text-shadow: 0 0 calc(12 * var(--px)) #fff, 0 0 calc(24 * var(--px)) #fff;
}

/* The clicked project's text, shown centred on the main page after a
   thumbnail in the Projects overlay is clicked (projects.js). Styled like
   the biography (.intro) — same size, line width, line height and
   tracking — with the title's glow (.glow).
   Centred with inset + auto margins rather than a transform, and faded
   by fading the text and its glow copy separately: a transform or an
   opacity on the element itself would make it its own layer and stop
   the glow blending with the page behind. */
.project-caption {
  position: absolute;
  /* centred horizontally; vertically centred 5% of the window height
     above the middle (the box it centres in is shifted up 5vh) */
  inset: calc(-55.85 * var(--px)) 0 calc(55.85 * var(--px)) 0;
  margin: auto;
  /* 75% of the biography's line width */
  width: calc((100% - 2 * var(--page-margin)) * 0.75);
  height: fit-content; /* grows with the text */
  text-align: center;
  font-size: calc(53 * var(--px));
  font-weight: 400; /* Gravur Cond Regular (the biography is Light) */
  line-height: 1;
  letter-spacing: 0.03em;
  color: var(--ink);
  pointer-events: none; /* clicks go through to the page */
}
.project-caption > span,
.project-caption::before {
  transition: opacity 0.35s ease, color 0.3s ease, text-shadow 0.3s ease;
}
.project-caption:not(.shown) > span,
.project-caption:not(.shown)::before {
  opacity: 0;
}
/* while shown, its letters can be hovered (the rest stays click-through) */
.project-caption.shown > span {
  pointer-events: auto;
}

/* Hovering the caption (projects.js adds .hovered and picks a Halloween
   pair): like "Ames Gerould" / "Projects" — the text turns one colour,
   its glow the complement — with the glow radius 10% smaller
   (51.75 / 25.875 / 12.6px instead of 57.5 / 28.75 / 14px). */
.project-caption.hovered > span {
  color: var(--caption-colour);
}
.project-caption.hovered::before {
  mix-blend-mode: normal;
  text-shadow:
    0 0 calc(51.75 * var(--px)) var(--caption-glow),
    0 0 calc(25.875 * var(--px)) var(--caption-glow),
    0 0 calc(12.6 * var(--px)) color-mix(in srgb, var(--caption-glow) 15%, transparent);
}

/* While the project caption is showing, and "Ames Gerould", "Projects"
   (body.word-hover) or the caption (.hovered) is hovered, a full-screen white
   sheet fades from 0% to 20%, making everything behind the three texts
   less prominent — images, background, biography, CV, overlay — without
   changing their own opacity.
   Stacking: it's z-index 1 and comes after the image stack and the
   Projects overlay in the page (also z-index 1), so it covers them; the
   texts' glow (z 2) and letters (z 3) stay above it. */
.hover-veil {
  position: fixed;
  inset: 0;
  z-index: 1;
  background: #fff;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
/* only while the project caption is showing on the main page */
body:has(.project-caption.shown):is(.word-hover, :has(.project-caption.hovered)) .hover-veil {
  opacity: 0.35; /* ← hover veil strength while hovering (0 = none, 1 = solid white) */
}

/* Contact hovers — the email address, "Instagram @amesgerould", the phone
   number and the postal address
   (class .contact-hover, each with its own backdrop image). They only
   fire on the empty main page: every rule here needs body.stack-empty,
   which stack.js sets while no images are stacked.
   · its image covers the whole window, over the gradient background and
     under everything else (z-index -1, like the background, but later in
     the page, so on top of it), fading in and out with the hover;
   · the hovered line turns a random vivid colour with a glow in its
     complement (projects.js adds .lit and sets --ig-colour / --ig-glow);
   · "Ames Gerould" and "Projects" get their own hover colours (projects.js);
   · the project caption turns white with a "difference" blend, so each
     letter inverts the image behind it (white is needed: black in
     "difference" changes nothing);
   · the biography and the rest of the CV / contact text turn solid
     black; the hovered line keeps its own colour. */
.contact-backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
/* the Instagram image is lightened by a white wash layered over it —
   raise or lower the 0.35 to make it lighter or darker */
.instagram-backdrop {
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.35), rgba(255, 255, 255, 0.35)),
    url("../assets/media/web/instagram.webp");
}
.address-backdrop { background-image: url("../assets/media/web/address.webp"); }
.phone-backdrop   { background-image: url("../assets/media/web/phone.webp"); }
.email-backdrop   { background-image: url("../assets/media/web/email.webp"); }
body.stack-empty:not(.no-hover):has(.instagram-link:hover) .instagram-backdrop,
body.stack-empty:not(.no-hover):has(.phone-link:hover) .phone-backdrop,
body.stack-empty:not(.no-hover):has(.email-link:hover) .email-backdrop,
body.stack-empty:not(.no-hover):has(.contact-grid address:hover) .address-backdrop {
  opacity: 1;
}
/* the Instagram image shows at 75%, letting the gradient through */
body.stack-empty:not(.no-hover):has(.instagram-link:hover) .instagram-backdrop {
  opacity: 0.75;
}

/* Credit for the map (required by the OpenStreetMap licence), always in
   the bottom-left corner of the window — the map's own credit line can be
   trimmed off when the map is scaled to cover the window. It fades in and
   out with the map. */
.map-credit {
  position: absolute;
  left: calc(8 * var(--px));
  bottom: calc(6 * var(--px));
  padding: calc(2 * var(--px)) calc(6 * var(--px));
  font-size: calc(11 * var(--px));
  font-weight: 400;
  letter-spacing: 0.02em;
  color: #333;
  background: rgba(255, 255, 255, 0.75);
}

.contact-hover {
  transition: color 0.2s ease, text-shadow 0.2s ease;
}
body.stack-empty:not(.no-hover) .contact-hover.lit {
  color: var(--ig-colour);
  text-shadow: 0 0 calc(12 * var(--px)) var(--ig-glow), 0 0 calc(24 * var(--px)) var(--ig-glow);
}

.intro,
.info h2,
.cv li span,
.contact-grid a,
.contact-grid address {
  transition: color 0.3s ease, text-shadow 0.2s ease;
}
body.stack-empty:not(.no-hover):has(.contact-hover:hover) .project-caption > span {
  color: #fff;
  mix-blend-mode: difference;
}
/* the biography and the rest of the CV and Contact text: solid black */
body.stack-empty:not(.no-hover):has(.contact-hover:hover) :is(
  .intro,
  .info h2,
  .cv li span,
  .contact-grid a:not(.lit):not(.lit a), /* (a link inside the hovered line keeps its colour) */
  .contact-grid address:not(.lit)
) {
  color: #000;
}
/* ("Ames Gerould" and "Projects" instead get their own hover colours —
   projects.js runs the same colouring as hovering them directly.) */


/* =========================================================
   MOBILE (screens up to 720px wide)
   One fixed screen, like the desktop: "Ames Gerould", the biography, the
   CV with Contact below it, and "Projects" — nothing scrolls; vertical
   swipes stack images. There's no Projects overlay on mobile. Sizes use --m, one pixel of a 390 × 844 phone
   design, scaling with the screen. dvh = the browser's visible height
   (Safari's toolbar doesn't cut things off).
   Touch screens get body.no-hover (projects.js): taps act directly and
   hover-only effects are off.
   ========================================================= */

@media (max-width: 720px) {
  :root {
    --m: min(100vw / 390, 100dvh / 844);
    --display-size: calc(54 * var(--m));       /* "Ames Gerould", "Projects" */

    /* the frame for the image stack and the Projects sheet: nearly the
       full width, 62% of the screen's height, centred */
    --sheet-width: calc(100vw - 24 * var(--m));
    --sheet-height: calc(100dvh * 0.62);
    --sheet-left: calc(12 * var(--m));
    --sheet-top: calc((100dvh - var(--sheet-height)) / 2);
    --sheet-radius: calc(10 * var(--m));
    --sheet-padding: calc(16 * var(--m));
    --grow-origin: center;
    --page-margin: calc(16 * var(--m));
  }

  html, body {
    height: 100dvh;
    overflow: hidden;
    overscroll-behavior: none; /* no bounce or pull-to-refresh */
  }
  body {
    height: 100dvh;
  }

  /* "Ames Gerould" — no desktop nudges */
  .title {
    padding-top: calc(10 * var(--m));
    top: calc(8.44 * var(--m)); /* moved down 1% of the 844 screen height */
    left: 0;
  }

  /* biography */
  .intro {
    font-size: calc(26.36 * var(--m)); /* 15 + 15% + 15% + 15% + 10% + 5% */
    line-height: 1.1055; /* 1.1 + 0.5% */
    margin-top: calc(6 * var(--m));
    /* moved down 1.5% of the 844 screen height; a pure shift, so the CV
       and Contact below stay where they are */
    top: calc(12.66 * var(--m));
  }

  /* CV, then Contact below it */
  .info {
    font-size: calc(13.89 * var(--m)); /* 10.5 + 15% + 15% */
    font-weight: 300; /* Gravur Cond Light (the headings stay Bold, below) */
    grid-template-columns: 1fr;
    gap: calc(16 * var(--m));
    margin-top: 0;
    top: calc(17.45 * var(--m)); /* gap below the biography (was 42.77, then moved up 3% of the 844 screen height) */
  }
  .info h2 {
    margin-bottom: calc(6 * var(--m));
  }
  .cv-section {
    column-gap: calc(12 * var(--m));
  }
  .contact-section {
    left: 0;
    column-gap: calc(12 * var(--m));
  }
  .contact-section h2,
  .contact-grid > div:first-child {
    padding-left: 0;
  }
  /* "Curriculum Vitae" and "Contact": left-aligned with the years (the
     start of their section), not with the CV entries */
  .cv-section h2,
  .contact-section h2 {
    grid-column: 1 / -1;
    text-align: left;
    font-weight: 700; /* Gravur Cond Bold */
  }
  .contact-grid {
    row-gap: calc(8 * var(--m));
  }

  /* "Projects" */
  .nav {
    padding: calc(4 * var(--m)) calc(16 * var(--m)) calc(10 * var(--m));
    top: calc(-12.66 * var(--m)); /* moved up 1.5% of the 844 screen height */
  }

  /* image stack: 2 tiles per row (read once by stack.js at load) */
  .image-stack {
    --columns: 2;
  }

  /* the single-image view: the image fits inside the band */
  .stack-solo {
    top: 50%;
    height: auto;
    max-width: 100%;
    max-height: 100%;
    transform: translate(-50%, -50%);
  }

  /* project caption */
  .project-caption {
    font-size: calc(16 * var(--m));
    width: calc(100% - 2 * var(--page-margin));
    inset: calc(-24 * var(--m)) 0 calc(24 * var(--m)) 0;
  }

  /* No Projects overlay on mobile (projects.js doesn't open it): its
     layers are removed outright. Tapping "Projects" or "Ames Gerould"
     stacks one image layer instead, like one swipe. */
  .projects,
  .projects-invert,
  .projects-glow-layer {
    display: none;
  }
}

/* Touch screens (no hover): the overlay's thumbnails and list are always
   visible, instead of only while hovered */
body.no-hover .projects-panel:not(.focus) :is(.projects-grid, .projects-list) {
  opacity: 1;
  transition: none;
}
