/* ══ MODULAR GRID (version 2) ═══════════════════════════ */
/* Clears the fixed nav, which is 56px tall — the horizontal shell does the
   same with its `top: 66px` inset, so both directions start level. Full
   bleed sideways: no side padding, the grid runs edge to edge. */
.mod-grid-outer { width:100%; padding-top:66px; }
/* The inset is what keeps the first row clear of the fixed nav. A page whose
   first row is a picture across the whole width does not want to be kept
   clear of it — it wants to start at the top of the window and let the bar sit
   on top. Set by the renderer, never here, because whether it applies is a
   question about the blocks. */
.mod-grid-outer.bleeds-under-nav { padding-top:0; }

/* ── One screen ───────────────────────────────────────────────
 * A block pinned to the middle of the window makes the page the window: it is
 * exactly what fits, whatever runs past the fold is cut there, and there is
 * nothing to scroll to. The document's own pinning does the rest — the
 * vertical renderer simply does not add page-scrolls.
 *
 * The centred block leaves the grid altogether. Out of flow it has no cell to
 * take a size from, so it states one: the spans it was given, against the cell
 * the renderer publishes. Its old cell is left empty, which is what "it is not
 * in the grid any more" looks like. */
.mod-grid-outer.one-screen  { height:100vh; overflow:hidden; box-sizing:border-box; }
.mod-hscroll-outer.one-screen { overflow:hidden; }

.mod-block.is-centred {
  /* Its own box, and the box of the group it belongs to — which for a block in
     no group is the same box. All of it in cells times the cell size, so the
     arithmetic follows the window on every resize without anything being
     recomputed in script. */
  --b-w: calc(var(--b-cs, 1) * var(--mod-cell-w, 200px) + (var(--b-cs, 1) - 1) * var(--mod-gap, 4px));
  --b-h: calc(var(--b-rs, 1) * var(--mod-cell-h, 260px) + (var(--b-rs, 1) - 1) * var(--mod-gap, 4px));
  --g-w: calc(var(--g-cs, 1) * var(--mod-cell-w, 200px) + (var(--g-cs, 1) - 1) * var(--mod-gap, 4px));
  --g-h: calc(var(--g-rs, 1) * var(--mod-cell-h, 260px) + (var(--g-rs, 1) - 1) * var(--mod-gap, 4px));

  /* The group is what gets centred; each member is offset from the middle of
     it by however far it sits from the middle of the group. A lone block's
     offset works out at zero, so it lands dead centre exactly as before. */
  --g-dx: calc(var(--g-cx, 0) * (var(--mod-cell-w, 200px) + var(--mod-gap, 4px))
               + var(--b-w) / 2 - var(--g-w) / 2);
  --g-dy: calc(var(--g-cy, 0) * (var(--mod-cell-h, 260px) + var(--mod-gap, 4px))
               + var(--b-h) / 2 - var(--g-h) / 2);

  position: fixed;
  top: 50%; left: 50%;
  transform: translate(calc(-50% + var(--g-dx)), calc(-50% + var(--g-dy)));
  z-index: 20;
  width:  var(--b-w);
  height: var(--b-h);
}

/* homepage.css pins html and body to the viewport with overflow:hidden, because
   the project grid is a fixed canvas that scrolls inside itself. A vertical page
   is the opposite — the document itself scrolls — so it opts out. Set by the
   renderer, never by the stylesheet, so a horizontal page is untouched. */
html.page-scrolls, html.page-scrolls body { height: auto; overflow: visible; }

.mod-grid-container { position:relative; width:100%; background:var(--color-bg); }

.mod-grid {
  display:grid;
  grid-template-columns: repeat(var(--mod-cols, 4), 1fr);
  grid-auto-rows: var(--mod-cell-h, auto);
  gap: var(--mod-gap, 4px);
  width:100%;
  /* No background on the grid itself — empty spaces = transparent = page bg shows through */
}

/* ── Slideshow block ──────────────────────────────────────────
 * Every slide stacked in the same box, with only opacity between them: a
 * dissolve is one property on the compositor and a swap is the same rule at
 * zero duration. Nothing is measured and nothing reflows, so it is safe inside
 * a cell that is itself being resized.
 * js/slideshow.js decides which one is `is-current`; the first is marked in
 * the markup so the picture is right before any script has run. */
