/* ==========================================================================
   Keychain cluster — isolated component
   Stack: BG.webp (static) + 4 clickable tag overlays (Branding/Social/Web/Star)
   Drop this folder + the <div class="keychain-cluster">…</div> markup into
   any page and link this file to reuse the interaction as-is.
   ========================================================================== */

.keychain-cluster {
  position: fixed;
  top: 0;
  right: clamp(1rem, 18.44vw, 20.53rem);
  width: clamp(358px, 50.61vw, 974px);
  z-index: 50;
  pointer-events: none;

  /* Ambient motion — idle float, cursor-follow parallax, and (only on
     the phone layout, where the cluster sits in normal flow) a scroll
     parallax as it scrolls past. All driven by wireHeroMotion in
     keychain-cluster.js, one rAF loop that lerps toward fresh targets
     every frame — that lerp is what gives it inertia/weight instead of
     snapping straight to the cursor. No CSS transition on this
     transform: a transition fighting a value that already changes every
     16ms would just add lag on top of the intentional one. Defaults
     below (all neutral) are what renders if the JS never runs
     (prefers-reduced-motion) — the cluster just sits still. */
  --kc-x: 0px;
  --kc-y: 0px;
  --kc-rx: 0deg;
  --kc-ry: 0deg;
  transform: perspective(1600px) translate3d(var(--kc-x), var(--kc-y), 0)
             rotateX(var(--kc-rx)) rotateY(var(--kc-ry));
}

.keychain-canvas {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 3;
  perspective: 1400px;
  pointer-events: auto;

  /* Resting crop offset only (per breakpoint, see media queries below)
     — purely layout, never animated. The entrance sequence lives on
     the children now (.kc-bg, .kc-item), staggered, so "carabiner
     fades in, then keychains appear attached" is an actual sequence
     rather than the whole canvas fading in as one flat block. */
  --kc-rest-y: -20%;
  transform: translateY(var(--kc-rest-y));
}

@media (prefers-reduced-motion: reduce) {
  .keychain-cluster {
    transform: none;
  }
}

/* static base — carabiner, ring & keys. Never animates. */
.kc-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  z-index: 1;

  /* entrance, stage 1: the carabiner — heavy, fixed, arrives first and
     plainly (a fade only, no motion, since it "barely moves"). */
  opacity: 0;
  transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

.keychain-canvas.kc-ready .kc-bg {
  opacity: 1;
}

.kc-item {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  z-index: 2;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;

  /* entrance, stage 2: each tag fades in "attached" (opacity only — it
     doesn't slide in from anywhere, it's already hanging in place),
     staggered slightly per tag below. Once visible, keychain-cluster.js
     kicks --kc-sway's spring to swing it out and settle, driven by
     wireHeroMotion — no CSS transition on that rotation: it's already
     eased by the JS spring integration every frame, and a transition on
     top would just add lag to an intentionally physical motion. */
  --kc-sway: 0deg;
  opacity: 0;
  transform: rotate(var(--kc-sway));
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}

.keychain-canvas.kc-ready .kc-item {
  opacity: 1;
}

/* Each tag hangs from where its own ring actually attaches (measured
   from the artwork's own bounding box), not a shared/generic point —
   and staggered slightly on entrance so the four don't appear in
   perfect unison. */
.kc-item[data-key="web"],
.kc-item[data-key="web"] .kc-img {
  transform-origin: 68.6% 40.1%;
}
.kc-item[data-key="web"] {
  transition-delay: 0.05s;
}

.kc-item[data-key="star"],
.kc-item[data-key="star"] .kc-img {
  transform-origin: 20.5% 41.4%;
}
.kc-item[data-key="star"] {
  transition-delay: 0.16s;
}

.kc-item[data-key="branding"],
.kc-item[data-key="branding"] .kc-img {
  transform-origin: 28.4% 34.1%;
}
.kc-item[data-key="branding"] {
  transition-delay: 0.27s;
}

.kc-item[data-key="social"],
.kc-item[data-key="social"] .kc-img {
  transform-origin: 55.5% 31.3%;
}
.kc-item[data-key="social"] {
  transition-delay: 0.38s;
}

.kc-item:focus-visible {
  outline: none;
}

/* A tag with no href is a category that isn't open yet. It keeps its
   place on the ring — it belongs to the object the home page is a
   picture of — but it stops behaving like something to click. An <a>
   without an href is already inert and out of the tab order; this is
   about the two things that are still offered by hand. */
.kc-item:not([href]) {
  cursor: default;
}

