.single-object__hero-wrap {
  background-color: var(--brand-color-dark);
}

.single-object__hero {
  position: relative;
  overflow: clip;
  margin: 0;
  width: 100%;
  height: 100svh;
  max-height: 100svh;
  background-color: var(--brand-color-dark);
}

.single-object__hero img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Parallax + slight scale-up as the hero scrolls out of view — shares
   the single-hero-parallax keyframes defined in header.css (loaded on
   every page) with .single-post__hero img, gated behind @supports so
   browsers without animation-timeline (Safari, Firefox as of writing)
   keep the static image instead of an ambiguous fallback. */
@supports (animation-timeline: scroll()) {
  .single-object__hero img,
  .single-object__hero-video {
    animation: single-hero-parallax linear both;
    animation-timeline: scroll(root);
    animation-range: 0 100vh;
  }
}

/* Vimeo background loop over the featured image — invisible until
   object-hero-video.js sees real playback (.is-playing), so a slow or
   blocked video just leaves the image in place. */
.single-object__hero-video {
  position: absolute;
  inset: 0;
  overflow: clip;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1s var(--theme-ease-signature);
}

.single-object__hero-video.is-playing {
  opacity: 1;
}

/* object-fit: cover for an iframe — keep 16:9 but always at least as
   large as the hero in both directions, centered, overflow clipped. */
.single-object__hero-video-frame {
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100%, 100svh * 16 / 9);
  height: max(100%, 100vw * 9 / 16);
  border: 0;
  translate: -50% -50%;
}

/* An uploaded file renders as a plain <video> instead of an injected
   iframe (template-parts/object-hero.php) — object-fit: cover works
   natively here, so none of the manual 16:9-oversize math above is
   needed; this overrides it back to a plain full-cover fill. */
video.single-object__hero-video-frame {
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  translate: none;
}

/* One row: availability badge (Aanbod only) first, then the status
   badge(s) — read together as "Onder bod · Aanbod". */
.single-object__statuses {
  flex: none;
  align-self: flex-end;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.4rem;
}

.single-object__status {
  padding: 0.5em 1em;
  background: var(--theme-color-accent);
  color: #fff;
  text-transform: uppercase;
  font-size: 0.75rem;
  font-weight: 500;
  text-decoration: none;
  transition: opacity 0.3s var(--theme-ease-signature);
}

.single-object__status:hover {
  opacity: 0.8;
}

/* Matches .object-card__status's per-status colors (object-card.css)
   so the badge reads the same on the card grid and the object's own
   hero. */
.single-object__status--portefeuille {
  background: var(--brand-color-secondary);
}

.single-object__status--gerealiseerd {
  background: var(--brand-color-primary);
}

.single-object__hero-body {
  z-index: 1;
  text-transform: uppercase;
  position: absolute;
  bottom: 0;
  width: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--theme-space-xs);
  padding: calc(var(--theme-section-padding) * 2);
  background-image: linear-gradient(0deg, var(--brand-color-dark), transparent);
  color: var(--brand-color-light);
}

.single-object__hero-body-text {
  width: 100%;
  flex: 1 auto;
}


.single-object__hero-location {
  display: block;
  width: fit-content;
  margin: 0 0 1em;
  font-size: 0.875rem;
  font-weight: 500;
  color: color-mix(in srgb, var(--brand-color-light) 70%, transparent);
  text-decoration: none;
}

/* Only an <a> when the location section exists (jumps to #locatie). */
a.single-object__hero-location:hover {
  color: var(--brand-color-light);
  text-decoration: underline;
}

.single-object__title {
  margin: 0;
  font-size: clamp(1.75rem, 4vw, 3rem);
  font-weight: 700;
  /* Uppercase-only title: trim the line box to cap height on top and
     the baseline at the bottom, so the hero padding measures from the
     visible letters, not the font's built-in ascender/descender space.
     Browsers without text-box (Firefox, as of writing) keep the normal
     line box. */
  text-box: trim-both cap alphabetic;
}

