/* Single-page "scene" layer for the WhoNet site.
   Sits on top of the migrated Bootstrap/brand CSS (live.css) and adds:
   the fixed 3D-logo background, a viewport-locked scene container, the
   scene-to-scene transition and the staggered content reveal. */

:root {
  /* Fallbacks only. scene.js MEASURES the real nav/footer and overwrites these,
     so the reserved space tracks the actual chrome (which changes height across
     breakpoints - the phone footer is 2.5x the desktop one). */
  --nav-h: 69px;
  --bar-h: 54px;
  --scene-gap: 1.25rem;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --panel: rgba(255, 255, 255, 0.76);
}

/* ---------- background + frame ------------------------------------------ */

html, body { height: 100%; }

/* ---------- the layout viewport must never widen ------------------------- */
/* The scene sections start as plain flow content - the .scene3d class is added
   by scene.js - so until the module runs an image sits at its INTRINSIC width.
   The news banner is 1500px, which made the document far wider than a phone.
   Mobile browsers respond by widening the LAYOUT viewport to fit, and
   window.innerWidth then reports that inflated width (measured: 1533x3318 for a
   390x844 screen). measure() sizes the whole 3D world from it, so every scene
   renders at ~25% and the page looks zoomed out - the "content loads in very
   small" report. Two independent guards, because the JS-added class is a race:
   the images cannot overflow, and the page cannot report a width it does not
   have. */
html { overflow-x: hidden; }

.scene img,
.scene svg,
#scenes img,
#css3d img { max-width: 100%; }

body.scene-mode {
  overflow: hidden;                /* the page itself never scrolls */
  background-color: #f5f5f5;
  overscroll-behavior: none;
}

/* Soft brand glow behind the 3D logo so it lifts off the light background. */
body.scene-mode::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(42% 40% at 72% 38%, rgba(157, 195, 77, 0.16), rgba(157, 195, 77, 0) 70%),
    radial-gradient(46% 46% at 22% 78%, rgba(39, 163, 255, 0.10), rgba(39, 163, 255, 0) 72%),
    linear-gradient(180deg, #fbfbfb 0%, #f2f2f2 100%);
}

/* The 3D logo itself: fixed, full viewport, behind the content. */
#scene-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: block;
  pointer-events: none;            /* background only - never eats clicks */
}

/* ---------- nav --------------------------------------------------------- */

body.scene-mode .scene-nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 30;
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: saturate(1.2) blur(10px);
  -webkit-backdrop-filter: saturate(1.2) blur(10px);
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}

body.scene-mode .scene-nav .navbar-brand img {
  width: 56px;
  height: 56px;
  object-fit: contain;
}

body.scene-mode .scene-nav .navbar-brand span {
  font-size: 1.35rem;
}

.scene-nav .nav-link {
  background: none;
  border: 0;
  cursor: pointer;
  font-weight: 500;
  position: relative;
  transition: color 0.25s var(--ease);
}

/* active-scene marker */
.scene-nav .nav-link::after {
  content: '';
  position: absolute;
  left: 1.5rem; right: 1.5rem;
  bottom: 0.35rem;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.35s var(--ease);
}
.scene-nav .nav-link.is-current::after { transform: scaleX(1); }
.scene-nav .nav-link:hover { color: #7e9c3e !important; }

/* The collapse is our OWN container, not Bootstrap's `.navbar-collapse`, so
   nothing gives it a row layout on desktop: as a plain block the scene links and
   the social list stack, and the bar grows from ~69px to ~93px. */
@media (min-width: 1200px) {
  .scene-nav .nav-collapse {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex: 1 1 auto;
  }
}

/* ---------- social links in the top menu --------------------------------- */

/* The section is generated from the `socials` table. Icon-only, so the label
   becomes the accessible name and the tooltip (see index.php). */
.scene-nav .nav-social {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: 0.9rem;
  padding-left: 1rem;
  border-left: 1px solid rgba(33, 37, 41, 0.12);
}

.scene-nav .nav-social a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  /* Match the menu items exactly: the same brand green the `.nav-link`s get
     from `text-primary`, darkening on hover exactly like `.nav-link:hover`.
     The glyph is an inline SVG with fill="currentColor", so it follows this. */
  color: #9dc34d;
  transition: color 0.25s var(--ease), background-color 0.25s var(--ease);
}

.scene-nav .nav-social a:hover,
.scene-nav .nav-social a:focus-visible {
  color: #7e9c3e;
  background: rgba(157, 195, 77, 0.14);
}

