3D Neural Login UI in Flutter (With Animated Galaxy Background)

3D Neural Login UI in Flutter (With Animated Galaxy Background)

When designing interfaces for AI platforms, fintech dashboards, or futuristic applications, traditional UI patterns often feel outdated. I wanted to experiment with something deeper — something that felt alive.

So I built a 3D rotating neural sphere with a galaxy background and a glassmorphism login interface using pure Flutter.

No third-party 3D engine.

No external animation packages.

Just clean Flutter + CustomPainter.

Animated Galaxy Background

To create depth, I added a moving starfield in the background.

Using CustomPainter, small stars are drawn on the canvas and animated vertically. The slow movement creates a calm, space-like atmosphere without distracting the user.

This keeps performance smooth while making the UI feel dynamic.

3D Rotating Particle Sphere

The main highlight is a rotating sphere made of tiny particles.

Each particle is placed using mathematical angles and converted into 2D positions. By adjusting opacity and size based on depth, the sphere looks 3D — even though Flutter is a 2D framework.

The sphere:

Can also be rotated manually using gestures
Rotates automatically

This adds interaction and makes the interface feel premium.

Glassmorphism Login Panel

On top of everything, I placed a blurred glass-style login card.

Using:

  • Backdrop blur
  • Soft white borders
  • Minimal typography

The form feels like a floating “neural access terminal.”

Full Source Code:

import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/material.dart';

void main() => runApp(
  const MaterialApp(debugShowCheckedModeBanner: false, home: LucrativeSphere()),
);

class LucrativeSphere extends StatefulWidget {
  const LucrativeSphere({super.key});

  @override
  State<LucrativeSphere> createState() => _LucrativeSphereState();
}