/* object_hero_title is wysiwyg — bold = accent color, same convention
   as the front-page headings (section-about/section-realestate). */
.single-object__title strong {
  color: var(--theme-color-accent);
}

/* In-page section nav (object-section-nav.php), right below the tagline.
   Sticky within .single-object__nav-scope (description through Locatie,
   so it scrolls away before the form). It sticks in the fixed header's
   own slot, above it: the link block (columns 3-6) lands exactly on the
   header's menu cell, the object's name its logo cell — so it covers the
   whole header, which auto-hides behind it (header-autohide.js). The bar
   is
   transparent; the link list and buttons carry their own background.
   Link hover/current states come from header.css's shared nav :is() list
   (same as the header menu); the current link is set by
   object-section-nav.js. */
.single-object__section-nav {
  position: sticky;
  top: var(--theme-section-padding);
  /* Above the fixed header (z-index 50, header.css). */
  z-index: 3;
  /* Same columns as the header it slides over: the object's name over
     the logo cell (columns 1-2), the link block over the menu cell
     (columns 3-6). */
  display: grid;
  grid-template-columns: repeat(var(--theme-grid-columns), 1fr);
  align-items: center;
  min-height: var(--theme-header-height);
  margin: 0 var(--theme-section-padding);
}

/* Sticky page-background bar above the stuck section nav — same as the
   other sections' frame bars (e.g. about.css's .section-team__frame):
   covers the strip between the viewport top and the header/nav, so
   content doesn't show scrolling past above them. Negative margin: takes
   no room in the layout. Stays put when the header hides — the nav keeps
   the header's slot. */
.single-object__nav-scope::before {
  content: "";
  display: block;
  position: sticky;
  top: 0;
  /* Below the fixed header (50) and the section nav (60), above the
     scrolling content. */
  z-index: 2;
  height: var(--theme-section-padding);
  margin-bottom: calc(-1 * var(--theme-section-padding));
  background-color: var(--theme-color-background);
}

/* Link block under the header's menu cell (columns 3-6). */
.single-object__section-nav-list {
  grid-column: 3 / span 4;
  grid-row: 1;
  display: flex;
  align-self: stretch;
  gap: var(--theme-space-sm);
  margin: 0;
  padding: 0 var(--theme-space-md);
  list-style: none;
  background-color: var(--brand-color-light);
  /* The vertical brand-grid lines' colour (brand-grid.css), at half its
     strength — a subtle edge. */
  box-shadow: 0 1px 0 var(--object-nav-edge);
}

.single-object__section-nav-list li {
  align-content: center;
}

/* Object name in the 2 left columns — over the header's logo cell once
   stuck: street name + place, same light background as the link block;
   a link back to the top of the page. */
.single-object__section-nav-title {
  grid-column: 1 / span 2;
  grid-row: 1;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.15em;
  margin: 0;
  padding: 0 var(--theme-section-padding);
  background-color: var(--brand-color-dark);
  box-shadow: 0 1px 0 var(--object-nav-edge);
  line-height: 1.2;
  /* A link back to the top (the hero) — keeps the text look, accent on
     hover like the header menu. */
  color: var(--brand-color-light);
  text-decoration: none;
  transition: color 0.3s var(--theme-ease-signature);
}

.single-object__section-nav-title:is(:hover, :focus-visible) {
  color: var(--theme-color-accent);
}