.slideshow-block { position:relative; width:100%; height:100%; overflow:hidden; }
.slideshow-block:not(.is-single) { cursor:pointer; }
/* The opacity is on a wrapper, not on the <img>, because transitions.css owns
   every image's opacity on these pages — `#pageContent img.img-loaded` out-
   specifies anything written here, so slides driven directly all sat at 1 and
   the stack showed only its last picture. Two elements, two jobs. */
.slideshow-block .slide {
  position:absolute; inset:0;
  opacity:0;
  transition:opacity var(--slide-fade, var(--duration-slow)) var(--ease-out);
}
.slideshow-block .slide img { width:100%; height:100%; display:block; }
.slideshow-block .slide.is-current { opacity:1; }
/* A cut, not a fade. The rule is the same one; the duration is what differs. */
.slideshow-block[data-transition="swap"] { --slide-fade: 0s; }

.mod-block {
  position:relative;
  overflow:hidden;
  background: var(--mod-cell-bg, transparent);
  /* height fills the row via grid */
}

/* Media fills the block */
.mod-block img,
.mod-block video {
  width:100%; height:100%; display:block;
}

/* Text block content */
/* Padding proportional to the block, not to the window.
   `2.5vw` asked the viewport how much room a cell had, and the viewport does
   not know: a cell is the window's *height* over the track count, so on a short
   wide screen the padding stayed at its 32px ceiling while the cell it sat in
   shrank to 156px. Sixty per cent of the block went to padding, text-fit hit
   its floor trying to squeeze the rest into what was left, and the heading
   clipped. A percentage resolves against the block's own width, so the two now
   shrink together. The 32px ceiling is unchanged, which means every block wide
   enough to reach it looks exactly as it did. */
.mod-text {
  padding: clamp(12px, 10%, 32px);
  height:100%;
  overflow:hidden;
  box-sizing: border-box;
}
/* A page-level text colour, when the layout sets one. Two classes out-specify
   the tag styles in theme.css, which name their own tokens; :where() adds no
   specificity of its own, so a colour set inline on a selection still wins —
   which is the precedence anyone would expect, and it needs no code to hold. */
html.has-page-ink .mod-text :where(h1,h2,h3,h4,h5,h6,p,blockquote,li,figcaption,small,span,div,a) {
  color: var(--page-ink);
}

.mod-text h1 { margin-bottom:8px; }
.mod-text h2 { margin-bottom:6px; }
.mod-text h3 { margin-bottom:4px; }
.mod-text p { margin-bottom:8px; }
.mod-text p:last-child { margin-bottom:0; }

/* Description block — structured metadata */
.mod-desc-block { display:flex; flex-direction:column; justify-content:flex-start; padding:clamp(8px, 1.5vw, 25px); margin:0; overflow:hidden auto; }
.desc-row {
  display:flex; align-items:baseline;
}
.desc-cat-link {
  color:inherit; text-decoration:underline; text-underline-offset:3px;
  text-decoration-color:var(--color-border); transition:text-decoration-color .2s;
}
.desc-cat-link:hover { text-decoration-color:var(--color-ink); }

/* Edit layout link */
.mod-edit-link {
  display:block; text-align:center; padding:18px 0;
  border-top:1px solid var(--color-faint);
  font-family:var(--font-mono); font-size:8px; letter-spacing:0.18em;
  text-transform:uppercase; color:var(--color-muted); text-decoration:none;
}
.mod-edit-link:hover { color:var(--color-ink); }

/*
 * project.css — The Gentle Studio
 * Project detail pages: full bleed, block-based layout
 */

body { overflow-x: hidden; }

/* ── Page body ───────────────────────────────────────────── */
.project-page {
  padding: 66px 10px 10px; /* nav height + 10px on all sides */
}

/* ── BLOCK: Hero ─────────────────────────────────────────── */
.block-hero {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--color-faint);
}
.block-hero .hero-img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  filter: saturate(0.9) brightness(0.95);
  transform: scale(1.04);
  transition: transform 8s ease;
}
.block-hero .hero-img.loaded { transform: scale(1); }

