feat: initial commit — Falah Coach Portal v0.1
Deploy to Netlify / build-and-deploy (push) Failing after 14m42s
Deploy to Netlify / build-and-deploy (push) Failing after 14m42s
- React 18 + Vite 5 + TailwindCSS 3 SPA - Mission Control dashboard - AI Co-Pilot & Simulator view - Tactical Audio Briefings library - MS Loop Wiki integration view - Global audio player in sidebar - Netlify deployment config with SPA redirect - GitHub Actions CI/CD workflow
This commit is contained in:
+452
@@ -0,0 +1,452 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
MessageSquareQuote,
|
||||
Headphones,
|
||||
BookOpen,
|
||||
PlayCircle,
|
||||
PauseCircle,
|
||||
SkipForward,
|
||||
SkipBack,
|
||||
TerminalSquare,
|
||||
Bot,
|
||||
User,
|
||||
Zap,
|
||||
TrendingUp,
|
||||
FileText,
|
||||
ChevronRight,
|
||||
FolderOpen
|
||||
} from 'lucide-react';
|
||||
|
||||
/* ==========================================================================
|
||||
MOCK DATA
|
||||
========================================================================== */
|
||||
|
||||
const MOCK_AUDIO_TRACKS = [
|
||||
{ id: 1, title: 'The 100-Day Turnaround', duration: '14:20', tag: 'Strategy', author: 'Senior Partner A' },
|
||||
{ id: 2, title: 'Negotiating with Hostile Boards', duration: '22:15', tag: 'Leadership', author: 'Principal B' },
|
||||
{ id: 3, title: 'M&A Synergy Identification', duration: '18:45', tag: 'Finance', author: 'Director C' },
|
||||
{ id: 4, title: 'Digital Transformation Pitfalls', duration: '31:10', tag: 'Technology', author: 'Principal D' },
|
||||
{ id: 5, title: 'Pricing Strategies in Inflation', duration: '12:55', tag: 'Economics', author: 'Partner E' },
|
||||
];
|
||||
|
||||
const MOCK_WIKI_DIRECTORY = [
|
||||
{
|
||||
folder: 'Core Frameworks',
|
||||
items: ['Falah MECE Guidelines', 'Value Creation Matrix', 'Go-To-Market Playbook']
|
||||
},
|
||||
{
|
||||
folder: 'Banking Edition',
|
||||
items: ['Retail Bank Cost Reduction', 'Fintech Threat Analysis', 'Regulatory Compliance 2026']
|
||||
},
|
||||
{
|
||||
folder: 'Telco Edition',
|
||||
items: ['5G Monetization', 'Churn Reduction Models']
|
||||
}
|
||||
];
|
||||
|
||||
const MOCK_CHAT_HISTORY = [
|
||||
{ role: 'system', text: 'Falah Simulator initialized. Persona: Hostile Telco CIO. Objective: Justify £2M Agile Transformation.' },
|
||||
{ role: 'user', text: 'Good morning. I\'d like to walk you through the projected ROI for the Agile Transformation initiative.' },
|
||||
{ role: 'assistant', text: 'I don\'t have time for buzzwords today. You want £2M of my budget to "transform" teams that are already delivering. Where is the hard financial justification? I need EBITDA impact, not velocity charts.' },
|
||||
];
|
||||
|
||||
/* ==========================================================================
|
||||
SUB-VIEWS
|
||||
========================================================================== */
|
||||
|
||||
const MissionControlView = ({ setActiveView, setActiveAudio }) => {
|
||||
return (
|
||||
<div className="space-y-8 animate-fade-in pr-6">
|
||||
<header>
|
||||
<h1 className="text-4xl font-bold tracking-tight text-white mb-2">Mission Control</h1>
|
||||
<p className="text-slate-400">Welcome back. Your Falah Coach portal is ready.</p>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Featured Audio Widget */}
|
||||
<div className="lg:col-span-2 bg-slate-900 border border-slate-800 rounded-2xl p-6 relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 p-32 bg-emerald-500/10 rounded-full blur-[100px] -mr-16 -mt-16 pointer-events-none"></div>
|
||||
<div className="relative z-10 flex flex-col h-full justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-emerald-400 font-medium mb-4 text-sm tracking-wide uppercase">
|
||||
<Zap size={16} /> Featured Briefing
|
||||
</div>
|
||||
<h2 className="text-3xl font-semibold text-white mb-3 tracking-tight">The 100-Day Turnaround</h2>
|
||||
<p className="text-slate-400 max-w-lg mb-6 leading-relaxed">
|
||||
Master the critical first 100 days of a private equity backed turnaround. Tactical insights from our top Senior Partners.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setActiveAudio(MOCK_AUDIO_TRACKS[0])}
|
||||
className="flex items-center gap-2 bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold py-3 px-6 rounded-lg transition-colors w-max"
|
||||
>
|
||||
<PlayCircle size={20} />
|
||||
Play Briefing (14:20)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Starts */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
onClick={() => setActiveView('simulator')}
|
||||
className="bg-slate-900 border border-slate-800 hover:border-blue-500/50 rounded-2xl p-6 cursor-pointer transition-all hover:-translate-y-1 group"
|
||||
>
|
||||
<div className="bg-blue-500/10 text-blue-400 p-3 rounded-lg w-max mb-4 group-hover:bg-blue-500 group-hover:text-white transition-colors">
|
||||
<Bot size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-1">AI Simulator</h3>
|
||||
<p className="text-slate-400 text-sm">Practice hostile boardroom scenarios.</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => setActiveView('wiki')}
|
||||
className="bg-slate-900 border border-slate-800 hover:border-emerald-500/50 rounded-2xl p-6 cursor-pointer transition-all hover:-translate-y-1 group"
|
||||
>
|
||||
<div className="bg-emerald-500/10 text-emerald-400 p-3 rounded-lg w-max mb-4 group-hover:bg-emerald-500 group-hover:text-white transition-colors">
|
||||
<BookOpen size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-1">Loop Wiki</h3>
|
||||
<p className="text-slate-400 text-sm">Review the Falah MECE Guidelines.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AICoPilotView = () => {
|
||||
const [messages, setMessages] = useState(MOCK_CHAT_HISTORY);
|
||||
const [input, setInput] = useState('');
|
||||
|
||||
const handleSend = (e) => {
|
||||
e.preventDefault();
|
||||
if (!input.trim()) return;
|
||||
setMessages([...messages, { role: 'user', text: input }]);
|
||||
setInput('');
|
||||
// Mock response
|
||||
setTimeout(() => {
|
||||
setMessages(prev => [...prev, {
|
||||
role: 'assistant',
|
||||
text: 'That response lacks financial rigor. Reframe your answer focusing on cost-of-delay and projected OPEX reduction over the next 3 quarters.'
|
||||
}]);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col pb-4 animate-fade-in pr-6">
|
||||
<header className="mb-6">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-white mb-1">AI Co-Pilot & Simulator</h1>
|
||||
<p className="text-slate-400 text-sm">Engage in high-pressure executive roleplay to refine your delivery.</p>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 bg-slate-900 border border-slate-800 rounded-2xl flex flex-col overflow-hidden">
|
||||
<div className="flex-1 p-6 overflow-y-auto space-y-6">
|
||||
{messages.map((msg, i) => (
|
||||
<div key={i} className={`flex gap-4 ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||
|
||||
{msg.role !== 'user' && (
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0 ${msg.role === 'system' ? 'bg-slate-800 text-slate-400' : 'bg-blue-500/20 text-blue-400'}`}>
|
||||
{msg.role === 'system' ? <TerminalSquare size={16} /> : <Bot size={16} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`max-w-[70%] p-4 rounded-2xl ${
|
||||
msg.role === 'user'
|
||||
? 'bg-blue-600 text-white rounded-tr-sm'
|
||||
: msg.role === 'system'
|
||||
? 'bg-slate-800 text-slate-300 font-mono text-sm border border-slate-700'
|
||||
: 'bg-slate-800 text-slate-200 border border-slate-700 rounded-tl-sm'
|
||||
}`}>
|
||||
{msg.text}
|
||||
</div>
|
||||
|
||||
{msg.role === 'user' && (
|
||||
<div className="w-8 h-8 rounded-full bg-slate-700 text-slate-300 flex items-center justify-center shrink-0">
|
||||
<User size={16} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-slate-850 border-t border-slate-800">
|
||||
<div className="flex gap-2 mb-3 overflow-x-auto pb-1 hide-scrollbar">
|
||||
{['/simulate Telco CIO', '/mentor DevSecOps', '/grade'].map(cmd => (
|
||||
<button
|
||||
key={cmd}
|
||||
onClick={() => setInput(cmd)}
|
||||
className="whitespace-nowrap px-3 py-1.5 bg-slate-800 hover:bg-slate-700 text-blue-400 text-xs font-mono rounded-md border border-slate-700 transition-colors"
|
||||
>
|
||||
{cmd}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<form onSubmit={handleSend} className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Deploy a response or command..."
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-4 pr-12 text-slate-200 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all placeholder:text-slate-500"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-blue-400 p-2"
|
||||
>
|
||||
<Zap size={20} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AudioLibraryView = ({ setActiveAudio }) => {
|
||||
return (
|
||||
<div className="animate-fade-in pr-6">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-white mb-2">Tactical Audio Briefings</h1>
|
||||
<p className="text-slate-400">Exclusive micro-learning tracks from Falah's leadership network.</p>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{MOCK_AUDIO_TRACKS.map(track => (
|
||||
<div
|
||||
key={track.id}
|
||||
onClick={() => setActiveAudio(track)}
|
||||
className="group bg-slate-900 border border-slate-800 hover:border-emerald-500/50 rounded-2xl p-6 cursor-pointer transition-all hover:shadow-lg hover:shadow-emerald-500/5 relative overflow-hidden"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-12">
|
||||
<span className="px-2.5 py-1 bg-slate-800 text-slate-300 text-xs font-semibold rounded-md uppercase tracking-wider">
|
||||
{track.tag}
|
||||
</span>
|
||||
<div className="w-10 h-10 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity scale-75 group-hover:scale-100">
|
||||
<PlayCircle size={24} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2 group-hover:text-emerald-400 transition-colors">{track.title}</h3>
|
||||
<div className="flex justify-between items-center text-sm text-slate-500">
|
||||
<span>{track.author}</span>
|
||||
<span className="flex items-center gap-1 font-mono"><Headphones size={14}/> {track.duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MSLoopWikiView = () => {
|
||||
const [activePage, setActivePage] = useState('Falah MECE Guidelines');
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col pb-4 animate-fade-in pr-6">
|
||||
<header className="mb-6">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-white mb-1">MS Loop Integration</h1>
|
||||
<p className="text-slate-400 text-sm">Falah Playbooks & Living Intellectual Property.</p>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 flex gap-6 h-[calc(100vh-140px)] overflow-hidden">
|
||||
{/* Left Col: Directory */}
|
||||
<div className="w-64 shrink-0 bg-slate-900 border border-slate-800 rounded-2xl p-4 overflow-y-auto custom-scrollbar">
|
||||
<div className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-4 ml-2">Directory</div>
|
||||
<div className="space-y-4">
|
||||
{MOCK_WIKI_DIRECTORY.map((dir, idx) => (
|
||||
<div key={idx}>
|
||||
<div className="flex items-center gap-2 text-slate-300 font-medium text-sm mb-2 px-2">
|
||||
<FolderOpen size={16} className="text-blue-400/70" />
|
||||
{dir.folder}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{dir.items.map(item => (
|
||||
<button
|
||||
key={item}
|
||||
onClick={() => setActivePage(item)}
|
||||
className={`w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-md transition-colors text-left ${
|
||||
activePage === item
|
||||
? 'bg-blue-500/10 text-blue-400 font-medium'
|
||||
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800'
|
||||
}`}
|
||||
>
|
||||
<FileText size={14} />
|
||||
<span className="truncate">{item}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Col: Iframe container */}
|
||||
<div className="flex-1 bg-white rounded-2xl overflow-hidden flex flex-col shadow-2xl relative">
|
||||
{/* Iframe Chrome */}
|
||||
<div className="bg-slate-100 border-b border-slate-200 p-3 flex items-center gap-4">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-3 h-3 rounded-full bg-slate-300"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-slate-300"></div>
|
||||
<div className="w-3 h-3 rounded-full bg-slate-300"></div>
|
||||
</div>
|
||||
<div className="bg-white border border-slate-200 text-slate-400 text-xs px-3 py-1.5 rounded flex-1 flex justify-between items-center shadow-sm max-w-md">
|
||||
<div className="truncate text-slate-500 font-mono text-[11px]"><span className="text-slate-400">loop.microsoft.com/falah/</span>{activePage.toLowerCase().replace(/ /g, '-')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Simulated content area */}
|
||||
<div className="flex-1 p-12 overflow-y-auto text-slate-800 bg-[#fbfbfb]">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-slate-900 border-b border-slate-200 pb-4">{activePage}</h1>
|
||||
|
||||
<div className="prose prose-slate max-w-none">
|
||||
<p className="text-lg leading-relaxed mb-6 text-slate-600">
|
||||
This document serves as the canonical reference for exactly how our consultants should approach problem structuring in this domain.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4 mb-8">
|
||||
<div className="p-4 bg-blue-50 border border-blue-100 rounded-lg">
|
||||
<h4 className="font-semibold text-blue-900 m-0 mb-2">Key Objective</h4>
|
||||
<p className="text-sm text-blue-800 m-0">Drive unambiguous clarity to executive stakeholders within 3 minutes of engagement.</p>
|
||||
</div>
|
||||
<div className="p-4 bg-emerald-50 border border-emerald-100 rounded-lg">
|
||||
<h4 className="font-semibold text-emerald-900 m-0 mb-2">Success Metric</h4>
|
||||
<p className="text-sm text-emerald-800 m-0">Achieve client alignment before the first break in the agenda.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-2xl font-semibold mb-4 mt-8 text-slate-800">1. Executive Summary</h3>
|
||||
<p className="text-slate-600 leading-relaxed mb-4">
|
||||
The imperative here is not just structural elegance, but actionable intelligence. When framing the issue, prioritize the financial delta and the organizational capacity required to capture it.
|
||||
</p>
|
||||
<ul className="space-y-2 list-disc pl-5 text-slate-600 mb-8">
|
||||
<li>Identify the core constraint (Capital, Talent, or Regulatory).</li>
|
||||
<li>Map the adjacent capabilities required.</li>
|
||||
<li>Draft the 100-day execution roadmap.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
MAIN APP SHELL
|
||||
========================================================================== */
|
||||
|
||||
export default function App() {
|
||||
const [activeView, setActiveView] = useState('missionControl');
|
||||
const [activeAudio, setActiveAudio] = useState(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const togglePlay = () => setIsPlaying(!isPlaying);
|
||||
|
||||
const navItems = [
|
||||
{ id: 'missionControl', label: 'Mission Control', icon: LayoutDashboard },
|
||||
{ id: 'simulator', label: 'AI Co-Pilot', icon: MessageSquareQuote },
|
||||
{ id: 'audio', label: 'Audio Library', icon: Headphones },
|
||||
{ id: 'wiki', label: 'Loop Wiki', icon: BookOpen },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-slate-950 text-slate-100 font-sans overflow-hidden selection:bg-blue-500/30">
|
||||
|
||||
{/* Sidebar Layout */}
|
||||
<div className="w-72 border-r border-slate-800 flex flex-col bg-slate-900 shadow-2xl relative z-20">
|
||||
|
||||
{/* Brand */}
|
||||
<div className="p-6">
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<div className="w-8 h-8 bg-white rounded-lg flex items-center justify-center">
|
||||
<TrendingUp className="text-slate-950" size={20} strokeWidth={2.5}/>
|
||||
</div>
|
||||
<span className="font-bold text-xl tracking-wide text-white uppercase">FALAH</span>
|
||||
</div>
|
||||
<p className="text-[10px] uppercase font-bold tracking-[0.2em] text-slate-500 ml-11">Coach Portal</p>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 px-4 mt-6 space-y-2 overflow-y-auto">
|
||||
<div className="text-xs font-semibold text-slate-600 uppercase tracking-wider mb-4 ml-3">Platform</div>
|
||||
{navItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = activeView === item.id;
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => setActiveView(item.id)}
|
||||
className={`w-full flex items-center justify-between px-3 py-2.5 rounded-lg transition-all duration-200 group ${
|
||||
isActive
|
||||
? 'bg-blue-600 shadow-md shadow-blue-500/20'
|
||||
: 'hover:bg-slate-800 text-slate-400 hover:text-slate-100'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon size={18} className={isActive ? 'text-white' : 'text-slate-500 group-hover:text-blue-400 transition-colors'}/>
|
||||
<span className={`font-medium ${isActive ? 'text-white' : ''}`}>{item.label}</span>
|
||||
</div>
|
||||
{isActive && <ChevronRight size={16} className="text-blue-200" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Global Audio Player */}
|
||||
<div className="mt-auto border-t border-slate-800 p-4 bg-slate-950/50 backdrop-blur-md">
|
||||
{activeAudio ? (
|
||||
<div className="animate-fade-in-up">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-[10px] font-bold text-emerald-400 uppercase tracking-wider">Now Playing</span>
|
||||
<span className="text-xs text-slate-500 font-mono">{isPlaying ? '02:14' : '00:00'} / {activeAudio.duration}</span>
|
||||
</div>
|
||||
<p className="font-semibold text-sm text-white truncate mb-1" title={activeAudio.title}>
|
||||
{activeAudio.title}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 mb-3 truncate">{activeAudio.author}</p>
|
||||
|
||||
<div className="flex items-center justify-between mt-3 px-2">
|
||||
<button className="text-slate-400 hover:text-white transition-colors"><SkipBack size={18} /></button>
|
||||
<button
|
||||
onClick={togglePlay}
|
||||
className="text-white bg-emerald-500 hover:bg-emerald-400 rounded-full w-10 h-10 flex items-center justify-center transition-all shadow-lg shadow-emerald-500/20"
|
||||
>
|
||||
{isPlaying ? <PauseCircle size={24} className="text-slate-950" /> : <PlayCircle size={24} className="text-slate-950" />}
|
||||
</button>
|
||||
<button className="text-slate-400 hover:text-white transition-colors"><SkipForward size={18} /></button>
|
||||
</div>
|
||||
{/* Fake progress bar */}
|
||||
<div className="w-full bg-slate-800 h-1 mt-4 rounded-full overflow-hidden">
|
||||
<div className="bg-emerald-500 h-full w-1/4 rounded-full transition-all duration-1000 ease-linear"></div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-4 text-center">
|
||||
<Headphones className="mx-auto text-slate-700 mb-2" size={24} />
|
||||
<p className="text-xs text-slate-500 font-medium">Select an audio brief to begin</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<main className="flex-1 overflow-y-auto p-8 relative scroll-smooth overflow-x-hidden">
|
||||
{/* Ambient background glow */}
|
||||
<div className="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] bg-blue-600/5 rounded-full blur-[120px] pointer-events-none"></div>
|
||||
|
||||
<div className="max-w-6xl mx-auto h-full relative z-10">
|
||||
{activeView === 'missionControl' && <MissionControlView setActiveView={setActiveView} setActiveAudio={setActiveAudio}/>}
|
||||
{activeView === 'simulator' && <AICoPilotView />}
|
||||
{activeView === 'audio' && <AudioLibraryView setActiveAudio={setActiveAudio}/>}
|
||||
{activeView === 'wiki' && <MSLoopWikiView />}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html, body, #root {
|
||||
@apply h-full;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
@apply bg-slate-800 rounded;
|
||||
}
|
||||
.custom-scrollbar:hover::-webkit-scrollbar-thumb {
|
||||
@apply bg-slate-700;
|
||||
}
|
||||
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
Reference in New Issue
Block a user