.single-object__section-nav-street {
  font-weight: 700;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.single-object__section-nav-place {
  font-size: 0.8125rem;
  text-transform: uppercase;
  color: var(--theme-color-text-muted);
}

/* Small screens: one scrollable row instead of wrapping. */
@media (width <= 768px) {
  .single-object__section-nav {
    display: flex;
    gap: var(--theme-space-sm);
    overflow-x: auto;
    scrollbar-width: none;
  }

  .single-object__section-nav-title {
    flex: none;
  }

  /* Without this it only shrinks to fit its own tabs, leaving the page's
     plain background showing past it instead of its light background
     spanning the rest of the bar — still scrolls via the parent's
     overflow-x when the tabs themselves don't fit. */
  .single-object__section-nav-list {
    flex: 1;
  }

  .single-object__section-nav-list li {
    flex: none;
  }
}


/* Height the stuck section nav covers at the top of the viewport: its bar
   (section padding) + the nav itself (= the header height). The sections
   the nav jumps to (#omschrijving, #video, #fotogalerij, #locatie) take
   this as their top padding, and jumps land them flush at the viewport
   top (no scroll-margin; the article opts out of smooth-scroll.js's
   default offset via data-scroll-offset="0") — so their content always
   starts clear of the nav, whether jumped to or scrolled to. */
.single-object {
  --object-nav-offset: calc(var(--theme-section-padding) + var(--theme-header-height));
  /* Section nav's bottom edge: the brand-grid line colour at half strength. */
  --object-nav-edge: color-mix(in srgb, var(--theme-grid-line-color) 50%, transparent);
}

/* Full-width band between hero and layout carrying object_tagline. */
.single-object__tagline {
  padding: calc(var(--theme-space-xl) * 2) calc(var(--theme-section-padding) * 2);
  background: var(--brand-color-dark);
  color: var(--brand-color-sand);
  text-align: left;
}

.single-object__tagline-text {
  font-size: clamp(2.25rem, 6.5vw, 2.75rem);
  line-height: 1.4;
  text-wrap: balance;
}

/* Placeholder until the form is built — keeps the section's real
   footprint so the page rhythm can be judged. */
.single-object__form {
  min-height: 50svh;
  padding: var(--theme-space-xl) var(--theme-section-padding);
  background: var(--brand-color-dark);
  color: var(--brand-color-light);
}

.single-object__form-heading {
  margin: 0;
  text-align: center;
}

/* Same 6-column setup as .brand-grid (see brand-grid.css) copied
   directly here rather than applying that class — .single-object__content-col
   spans col-4, .single-object__meta-col spans col-2 (set in
   single-object.php markup via the shared .col-N utilities). */
.single-object__layout {
  display: grid;
  grid-template-columns: repeat(var(--theme-grid-columns), 1fr);
  /* gap: var(--theme-space-lg); */
  margin: 0 auto;
  padding: var(--theme-section-padding);
  padding-top: var(--object-nav-offset);
}

/* Same sticky setup as the meta column below: the -col wrapper is the
   grid item (stretches to the row's height, set by whichever of the two
   columns is taller) and the content inside it sticks at the same top as
   .single-object__meta — so the shorter of the two stays in view while
   the longer one scrolls past. */
.single-object__content-col {
  position: relative;
}

.single-object__content {
  position: sticky;
  /* Just below the stuck section nav (its bar + itself). */
  top: var(--object-nav-offset);
  max-width: 60rem;
  padding: var(--theme-section-padding);
  font-size: var(--theme-font-size-copy);
}

/* "Meer info" / brochure / form buttons in one row below the description. */
.single-object__content-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--theme-space-xs);
  margin-top: var(--theme-space-sm);
}

.single-object__content > p:first-child {
  font-weight: 500;
  font-size: 1.1em;
  /* Trim the intro line's top/bottom to cap height + baseline, so it
     lines up with the column's padding edge (and the meta beside it)
     instead of the font's built-in line-box space. Browsers without
     text-box keep the normal line box. */
  text-box: trim-both cap alphabetic;
}

/* Wraps .single-object__meta so it has a full-height ancestor to stick
   within — as a grid item, this column already stretches to match
   .single-object__content's height (::before reinforces that height so
   the column never collapses to the meta list's own shorter content),
   letting the sticky meta list travel down to bottom: 3rem as the page
   scrolls instead of being stuck at the top of a short column. */
.single-object__meta-col {
  position: relative;
  /* Top padding lives on the sticky .single-object__meta itself (like
     .single-object__content's), so both columns line up both in place
     and while stuck. */
  padding: 0 0 var(--theme-section-padding);
}