/* Video cover — same sizing as image cover, autoplay muted loop */
.block-hero .hero-video {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  filter: saturate(0.9) brightness(0.95);
  position: absolute; top: 0; left: 0;
}
.block-hero .hero-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(to top, var(--veil-strong) 0%, transparent 60%);
  pointer-events: none;
}
.block-hero .hero-caption {
  position: absolute;
  bottom: 52px; left: 52px; right: 52px;
  display: flex; justify-content: space-between; align-items: flex-end;
}
.block-hero .hero-title {
  font-size: clamp(48px, 6vw, 88px);
  font-weight: var(--font-display-weight);
  font-style: var(--font-display-style);
  color: var(--color-overlay); line-height: 0.95;
  letter-spacing: -0.02em;
  text-shadow: 0 2px 40px var(--veil-medium);
}
.block-hero .hero-meta {
  font-family: var(--font-mono); font-size: var(--font-size-sm);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--color-on-image); text-align: right; line-height: 1.9;
}

/* ── BLOCK: Info ─────────────────────────────────────────── */
.block-info {
  padding: 80px 60px;
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 80px;
  align-items: start;
}
.info-sidebar { position: sticky; top: 80px; }
.info-label { font-family: var(--font-mono); font-size: var(--font-size-xs); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--color-muted); margin-bottom: 8px; }
.info-value { font-size: var(--font-size-md); font-weight: var(--font-display-weight); color: var(--color-ink); margin-bottom: 28px; }
.info-divider { width: 32px; height: 1px; background: var(--color-border); margin: 24px 0; }
.info-description { font-size: clamp(18px, 2vw, 24px); font-weight: var(--font-display-weight); line-height: 1.65; color: var(--color-accent); }
.edit-layout-link { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: var(--font-size-xs); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--color-muted); text-decoration: none; margin-top: 28px; transition: color var(--duration-fast); }
.edit-layout-link:hover { color: var(--color-ink); }

/* ── BLOCK: Gallery (full bleed) ─────────────────────────── */
.block-gallery { width: 100%; }
.gallery-gap-sm  { gap: 2px; }
.gallery-gap-md  { gap: var(--gap-md); }
.gallery-gap-lg  { gap: var(--gap-lg); }

/* Grid */
.layout-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols, 2), 1fr);
}
.layout-grid .gallery-item { overflow: hidden; background: var(--color-faint); }
.layout-grid .gallery-item.span-2 { grid-column: span 2; }
.layout-grid .gallery-item img {
  width: 100%; display: block; object-fit: cover;
  transition: transform var(--duration-slow) var(--ease-out), filter var(--duration-normal) ease;
  filter: saturate(0.85) brightness(0.96);
}
.layout-grid .gallery-item img:hover { transform: scale(1.04); filter: saturate(1) brightness(1); }

/* Masonry */
.layout-masonry { columns: var(--masonry-cols, 3); column-gap: var(--gallery-gap, 4px); padding: 0; }
.layout-masonry .gallery-item { break-inside: avoid; margin-bottom: var(--gallery-gap, 4px); overflow: hidden; background: var(--color-faint); }
.layout-masonry .gallery-item img { width: 100%; display: block; object-fit: cover; transition: transform var(--duration-slow) var(--ease-out), filter var(--duration-normal); filter: saturate(0.85) brightness(0.96); }
.layout-masonry .gallery-item img:hover { transform: scale(1.04); filter: saturate(1) brightness(1); }

/* Free */
.layout-free { position: relative; min-height: 600px; }
.layout-free .gallery-item { position: absolute; overflow: hidden; background: var(--color-faint); box-shadow: 0 4px 32px var(--veil-faint); }
.layout-free .gallery-item img { width: 100%; height: 100%; object-fit: cover; display: block; transition: filter var(--duration-normal); filter: saturate(0.85) brightness(0.96); }
.layout-free .gallery-item:hover img { filter: saturate(1) brightness(1); }

