File size: 6,533 Bytes
1955a0f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { Link, useLocation, useNavigate } from "react-router-dom";
import { LayoutDashboard } from "lucide-react";
import { cn } from "../utils/cn";
import { useTheme } from "../context/ThemeContext";
import AnimatedLogo from "./../components/AnimatedLogo";
import Logo from "./../components/Logo";
import { useLaunch } from "./LaunchContext";
import { MODULES, ModuleId, moduleHref } from "./registry";

/**
 * Platform sidebar — wires the BRIDGE MODULES direct-launch section plus the
 * active module's navigation registry. Launching a module plays its
 * random-reassembly animation (re-seeded every launch).
 */
export default function PlatformSidebar({ module, onClose }: { module: ModuleId; onClose?: () => void }) {
  const location = useLocation();
  const navigate = useNavigate();
  const { config } = useTheme();
  const { startLaunch } = useLaunch();
  const def = MODULES[module];

  const launch = (target: ModuleId | "hub") => {
    onClose?.();
    startLaunch(target);
  };

  const isActiveModule = (id: ModuleId) => location.pathname.startsWith(MODULES[id].basePath);
  const isActivePath = (path: string) => {
    const full = moduleHref(module, path);
    return path === "" ? location.pathname === full : location.pathname.startsWith(full);
  };

  return (
    <div className="w-64 border-r border-white/5 bg-zinc-950/90 md:bg-zinc-950/60 backdrop-blur-2xl flex flex-col shrink-0 h-full relative z-20 shadow-[4px_0_24px_-4px_rgba(0,0,0,0.8)]">
      <div className="h-16 flex items-center px-6 border-b border-white/5 shrink-0 gap-3">
        <AnimatedLogo className="w-7 h-7" />
        <Logo wordmark={def.wordmark} />
      </div>

      <nav className="flex-1 overflow-y-auto py-4 px-3 space-y-1">
        {/* BRIDGE MODULES — direct-launch section (Phase 5) */}
        <div className="mb-4">
          <div className="px-3 pb-2 text-[9px] font-bold tracking-[0.22em] uppercase text-zinc-600 font-mono">
            Bridge Modules
          </div>
          <div className="space-y-1.5">
            {(["e", "s"] as ModuleId[]).map((id) => {
              const m = MODULES[id];
              const active = isActiveModule(id);
              const Icon = m.icon;
              return (
                <button
                  key={id}
                  type="button"
                  onClick={() => launch(id)}
                  aria-current={active ? "page" : undefined}
                  className={cn(
                    "w-full flex items-center gap-3 px-3 py-2.5 text-[13px] font-medium rounded-lg transition-all duration-300 relative group overflow-hidden border text-left",
                    active
                      ? "bg-white/5 border-white/10 shadow-[inset_0_1px_1px_rgba(255,255,255,0.05)]"
                      : "text-zinc-400 hover:text-zinc-200 hover:bg-white/5 border-transparent"
                  )}
                  style={active ? { color: m.theme.accentText } : {}}
                >
                  {active && (
                    <span
                      className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 rounded-r-full"
                      style={{ backgroundColor: m.theme.accent, boxShadow: `0 0 15px ${m.theme.accent}` }}
                    />
                  )}
                  <span
                    className="w-5 h-5 rounded-md flex items-center justify-center shrink-0 border transition-transform duration-300 group-hover:scale-110"
                    style={{
                      backgroundColor: `${m.theme.accent}14`,
                      borderColor: `${m.theme.accent}44`,
                    }}
                  >
                    <Icon className="w-3 h-3" style={{ color: m.theme.accent }} />
                  </span>
                  <span className="flex-1">{m.label}</span>
                  <span
                    className="w-1.5 h-1.5 rounded-full shrink-0 transition-opacity"
                    style={{
                      backgroundColor: m.theme.accent,
                      boxShadow: `0 0 8px ${m.theme.accent}`,
                      opacity: active ? 1 : 0.35,
                    }}
                  />
                </button>
              );
            })}
          </div>
          <button
            type="button"
            onClick={() => launch("hub")}
            className="mt-1.5 w-full flex items-center gap-3 px-3 py-2 text-[11px] font-medium rounded-lg text-zinc-500 hover:text-zinc-300 hover:bg-white/5 border border-transparent transition-all duration-300 text-left"
          >
            <LayoutDashboard className="w-3.5 h-3.5" />
            Platform Hub
          </button>
        </div>

        {/* Active module navigation */}
        <div className="px-3 pb-2 pt-1 text-[9px] font-bold tracking-[0.22em] uppercase text-zinc-600 font-mono">
          {def.label}
        </div>
        {def.nav.map((item) => {
          const isActive = isActivePath(item.path);
          const full = moduleHref(module, item.path);
          return (
            <button
              key={item.name}
              type="button"
              onClick={() => {
                onClose?.();
                if (location.pathname !== full) navigate(full);
              }}
              aria-current={isActive ? "page" : undefined}
              className={cn(
                "w-full flex items-center gap-3 px-3 py-2.5 text-[13px] font-medium rounded-lg transition-all duration-300 relative group overflow-hidden border text-left",
                isActive
                  ? "bg-white/5 border-white/10 shadow-[inset_0_1px_1px_rgba(255,255,255,0.05)]"
                  : "text-zinc-400 hover:text-zinc-200 hover:bg-white/5 border-transparent"
              )}
              style={isActive ? { color: config.accentText } : {}}
            >
              {isActive && (
                <div
                  className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 rounded-r-full"
                  style={{
                    backgroundColor: config.accent,
                    boxShadow: `0 0 15px ${config.accent}`,
                  }}
                />
              )}
              <item.icon
                className={cn("w-4 h-4 transition-transform duration-300 group-hover:scale-110", isActive ? "" : "text-zinc-500")}
                style={isActive ? { color: config.accent, filter: `drop-shadow(0 0 8px ${config.accent})` } : {}}
              />
              {item.name}
            </button>
          );
        })}
      </nav>
    </div>
  );
}