/* .single-object__meta-col::before {
  content: "";
  display: block;
  height: 20%;
} */

/* Sticky container for the "Kenmerken" (template-parts/object-meta.php). */
.single-object__meta {
  position: sticky;
  /* Same top + top padding as .single-object__content. */
  top: var(--object-nav-offset);
  padding-top: var(--theme-section-padding);
  padding-left: var(--theme-section-padding);
}

/* Rows, label above value, two per line where they fit — decided per
   item, not for the whole list: each item aims for half the width, but
   never narrower than its own label/value (min-width: max-content). Two
   short ones share a line; one that doesn't fit in half takes a line of
   its own and the next ones pair up again. */
.single-object__meta-list {
  --meta-gap: var(--theme-section-padding);
  display: flex;
  flex-wrap: wrap;
  gap: var(--theme-space-xs) var(--meta-gap);
  margin: 0;
}

.single-object__meta-item {
  flex: 1 1 calc(50% - var(--meta-gap) / 2);
  min-width: max-content;
  padding-right: calc(var(--theme-section-padding) * 0.25);
}

/* Long free text (ligging, bereikbaarheid, voorzieningen) and the price
   take the full width instead of being squeezed into half a column. */
.single-object__meta-item--wide {
  flex-basis: 100%;
  /* Long free text wraps within the column instead of setting its
     width; a single overlong word still breaks. */
  min-width: 0;
  overflow-wrap: anywhere;
  padding-right: var(--theme-section-padding);
}

.single-object__meta-item--price .single-object__meta-value {
  font-size: clamp(1.5rem, 2.5vw, 2rem);
  font-weight: 700;
  line-height: 1.2;
  color: var(--theme-color-text);
}

.single-object__meta-label {
  margin: 0 0 0.25em;
  text-transform: uppercase;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--brand-color-sand);
  letter-spacing: 0.5px;
}

.single-object__meta-value {
  margin: 0;
  color: var(--theme-color-text-muted, color-mix(in srgb, var(--theme-color-text) 60%, transparent));
}

/* Was max-width: 48rem (768px) along with the hero-body/statuses
   rules below — moved up to 1024px on its own since .single-object__meta-col
   (col-2, a third of the page) got visibly cramped there before content
   actually needed to stack, its 2-column meta grid squeezed into a
   narrow sidebar. Full-width stacking sidesteps that entirely instead
   of trying to fix the cramped in-between state — once stacked, the
   description comes first and the kenmerken follow (order below). */
@media (width <= 1024px) {
  .single-object__content-col,
  .single-object__meta-col {
    grid-column: span var(--theme-grid-columns);
  }

  /* Stacked: description first, kenmerken below it (desktop keeps the
     source order, meta left of the content). */
  .single-object__meta-col {
    order: 1;
  }

  /* The trim only exists to line the intro up with the meta column
     beside it — nothing to line up with once stacked. */
  .single-object__content > p:first-child {
    text-box: normal;
  }

  /* Fallback for the sticky/height-matching trick above (documented on
     .single-object__meta-col): it only works when meta-col shares a
     row-stretched grid row with .single-object__content, which stops
     being true once both go full-width and stack into separate rows.
     meta-col's own height then collapses toward its ::before's now-0
     percentage height instead of matching its real (taller, once
     stacked) content — and since position: relative doesn't clip,
     .single-object__meta (sticky) and the contact card after it
     visually overflow past that short box into .single-object__content
     below. Turning off the sticky/spacer mechanism here lets meta-col
     size itself to its own real content instead. */
  .single-object__meta-col::before {
    display: none;
  }

  .single-object__meta,
  .single-object__content {
    position: static;
    bottom: auto;
  }
}

@media (max-width: 48rem) {
  .single-object__hero-body {
    flex-direction: column;
    align-items: flex-start;
  }

  .single-object__statuses {
    align-self: flex-start;
    justify-content: flex-start;
  }
}

/* Full-width section on the same 6-column grid as .single-object__layout,
   so the card inside keeps its span-3 width instead of stretching. */
