Spaces:
Running
Running
| import { useState } from 'react' | |
| import { COMPANIES } from '../lib/companies' | |
| import { VX_SERVER } from '../lib/webrtc' | |
| const DEFAULT_KEY = 'vk_Qg95CQBuNFuiiHMCVtgo9bh9kx0N1ACnaj8zdJyg5qsBDWTjxt2ZoegM2UCdKfCY' | |
| export default function Lobby({ onJoin }) { | |
| const [name, setName] = useState('Ayush') | |
| const [company, setCompany] = useState(null) | |
| const [apiKey] = useState(DEFAULT_KEY) | |
| const [server] = useState(VX_SERVER) | |
| const [error, setError] = useState('') | |
| const [loading, setLoading] = useState(false) | |
| async function handleStart() { | |
| if (!name.trim()) return setError('Enter your name') | |
| if (!company) return setError('Choose a company to interview for') | |
| if (!apiKey.trim()) return setError('Enter your API key') | |
| if (!server.trim()) return setError('Enter the server address') | |
| setError('') | |
| setLoading(true) | |
| await onJoin({ name: name.trim(), company: COMPANIES[company], apiKey: apiKey.trim(), server: server.trim() }) | |
| setLoading(false) | |
| } | |
| function handleKey(e) { | |
| if (e.key === 'Enter' && !loading) handleStart() | |
| } | |
| return ( | |
| <div style={styles.page}> | |
| <div style={styles.glow1} /> | |
| <div style={styles.glow2} /> | |
| <div style={styles.inner}> | |
| <p style={styles.pill}>AI-powered mock interview</p> | |
| <h1 style={styles.title}> | |
| Practice like it's<br /> | |
| <span style={{ color: 'var(--accent)' }}>the real thing.</span> | |
| </h1> | |
| <p style={styles.subtitle}> | |
| Pick a company. Start talking. Get honest feedback — on your answers <em>and</em> your presence. | |
| </p> | |
| {/* name */} | |
| <div style={styles.fieldGroup}> | |
| <label style={styles.label}>Your name</label> | |
| <input | |
| style={styles.input} | |
| value={name} | |
| onChange={e => setName(e.target.value)} | |
| onKeyDown={handleKey} | |
| placeholder="e.g. Ayush" | |
| autoComplete="off" | |
| /> | |
| </div> | |
| {/* company picker */} | |
| <div style={styles.fieldGroup}> | |
| <label style={styles.label}>Interview for</label> | |
| <div style={styles.companyGrid}> | |
| {Object.values(COMPANIES).map(c => ( | |
| <CompanyCard | |
| key={c.id} | |
| company={c} | |
| selected={company === c.id} | |
| onSelect={() => { setCompany(c.id); setError('') }} | |
| /> | |
| ))} | |
| </div> | |
| </div> | |
| {error && <p style={styles.error}>{error}</p>} | |
| <button | |
| style={{ ...styles.startBtn, opacity: loading ? 0.5 : 1, cursor: loading ? 'not-allowed' : 'pointer' }} | |
| onClick={handleStart} | |
| disabled={loading} | |
| > | |
| {loading ? 'Starting…' : 'Start interview →'} | |
| </button> | |
| <p style={styles.fine}> | |
| Your camera and microphone will be used. Everything stays in session. | |
| </p> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| function CompanyCard({ company, selected, onSelect }) { | |
| return ( | |
| <button | |
| onClick={onSelect} | |
| style={{ | |
| ...styles.companyCard, | |
| borderColor: selected ? company.accent : 'var(--border2)', | |
| background: selected ? `${company.accent}10` : 'var(--surface)', | |
| }} | |
| > | |
| <span style={{ ...styles.companyIcon, background: `${company.accent}20`, color: company.accent }}> | |
| {company.icon} | |
| </span> | |
| <div style={styles.companyInfo}> | |
| <span style={styles.companyName}>{company.name}</span> | |
| <span style={styles.companyTag}>{company.tagline}</span> | |
| <div style={styles.companyPills}> | |
| {company.questionStyle.map(s => ( | |
| <span key={s} style={styles.qpill}>{s}</span> | |
| ))} | |
| </div> | |
| </div> | |
| {selected && ( | |
| <span style={{ ...styles.checkmark, color: company.accent }}>✓</span> | |
| )} | |
| </button> | |
| ) | |
| } | |
| const styles = { | |
| page: { | |
| minHeight: '100vh', | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| padding: '40px 20px', | |
| position: 'relative', | |
| overflowY: 'auto', | |
| overflowX: 'hidden', | |
| }, | |
| glow1: { | |
| position: 'fixed', | |
| top: 0, left: 0, right: 0, bottom: 0, | |
| background: 'radial-gradient(ellipse 60% 50% at 20% 80%, rgba(200,240,96,.05) 0%, transparent 70%)', | |
| pointerEvents: 'none', | |
| }, | |
| glow2: { | |
| position: 'fixed', | |
| top: 0, left: 0, right: 0, bottom: 0, | |
| background: 'radial-gradient(ellipse 50% 50% at 80% 20%, rgba(96,208,240,.04) 0%, transparent 70%)', | |
| pointerEvents: 'none', | |
| }, | |
| inner: { | |
| width: '100%', | |
| maxWidth: 560, | |
| display: 'flex', | |
| flexDirection: 'column', | |
| gap: 0, | |
| animation: 'slide-up .4s ease', | |
| position: 'relative', | |
| zIndex: 1, | |
| }, | |
| pill: { | |
| display: 'inline-block', | |
| alignSelf: 'flex-start', | |
| fontFamily: 'var(--mono)', | |
| fontSize: 10, | |
| letterSpacing: '.2em', | |
| textTransform: 'uppercase', | |
| color: 'var(--accent)', | |
| border: '1px solid rgba(200,240,96,.25)', | |
| padding: '4px 10px', | |
| borderRadius: 20, | |
| marginBottom: 20, | |
| }, | |
| title: { | |
| fontSize: 'clamp(38px, 6vw, 64px)', | |
| fontWeight: 800, | |
| lineHeight: .95, | |
| letterSpacing: '-.03em', | |
| marginBottom: 16, | |
| }, | |
| subtitle: { | |
| fontFamily: 'var(--mono)', | |
| fontSize: 13, | |
| color: 'var(--muted)', | |
| lineHeight: 1.6, | |
| marginBottom: 36, | |
| }, | |
| fieldGroup: { | |
| display: 'flex', | |
| flexDirection: 'column', | |
| gap: 8, | |
| marginBottom: 20, | |
| }, | |
| label: { | |
| fontFamily: 'var(--mono)', | |
| fontSize: 10, | |
| letterSpacing: '.18em', | |
| color: 'var(--muted)', | |
| textTransform: 'uppercase', | |
| }, | |
| input: { | |
| background: 'var(--surface)', | |
| border: '1px solid var(--border2)', | |
| color: 'var(--text)', | |
| fontFamily: 'var(--mono)', | |
| fontSize: 13, | |
| padding: '12px 14px', | |
| borderRadius: 8, | |
| outline: 'none', | |
| transition: 'border-color .2s', | |
| width: '100%', | |
| }, | |
| companyGrid: { | |
| display: 'flex', | |
| flexDirection: 'column', | |
| gap: 10, | |
| }, | |
| companyCard: { | |
| display: 'flex', | |
| alignItems: 'flex-start', | |
| gap: 14, | |
| padding: '14px 16px', | |
| borderRadius: 10, | |
| border: '1.5px solid', | |
| cursor: 'pointer', | |
| textAlign: 'left', | |
| transition: 'border-color .15s, background .15s', | |
| fontFamily: 'var(--sans)', | |
| width: '100%', | |
| position: 'relative', | |
| }, | |
| companyIcon: { | |
| width: 40, | |
| height: 40, | |
| borderRadius: 10, | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| fontSize: 18, | |
| fontWeight: 800, | |
| flexShrink: 0, | |
| }, | |
| companyInfo: { | |
| display: 'flex', | |
| flexDirection: 'column', | |
| gap: 3, | |
| flex: 1, | |
| }, | |
| companyName: { | |
| fontSize: 15, | |
| fontWeight: 700, | |
| color: 'var(--text)', | |
| }, | |
| companyTag: { | |
| fontFamily: 'var(--mono)', | |
| fontSize: 10, | |
| color: 'var(--muted)', | |
| letterSpacing: '.12em', | |
| }, | |
| companyPills: { | |
| display: 'flex', | |
| flexWrap: 'wrap', | |
| gap: 4, | |
| marginTop: 6, | |
| }, | |
| qpill: { | |
| fontFamily: 'var(--mono)', | |
| fontSize: 9, | |
| letterSpacing: '.1em', | |
| color: 'var(--muted)', | |
| border: '1px solid var(--border)', | |
| padding: '2px 7px', | |
| borderRadius: 20, | |
| }, | |
| checkmark: { | |
| position: 'absolute', | |
| top: 12, | |
| right: 14, | |
| fontSize: 16, | |
| fontWeight: 700, | |
| }, | |
| error: { | |
| fontFamily: 'var(--mono)', | |
| fontSize: 11, | |
| color: 'var(--danger)', | |
| marginBottom: 12, | |
| }, | |
| startBtn: { | |
| background: 'var(--accent)', | |
| color: '#0a0a0a', | |
| border: 'none', | |
| fontFamily: 'var(--sans)', | |
| fontSize: 14, | |
| fontWeight: 700, | |
| letterSpacing: '.06em', | |
| padding: '16px 24px', | |
| borderRadius: 8, | |
| transition: 'opacity .15s, transform .1s', | |
| marginTop: 4, | |
| marginBottom: 16, | |
| }, | |
| fine: { | |
| fontFamily: 'var(--mono)', | |
| fontSize: 10, | |
| color: 'var(--muted)', | |
| letterSpacing: '.1em', | |
| textAlign: 'center', | |
| }, | |
| } | |