#skills.skill-orbit {
  --orbit-paper: #eadbc6;
  --orbit-paper-light: #fff8ec;
  --orbit-ink: #21170f;
  --orbit-ink-soft: #5c4c3e;
  --orbit-enamel: #fffaf0;
  --orbit-accent: #bf672f;
  --skill-photo-handoff: 0;
  --project-skill-aperture: 78vmax;
  --project-skill-core-scale: 1;
  --project-skill-orbit-reveal: 1;
  isolation: isolate;
  overflow: hidden;
  color: var(--orbit-ink);
  background:
    radial-gradient(circle at 50% 46%, rgba(255,255,255,.74) 0 14%, rgba(255,255,255,.16) 35%, transparent 61%),
    linear-gradient(135deg, var(--orbit-paper-light), var(--orbit-paper));
}

#skills.skill-orbit::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset: 0;
  pointer-events: none;
  opacity: .26;
  background:
    repeating-radial-gradient(circle at 50% 50%, rgba(65,45,29,.05) 0 1px, transparent 1px 5px);
  mix-blend-mode: multiply;
  -webkit-mask-image: radial-gradient(ellipse at center, #000 0 28%, transparent 76%);
  mask-image: radial-gradient(ellipse at center, #000 0 28%, transparent 76%);
}

#skills.skill-orbit > .scene-inner,
#skills.skill-orbit .wrap {
  min-height: inherit;
}

#skills.skill-orbit .wrap {
  isolation: isolate;
}

#paper-sheet {
  display: none !important;
}

.skill-orbit__arrival {
  display: none;
}

.skill-orbit__stage {
  position: absolute;
  z-index: 3;
  inset: 0;
  overflow: hidden;
  perspective: 1180px;
  perspective-origin: 50% 47%;
  background:
    radial-gradient(circle at 50% 46%, rgba(255,255,255,.74) 0 14%, rgba(255,255,255,.16) 35%, transparent 61%),
    linear-gradient(135deg, var(--orbit-paper-light), var(--orbit-paper));
}

.skill-orbit__rings {
  position: absolute;
  z-index: 3;
  top: 48%;
  left: 50%;
  width: 1px;
  height: 1px;
  pointer-events: none;
  transform-style: preserve-3d;
  opacity: min(calc(1 - var(--skill-photo-handoff)), var(--project-skill-orbit-reveal));
  will-change: transform, opacity;
}

.skill-orbit__rings--front {
  z-index: 7;
}

.skill-orbit__rings--front .skill-orbit__label {
  color: var(--orbit-ink);
  -webkit-text-stroke: .042em var(--orbit-enamel);
  paint-order: stroke fill;
  text-shadow:
    0 1px 0 rgba(255,255,255,.9),
    0 3px 12px rgba(34,23,15,.22);
}

.skill-orbit__arm {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  transform-style: preserve-3d;
  will-change: transform, opacity;
}

.skill-orbit__label {
  position: absolute;
  top: 0;
  left: 0;
  width: max-content;
  color: var(--orbit-ink-soft);
  /* Forty-one words share one ring. At display size they overlapped into an
     unreadable knot — "Clean architecture" landing on top of "SwiftUI" — so
     the ring is set at reading size instead. The orbit is still the subject;
     the type just stops fighting itself for the same arc. */
  font: 600 clamp(1.2rem, 2.1vw, 2.45rem)/.95 var(--sans);
  font-size: calc(clamp(1.2rem, 2.1vw, 2.45rem) * var(--label-scale, 1));
  letter-spacing: -.045em;
  white-space: nowrap;
  text-rendering: geometricPrecision;
  -webkit-text-stroke: .025em rgba(255,250,240,.88);
  paint-order: stroke fill;
  text-shadow: 0 2px 9px rgba(34,23,15,.14);
  transform: translate(-50%, -50%) scale(var(--project-skill-core-scale));
  transform-origin: 50% 50%;
  backface-visibility: visible;
  will-change: opacity;
}

.skill-orbit__label[data-ring="1"] {
  font-weight: 520;
}

.skill-orbit__label[data-ring="2"] {
  font-weight: 680;
}

.skill-orbit__label[data-ring="3"] {
  font-weight: 540;
}

.skill-orbit__label[data-ring="4"] {
  font-weight: 650;
}

.skill-orbit__model-link {
  position: absolute;
  z-index: 5;
  top: 48%;
  left: 50%;
  display: grid;
  place-items: center;
  width: clamp(250px, 27vw, 400px);
  aspect-ratio: 1;
  border-radius: 27%;
  color: var(--orbit-ink);
  text-decoration: none;
  transform: translate(-50%, -50%);
  outline: none;
  will-change: transform, opacity;
}

.skill-orbit__model-link {
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  opacity: calc(1 - var(--skill-photo-handoff));
  transform: translate(-50%, -50%) scale(calc(1 - var(--skill-photo-handoff) * .22));
  cursor: grab;
}