.single-object__contact {
  display: grid;
  grid-template-columns: repeat(var(--theme-grid-columns), 1fr);
  padding: var(--theme-section-padding);
}

/* Directly above the (dark) form section, the card overlaps the form's
   top edge a little — the section drops its bottom padding and pulls the
   form up under it; the form adds the same amount to its top padding so
   its own heading keeps clear of the card. */
.single-object__contact:has(+ .single-object__form) {
  position: relative;
  z-index: 1;
  padding-bottom: 0;
  margin-bottom: calc(-1 * var(--theme-space-xl));
}

.single-object__contact + .single-object__form {
  padding-top: calc(var(--theme-space-xl) * 2);
}

/* Gallery section (wraps the slider): inset by the section padding like
   the video and location, top clears the stuck section nav. */
.single-object__gallery {
  padding: var(--theme-section-padding);
  padding-top: var(--object-nav-offset);
}

/* Playable video above the gallery: full grid width, 16:9. */
.single-object__video {
  padding: var(--theme-section-padding);
  padding-top: var(--object-nav-offset);
}

.single-object__video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  /* Full content width would otherwise size the box however wide the
     column is, with no ceiling — on a wide/short viewport a 16:9 box
     at that width can end up taller than the screen itself. Capping
     max-width to what a 16:9 box spanning the viewport height allows
     shrinks (and centers) it instead, so the frame never exceeds
     roughly one screen tall while staying true 16:9 either way. */
  max-width: calc((100svh - 2 * var(--theme-section-padding)) * 16 / 9);
  margin-inline: auto;
  border: var(--theme-line-width) solid var(--theme-line-color);
  background-color: var(--brand-color-dark);
  /* The control bar slides down out of the frame when hidden. */
  overflow: clip;
}

.single-object__video-frame iframe,
.single-object__video-frame video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  /* Never takes pointer events — our own controls drive the player
     (object-video.js), and an iframe would otherwise swallow wheel events
     and stall Lenis' smooth scroll. */
  pointer-events: none;
}

/* Overlay (object-video.js): transparent, covers the player — starts it,
   then toggles play/pause on click. The big play symbol shows while
   paused only. */