.scene-nav .nav-toggle { display: none; }

@media (max-width: 1199.98px) {
  .scene-nav .nav-collapse {
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: rgba(255, 255, 255, 0.98);
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
    padding: 0.75rem 1rem 1rem;
    display: none;
    max-height: calc(100vh - var(--nav-h));
    overflow-y: auto;
  }
  .scene-nav .nav-collapse.is-open { display: block; }
  .scene-nav .nav-list { flex-direction: column; align-items: stretch; }
  /* In the dropped-down menu the divider belongs on top, not on the left. */
  .scene-nav .nav-social {
    justify-content: center;
    margin: 0.6rem 0 0;
    padding: 0.85rem 0 0;
    border-left: 0;
    border-top: 1px solid rgba(33, 37, 41, 0.12);
  }
  .scene-nav .nav-link { padding: 0.65rem 0.25rem !important; text-align: left; }
  .scene-nav .nav-link::after { left: 0.25rem; right: auto; width: 2rem; }
  .scene-nav .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px; height: 44px;
    border: 1px solid rgba(0, 0, 0, 0.1);
    border-radius: 10px;
    background: #fff;
    color: #9dc34d;
  }
  .scene-nav .nav-toggle svg { width: 20px; height: 20px; }
}

/* ---------- 3D content stage ------------------------------------------- */

/* Scenes are adopted out of here into CSS3DObjects at boot. Hidden until then
   so all ten panels never flash as a stack on first paint. */
#scenes { visibility: hidden; }

/* The CSS3D overlay: real DOM placed in the same 3D world as the logo. Sits
   above the WebGL canvas; the nav/footer chrome sits above this. */
#css3d {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;       /* only the panels themselves are interactive */
}

/* A scene panel living in 3D. Size is set in px by JS so it maps 1:1 at z=0. */
.scene3d {
  /* Reserve the fixed nav (top) and footer bar (bottom). Without this the scene
     is viewport-sized and its content renders UNDER both - and since the page
     itself never scrolls (body overflow:hidden), it could not be reached. The
     element scrolls internally instead, so longer scenes stay fully readable. */
  padding: calc(var(--nav-h) + var(--scene-gap)) 0 calc(var(--bar-h) + var(--scene-gap));
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  background: transparent;
}
.scene3d::-webkit-scrollbar { width: 8px; }
.scene3d::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.16); border-radius: 8px; }

/* Panel: keeps text legible over the 3D logo behind it. */
.scene-panel {
  background: var(--panel);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 34px;
  box-shadow: 0 18px 60px rgba(33, 37, 41, 0.10);
  padding: 2rem;
}
.scene-panel--tight { padding: 1.5rem; }

/* ---------- the home page's logo strip -----------------------------------
   Its own card under the hero, so the heading and the images read as one
   block. The row NEVER wraps: on a narrow screen the strip drops images and
   shrinks them instead, because five logos squeezed onto one line are
   unreadable. Fewer, larger images is the mobile answer, not smaller ones. */
.home-strip-card { padding: 1.5rem 1.75rem; }

.home-strip__heading {
  margin: 0 0 1.25rem;
  color: #9dc34d;
  font-size: 1.3rem;
  font-weight: 600;
  text-align: center;
}

/* The strip is a carousel that slides one place to the LEFT every 5s (see strip.js): the
   leftmost image leaves the window and the next slides in from the right. `--strip-n` is how
   many are on show, set per breakpoint by the script, and each slot is exactly a share of
   the row - so the ones beyond `--strip-n` sit outside the WINDOW, waiting to slide in.
   That is what keeps it one line at every width.

   The window clips; the row inside it is what moves. Transforming the window itself would
   carry its clip along and the images would slide out of the card, still visible. */
.home-strip {
  --strip-n: 5;
  --strip-gap: 0.75rem;
  overflow: hidden;           /* the waiting images are out here */
}

.home-strip__row {
  display: flex;
  flex-wrap: nowrap;          /* one line, always */
  align-items: center;
  gap: var(--strip-gap);
  transition: transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}

