function LoginScreen({ onLogin }) {
  const [ident, setIdent] = React.useState(''); // username, or email as a fallback
  const [pass,  setPass]  = React.useState('');
  const [error, setError] = React.useState('');
  const [busy,  setBusy]  = React.useState(false);

  async function submit() {
    if (busy) return;
    setError('');
    const id = ident.trim();
    if (!id || !pass) { setError('Enter your username and password.'); return; }
    setBusy(true);

    // Usernames can't contain '@', so anything with one is an email and signs in
    // directly. A username is resolved server-side by the `login` Edge Function (so
    // the account email is never exposed), which hands back a session to adopt.
    let signInError;
    if (id.includes('@')) {
      ({ error: signInError } = await window.supabaseClient.auth.signInWithPassword({
        email: id.toLowerCase(),
        password: pass,
      }));
    } else {
      const { data, error } = await window.supabaseClient.functions.invoke('login', {
        body: { username: id, password: pass },
      });
      signInError = error || (data?.session ? null : new Error('no session'));
      if (!signInError) ({ error: signInError } = await window.supabaseClient.auth.setSession(data.session));
    }
    if (signInError) {
      setError('Incorrect username or password.');
      setBusy(false);
      return;
    }

    // Suspended accounts are banned at the auth layer, but re-check here as a guard
    // in case a profile is suspended without an active-session ban.
    const profile = await window.ayaniFetchProfile();
    if (!profile || profile.status !== 'active') {
      await window.supabaseClient.auth.signOut();
      setError('This account is not active. Contact your administrator.');
      setBusy(false);
      return;
    }

    window.ayaniLogActivity();
    onLogin(profile.role);
  }

  // Slow the Access button's glow spin while hovered. Changing the CSS
  // animation-duration would make the light jump, so ease the playback rate of the
  // running ::before animation instead — it keeps its position and just slows.
  function setGlowRate(e, rate) {
    const anims = e.currentTarget.getAnimations ? e.currentTarget.getAnimations({ subtree: true }) : [];
    anims.forEach(a => a.updatePlaybackRate ? a.updatePlaybackRate(rate) : (a.playbackRate = rate));
  }

  function onKeyDown(e) {
    if (e.key === 'Enter') submit();
  }

  return (
    <div className="ay-screen ay-login" data-screen-label="01 Login">
      <AyaniBeam reveal meteors />
      <img className="ay-login-beam" src="assets/beam.jpg" alt="" />
      <div className="ay-login-center">
        <div className="ay-login-form">
          <label className="ay-field">
            <input type="text" autoComplete="username" autoCapitalize="none" autoCorrect="off" spellCheck={false}
              placeholder="Username" value={ident} onChange={e => setIdent(e.target.value)} onKeyDown={onKeyDown} />
          </label>
          <label className="ay-field">
            <input className="ay-pass-input" type="password" autoComplete="current-password" placeholder="• • • • • • • •"
              value={pass} onChange={e => setPass(e.target.value)} onKeyDown={onKeyDown} />
          </label>
          {error && <div className="ay-login-error" role="alert">{error}</div>}
          <button className="ay-btn" onClick={submit} disabled={busy}
            onMouseEnter={e => setGlowRate(e, 0.35)} onMouseLeave={e => setGlowRate(e, 1)}>{busy ? 'Accessing…' : 'Access'}</button>
        </div>
      </div>
    </div>
  );
}
window.LoginScreen = LoginScreen;