/* ── BLOCK: Text ─────────────────────────────────────────── */
.block-text {
  padding: 80px 60px;
}
.block-text-inner {
  max-width: var(--text-maxwidth, 800px);
}
.block-text-inner.align-left   { margin-right: auto; }
.block-text-inner.align-center { margin: 0 auto; text-align: center; }
.block-text-inner.align-right  { margin-left: auto; text-align: right; }

.block-text-inner h1 { margin-bottom: 24px; }
.block-text-inner h2 { margin-bottom: 20px; }
.block-text-inner h3 { margin-bottom: 16px; }
.block-text-inner p  { margin-bottom: 20px; }
.block-text-inner p:last-child { margin-bottom: 0; }
.block-text-inner strong { font-weight: 500; color: var(--color-ink); }
.block-text-inner em { font-style: italic; }

/* ── BLOCK: Video ────────────────────────────────────────── */
.block-video { width: 100%; background: var(--color-ink); }
.block-video.padded { padding: 60px; }
.video-wrapper { position: relative; width: 100%; background: var(--color-media-bg); }
.video-wrapper iframe,
.video-wrapper video {
  position: absolute; top: 0; left: 0;
  width: 100%; height: 100%; border: none; display: block;
}

/* ── BLOCK: Spacer ───────────────────────────────────────── */
.block-spacer { background: var(--color-bg); }

/* ── BLOCK: Divider ──────────────────────────────────────── */
.block-divider { padding: 0 60px; }
.block-divider hr { border: none; border-top: 1px solid var(--color-border); }

/* ── Fixed Prev/Next Arrows ──────────────────────────────── */
.project-arrow {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  transition: opacity var(--duration-fast);
  opacity: 0.4;
}
.project-arrow:hover { opacity: 1; }
.project-arrow-prev { left: 28px; }
.project-arrow-next { right: 28px; }

.arrow-btn {
  width: 44px; height: 44px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 18px; color: var(--color-ink);
  transition: background var(--duration-fast), border-color var(--duration-fast);
}
.project-arrow:hover .arrow-btn {
  background: var(--color-ink); color: var(--color-bg);
  border-color: var(--color-ink);
}
.arrow-title {
  font-family: var(--font-mono); font-size: 7px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--color-muted);
  max-width: 80px; text-align: center; line-height: 1.5;
  opacity: 0; transition: opacity var(--duration-fast);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.project-arrow:hover .arrow-title { opacity: 1; }

/* ── Prev/Next Footer ────────────────────────────────────── */
.project-next-footer {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--color-border);
  margin-top: 0;
}
.next-footer-item {
  position: relative;
  overflow: hidden;
  height: 300px;
  text-decoration: none;
  display: block;
  background: var(--color-faint);
}
.next-footer-item + .next-footer-item {
  border-left: 1px solid var(--color-border);
}
.next-footer-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  filter: saturate(0.75) brightness(0.85);
  transition: transform var(--duration-slow) var(--ease-out), filter var(--duration-normal);
  transform: scale(1.02);
}
.next-footer-item:hover .next-footer-img { transform: scale(1.04); filter: saturate(1) brightness(0.9); }
.next-footer-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(to top, var(--veil-strong) 0%, var(--veil-faint) 60%, transparent 100%);
}
.next-footer-label {
  position: absolute; bottom: 0; left: 0; right: 0; padding: 32px 36px;
  display: flex; flex-direction: column; gap: 6px;
}
.next-footer-dir {
  font-family: var(--font-mono); font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--color-on-image);
}
.next-footer-title {
  font-family: var(--font-display); font-size: clamp(20px, 2.5vw, 32px);
  font-weight: var(--font-display-weight); font-style: var(--font-display-style);
  color: var(--color-overlay); line-height: 1.0;
}
.next-footer-item.prev .next-footer-label { align-items: flex-start; }
.next-footer-item.next .next-footer-label { align-items: flex-end; text-align: right; }

/* Single item spans full width */
.project-next-footer.single .next-footer-item { grid-column: 1 / -1; }

/* ── Page title / eyebrow (site pages) ───────────────────── */
.page-eyebrow {
  margin-bottom: 48px;
}
.page-title-display {
  margin-bottom: 56px;
}

