<!-- SOLUV · part 488 -->
x}
  .sp-status[data-state=error]{color:#ff9a8f;border-color:#ff6b5a}
  .sp-status[data-state=live]{color:#9ff5c8;border-color:#4fe3a0}
  .sp-reading .sp-alt{display:none}
  .sp-alt{position:absolute;left:10px;top:calc(max(8px,env(safe-area-inset-top)) + 84px);height:34vh;width:44px;z-index:2;pointer-events:none;display:flex;flex-direction:column;align-items:center}
  .sp-alt-bar{position:relative;flex:1;width:2px;background:linear-gradient(#5ff4ff,#5ff4ff22);box-shadow:0 0 6px #00d0ff88}
  .sp-alt-bar i{position:absolute;left:-5px;width:12px;height:1px;background:#5ff4ffaa}
  .sp-alt-bar i:nth-child(1){top:0}.sp-alt-bar i:nth-child(2){top:25%}.sp-alt-bar i:nth-child(3){top:50%}.sp-alt-bar i:nth-child(4){top:75%}.sp-alt-bar i:nth-child(5){top:100%}
  .sp-alt-mark{position:absolute;left:-7px;width:16px;height:16px;margin-bottom:-8px;border:2px solid #e8fdff;border-radius:50%;box-sizing:border-box;box-shadow:0 0 10px #5ff4ff;transition:bottom .1s linear}
  .sp-alt-n{margin-top:8px;font:700 16px/1 ${F};color:#e8fdff;text-shadow:0 0 8px #00d0ff}
  .sp-alt-u{font:12px/1.2 ${F};letter-spacing:.2em;color:#7fc6da}
  .sp-bottom{flex:none;display:flex;flex-direction:column;gap:6px;padding:6px 8px max(8px,env(safe-area-inset-bottom));background:#000;border-top:1px solid #0e3a48;z-index:4}
  .sp-ctrls{display:flex;flex-direction:row;gap:4px}
  .sp-btn{box-sizing:border-box;flex:1 1 0;min-width:48px;height:48px;min-height:48px;border-radius:24px;border:1px solid #2bb8d8aa;background:#00141dcc;color:#cff7ff;font:600 15px/1 ${F};letter-spacing:.04em;padding:0;cursor:pointer;touch-action:manipulation;display:flex;align-items:center;justify-content:center;text-decoration:none;box-shadow:0 0 12px #00d0ff33,inset 0 0 10px #00d0ff22;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
  .sp-btn.sp-rise{color:#001018;background:linear-gradient(#bff8ff,#5ff4ff);border-color:#bff8ff;box-shadow:0 0 16px #5ff4ffaa}
  .sp-btn.sp-rise.sp-on{background:#003040;color:#bff8ff}
  .sp-btn.sp-live{color:#9ff5c8;border-color:#4fe3a0aa}
  .sp-btn.sp-home{color:#ffd98a;border-color:#c9a227aa}
  .sp-compose{display:flex;gap:6px;margin:0}
  .sp-input{flex:1;min-width:0;height:48px;box-sizing:border-box;border-radius:24px;border:1px solid #2bb8d8aa;background:#00101acc;color:#fff;font:17px/1 ${F};padding:0 16px;outline:none;-webkit-appearance:none;appearance:none}
  .sp-input:focus{border-color:#bff8ff}.sp-input:disabled{opacity:.75}
  .sp-send,.sp-login{box-sizing:border-box;min-width:64px;height:48px;border-radius:24px;border:1px solid #5ff4ff;background:#003040;color:#bff8ff;font:600 16px/1 ${F};display:flex;align-items:center;justify-content:center;text-decoration:none;cursor:pointer;touch-action:manipulation;padding:0 14px;white-space:nowrap}
  .sp-send:disabled{opacity:.5}
  .sp-login{display:none}
  .sp-compose.sp-guest .sp-send{display:none}.sp-compose.sp-guest .sp-login{display:flex}
  .sp-compose.sp-away,.sp-goto.sp-away{display:none}
  .sp-goto{box-sizing:border-box;width:100%;min-height:48px;height:48px;border-radius:24px;border:1px solid #4fe3a0aa;background:#00140dcc;color:#9ff5c8;font:600 16px/1 ${F};cursor:pointer;touch-action:manipulation}
  .sp-voice{position:absolute;right:8px;top:max(8px,env(safe-area-inset-top));z-index:5;box-sizing:border-box;min-width:56px;height:48px;min-height:48px;border-radius:24px;border:1px solid #2bb8d8aa;background:#00141dcc;color:#bff8ff;font:600 15px/1 ${F};cursor:pointer;touch-action:manipulation;padding:0 10px}
  .sp-compose.sp-guest .sv-mic{display:none}
  .sp-archive{position:absolute;left:8px;right:8px;top:calc(max(8px,env(safe-area-inset-top)) + 72px);z-index:4;box-sizing:border-box;min-height:48px;display:flex;align-items:center;padding:0 14px;border-radius:24px;border:1px solid #c9a227aa;background:#1d1606dd;color:#ffd98a;font:600 15px/1.2 ${F};text-decoration:none}
  .sp-archive[hidden]{display:none}
  `;
  document.head.appendChild(s);
}


===== FILE: public/soluv/world/chat-wall.js =====
/*
 * chat-wall.js: Playa's portable, live, dense chat wall for the /soluv world.
 * One wall = one chat room. It is a plain THREE.Group you can drop into ANY three.js scene
 * (the /soluv world, Solid's saga, /playa ...). It does not own the page, the renderer, the camera or the loop.
 *
 *   import { createChatWall } from '/soluv/world/chat-wall.js';
 *   const wall = createChatWall({ THREE, room: 1, position: [0, 0, -40], rotationY: 0, scale: 1 });
 *   scene.add(wall);            // that's all: it updates itself in onBeforeRender, loads its room when you look at it
 *   wall.wall.dispose();        // when you are done
 *
 * THREE is injected (works with the page's own three: r128 global in the saga, r186 module in /soluv).
 * THE LAW, Minimum 0.13: window.MINIMUM = 0.13 (an avatar's eye). One text em = exactly MINIMUM world units,
 * so the smallest glyph is eye-sized; every solid part is >= MINIMUM on every axis; scale is clamped to >= 1.
 * Data thrift: nothing