.single-object__video-activate {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

/* Play symbol: light square (::before, square edges like the site's
   buttons) with a dark right-pointing triangle (::after), stacked in the
   button's single grid cell. */
.single-object__video-activate::before,
.single-object__video-activate::after {
  content: "";
  grid-area: 1 / 1;
  transition: transform 0.3s var(--theme-ease-signature), opacity 0.3s var(--theme-ease-signature);
}

.single-object__video-activate::before {
  width: 5rem;
  height: 5rem;
  background-color: color-mix(in srgb, var(--brand-color-dark) 90%, transparent);
  border: 1px solid;
  backdrop-filter: blur(10px);
}

.single-object__video-activate::after {
  width: 1.5rem;
  height: 1.75rem;
  margin-left: 0.3rem; /* optical centering of the triangle */
  background-color: var(--brand-color-accent);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.single-object__video-activate:is(:hover, :focus-visible)::before,
.single-object__video-activate:is(:hover, :focus-visible)::after {
  transform: scale(1.1);
}

.single-object__video-activate::before,
.single-object__video-activate::after {
  opacity: 1;
}

.single-object__video-frame.is-playing .single-object__video-activate::before,
.single-object__video-frame.is-playing .single-object__video-activate::after {
  opacity: 0;
}

/* Control bar — slides up into the frame while the video plays, and back
   down out of it when paused (the big play button takes over then). On
   hover screens it also slides away while playing unless the pointer is
   over the video or a control has focus. */
.single-object__video-bar {
  position: absolute;
  /* inset: auto 0 0; */
  left: var(--theme-section-padding);
  right: var(--theme-section-padding);
  bottom: var(--theme-section-padding);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--theme-space-xs);
  padding: 0.5rem var(--theme-space-xs);
  background-color: color-mix(in srgb, var(--brand-color-dark) 65%, transparent);
  backdrop-filter: blur(var(--theme-blur));
  color: var(--brand-color-light);
  translate: 0 200%;
  transition: translate 0.4s var(--theme-ease-signature);
  border: 1px solid var(--theme-line-color);
}

.single-object__video-frame.is-playing .single-object__video-bar {
  translate: 0 0;
}

@media (hover: hover) {
  .single-object__video-frame.is-playing:not(:hover, :focus-within) .single-object__video-bar {
    translate: 0 200%;
  }
}

.single-object__video-control {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition: color 0.2s var(--theme-ease-signature);
}

.single-object__video-control:is(:hover, :focus-visible) {
  color: var(--theme-color-accent);
}

.single-object__video-icon {
  width: 1.25rem;
  height: 1.25rem;
}

/* Swap play/pause and sound/muted icons by state. */
.single-object__video-icon--pause,
.single-object__video-frame.is-playing .single-object__video-icon--play,
.single-object__video-icon--muted,
.single-object__video-frame.is-muted .single-object__video-icon--sound {
  display: none;
}

.single-object__video-frame.is-playing .single-object__video-icon--pause,
.single-object__video-frame.is-muted .single-object__video-icon--muted {
  display: block;
}

/* Seek slider: takes the remaining width. Square track + square handle
   (native sliders are rounded), played part in the accent — the fill is
   --seek-progress (0-100%), kept up to date by object-video.js. */
.single-object__video-seek {
  --seek-progress: 0%;
  --seek-track: 4px;
  --seek-thumb: 0.875rem;
  flex: 1;
  min-width: 0;
  height: var(--seek-thumb);
  margin: 0;
  background: transparent;
  cursor: pointer;
  appearance: none;
}

.single-object__video-seek::-webkit-slider-runnable-track {
  height: var(--seek-track);
  border-radius: 0;
  background: linear-gradient(to right, var(--theme-color-accent) var(--seek-progress), color-mix(in srgb, var(--brand-color-light) 30%, transparent) var(--seek-progress));
}

.single-object__video-seek::-moz-range-track {
  height: var(--seek-track);
  border-radius: 0;
  background: color-mix(in srgb, var(--brand-color-light) 30%, transparent);
}

.single-object__video-seek::-moz-range-progress {
  height: var(--seek-track);
  border-radius: 0;
  background: var(--theme-color-accent);
}

.single-object__video-seek::-webkit-slider-thumb {
  width: var(--seek-thumb);
  height: var(--seek-thumb);
  /* Centre the handle on the thin track. */
  margin-top: calc((var(--seek-track) - var(--seek-thumb)) / 2);
  border: 0;
  border-radius: 0;
  background: var(--brand-color-light);
  appearance: none;
}

.single-object__video-seek::-moz-range-thumb {
  width: var(--seek-thumb);
  height: var(--seek-thumb);
  border: 0;
  border-radius: 0;
  background: var(--brand-color-light);
}

.single-object__video-seek:focus-visible {
  outline: var(--theme-line-width) solid var(--theme-color-accent);
  outline-offset: 2px;
}

.single-object__video-time {
  flex: none;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

/* Fullscreen (our frame, not the iframe): fill the screen, keep 16:9
   inside it. */
.single-object__video-frame:fullscreen {
  aspect-ratio: auto;
  width: 100%;
  height: 100%;
  border: 0;
}

@media (width <= 768px) {
  .single-object__video-time {
    display: none;
  }
}

.single-object__contact-card {
  grid-column: span 3;
  margin: var(--theme-space-lg) var(--theme-section-padding) 0;
  border: var(--theme-line-width) solid var(--theme-line-color);
  background-color: var(--brand-color-light);
  display: flex;
  gap: var(--theme-space-xs);
  align-items: center;
}

/* Same source-order-vs-specificity tie as .single-object__content/
   meta-col above — placed after the base rule (not folded into the
   earlier <=1024px block) so this actually wins at matching widths
   instead of losing to it by appearing later in the file. */
@media (width <= 1024px) {
  .single-object__contact-card {
    grid-column: span var(--theme-grid-columns);
  }
}

.single-object__contact-card-media {
  flex: none;
  align-self: stretch;
  width: 30%;
  aspect-ratio: 3 / 4;
  overflow: clip;
}

.single-object__contact-card-body {
  padding: var(--theme-space-xs) var(--theme-space-xs) var(--theme-space-xs) 0;
}

.single-object__contact-card-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.single-object__contact-card-name {
  display: block;
  margin: 0;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 1.1rem;
  color: inherit;
  text-decoration: none;
}

.single-object__contact-card-name:hover {
  text-decoration: underline;
}

.single-object__contact-card-role {
    margin: 0;
    text-transform: uppercase;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 1px;
    color: var(--brand-color-sand);
}

.single-object__contact-card-actions {
  display: flex;
  gap: .5rem;
  margin-top: 1em;
}

/* .section-objects has its own top padding (parts/section-objects.css)
   meant to sit under a hero it directly follows on other pages — here
   it follows plain content, so drop that top padding and let the
   heading pull up to overlap the section above instead (same pattern
   as .single-post__related on single.css). margin-top on the section
   itself makes room for that overlap so it eats into empty space
   rather than the content above it. flow-root establishes a new block
   formatting context so the section contains the h2's negative
   margin-top without the section's own box shifting up too. */
.section-objects.single-object__related {
  margin-top: 10rem;
  padding-top: 0;
  display: flow-root;
  position: relative;
  z-index: 3;
}

.section-objects.single-object__related h2 {
  margin-top: -10rem;
}


/* Locatie — full-width Mapbox map as the section's background (min
   100svh), text card on top of it at the left (col-2), like the
   contact page's info card over its map. object-map.js pads the map on
   the card's side so the marker centers in the visible part. */
.single-object__location {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--theme-grid-columns), 1fr);
  align-items: start;
  padding: var(--theme-section-padding);
  padding-top: var(--object-nav-offset);
}