.skill-orbit__model-link::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 9%;
  border-radius: 50%;
  background: rgba(91,60,35,.13);
  filter: blur(36px);
  transform: translate3d(0, 13%, -1px) scale(.82);
  transition: transform .6s cubic-bezier(.16,1,.3,1);
}

.skill-orbit__model-link:hover::before,
.skill-orbit__model-link:focus-visible::before,
.skill-orbit__model-link.is-dragging::before {
  transform: translate3d(0, 14%, -1px) scale(.94);
}

#skill-orbit-canvas,
.skill-orbit__fallback {
  display: block;
  width: 100%;
  height: 100%;
}

#skill-orbit-canvas {
  position: relative;
  z-index: 2;
  touch-action: pan-y;
  cursor: grab;
}

.skill-orbit__model-link.is-dragging #skill-orbit-canvas {
  cursor: grabbing;
}

.skill-orbit__fallback {
  position: absolute;
  z-index: 1;
  inset: 12%;
  width: 76%;
  height: 76%;
  border: 2px solid rgba(255,250,240,.88);
  border-radius: 50%;
  background:
    radial-gradient(circle at 38% 32%, rgba(255,255,255,.9) 0 5%, transparent 22%),
    radial-gradient(circle, var(--orbit-accent) 0 33%, #231a14 34% 43%, transparent 44%),
    conic-gradient(from 22deg, #cfbea8, var(--orbit-accent), #8c7964, #cfbea8);
  filter: drop-shadow(0 24px 32px rgba(75,48,27,.25));
  transition: opacity .25s ease;
}

.skill-orbit__fallback::before,
.skill-orbit__fallback::after,
.skill-orbit__fallback i {
  content: "";
  position: absolute;
  inset: -18%;
  border: 2px solid rgba(43,33,24,.58);
  border-radius: 50%;
  transform: rotateX(62deg) rotateZ(18deg);
}

.skill-orbit__fallback::after {
  inset: -30%;
  border-width: 1px;
  transform: rotateY(64deg) rotateZ(-28deg);
}

.skill-orbit__fallback i {
  inset: -7%;
  border-color: rgba(255,250,240,.82);
  transform: rotateX(48deg) rotateY(42deg);
}

.skill-orbit.has-webgl .skill-orbit__fallback {
  opacity: 0;
}

.skill-orbit__status,
#skills .skill-orbit__manifest {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: -1px !important;
  padding: 0 !important;
  overflow: hidden !important;
  clip-path: inset(50%) !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

.skill-orbit__model-link:focus-visible {
  outline: 2px solid var(--orbit-accent);
  outline-offset: 10px;
}

/* Chromium may promote project videos to their own composited surfaces. Hide
   that preceding stage while the pinned Skills chamber covers the viewport so
   the warm paper remains genuinely clean instead of leaking old project frames.
   Fade rather than snap so a fast seam crossing never pops. */
#projects .cinematic-selector {
  transition: opacity .35s ease, visibility 0s linear 0s;
}

body.skills-orbit-active #projects .cinematic-selector {
  opacity: 0;
  visibility: hidden;
  transition: opacity .35s ease, visibility 0s linear .35s;
}

body.skills-orbit-active .fw-sync-layer {
  opacity: 0 !important;
  visibility: hidden !important;
}

/* Projects → Skills calibration aperture.
   The section remains in document flow; its inner stage borrows the viewport
   only across the boundary, then returns to identical pinned geometry. */
body.project-skills-handoff-active #skills.skill-orbit {
  overflow: visible;
  background: transparent;
}

body.project-skills-handoff-active #skills.skill-orbit::before {
  opacity: 0;
}

body.project-skills-handoff-active #skills > .scene-inner,
body.project-skills-handoff-active #skills .wrap {
  transform: none !important;
}

/* `position: fixed` cannot borrow the viewport here. ScrollTrigger writes a
   transform onto #skills to pin it, which makes the section the containing
   block for fixed descendants — so `top: 84px` measured from the section, not
   the viewport, and left the stage hanging 84px low with its own straight top
   edge exposed.

   The stage is translated onto the aperture instead. --project-skill-lift is
   exactly the distance the section still has to travel before it lands under
   the header, so the stage sits at its final position from the first frame of
   the boundary and resolves to identity — a zero lift — at the last. The
   circle below is therefore concentric with #iris-stage's by construction, at
   any scroll position and any viewport: the two draw one disc, the section's
   own top edge is above it, and the straight horizontal seam this boundary
   used to be is gone.

   The paper comes from #iris-stage for the same reason. One full-viewport
   surface has no seam in it; a section-sized one has to meet the strip above
   the header somewhere. */
body.project-skills-handoff-active .skill-orbit__stage {
  z-index: 8;
  background: transparent;
  clip-path: circle(var(--project-skill-aperture) at 50% 48%);
  transform: translate3d(0, var(--project-skill-lift, 0px), 0);
  will-change: clip-path, transform;
}

/* The instrument resolves as the stop opens: it enters at the aperture's own
   scale rather than arriving full-size behind it. (The same variable already
   sets the words; the model is the thing it was named for.) */
body.project-skills-handoff-active .skill-orbit__model-link {
  transform: translate(-50%, -50%) scale(var(--project-skill-core-scale, 1));
}