/* ── Error / empty states ────────────────────────────────── */
.error-state { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 80vh; text-align: center; gap: 20px; }
.error-state h2 { }
.error-state p  { }

/* ══ MODULAR GRID — see top of file for base rules ════ */

/* ════════════════════════════════════════════════════════════
   HORIZONTAL SCROLL — modular grid rotated 90°
   ════════════════════════════════════════════════════════════ */
.mod-hscroll-outer {
  position: fixed;
  top: 66px; left: 10px; right: 10px; bottom: 10px;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none;
  background: var(--color-bg);
}
.mod-hscroll-outer::-webkit-scrollbar { display: none; }

.mod-grid-horizontal {
  display: grid;
  grid-template-rows: repeat(var(--mod-rows, 4), 1fr);
  grid-auto-flow: column;
  grid-auto-columns: var(--mod-cell-w, 200px);
  gap: var(--mod-gap, 4px);
  height: 100%;
  width: max-content;
  padding: 0;
}

.mod-grid-horizontal .mod-block {
  position: relative;
  overflow: hidden;
  background: var(--mod-cell-bg, transparent);
}
.mod-grid-horizontal .mod-block img,
.mod-grid-horizontal .mod-block video {
  width: 100%; height: 100%; display: block;
}

/* End panel — spans all rows */
/* ── End panel ───────────────────────────────────────────────
 * Two full-bleed halves at the end of a horizontal project: next above,
 * previous below. Each carries that project's own cover, faded back so the
 * type reads over it, with the title and client set vertically.
 *
 * It is placed on an explicit grid column by the renderer. Left to auto-flow it
 * dropped into the first gap wide enough to hold it, which in a column-flow
 * grid is rarely the end — it sat underneath the rightmost images.
 */
.mod-end-panel {
  grid-row: 1 / -1;

  /* How far the type keeps off the ends of the panel. One value, because the
     copyright in the gutter has to finish level with the previous project's
     label beside it — two numbers would drift apart the first time either
     changed. */
  --end-inset: 28px;
  --end-gutter-w: 20px;
  /* And on the other side of the gutter's type, between it and the cards. */
  --end-gutter-clear: 10px;
  /* What a card keeps between its own edge and its type. */
  --end-edge: 20px;
  /* Half of what the Back to Top button used to hold open between the cards,
     now that it has moved out of the stack and into the gutter. */
  --end-card-gap: 34px;

  /* A cell for the cards, and the gutter beside them. The cards are pinned to
     the grid's own column width and the gutter takes what its type needs, so
     the panel is the sum — the gutter is not a column of its own, a grid track
     here being a full cell wide. */
  width: max-content;
  display: flex; flex-direction: row;
  gap: 0;
  padding: 0;
  box-sizing: border-box;
  position: relative;
}

.end-stack {
  /* The standard width, not this project's own track — see END_CARD_TRACKS in
     pages/project.html. Falls back to the track if the renderer is older. */
  flex: 0 0 var(--end-card-w, var(--mod-cell-w, 200px));
  display: flex; flex-direction: column;
  gap: var(--end-card-gap);
}

/* Too narrow for horizontal type, so everything in it is set the way the
   titles are — turned 270°, reading bottom to top. Back to Top rides level
   with the seam between the two cards; the mark sits at the bottom.
   It sizes to its own type rather than to --end-gutter-w, which is only the
   least separation the project will accept from the cards. */
.end-gutter {
  flex: 0 0 auto;
  /* Clear on the left, so the type never crowds the last block of the project.
     min-width holds the same distance open should the padding ever go. */
  padding-left: var(--end-gutter-w);
  padding-right: var(--end-gutter-clear);
  min-width: var(--end-gutter-w);
  box-sizing: content-box;
  display: flex; flex-direction: column;
  align-items: flex-end;         /* the lines of type sit against the cards */
  justify-content: center;       /* Back to Top level with the seam */
  position: relative;
}
.end-copyright {
  position: absolute;
  /* The padding box is what `right` anchors to, so the gutter's own
     padding-right does not move this — it has to be told the same distance. */
  right: var(--end-gutter-clear);
  /* A pixel inside the cards' inset: the label beside it is centred in its
     card and lands a touch lower, and this is what makes the two read level.
     Still derived from the one inset, so it follows if that ever moves. */
  bottom: calc(var(--end-inset) - 1px);
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
}