.home-strip__slot {
  flex: 0 0 calc((100% - (var(--strip-n) - 1) * var(--strip-gap)) / var(--strip-n));
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.home-strip__slot img {
  max-width: 100%;
  max-height: 40px;           /* the logos are wide; cap the height so they stay legible */
  object-fit: contain;
  opacity: 0.8;
}

@media (max-width: 575.98px) {
  .home-strip-card { padding: 1.25rem 1rem; }
  .home-strip { --strip-gap: 0.5rem; }
  .home-strip__slot img { max-height: 34px; }
}

@media (prefers-reduced-motion: reduce) {
  .home-strip__row { transition: none; }
}

/* The whole set is on show, so nothing is waiting to slide in. The script stops stepping
   and this makes it explicit: no transform, no transition, no gap. */
.home-strip--full .home-strip__row {
  transition: none;
  will-change: auto;
}

/* Cards already carry their own white background, so let them sit on the
   background directly; only give them a lift. */
.scene3d .card { border-radius: 22px; }

/* ---------- per-element content entrance -------------------------------- */

/* Entrances are driven by the Web Animations API in scene.js, which gives every
   unit its own randomised direction and delay, so a scene ASSEMBLES to fill the
   view rather than sliding in as one block. It is deliberately NOT a CSS
   transition on a custom property: the browser can capture the transition's
   start state before the property is written, which silently collapsed every
   unit onto the same default offset and lost the randomisation entirely. */
.scene3d .reveal { opacity: 1; transform: none; }

/* While a scene is assembling or dispersing, clip to the panel so units are seen
   arriving from BEYOND the edge, and their travel cannot enlarge the scroll area
   for the moment the animation runs. */
.scene3d.is-entering,
.scene3d.is-leaving { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .scene3d .reveal { transform: none; opacity: 1; }
}

/* First paint must not animate the initial scene into place. */
.scene-boot .scene3d,
.scene-boot .scene3d .reveal {
  transition: none !important;
  opacity: 1;
  transform: none;
  visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
  .scene3d, .scene3d .reveal, .scene-nav .nav-link::after,
  .showcase-card, .showcase-dot, .showcase-arrow { transition-duration: 0.01ms !important; }
  .scene3d { transform: none; }
  .scene3d .reveal { transform: none; opacity: 1; }
}

/* ---------- slim footer bar -------------------------------------------- */

body.scene-mode .scene-bar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 30;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: saturate(1.2) blur(10px);
  -webkit-backdrop-filter: saturate(1.2) blur(10px);
  border-top: 1px solid rgba(0, 0, 0, 0.06);
}
body.scene-mode .scene-bar .scene-bar-links {
  display: flex;
  gap: 1.1rem;
  align-items: center;
  flex-wrap: wrap;
}
body.scene-mode .scene-bar a {
  color: #595a5b;
  text-decoration: none;
  font-size: 0.82rem;
  cursor: pointer;
  transition: color 0.2s var(--ease);
}
body.scene-mode .scene-bar a:hover { color: #9dc34d; }
body.scene-mode .scene-bar a.is-current { color: #9dc34d; font-weight: 600; }

@media (max-width: 767.98px) {
  body.scene-mode .scene-bar { height: auto; min-height: var(--bar-h); padding: 0.5rem 0; }
  body.scene-mode .scene-bar .scene-bar-inner { flex-direction: column; gap: 0.35rem; }
}

/* ---------- helpers ----------------------------------------------------- */

.scene3d h2, .scene3d h3 { text-wrap: balance; }
.scene3d .section { padding: 0; }

/* `h-100` centres content in the scene, but a FIXED 100% height clips the top as
   soon as the content is taller than the box: flex centring distributes the
   overflow equally above and below, so the heading ends up under the nav. Giving
   the box a min-height instead lets it GROW with tall content (top-aligned, never
   clipped) while still centring content that fits. */
/* `!important` is required to beat Bootstrap's `.h-100{height:100%!important}`. */
.scene3d .container.h-100 { height: auto !important; min-height: 100%; }

/* Scene titles: the live site's display-1/fs-1 sizes are built for a scrolling
   page; in a viewport-locked scene they need to come down a notch. */
.scene3d .hero-title { font-size: clamp(2.1rem, 4.6vw, 3.6rem); line-height: 1.05; }
.scene3d h1.fs-1 { font-size: clamp(1.7rem, 3.2vw, 2.5rem); }
.scene3d .display-1 { font-size: clamp(1.9rem, 3.6vw, 2.9rem); line-height: 1.08; }
.scene3d .scene-sub { color: rgba(33, 37, 41, 0.66); }

/* service / news card grids inside a viewport-locked scene */
.scene3d .aspect-ratio-62-5 { border-radius: 18px; overflow: hidden; }

/* ---------- fit-to-viewport compaction ---------------------------------
   These three scenes carry more content than the live site's scroll length
   allowed for; trim the media so each fits the scene box without scrolling.

   The news and services grids wear each other's card treatment: the compact
   one (short 27% image strip, smaller type) now styles NEWS, and the taller
   default treatment (62.5% image, Bootstrap card sizing) now styles SERVICES.
   Both live here, adjacent, so the pairing stays obvious and a later edit
   cannot quietly apply to only one of the two. */

#scene-news .aspect-ratio-62-5 { padding-bottom: 27%; }
#scene-news .card-body { padding: 0.85rem 1rem; }
#scene-news .card-title { font-size: 1.02rem; margin-bottom: 0.4rem; }
#scene-news .card-text { font-size: 0.84rem; line-height: 1.42; }
#scene-news .row.g-4 { --bs-gutter-y: 1rem; }

#scene-testimonials img.img-fluid { max-height: 190px; width: auto; object-fit: contain; }
#scene-testimonials .card-text { font-size: 0.9rem; }
/* The card's image area: the uploaded portrait, or a colour circle when the
   record has no image. The colour is derived from the row, not stored, so it is
   stable across page loads - see testimonial_avatar_colour(). */
#scene-testimonials .testimonial-avatar {
  display: block;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: 1.5rem;
  background: rgba(33, 37, 41, 0.06);
}
#scene-testimonials .card-body { padding: 0.5rem; }
#scene-testimonials .row.g-4 { --bs-gutter-y: 1rem; }


@media (max-width: 767.98px) {
  .scene-panel { padding: 1.25rem; border-radius: 22px; }
  /* NOTE: no .scene3d padding override here - the reserved nav/footer padding is
     viewport-independent and comes from the --nav-h/--bar-h variables. */

  /* The brand strip is a single non-wrapping row of `col`s; let it wrap so the
     logos are not cut off. */
  .scene3d .row.justify-content-center > .col { flex: 0 0 33.333%; max-width: 33.333%; }

  /* A shorter perspective on a phone keeps the side cards from swinging off
     the panel entirely, and the caption wraps to more lines. */
  .showcase-stage { perspective: 1100px; }
  .showcase-info { min-height: 168px; margin-top: 1.25rem; }
  .showcase-desc { font-size: 0.94rem; }
}

/* ---------- leaving for an external site -------------------------------- */

/* The nav and footer bar fade out with the content so the logo's departure plays
   against the plain background. Applied by scene.js before an external link. */
body.scene-mode.is-departing .scene-nav,
body.scene-mode.is-departing .scene-bar {
  opacity: 0;
  pointer-events: none;
  transition: opacity 420ms var(--ease);
}

/* ---------- news article ------------------------------------------------ */

/* Rendered from admin-authored markdown, so these styles apply to generated
   markup (.rich-body is the md_to_html() output). */
.scene3d .news-banner {
  display: block;
  width: 100%;
  max-height: 260px;
  object-fit: cover;
  border-radius: 22px;
  margin-bottom: 1.25rem;
}

.scene3d .news-date {
  font-weight: 600;
  letter-spacing: 0.02em;
}

.rich-body { font-size: 1.05rem; }

/* Service detail bodies open with the centred subtitle the original page had. */
.rich-body--lead > p:first-child {
  text-align: center;
  color: rgba(33, 37, 41, 0.66);
  padding-bottom: 0.5rem;
}

.rich-body > :first-child { margin-top: 0; }
.rich-body > :last-child  { margin-bottom: 0; }

.rich-body h1, .rich-body h2, .rich-body h3, .rich-body h4 {
  color: #9dc34d;
  font-weight: 700;
  padding-top: 0.6rem;
}

.rich-body h1 { font-size: 1.8rem; }
.rich-body h2 { font-size: 1.4rem; }
.rich-body h3 { font-size: 1.15rem; }
.rich-body h4 { font-size: 1.02rem; }

.rich-body img {
  max-width: 100%;
  height: auto;
  border-radius: 18px;
  margin: 0.5rem 0;
}

.rich-body a { color: #9dc34d; }

.rich-body blockquote {
  margin: 0 0 1rem;
  padding: 0.5rem 0 0.5rem 1rem;
  border-left: 3px solid #9dc34d;
  color: rgba(33, 37, 41, 0.72);
}

.rich-body pre {
  background: #f4f5f2;
  padding: 0.9rem 1rem;
  border-radius: 12px;
  overflow-x: auto;
  font-size: 0.85rem;
}

.rich-body code {
  background: #f4f5f2;
  padding: 0.12rem 0.4rem;
  border-radius: 6px;
  font-size: 0.88em;
  color: #4d6b1f;
}

.rich-body pre code { background: none; padding: 0; color: inherit; }

/* Home hero body. The standfirst used to be a hand-written
   `<p class="scene-sub fs-5 pb-4">`; it is the page's markdown now, so restate
   that look here. The CTA convention centres a page's button, but the hero's
   "Get In Touch" sits in a left-aligned panel - hence the override. */
.home-body > p {
  font-size: 1.25rem;
  color: rgba(33, 37, 41, 0.66);
  margin-bottom: 1.5rem;
}
/* The CTA convention wraps the button in Bootstrap's `.text-center`, which is
   `!important` - so this override needs to be too. The hero's button is
   left-aligned with the copy, as it was before the content moved into the DB. */
#scene-home .home-body > p:last-child { text-align: left !important; }

/* Editable pages (see inc/pages.php). The content card now SPANS THE FULL WIDTH
   (col-12), and the page artwork is square, so an uncapped image is ~1240px tall
   and buries the copy under it. Cap the HEIGHT - the width then follows the
   aspect ratio - and centre it. Scoped to .page-body so news and service bodies
   keep their own sizing.
   NOTE: `#scene-about img { max-height: 215px }` used to live here, from the
   old two-band layout. Being an ID selector it outranked this rule, so every
   About image silently rendered as a 215px-tall thumbnail. */
.page-body img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 420px;
  object-fit: contain;
  margin: 1.25rem auto;
}

/* Pipe tables in authored bodies (see inc/markdown.php). Styled here rather than
   by having the renderer emit Bootstrap classes - it stays framework-agnostic,
   and this is the site's own table look: rules between rows, no outer border. */
.rich-body table {
  width: auto;
  max-width: 100%;
  margin: 0 0 1rem;
  border-collapse: collapse;
}

.rich-body th,
.rich-body td {
  padding: 0.4rem 0.9rem 0.4rem 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid rgba(33, 37, 41, 0.12);
}

.rich-body thead th {
  font-weight: 600;
  white-space: nowrap;
  border-bottom: 2px solid rgba(33, 37, 41, 0.22);
}

.rich-body tbody tr:last-child td,
.rich-body tbody tr:last-child th { border-bottom: 0; }


/* ---------- showcase carousel ------------------------------------------------
   A coverflow strip. The cards sit on one line through the middle of the stage:
   the selected card faces the viewer at z=0, and every other card is pushed
   sideways, receded and rotated about Y - the rotation's SIGN follows the side,
   which is what makes the left and right cards tilt opposite ways. The numbers
   (x / z / rotateY / scale) are computed per card in scene.js, because they
   depend on the wrap-around offset; this file owns the shape, the transition and
   the caption. Widths come in on --sc-w so the card and its spacing stay in
   step at every viewport. */
.showcase { --sc-w: 420px; }

.showcase-stage {
  position: relative;
  height: 300px;                 /* replaced from JS once the width is known */
  perspective: 1600px;
  perspective-origin: 50% 42%;
}

.showcase-card {
  position: absolute;
  left: 50%;
  top: 0;
  width: var(--sc-w);
  padding: 0;
  border: 0;
  background: none;
  border-radius: 18px;
  cursor: pointer;
  transform: translate(-50%, 0);
  transition: transform 620ms var(--ease), opacity 420ms var(--ease);
  will-change: transform, opacity;
}

.showcase-card:focus-visible { outline: 3px solid #9dc34d; outline-offset: 4px; }

.showcase-shot {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top center;   /* a site screenshot reads best from the top */
  border-radius: 18px;
  background: #e9ecef;
  box-shadow: 0 16px 38px rgba(20, 26, 12, 0.16);
  transition: box-shadow 300ms var(--ease);
}

.showcase-card.is-active .showcase-shot {
  box-shadow: 0 26px 58px rgba(20, 26, 12, 0.26);
  outline: 3px solid rgba(157, 195, 77, 0.6);
  outline-offset: 3px;
}

/* No screenshot uploaded: a tile with the title's first letter, so the card
   still looks deliberate rather than broken. */
.showcase-shot--tile {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(140deg, #9dc34d, #7e9c3e);
  color: #fff;
  font-size: 3rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.showcase-info {
  margin-top: 1.75rem;
  text-align: center;
  /* Reserve the tallest caption so selecting a card does not shunt the strip. */
  min-height: 128px;
}

.showcase-title { line-height: 1.15; }

.showcase-desc {
  max-width: 60ch;
  margin-left: auto;
  margin-right: auto;
  color: rgba(33, 37, 41, 0.66);
}

.showcase-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  margin-top: 0.35rem;
}

.showcase-arrow {
  width: 42px;
  height: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(157, 195, 77, 0.16);
  color: #6f8c33;
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 200ms var(--ease), color 200ms var(--ease);
}

.showcase-arrow:hover,
.showcase-arrow:focus-visible { background: #9dc34d; color: #fff; }

.showcase-dots { display: flex; align-items: center; gap: 0.55rem; }

.showcase-dot {
  width: 9px;
  height: 9px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(33, 37, 41, 0.18);
  cursor: pointer;
  transition: background-color 200ms var(--ease), transform 200ms var(--ease);
}

.showcase-dot:hover { background: rgba(33, 37, 41, 0.34); }

.showcase-dot.is-active {
  background: #9dc34d;
  transform: scale(1.4);
}

/* ---------- easter egg: overlay shell + corrupt logo ---------------------
   Only the SHELL lives here. It has to style the "Loading..." prompt, which
   appears before the game's own stylesheet has been fetched; everything else
   about the game's UI is in invaders.css, which loads on demand with the game. */
.inv-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  text-align: center;
  pointer-events: none;            /* never blocks the canvas */
}

.inv-overlay--hidden { display: none; }

/* The panel is the one part that must accept clicks (the Start button). */
.inv-overlay .inv-panel { pointer-events: auto; }

.inv-line {
  margin: 0;
  padding: 0.75rem 1.25rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 12px 40px rgba(33, 37, 41, 0.16);
  color: #595a5b;
  font-size: 1rem;
}

/* The corrupt logo: the player LOST the game. The 3D jitter and the colour
   flicker are in logo3d.js; this wash makes the damage obvious even when the
   jitter happens to be small. It rides the `is-glitching` class, so it appears
   only while a burst is running - the damage is episodic, not permanent. */
body.is-corrupt.is-glitching::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: repeating-linear-gradient(
    0deg,
    rgba(209, 43, 63, 0.07) 0 2px,
    transparent 2px 5px
  );
}

/* ---------- staff business cards -----------------------------------------
   Migrated from the standalone card at whonet.com.au/staff/kurtis/: a circular
   photo, the name centred under it, then the card's links. The card's own green
   button styling and its separate stylesheet are gone - the links now wear the
   site's own button, and a link that names a network renders as an icon in the
   same style as the top menu's social links. */
.staff-card { text-align: center; }

.staff-photo {
  display: block;
  width: 96px;
  height: 96px;
  margin: 4px auto 18px;
  border-radius: 50%;
  object-fit: cover;
  background-color: #f5f5f5;
}

/* No photo uploaded: the person's initial, like the testimonial avatars. */
.staff-photo--initial {
  background-color: #9dc34d;
  color: #fff;
  font-size: 2.5rem;
  font-weight: 600;
  line-height: 96px;
  user-select: none;
}

.staff-name {
  margin: 0 0 0.25rem;
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.25;
  color: #212529;
}

.staff-role {
  margin: 0 0 1rem;
  font-size: 0.95rem;
  color: rgba(33, 37, 41, 0.66);
}

.staff-bio {
  max-width: 560px;
  margin: 0 auto 1.25rem;
  font-size: 0.95rem;
  line-height: 1.6;
  color: rgba(33, 37, 41, 0.66);
}

.staff-bio p { margin: 0 0 0.75rem; }
.staff-bio p:last-child { margin-bottom: 0; }

.staff-links {
  max-width: 420px;
  margin: 0 auto;
}

/* The links are the site's OWN buttons now (`btn btn-primary text-light`), just
   full width, so a card keeps the big tap targets a phone needs. Only the width
   is local - the colours, radius and hover all come from the shared button. */
.staff-link-btn {
  display: block;
  width: 100%;
  margin-bottom: 14px;
}

/* Icon links render like the top menu's social links: a 36px square, brand green,
   with the label carried as the accessible name and tooltip. */
.staff-social {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  margin: 18px 0 0;
  padding: 0;
}

.staff-social a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  color: #9dc34d;
  transition: color 0.25s var(--ease), background-color 0.25s var(--ease);
}

.staff-social a:hover,
.staff-social a:focus-visible {
  color: #7e9c3e;
  background: rgba(157, 195, 77, 0.14);
}

.staff-social a:focus-visible {
  outline: 2px solid #595a5b;
  outline-offset: 2px;
}