class _LucrativeSphereState extends State<LucrativeSphere>
    with TickerProviderStateMixin {
  late AnimationController _autoRotationController;
  final List<Particle> _particles = [];
  final List<GalaxyStar> _galaxyStars = [];

  double _manualRotationX = 0;
  double _manualRotationY = 0;

  @override
  void initState() {
    super.initState();
    final random = math.Random();

    for (int i = 0; i < 1000; i++) {
      _particles.add(
        Particle(
          theta: random.nextDouble() * 2 * math.pi,
          phi: random.nextDouble() * math.pi,
          size: random.nextDouble() * 1.5 + 0.2,
        ),
      );
    }

    for (int i = 0; i < 200; i++) {
      _galaxyStars.add(
        GalaxyStar(
          x: random.nextDouble(),
          y: random.nextDouble(),
          size: random.nextDouble() * 3,
          velocity: random.nextDouble() * 0.02 + 0.005,
        ),
      );
    }

    _autoRotationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 50),
    )..repeat();
  }

  @override
  void dispose() {
    _autoRotationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF010105),
      body: GestureDetector(
        onPanUpdate: (details) {
          setState(() {
            _manualRotationY += details.delta.dx * 0.008;
            _manualRotationX -= details.delta.dy * 0.008;
          });
        },
        child: Stack(
          alignment: Alignment.center,
          children: [
            // 1. Background Galaxy
            AnimatedBuilder(
              animation: _autoRotationController,
              builder:
                  (context, child) => CustomPaint(
                    size: MediaQuery.of(context).size,
                    painter: GalaxyPainter(
                      _galaxyStars,
                      _autoRotationController.value,
                    ),
                  ),
            ),

            // 2. The 3D Sphere
            Positioned(
              top: 40,

              child: AnimatedBuilder(
                animation: _autoRotationController,
                builder:
                    (context, child) => CustomPaint(
                      size: MediaQuery.of(context).size / 1.6,
                      painter: SpherePainter(
                        particles: _particles,
                        rotationY:
                            (_autoRotationController.value * 2 * math.pi) +
                            _manualRotationY,
                        rotationX: _manualRotationX,
                      ),
                    ),
              ),
            ),

            // 3. The Lucrative Glass Form
            Positioned(bottom: 200, child: _buildGlassForm()),
          ],
        ),
      ),
    );
  }

  Widget _buildGlassForm() {
    return ClipRRect(
      borderRadius: BorderRadius.circular(30),
      child: BackdropFilter(
        // This hides/diffuses the particles behind the form
        filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
        child: Container(
          width: 300,
          padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 50),
          decoration: BoxDecoration(
            color: Colors.white.withOpacity(0.03),
            borderRadius: BorderRadius.circular(30),
            border: Border.all(color: Colors.white.withOpacity(0.12), width: 1),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text(
                "NEURAL ACCESS",
                style: TextStyle(
                  color: Colors.white,
                  letterSpacing: 8,
                  fontSize: 14,
                  fontWeight: FontWeight.w200,
                ),
              ),
              const SizedBox(height: 40),
              _buildField("IDENTITY_TOKEN"),
              const SizedBox(height: 20),
              _buildField("SECURITY_KEY", isPassword: true),
              const SizedBox(height: 40),
              _buildButton(),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildField(String hint, {bool isPassword = false}) {
    return TextField(
      obscureText: isPassword,
      style: const TextStyle(color: Colors.white, fontSize: 13),
      decoration: InputDecoration(
        hintText: hint,
        hintStyle: TextStyle(
          color: Colors.white.withOpacity(0.2),
          letterSpacing: 2,
        ),
        enabledBorder: UnderlineInputBorder(
          borderSide: BorderSide(color: Colors.white.withOpacity(0.1)),
        ),
        focusedBorder: const UnderlineInputBorder(
          borderSide: BorderSide(color: Colors.blueAccent),
        ),
      ),
    );
  }

  Widget _buildButton() {
    return Container(
      width: double.infinity,
      height: 45,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(4),
        boxShadow: [
          BoxShadow(color: Colors.white.withOpacity(0.1), blurRadius: 20),
        ],
      ),
      child: TextButton(
        onPressed: () {},
        child: const Text(
          "INITIALIZE",
          style: TextStyle(
            color: Colors.black,
            fontWeight: FontWeight.w900,
            letterSpacing: 2,
            fontSize: 12,
          ),
        ),
      ),
    );
  }
}

// --- Logic Classes ---

class Particle {
  final double theta, phi, size;
  Particle({required this.theta, required this.phi, required this.size});
}

class GalaxyStar {
  final double x, y, size, velocity;
  GalaxyStar({
    required this.x,
    required this.y,
    required this.size,
    required this.velocity,
  });
}

class SpherePainter extends CustomPainter {
  final List<Particle> particles;
  final double rotationY, rotationX;
  SpherePainter({
    required this.particles,
    required this.rotationY,
    required this.rotationX,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width * 0.45;
    final paint = Paint();

    for (var p in particles) {
      double rT = p.theta + rotationY;
      double rP = p.phi + rotationX;
      double x = radius * math.sin(rP) * math.cos(rT);
      double y = radius * math.cos(rP);
      double z = radius * math.sin(rP) * math.sin(rT);

      double zDepth = (z + radius) / (2 * radius);
      paint.color = Colors.white.withOpacity(0.1 + (zDepth * 0.7));
      canvas.drawCircle(
        Offset(center.dx + x, center.dy + y),
        p.size * (0.5 + zDepth),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

class GalaxyPainter extends CustomPainter {
  final List<GalaxyStar> stars;
  final double progress;
  GalaxyPainter(this.stars, this.progress);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white.withOpacity(0.2);
    for (var s in stars) {
      double movingY = (s.y + (progress * s.velocity * 5)) % 1.0;
      canvas.drawCircle(
        Offset(s.x * size.width, movingY * size.height),
        s.size,
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

Flutter is more powerful than we think.

With just math and CustomPainter, we can build immersive 3D-like interfaces — without any external engine.

Sometimes, great UI is not about more packages.

It’s about creativity.

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *