File size: 8,478 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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import { useEffect } from "react";
import { motion } from "motion/react";
import { ArrowRight, Boxes, CheckCircle2, XCircle, Zap } from "lucide-react";
import GlassCard from "../components/GlassCard";
import AnimatedLogo from "../components/AnimatedLogo";
import { useApiKey } from "../context/ApiKeyContext";
import { MINT_THEME } from "../context/ThemeContext";
import { useLaunch } from "./LaunchContext";
import { MODULES, MODULE_IDS } from "./registry";
import { preloadModulePages } from "./lazyPages";

const MODULE_CAPS: Record<string, string[]> = {
  e: ["Sequence automation", "Unified inbox", "Warmup radar", "Deliverability analytics"],
  s: ["Campaign management", "Lead database", "Webhook automation", "Email warmup"],
};

/**
 * BRIDGE platform hub — the launcher. Real platform primitives only
 * (GlassCard, AnimatedLogo, API-key status). Buttons trigger the module
 * launch animation (re-seeded per launch) via LaunchContext.
 */
export default function ModuleHub() {
  const { startLaunch } = useLaunch();
  const { activeKey } = useApiKey();

  useEffect(() => {
    // Preload both modules' chunks so the first launch never janks.
    const idle = window.requestIdleCallback ?? ((cb: () => void) => window.setTimeout(cb, 200));
    const handle = idle(() => {
      preloadModulePages("e");
      preloadModulePages("s");
    });
    return () => {
      if (typeof window.cancelIdleCallback === "function" && typeof handle === "number") {
        window.cancelIdleCallback(handle);
      }
    };
  }, []);

  return (
    <div className="min-h-screen bg-[#05070a] text-zinc-100 relative overflow-hidden">
      {/* Ambient layers — platform primitives */}
      <div
        className="absolute inset-0 pointer-events-none"
        style={{
          background:
            "radial-gradient(ellipse at top right, rgba(0, 229, 188, 0.10), transparent 60%), radial-gradient(ellipse at bottom left, rgba(99, 102, 241, 0.12), transparent 60%)",
        }}
      />

      <div className="relative z-10 max-w-5xl mx-auto px-4 md:px-8 py-12 md:py-20">
        {/* Header */}
        <div className="flex flex-col items-start gap-6 mb-12">
          <div className="flex items-center gap-3">
            <AnimatedLogo className="w-10 h-10" />
            <div>
              <div className="text-lg font-bold tracking-[0.08em] text-white">
                BRIDGE<span className="text-[#00e5bc]">Modules</span>
              </div>
              <div className="text-[9px] font-bold tracking-[0.2em] text-zinc-500 uppercase font-mono">
                Outbound Systems OS · Platform Hub
              </div>
            </div>
          </div>
          <div>
            <h1 className="text-2xl md:text-3xl font-bold text-white tracking-tight">
              Launch a workspace.
            </h1>
            <p className="text-sm text-zinc-400 mt-2 max-w-xl">
              Two autonomous outbound engines, one command platform. Each module
              assembles its own dashboard from live primitives when launched.
            </p>
          </div>
          <div className="flex items-center gap-2">
            {activeKey ? (
              <div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wider px-2.5 py-1 rounded-full border shadow-sm text-[#00e5bc] bg-[rgba(0,229,188,0.12)] border-[rgba(0,229,188,0.3)]">
                <CheckCircle2 className="w-3 h-3" />
                API Connected
              </div>
            ) : (
              <div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wider text-rose-300 bg-rose-500/10 border border-rose-500/20 px-2.5 py-1 rounded-full">
                <XCircle className="w-3 h-3" />
                API Key Missing — set one in a module's Settings
              </div>
            )}
          </div>
        </div>

        {/* Module launcher cards — real primitives */}
        <div className="grid md:grid-cols-2 gap-4 md:gap-6">
          {MODULE_IDS.map((id) => {
            const def = MODULES[id];
            const Icon = def.icon;
            const theme = def.theme;
            const mint = id === "e" ? MINT_THEME : null;
            return (
              <motion.div
                key={id}
                initial={{ opacity: 0, y: 18 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1], delay: id === "e" ? 0.05 : 0.15 }}
              >
                <GlassCard className="p-6 md:p-7 h-full flex flex-col group hover:border-white/10 transition-all duration-500">
                  <div className="flex items-start justify-between mb-5">
                    <div
                      className="w-11 h-11 rounded-xl flex items-center justify-center border"
                      style={{
                        backgroundColor: `${theme.accent}14`,
                        borderColor: `${theme.accent}44`,
                        boxShadow: `0 0 24px ${theme.accent}22`,
                      }}
                    >
                      <Icon className="w-5 h-5" style={{ color: theme.accent }} />
                    </div>
                    <span
                      className="text-[9px] font-bold font-mono uppercase tracking-[0.18em] px-2 py-1 rounded-md border"
                      style={{
                        color: theme.accentText,
                        borderColor: `${theme.accent}44`,
                        backgroundColor: `${theme.accent}14`,
                      }}
                    >
                      {theme.code}
                    </span>
                  </div>

                  <h2 className="text-xl font-bold text-white tracking-tight">
                    BRIDGE
                    <span style={{ color: theme.accent }}>{def.wordmark}</span>
                  </h2>
                  <p className="text-[13px] text-zinc-400 mt-2 leading-relaxed flex-1">
                    {def.blurb}
                  </p>

                  <ul className="mt-5 space-y-1.5">
                    {MODULE_CAPS[id].map((cap) => (
                      <li key={cap} className="flex items-center gap-2 text-xs text-zinc-300">
                        <span
                          className="w-1 h-1 rounded-full shrink-0"
                          style={{ backgroundColor: theme.accent, boxShadow: `0 0 6px ${theme.accent}` }}
                        />
                        {cap}
                      </li>
                    ))}
                  </ul>

                  <div className="mt-6 flex items-center justify-between gap-3">
                    <div className="flex items-center gap-1.5 text-[10px] font-mono text-zinc-500 uppercase tracking-wider">
                      <Boxes className="w-3.5 h-3.5" />
                      Slow-mo assembly on launch
                    </div>
                    <button
                      type="button"
                      onClick={() => startLaunch(id)}
                      onMouseEnter={() => preloadModulePages(id)}
                      className="flex items-center gap-2 px-4 py-2.5 rounded-lg text-[13px] font-bold transition-all duration-300 active:scale-[0.98] border"
                      style={{
                        backgroundColor: theme.buttonBg,
                        color: theme.buttonText,
                        borderColor: theme.buttonBg,
                        boxShadow: `0 0 24px ${theme.accent}33`,
                      }}
                      aria-label={`Launch ${def.label}`}
                    >
                      {id === "e" ? <Zap className="w-4 h-4" /> : <Boxes className="w-4 h-4" />}
                      Launch {def.label.replace(/^Module /, "")}
                      <ArrowRight className="w-4 h-4 group-hover:translate-x-0.5 transition-transform" />
                    </button>
                  </div>

                  {mint && (
                    <div className="mt-4 text-[10px] font-mono text-zinc-600 uppercase tracking-[0.16em]">
                      GreenTheme build · accent {mint.accent}
                    </div>
                  )}
                </GlassCard>
              </motion.div>
            );
          })}
        </div>

        <div className="mt-10 text-center text-[10px] font-mono text-zinc-600 uppercase tracking-[0.2em]">
          Bridge Modules · Direct URLs — /modules/e · /modules/s
        </div>
      </div>
    </div>
  );
}