.single-object__location--map,
.single-object__location--map-only {
  min-height: 100svh;
}

/* Within the grid (inset by the section padding, same edges as the rest
   of the page's content), full height of the section; the heading/text
   sit on top of it. */
.single-object__location-map {
  position: absolute;
  inset: var(--theme-section-padding);
  /* Top edge clears the stuck section nav, like the section's content. */
  top: var(--object-nav-offset);
  border: var(--theme-line-width) solid var(--theme-line-color);
  background-color: var(--brand-color-dark);
  overflow: clip;
}

/* Heading + text sit directly on the map (no card). Sticky within the
   section (its grid area spans the full 100svh+ map), parked just below
   the fixed header — fully shrunk by the time this far down the page
   (header.css: top offset + shrunk height). */
.single-object__location-text,
.single-object__location-heading--solo {
  position: sticky;
  top: calc(var(--theme-section-padding) + var(--theme-header-height) + var(--theme-space-sm));
  z-index: 1;
}

.single-object__location-text {
  grid-column: span 2;
  display: flex;
  flex-direction: column;
  padding: var(--theme-space-md);
  backdrop-filter: blur(var(--theme-blur));
}

/* No map: plain text on the page background, at a readable width. */
.single-object__location--text .single-object__location-text {
  grid-column: span 4;
  padding: var(--theme-space-md) 0;
}

.single-object__location-heading {
  margin: 0 0 var(--theme-space-sm);
  font-size: clamp(1.5rem, 2.5vw, 2.25rem);
  font-weight: 700;
}

/* Map only: just the heading, top-left over the map. */
.single-object__location-heading--solo {
  grid-column: 1 / -1;
  padding: var(--theme-space-md);
}

.single-object__location-content > :first-child {
  margin-top: 0;
}

.single-object__location-route {
  margin: 0;
  padding-top: var(--theme-space-sm);
}

/* Over the (dark) map: light text with the same dark halo as the map's
   own labels. */