/* The evidence steps back while the aperture opens. Keep this boundary on
   transform + opacity: filtering the full-width selector reconvolved a large
   raster on every scrubbed frame. */
body.project-skills-handoff-active #projects .cinematic-selector {
  opacity: calc(1 - var(--project-skills-exit, 0));
  transform:
    translate3d(0, var(--project-skills-y, 0), 0)
    scale(var(--project-skills-scale, 1));
  transform-origin: 50% 100%;
  will-change: transform, opacity;
}

body.project-skills-handoff-active .fw-sync-layer {
  z-index: 3;
}

body.skills-orbit-active #project-assistant-toggle {
  opacity: 0;
  pointer-events: none;
}

@media (max-width: 900px) {
  .skill-orbit__model-link {
    width: clamp(226px, 35vw, 320px);
  }

  /* Deliberately not raised with the desktop ring: these two breakpoints drop
     the `--label-scale` multiplier, so a bigger cut here would take the long
     labels' anti-overlap step-down with it. */
  .skill-orbit__label {
    font-size: clamp(.92rem, 1.9vw, 1.35rem);
  }
}

@media (max-width: 640px) {
  #skills.skill-orbit {
    background:
      radial-gradient(circle at 50% 45%, rgba(255,255,255,.78) 0 12%, rgba(255,255,255,.14) 36%, transparent 63%),
      linear-gradient(145deg, var(--orbit-paper-light), var(--orbit-paper));
  }

  .skill-orbit__stage,
  .skill-orbit__rings {
    perspective-origin: 50% 45%;
  }

  .skill-orbit__rings,
  .skill-orbit__model-link {
    top: 45%;
  }

  .skill-orbit__model-link {
    width: min(57vw, 226px);
  }

  .skill-orbit__label {
    font-size: clamp(.78rem, 2.6vw, 1.02rem);
  }

  /* The stage no longer needs to be re-sized into the viewport by hand — the
     lift puts it there — but the phone ring sits at 45%, so the circle it is
     clipped to has to as well or the two stop being concentric. */
  body.project-skills-handoff-active .skill-orbit__stage {
    clip-path: circle(var(--project-skill-aperture) at 50% 45%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .skill-orbit__arrival {
    display: none;
  }

  .skill-orbit__arm,
  .skill-orbit__rings,
  .skill-orbit__label {
    will-change: auto;
  }

  .skill-orbit__model-link {
    transition: none;
  }
}

/* ---- The forty-one words ARE the retrieval path now. The discipline selector
   and the evidence panel that used to flank the orbit are gone: each word is a
   link to the record that proves it, so it carries the affordances of one and
   states its discipline in its accessible name instead of in a legend.

   The ring container stays pointer-events:none; renderLabels re-enables only
   the arms whose word is actually facing the visitor, so a label hidden behind
   the model cannot catch a click meant for the model. ---- */
.skill-orbit__rings--front .skill-orbit__arm {
  pointer-events: none;
}

a.skill-orbit__label {
  cursor: pointer;
  text-decoration: none;
  transition: color .18s ease;
}

/* Drawn rather than underlined: text-decoration would sit under a label that is
   already carrying a text stroke for legibility against the photograph. */
a.skill-orbit__label::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: -.16em;
  left: 0;
  height: .07em;
  background: var(--orbit-accent);
  transform: scaleX(0);
  transition: transform .22s cubic-bezier(.2, .7, .2, 1);
}

.skill-orbit__arm.is-held a.skill-orbit__label,
a.skill-orbit__label:hover,
a.skill-orbit__label:focus-visible {
  color: var(--orbit-accent);
}

.skill-orbit__arm.is-held a.skill-orbit__label::after,
a.skill-orbit__label:hover::after,
a.skill-orbit__label:focus-visible::after {
  transform: scaleX(1);
}

a.skill-orbit__label:focus-visible {
  outline: 2px solid var(--orbit-accent);
  outline-offset: .3em;
}

@media (prefers-reduced-motion: reduce) {
  a.skill-orbit__label,
  a.skill-orbit__label::after {
    transition: none;
  }
}

/* Reduced motion skips the scroll-driven ownership handoff, so the same
   navigation affordance ships as a plain link, visible only in that mode. */
.skill-orbit__continue {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .skill-orbit__continue {
    position: absolute;
    z-index: 12;
    left: 50%;
    bottom: clamp(22px, 5vh, 48px);
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 10px 18px;
    border: 1px solid rgba(43, 31, 20, .32);
    border-radius: 2px;
    background: var(--orbit-paper-light);
    color: var(--orbit-ink);
    font: 600 .72rem/1 var(--sans);
    letter-spacing: .1em;
    text-transform: uppercase;
    text-decoration: none;
    transform: translateX(-50%);
  }
}

/* The assistant launcher must not materialize inside the Skills→Photography
   handoff; the pin's own class window ends before the covering hold does. */
body:has(.s2-stage.on) #project-assistant-toggle {
  opacity: 0;
  pointer-events: none;
}
