/* ============================================================
   DS hub: the hub itself has no single brand color, it hosts
   all four. Loaded after ds-shared.css.

   The hero is a real AI-rendered shot of the "lab" (looping video,
   static image as poster/fallback), not a procedural 3D scene:
   four glass-walled rooms are already visible and labeled in the
   footage itself. This file only adds the invisible clickable
   hotspots over each room and the hover/flight chrome.
   ============================================================ */

:root {
  --bg: #f4f4f6;
  --bg-elevated: #ffffff;
  --bg-card: #eaeaee;
  --accent: #e6e7eb;
  --accent-soft: rgba(21, 23, 28, 0.05);
  --accent-border: rgba(21, 23, 28, 0.16);
  --cream: #15171c;
  --text-primary: #15171c;
  --text-muted: rgba(21, 23, 28, 0.68);
  --text-faint: rgba(21, 23, 28, 0.55);
  --border: rgba(15, 17, 20, 0.1);
  --border-strong: rgba(15, 17, 20, 0.18);

  --shadow-accent: 0 8px 24px -8px rgba(15, 17, 20, 0.18);

  --room-videoart: #d1414c;
  --room-web: #3d8ef0;
  --room-lab: #4ade80;
  --room-creative: #ec4899;
}

/* White corridor, not a neon dashboard: a real lab reads futuristic
   through clean architecture and dimensional shadow, not glowing
   lines on white (that's just the AI-purple-glow cliché in a
   different color). Color comes from the four room photos, which
   stay exactly as dark and vivid as before, and now pop hard against
   a calm, almost-white hallway instead of getting lost in more dark.
   A faint blueprint dot-grid is the one deliberate "lab" texture. */
body.hub {
  min-height: 100vh;
  overflow-x: hidden;
  background-image: radial-gradient(var(--border) 1px, transparent 1px);
  background-size: 28px 28px;
}

/* ds-shared.css styles the navbar/footer/a11y toggle for the dark
   rooms (white glow, dark icon on a light accent circle). Scoped
   overrides here retune just those three shared components for the
   hub's light corridor, without touching how they look anywhere
   else on the site. */
body.hub .navbar {
  background: color-mix(in srgb, var(--bg-elevated) 88%, transparent);
  border-bottom: 1px solid var(--border);
  box-shadow: none;
}

body.hub footer {
  border-top: 1px solid var(--border);
  box-shadow: none;
}

body.hub .a11y-toggle {
  color: var(--text-primary);
}

/* The navbar logo elsewhere on the site is a dramatic full-art image
   with an opaque dark background baked in - fine on a dark room page,
   but it read as a stray dark square on the hub's light corridor.
   The hub uses a plain text mark instead (see index.html), so the
   otherwise-unused .logo-mark dot needs real contrast here too. */
body.hub .logo-mark {
  background: var(--text-primary);
  box-shadow: 0 0 0 3px var(--border);
}

body.hub .logo {
  color: var(--text-primary);
}

/* .btn-outline:hover normally swaps to var(--accent) text, which is a
   deliberately light neutral here (kept light so the hardcoded dark
   text on .lang-btn.active still reads) - fine as a fill color, but
   invisible as hover TEXT on a white navbar. Ink + a soft tint reads
   correctly instead. */
body.hub .btn-outline:hover {
  border-color: var(--text-primary);
  color: var(--text-primary);
  background: var(--accent-soft);
}

.hub-brand-flash {
  position: fixed;
  inset: 0;
  z-index: 4000;
  background: var(--room-videoart);
  opacity: 0;
  pointer-events: none;
}

/* Set by a tiny blocking script in <head>, before first paint, only when
   returning from a room's exit link (sessionStorage flag). Keeps the
   flash opaque in that room's color from frame one, so there's no pop
   before hub.js reverses the return zoom. */
html.hub-returning .hub-brand-flash {
  opacity: 1;
  background: var(--hub-return-color, var(--room-videoart));
}

/* Welcome splash: plays on every fresh load/refresh of the hub root
   (not on a return-from-room arrival, which already has its own
   color-flash handoff). Pure CSS entrance (visible by default, no
   flash-of-unstyled-content risk), JS only schedules the exit. */
