<!-- SOLID CITY · part 2117 -->
rStop(0,'#fff8e8'); gpgr.addColorStop(0.25,'#f2c86a');
  gpgr.addColorStop(0.5,'#9a6ae8'); gpgr.addColorStop(0.75,'#4a6ae8'); gpgr.addColorStop(1,'#1a1a3a');
  gpx.fillStyle=gpgr; gpx.beginPath(); gpx.arc(64,64,64,0,7); gpx.fill();
  gpx.strokeStyle='#f2c86a'; gpx.lineWidth=3;
  for (var gpr=12; gpr<64; gpr+=13) { gpx.beginPath(); gpx.arc(64,64,gpr,0,7); gpx.stroke(); }
  var portTex = new T.CanvasTexture(gpc); portTex.encoding=T.sRGBEncoding;
  var gateDiscs = [];
  ring.forEach(function(gt, gi){
    var nxt = ring[(gi+1)%ring.length];
    var bg = buildPortalGate(T, root, foot, {
      gold:gold, textPlate:textPlate, portTex:portTex,
      x:gt.x, z:gt.z, y:gt.y, nextName:nxt.name
    });
    gt.disc = bg.disc; gt.next = nxt;
    gateDiscs.push(bg.disc);
    if (gt.name==='THE PLAZA') portalPlaza = gt;
  });
  // transit overlay: gold/violet swirl (wormhole language)
  var transitOverlay = document.createElement('div');
  transitOverlay.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:9998;opacity:0;' +
    'background:radial-gradient(circle at 50% 50%, rgba(255,248,232,1) 0%, rgba(242,200,106,0.9) 25%, rgba(154,106,232,0.55) 55%, rgba(26,26,58,0.85) 100%);' +
    'transition:opacity 0.45s ease;';
  document.body.appendChild(transitOverlay);
  var transit = { active:false, t:0, dest:null, cd:0 };
  function startTransit(dest){
    if (transit.active) return;
    transit.active = true; transit.t = 0; transit.dest = dest;
    transitOverlay.style.opacity = '1';
  }
  root.userData={
    footprint:foot,center:{x:0,z:0},radius:260,height:580,
    elevators:elevators, standFloors:standFloors,
    update:function(t,dt,A){
      if(beaconM){var p=0.6+0.4*Math.sin(t*2.4);beaconM.scale.setScalar(p);}
      if(beamM){beamM.material.opacity=0.2+0.12*Math.sin(t*2.4);}
      if(fw){fw.position.y=2.6+Math.sin(t*3)*0.15;}
      if(fj){fj.scale.y=1+Math.sin(t*5)*0.12;}
      for(var b=0;b<boats.length;b++){boats[b].position.y=-2.5+Math.sin(t*1.2+b*1.7)*0.5;boats[b].rotation.z=Math.sin(t*0.9+b)*0.04;}
      if(window._hyperPortholes){ for(var ph=0; ph<window._hyperPortholes.length; ph++){ var hm=window._hyperPortholes[ph]; hm.material.opacity=0.85+0.15*Math.sin(t*2+ph*0.7); hm.rotation.z=t*0.3+ph; } }
      if(window._hyperShips){ for(var sh3=0; sh3<window._hyperShips.length; sh3++){ var shp=window._hyperShips[sh3]; shp.position.y=Math.sin(t*0.8+shp.userData.ph)*0.6; shp.rotation.z=Math.sin(t*0.6+shp.userData.ph)*0.02; shp.rotation.x=Math.sin(t*0.5+shp.userData.ph)*0.015; } }
      if(window._hyperBeams){ for(var bm3=0; bm3<window._hyperBeams.length; bm3++){ window._hyperBeams[bm3].rotation.y=t*0.8; } }
      if(window._hyperBuoys){ for(var by3=0; by3<window._hyperBuoys.length; by3++){ var buo=window._hyperBuoys[by3]; if(buo.name==='buoy'){ buo.position.y=-2+Math.sin(t*1.5+buo.userData.ph)*0.5; buo.rotation.x=Math.sin(t+buo.userData.ph)*0.1; } else { buo.material.opacity=0.35+0.65*Math.abs(Math.sin(t*2+buo.userData.ph)); } } }
      if(window._hyperTrolleys){ for(var tr3=0; tr3<window._hyperTrolleys.length; tr3++){ var tro=window._hyperTrolleys[tr3]; tro.position.x=tro.userData.cx+Math.sin(t*0.25+tr3*1.7)*8; } }
      // elevators: animate cabs, carry the rider
      if(dt&&A&&A.S){
        var S=A.S;
        for(var e=0;e<elevators.length;e++){
          var el=elevators[e];
          if(el.moving){
            el.t+=dt;
            var dur=3.0, k=Math.min(1,el.t/dur);
            var ease=k<0.5?2*k*k:1-Math.pow(-2*k+2,2)/2; // easeInOut
            var y=el.from+(el.to-el.from)*ease;
            el.cab.position.y=y+1.75;
            // carry the player if inside the cab (world coords)
            var dx=S.p.x-(P.x+el.x), dz=S.p.z-(P.z+el.z);
            if(Math.abs(dx)<3&&Math.abs(dz)<3&&Math.abs(S.p.y-(P.y+y))<4){
              S.p.y=P.y+y+0.2;
            }
            if(k>=1){ el.moving=false; el.idx=el.toIdx; }
          }
        }
      }
      // portal gates: step into the disc → ring transit
      if(dt&&A&&A.S&&!transit.active){
        var S2=A.S;
        if(S2.mode==='walk'){
          if(transit.cd>0) transit.cd-=dt;
          else {
            for(var gi2=0;gi2<ring.length;gi2++){
              var gt2=ring[gi2];
              if(Math.abs(S2.p.x-(P.x+gt2.x))<1.6 && Math.abs(S2.p.z-(P.z+gt2.z))<1.6 && Math.abs(S2.p.y-(P.y+gt2.y))<3){
                var nd2=gt2.next;
                startTransit({x:P.x+nd2.x, y:P.y+nd2.y+0.5, z:P.z+nd2.z, name:nd2.name});
                break;
              }
            }
          }
        }
      }
      if(transit.active&&dt){
        transit.t+=dt;
        if(transit.t>=2.0){
          var td=transit.dest;
          A.S.p.x=td.x; A.S.p.y=td.y; A.S.p.z=td.z;
          transit.active=false; transit.cd=4.0;
          transitOverlay.style.opacity='0';
          if(td.world&&window.CityHyper&&window.CityHyper.narrate){
            try{window.CityHyper.narrate('<b>Solid City</b>: touch the Hyper Controller to return to 