.end-card {
  position: relative;
  flex: 1;                       /* half each; a lone card takes the whole panel */
  min-height: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  /* Flush left, so both titles start on the same edge rather than each being
     centred in its own half and drifting apart. */
  justify-content: flex-start;
  padding-left: var(--end-edge);
  box-sizing: border-box;
  text-decoration: none;
  color: var(--color-ink);
}

/* Horizontal, against the two vertical titles it sits between — and the gap
   between them at the same time. The body font, not the display one the titles
   use. */
/* The mark to the left of the words, centred against their length. Only the
   words are turned: an arrow means a direction on the screen, not a direction
   in the text, so turning it with the type would have it pointing down. */
.end-more {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  padding: 0;
  color: inherit;
  text-decoration: none;
  transition: color var(--duration-fast);
  /* It is a button, so undo what a button brings with it */
  background: none;
  border: 0;
  cursor: pointer;
}
.end-more-text {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
}
.end-more-mark {
  font-weight: 700;
  line-height: 1;
}
.end-more:hover small { color: var(--color-ink); }
/* Nothing to go back to. Hidden rather than removed so the gutter keeps the
   width its type gave it and the cards do not shift. */
.end-more.is-idle { visibility: hidden; }

/* No cover behind the type: these read as two title blocks, and an image under
   them was competing with the words rather than supporting them. */
/* The project's own cover, held back until the card is hovered — so the panel
   is two titles at rest, and the picture is the answer to reaching for one.
   Fills the card at whatever size the card already is.
   The opacity sits on a wrapper, not on the <img>, because transitions.css
   owns every image's opacity on a project page — it fades them in from 0 on
   load. Two elements, two jobs, and neither has to out-specify the other. */
.end-card-bg {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-normal) var(--ease-out);
}
.end-card:hover .end-card-bg { opacity: var(--end-card-cover-opacity, 0.5); }
.end-card-bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* The dim belongs to the type, not the whole card. On the card it multiplied
   against the cover coming up underneath, so a 50% cover arrived at 30%. */
.end-card-text { transition: opacity var(--duration-fast); }
.end-card:hover .end-card-text { opacity: 0.6; }

/* Turned 270°: writing-mode plus a half-turn, which reads bottom to top — the
   same pair js/block-style.js uses for a block at that rotation.
   The type is the title block's own — .desc-title, .desc-label, .desc-val — so
   these cards follow whatever the Style Editor sets for a project title rather
   than carrying sizes of their own. */
.end-card-text {
  position: relative;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  display: flex;
  align-items: baseline;
  gap: 18px;
  max-height: 100%;
  padding: var(--end-inset) 0;
  box-sizing: border-box;
}
/* No nowrap: a long title wraps to a second vertical line rather than running
   off the card. It cannot rely on text-fit to catch it either — the card centres
   its content, so an overflow spills from both ends and scrollHeight never
   reports it. */
.end-card-text .desc-title { white-space: normal; }
.end-card-text .desc-row   { gap: 8px; }

/* Small enough to stay out of the way of the two cards, since the panel is now
   their whole area. */
/* ── Turned a quarter ──────────────────────────────────────
   A page that scrolls down ends in a row, not a column, and that row is this
   same panel rotated clockwise: the gutter goes from the left edge to the top,
   still the side the reader arrives from; the cards go from stacked to side by
   side; and the type comes upright. Anything not named here it keeps, which is
   the point of turning one panel rather than writing a second.

   The pair turns with it. Next is the card furthest from the gutter either
   way, so a quarter turn puts previous on the left — which is the order a
   footer has always read in anyway. */
.mod-end-panel.is-down {
  width: auto;
  height: var(--end-panel-h, 400px);
  flex-direction: column;
}