.ds-splash {
  position: fixed;
  inset: 0;
  z-index: 8000;
  overflow: hidden;
  background: #08090b;
}

.ds-splash-media {
  position: absolute;
  inset: -4%;
  background: url("hub-media/image/splash.jpg?v=20260719b") center / cover no-repeat;
  animation: dsSplashZoom 3.4s ease-out forwards;
}

.ds-splash-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(8, 9, 11, 0.6) 0%, rgba(8, 9, 11, 0.15) 42%, rgba(8, 9, 11, 0.7) 100%);
}

.ds-splash-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  margin: 0;
  padding: 0 1.5rem;
  font-weight: 900;
  font-size: clamp(2.4rem, 9vw, 5.2rem);
  line-height: 1.28;
  color: #ffffff;
  letter-spacing: -0.02em;
  text-shadow: 0 2px 50px rgba(0, 0, 0, 0.6), 0 0 30px rgba(255, 255, 255, 0.5), 0 0 70px rgba(255, 255, 255, 0.25);
  opacity: 0;
  animation: dsSplashTextIn 1s ease-out 0.35s forwards;
}

@keyframes dsSplashZoom {
  from { transform: scale(1); }
  to { transform: scale(1.08); }
}

@keyframes dsSplashTextIn {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

.ds-splash.is-hiding {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.7s ease;
}

.ds-splash.is-gone {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .ds-splash-media { animation: none; }
  .ds-splash-text { animation: none; opacity: 1; transform: none; }
}

/* ============================================================
   The stage: footage of the lab, with a zoomable inner wrapper
   (hub.js animates this wrapper's transform for the "fly into a
   room" click effect, no 3D engine needed since the depth is
   already baked into the footage).

   The source image/video is a very wide panorama (~2.3:1). Letting
   it stretch full-bleed with object-fit:cover across a 100vh stage
   forces it to crop hard on the sides whenever the browser window
   isn't nearly as wide as the image itself - which is most normal
   windows, and gets worse the moment devtools or a sidebar narrows
   things further. That crop can zoom in tight enough on a single
   room that it looks exactly like an unwanted "fly into a room"
   animation firing on its own, even though nothing is animating.

   Fix: object-fit:contain on the media itself (native, universally
   supported, no custom layout math) so the whole panorama is always
   fully visible, never cropped into a false "zoom." Hotspots are
   positioned in pixels by JS (see positionHotspots() in hub.js),
   computed from the actual letterboxed image bounds, so they always
   land on the right room regardless of window size or aspect ratio.
   ============================================================ */

.hub-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1916 / 821;
  max-height: 100vh;
  background: var(--bg);
  overflow: hidden;
  /* The panel reads as a real screen set into a white wall: a soft
     outer shadow gives it depth against the light corridor, instead
     of the old inset glow that only made sense on a dark page. */
  box-shadow: 0 30px 60px -24px rgba(15, 17, 20, 0.28);
}

/* A light bloom sits ON TOP of the footage, using a "screen" blend
   so it genuinely lights the pixels beneath it rather than covering
   them. Centered on the top-middle "SYSTEMS" panel, which is the one
   part of the photo with no room color of its own and no
   hotspot glow near it, and reads noticeably darker/flatter than the
   four brand-colored rooms as a result. */
.hub-stage-zoom::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  mix-blend-mode: screen;
  background: radial-gradient(ellipse 34% 46% at 50% 24%, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.18) 55%, transparent 80%);
}

.hub-stage-zoom {
  position: absolute;
  inset: 0;
  z-index: 1;
  transform-origin: 50% 50%;
  will-change: transform;
}

.hub-bg-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  z-index: 1;
  filter: brightness(1.22) contrast(1.04) saturate(1.1);
}

/* Scrims for text legibility over busy footage */

.hub-scrim-bottom {
  position: absolute;
  bottom: 0;
  inset-inline: 0;
  height: 160px;
  z-index: 3;
  background: linear-gradient(to top, rgba(4, 5, 6, 0.7), transparent);
  pointer-events: none;
}

/* A real bar in normal document flow, not an overlay on the footage,
   so it can never collide with the room graphics beneath it (the
   hexagon art reaches almost to the top edge of the image, leaving
   no quiet zone for text to float over). Sits directly under the
   fixed navbar, above the stage. */
.hub-caption {
  position: relative;
  margin-top: var(--nav-height);
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 18px 24px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
}

.hub-caption::before,
.hub-caption::after {
  content: "";
  width: clamp(30px, 8vw, 90px);
  height: 1px;
  background: linear-gradient(to right, transparent, var(--text-faint), transparent);
}

.hub-caption span {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-primary);
}

/* ============================================================
   Room hotspots: real navigation, always present and keyboard
   reachable. Each hotspot is sized to roughly match its room's
   actual visual footprint in the footage - not the whole quadrant
   of the viewport. A click target covering half the screen means
   any incidental click anywhere in that huge area (very easy to
   trigger by accident with a trackpad) fires real navigation, and
   landing back on the hub with the cursor still inside another
   huge zone can trigger it again right away - which reads exactly
   like an uncontrollable loop. Keeping the zone close to the
   room's real size leaves most of the screen inert.

   Nothing is visible at rest - no border, no fill, no label. The
   footage stays completely clean. On hover/focus, a soft glow and
   the room name appear as feedback; nothing is drawn permanently.
   ============================================================ */

.hub-hotspots {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}

.hub-hotspot {
  --room-color: var(--room-videoart);
  position: absolute;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  pointer-events: auto;
  touch-action: manipulation;
  -webkit-backdrop-filter: brightness(1.28) saturate(1.2);
  backdrop-filter: brightness(1.28) saturate(1.2);
  /* Soft-edged spotlight, not a hard rectangle: the brighten/saturate
     effect (applied to the real footage behind via backdrop-filter)
     fades out toward the edges instead of cutting off sharply. Always
     on (not just on hover/focus) so every room reads as lit by
     default, not just the one currently being pointed at. */
  -webkit-mask-image: radial-gradient(ellipse at center, #000 55%, transparent 100%);
  mask-image: radial-gradient(ellipse at center, #000 55%, transparent 100%);
  transition: backdrop-filter 0.35s var(--ease-out), -webkit-backdrop-filter 0.35s var(--ease-out);
}

.hub-hotspot:hover,
.hub-hotspot:focus-visible {
  -webkit-backdrop-filter: brightness(1.55) saturate(1.45);
  backdrop-filter: brightness(1.55) saturate(1.45);
}

.hub-hotspot-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  padding: 8px 16px 8px 12px;
  border-radius: 999px;
  background: rgba(8, 9, 11, 0.6);
  border: 1px solid color-mix(in srgb, var(--room-color) 50%, transparent);
  box-shadow: 0 0 16px rgba(255, 255, 255, 0.18), 0 0 6px color-mix(in srgb, var(--room-color) 50%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #ffffff;
  text-shadow: 0 0 8px rgba(255, 255, 255, 0.4);
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.25s var(--ease-out), transform 0.25s var(--ease-out);
}

.hub-hotspot:hover .hub-hotspot-label,
.hub-hotspot:focus-visible .hub-hotspot-label {
  opacity: 1;
  transform: translateY(0);
}

.hub-hotspot-label::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--room-color) 70%, white);
  box-shadow: 0 0 10px rgba(255, 255, 255, 0.6), 0 0 8px color-mix(in srgb, var(--room-color) 80%, transparent);
  flex-shrink: 0;
}

.hub-hotspot-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Fallback positions only, used before JS runs (or if it fails to for
   any reason) - positionHotspots() in hub.js overrides these with exact
   pixel values computed from the real letterboxed image bounds, since
   the media uses object-fit:contain and the visible image position
   shifts with window size in a way plain CSS percentages can't track
   reliably. Physical left/right on purpose, not inset-inline-start/end:
   the footage is a fixed asset that never mirrors for RTL. */

.hub-hotspot[data-room="lab"] {
  --room-color: var(--room-lab);
  left: 2%;
  top: 3%;
  width: 32%;
  height: 38%;
}

.hub-hotspot[data-room="videoart"] {
  --room-color: var(--room-videoart);
  right: 2%;
  top: 3%;
  width: 32%;
  height: 38%;
}