/* Above all, it must not lift. Hovering any tag raises it over the
   others (see .is-active below) — which is right for a category you are
   about to open, and wrong for one that opens nothing: the star, drawn
   furthest left of the four, would jump out in front of Branding for as
   long as the cursor crossed it. It keeps its tilt, because that is the
   object responding to being touched; it just stays in its own layer. */
.kc-item:not([href]).is-active,
.kc-item:not([href]).is-selected {
  z-index: 2;
}

.kc-item.is-active,
.kc-item.is-selected {
  z-index: 3;
}

.kc-hitlayer {
  position: absolute;
  inset: 0;
  z-index: 4;
  cursor: pointer;
}

.kc-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;

  --rx: 0deg;
  --ry: 0deg;
  --tz: 0px;
  --sx: 0px;
  --sy: 14px;
  --sb: 20px;
  --sa: 0.28;

  /* pivot at the same per-tag ring point as the parent .kc-item's own
     sway (see the [data-key] rules above) — the top stays put and only
     the bottom swings/tilts outward, instead of the top receding to
     compensate, and the hover-tilt now pivots from the same physical
     hinge as the ambient swing rather than a different point. */
  transform: rotateX(var(--rx)) rotateY(var(--ry)) translateZ(var(--tz));
  /* a single drop-shadow — this recomputes every frame while a tag is
     being tracked, and drop-shadow is one of the more expensive filters
     to rasterize, so the never-used-by-default second shadow layer
     (previously here for the "selected" glow) was pure wasted cost on
     every hover. .is-selected below adds its own second layer instead. */
  filter: drop-shadow(var(--sx) var(--sy) var(--sb) rgba(0, 0, 0, var(--sa)));
  /* short and overshoot-free: this retargets on every pointermove while
     the cursor tracks across the tag, and a longer/bouncy easing (fine
     for a one-off transition) makes continuous tracking look like it's
     lagging a beat behind the actual pointer instead of following it. */
  transition: transform 0.12s ease-out,
              filter 0.12s ease-out;
  will-change: transform, filter;
}

/* persistent selection glow — independent of hover tilt */
.kc-item.is-selected .kc-img {
  filter: drop-shadow(var(--sx) var(--sy) var(--sb) rgba(0, 0, 0, var(--sa)))
          drop-shadow(0 0 1.5px rgba(255, 255, 255, 0.55));
}

/* hover shine — radial highlight that tracks the pointer, clipped to the
   tag's own silhouette so it never spills onto the transparent canvas */
.kc-glare {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
              rgba(255, 255, 255, 0.5), transparent 30%);
  mix-blend-mode: screen;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease-out;

  -webkit-mask-image: var(--mask-src);
  mask-image: var(--mask-src);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

.kc-item.is-active .kc-glare {
  opacity: 0.2;
}

/* WEB-KC's pale blue reads much brighter than the others under the same
   screen-blend glare (light base color + screen mode washes out further),
   so it gets a lower ceiling to look even with STAR/SOCIAL/BRANDING. */
.kc-item[data-key="web"].is-active .kc-glare {
  opacity: 0.1;
}

@media (prefers-reduced-motion: reduce) {
  .kc-img,
  .kc-glare {
    transition-duration: 0.05s;
  }

  .kc-bg,
  .kc-item {
    transition-duration: 0.05s;
    transition-delay: 0s;
  }
}

/* Tablet */
@media (max-width: 900px) {
  .keychain-cluster {
    width: clamp(149px, 18.62vw, 218px);
    right: clamp(0.75rem, 7.7vw, 4.66rem);
  }
}

/* Phone — a large centered hero instead of a small fixed corner tile:
   flows in-page above the intro/contact text (see page.css) rather
   than staying pinned while the page scrolls underneath it. */
@media (max-width: 600px) {
  .keychain-cluster {
    position: static;
    top: auto;
    right: auto;
    width: min(88vw, 420px);
    /* Sized from the real alpha bounds of every layer (BG + all 4 tags),
       not just the carabiner — BRANDING-KC's own artwork reaches down to
       ~89% of the canvas (well past BG's own ~67%), so a box sized only
       around the carabiner was clipping the bottom of that tag. No
       mobile override on --kc-rest-y below: it now reuses the same -20%
       as desktop, so the carabiner's top is covered by the same amount
       on both — this box is sized (72% of the canvas's own 2:3 ratio)
       to match that offset while still keeping the lowest tag's ~89%
       comfortably inside, instead of the taller box the previous, more
       conservative offset needed. */
    aspect-ratio: 2 / 2.16;
    overflow: hidden;
    margin: 0 auto;
  }
}