.is-down .end-gutter {
  min-width: 0;
  padding-right: 0;
  min-height: var(--end-gutter-w);
  /* Clear above instead of to the left — either way, the side the project is
     on. */
  padding-left: 0;
  padding-top: var(--end-gutter-w);
  flex-direction: row;
  align-items: flex-end;      /* the line of type sits against the cards */
  justify-content: center;    /* Back to Top on the seam between them */
}

.is-down .end-copyright {
  right: auto;
  bottom: 0;
  left: calc(var(--end-inset) - 1px);
  writing-mode: horizontal-tb;
  transform: none;
}

.is-down .end-more-text { writing-mode: horizontal-tb; transform: none; }
/* Back up the page rather than back along it. */
.is-down .end-more-mark { transform: rotate(90deg); }

.is-down .end-stack {
  flex: 1 1 auto;
  min-height: 0;
  flex-direction: row;
}

.is-down .end-card {
  flex: 1;
  min-width: 0;
  align-items: flex-start;    /* flush to the top, as they were flush left */
  padding-left: 0;
  padding-top: var(--end-edge);
}

.is-down .end-card-text {
  writing-mode: horizontal-tb;
  transform: none;
  flex-direction: column;
  align-items: flex-start;
  max-height: none;
  max-width: 100%;
  padding: 0 var(--end-inset);
}

.mod-end-panel .end-edit {
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-muted);
  text-decoration: none;
  margin-top: 16px;
}
.mod-end-panel .end-edit:hover { color: var(--color-ink); }

/* ── Utilities extracted from inline styles ──────────────
 * Previously style="" attributes in project.html / page.html. Kept here so
 * the public pages can be restyled without editing their markup.
 */
.embed-frame  { position: relative; width: 100%; height: 100%; }
.embed-iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: none; }

.back-link {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--color-ink); text-decoration: none; margin-top: 12px;
}
.link-ink   { color: var(--color-ink); }
.link-muted { color: var(--color-muted); text-decoration: none; }
.link-underline { text-decoration: underline; }

.edit-layout-bar  { text-align: center; padding: 20px 0; border-top: 1px solid var(--color-faint); }
.edit-layout-link {
  font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--color-muted); text-decoration: none;
}

.hero-img-fill { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: saturate(.9) brightness(.95); }
.info-mono         { font-family: var(--font-mono); font-size: 13px; }
.info-muted-italic { color: var(--color-muted); font-style: italic; }
.section-note {
  padding: 10px 60px; font-family: var(--font-mono); font-size: 9px;
  letter-spacing: 0.12em; color: var(--color-muted); text-transform: uppercase;
}
.video-full   { width: 100%; display: block; max-height: 90vh; background: var(--color-media-bg); }
.footer-blank { width: 100%; height: 100%; background: var(--color-faint); }

/* Shown when a standard page has no saved layout. Lived in pages/page.html
   until index.html could render a standard page at the site root too. */
.page-fallback {
  max-width: 860px;
  margin: 0 auto;
  padding: 120px 52px 100px;
}
.page-description {
  font-family: var(--font-display);
  font-size: clamp(16px, 2vw, 22px);
  font-weight: 300;
  line-height: 1.7;
  color: var(--color-accent);
  max-width: 640px;
}
.page-fallback-note {
  margin-top: 48px; font-family: var(--font-mono); font-size: 9px;
  letter-spacing: 0.14em; color: var(--color-muted);
}

/* Hover was previously two inline onmouseover/onmouseout handlers */
.btn-back-top {
  background: none; border: 1px solid var(--color-border); padding: 8px 16px; cursor: pointer;
  font-family: var(--font-mono); font-size: var(--font-size-xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--color-muted); transition: all .2s;
}
.btn-back-top:hover { background: var(--color-ink); color: var(--color-bg); }

/* ── BLOCK: Projects ─────────────────────────────────────
   The project grid living inside a layout block. The grid engine measures
   this box, so it only has to be a box — homepage.css styles what is in it. */
.projects-block { position: relative; width: 100%; height: 100%; overflow: hidden; }
/* Positioning only — the boxed look went with the one on the nav. */
.projects-filter {
  position: absolute; top: 8px; left: 50%; transform: translateX(-50%); z-index: 5;
  display: flex; white-space: nowrap;
}
.projects-filter:empty { display: none; }