.hub-hotspot[data-room="web"] {
  --room-color: var(--room-web);
  left: 2%;
  top: 54%;
  width: 32%;
  height: 40%;
}

.hub-hotspot[data-room="creative"] {
  --room-color: var(--room-creative);
  right: 2%;
  top: 54%;
  width: 32%;
  height: 40%;
}

/* ============================================================
   Mobile: the hero photo is an ultra-wide panorama (~2.3:1). On a
   tall phone screen, fitting the whole thing (object-fit:contain)
   letterboxes it down to a thin strip - technically correct, but
   small and unimpressive. Below 700px, swap it out entirely for 4
   stacked cards, each a tight, tall-friendly crop of one room from
   the SAME existing image (no new photos/videos needed): background-
   size/position values are computed from the exact same room
   boundaries used for the desktop hotspots (see ROOM_BOUNDS in
   hub.js), just expressed as CSS background-position math instead
   of JS pixel math.
   ============================================================ */

.hub-mobile-hero,
.hub-mobile-intro,
.hub-mobile-rooms {
  display: none;
}

@media (max-width: 700px) {
  .hub-stage {
    display: none;
  }
  .hub-caption {
    display: none;
  }

  /* Full-bleed logo hero at the very top of the page, starting at
     y:0 so the fixed, translucent navbar overlays on top of it
     (same "chrome floats over the art" treatment as the desktop
     panorama), instead of the navbar sitting on flat black. */

  .hub-mobile-hero {
    display: block;
    position: relative;
    width: 100%;
    height: 46vh;
    min-height: 260px;
    max-height: 420px;
    overflow: hidden;
    background: var(--bg);
  }

  .hub-mobile-hero-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
  }

  .hub-mobile-intro {
    display: block;
    padding: 24px 20px 8px;
    background: var(--bg);
    text-align: center;
  }

  .hub-mobile-intro h1 {
    font-size: 26px;
    font-weight: 800;
    margin-bottom: 10px;
  }

  .hub-mobile-intro p {
    font-size: 15px;
    line-height: 1.6;
    color: var(--text-muted);
    max-width: 44ch;
    margin-inline: auto;
  }

  .hub-mobile-rooms {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    padding: 20px 16px 16px;
    background: var(--bg);
  }

  .hub-mobile-room {
    --room-color: var(--room-videoart);
    position: relative;
    display: flex;
    align-items: flex-end;
    height: 30vw;
    min-height: 130px;
    max-height: 170px;
    border-radius: 14px;
    overflow: hidden;
    background-image: url("hub-media/image/hero.jpg");
    background-repeat: no-repeat;
    border: 1px solid color-mix(in srgb, var(--room-color) 35%, transparent);
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
  }

  .hub-mobile-room[data-room="lab"] {
    --room-color: var(--room-lab);
    background-size: 313% 263%;
    background-position: 3% 5%;
  }

  .hub-mobile-room[data-room="videoart"] {
    --room-color: var(--room-videoart);
    background-size: 313% 263%;
    background-position: 97% 5%;
  }

  .hub-mobile-room[data-room="web"] {
    --room-color: var(--room-web);
    background-image: url("hub-media/image/web-mobile.jpg");
    background-size: cover;
    background-position: center;
  }

  .hub-mobile-room[data-room="creative"] {
    --room-color: var(--room-creative);
    background-image: url("hub-media/image/creative-mobile.jpg");
    background-size: cover;
    background-position: center;
  }

  .hub-mobile-room::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(4, 5, 6, 0.85), transparent 60%);
  }

  .hub-mobile-room:active {
    border-color: var(--room-color);
  }

  .hub-mobile-room-name {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 12px;
    font-size: 14px;
    font-weight: 800;
    /* Always sits over the dark photo thumbnail itself, regardless of
       the surrounding page theme, so this stays hardcoded white
       rather than following --text-primary. */
    color: #ffffff;
  }

  .hub-mobile-room-name::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--room-color);
    box-shadow: 0 0 8px color-mix(in srgb, var(--room-color) 80%, transparent);
    flex-shrink: 0;
  }

  .hub-mobile-room-tag {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}