.single-object__location--map .single-object__location-text,
.single-object__location-heading--solo {
  color: var(--brand-color-light);
  text-shadow: 0 1px 3px rgb(48 54 76 / 90%), 0 0 12px rgb(48 54 76 / 80%);
}

.single-object__location--map .single-object__location-content {
  color: inherit;
}

.single-object__location--map .single-object__location-content strong {
  font-weight: 700;
}

/* Underline button reads as primary (dark) by default — light here. */
.single-object__location--map .single-object__location-route-button {
  color: var(--brand-color-light);
  text-shadow: none;
}

/* Descendant selector on purpose: mapbox-gl.css's own ".mapboxgl-map
   { position: relative }" lands on this same element and would tie on
   specificity (see contact.css for the same fix). */
.single-object__location-map .single-object__map-canvas {
  position: absolute;
  inset: 0;
  background-color: rgb(48 54 76 / 80%);
}

/* Same subtle line-art treatment as the contact map. */
.single-object__location-map .single-object__map-canvas canvas {
  mix-blend-mode: soft-light;
  opacity: 1;
  transition: opacity 1.5s 0.5s var(--theme-ease-signature);

  @starting-style {
    opacity: 0;
  }
}

.single-object__location-map .mapboxgl-ctrl-logo {
  display: none;
}

/* Fade + rise in on insertion — keyframes, see map-marker-in in reset.css. */
.single-object__map-marker,
.single-object__map-place-label,
.single-object__map-highway-label {
  animation: map-marker-in 1s var(--theme-ease-signature) both;
}

/* The object's title on a dark pill with a pointer tail — same shape
   as the contact page's GH&Co marker. */
/* An <a> to the route (object-map.js). */
.single-object__map-marker {
  /* Above the city/highway labels (map-labels.js) — also Mapbox markers,
     added later once the map is idle, so they'd paint on top. */
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  text-decoration: none;
}

.single-object__map-marker:is(:hover, :focus-visible) .single-object__map-marker-label {
  background-color: var(--brand-color-accent);
}

.single-object__map-marker:is(:hover, :focus-visible)::after {
  border-top-color: var(--brand-color-accent);
}

.single-object__map-marker-label {
  padding: 0.75rem 1rem;
  background: var(--brand-color-dark);
  box-shadow: 0 4px 16px rgb(0 0 0 / 30%);
  color: var(--brand-color-light);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background-color 0.3s var(--theme-ease-signature);
}

.single-object__map-marker::after {
  content: "";
  width: 0;
  height: 0;
  margin-top: -1px;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 8px solid var(--brand-color-dark);
  transition: border-top-color 0.3s var(--theme-ease-signature);
}

.single-object__map-place-label,
.single-object__map-highway-label {
  pointer-events: none;
  color: var(--brand-color-light);
  font-weight: 700;
  white-space: nowrap;
}

.single-object__map-place-label {
  font-size: 0.9375rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-shadow: 0 1px 3px rgb(48 54 76 / 90%), 0 0 8px rgb(48 54 76 / 70%);
}

.single-object__map-highway-label {
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  text-shadow: 0 1px 3px rgb(48 54 76 / 90%), 0 0 6px rgb(48 54 76 / 70%);
}

/* Card widens before it goes full width on mobile. */
@media (width <= 1024px) {
  .single-object__location-text {
    grid-column: span 3;
  }

  .single-object__location--text .single-object__location-text {
    grid-column: span var(--theme-grid-columns);
  }
}

/* Mobile: text spans the width at the top, in normal flow (not sticky),
   and the section adds a row of at least 60svh below it — so there's
   always open map below the text. object-map.js pads the map's top by
   the text's height, which centers the marker in that open part. */
@media (width <= 768px) {
  .single-object__location-text,
  .single-object__location--text .single-object__location-text {
    grid-column: 1 / -1;
  }

  .single-object__location-text,
  .single-object__location-heading--solo {
    position: static;
  }

  .single-object__location--map,
  .single-object__location--map-only {
    grid-template-rows: auto minmax(60svh, 1fr);
    min-height: 0;
  }
}
