Skip to content

Instantly share code, notes, and snippets.

@JaredEzz
Created January 30, 2026 01:22
Show Gist options
  • Select an option

  • Save JaredEzz/c8507e50f73b5ca803f98a0f2bad0639 to your computer and use it in GitHub Desktop.

Select an option

Save JaredEzz/c8507e50f73b5ca803f98a0f2bad0639 to your computer and use it in GitHub Desktop.
checkin-reminder
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'dart:async';
void main() {
runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Color(0xFF1E1E2C), // Dark theme background
body: Center(
child: FunCheckinReminder(),
),
),
));
}
class FunCheckinReminder extends StatefulWidget {
const FunCheckinReminder({super.key});
@override
State<FunCheckinReminder> createState() => _FunCheckinReminderState();
}
class _FunCheckinReminderState extends State<FunCheckinReminder>
with TickerProviderStateMixin {
// Mouse position for the 3D tilt effect
double x = 0.0;
double y = 0.0;
bool isHovering = false;
// Animation controllers
late AnimationController _colorController;
late AnimationController _wobbleController;
@override
void initState() {
super.initState();
// Cycle through colors continuously
_colorController = AnimationController(
vsync: this,
duration: const Duration(seconds: 5),
)..repeat();
// Controls the "wave" of the text
_wobbleController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
)..repeat();
}
@override
void dispose() {
_colorController.dispose();
_wobbleController.dispose();
super.dispose();
}
// Updates the x/y coordinates based on mouse position relative to center
void _updateLocation(PointerEvent details) {
setState(() {
// We limit the rotation so it doesn't flip over completely
// values usually range from -1 to 1 for full width
// We divide by a factor to make the tilt subtle
x = (details.localPosition.dx - 150) / 300;
y = (details.localPosition.dy - 100) / 200;
});
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => isHovering = true),
onExit: (_) => setState(() {
isHovering = false;
x = 0;
y = 0;
}),
onHover: _updateLocation,
child: AnimatedBuilder(
animation: Listenable.merge([_colorController, _wobbleController]),
builder: (context, child) {
// Dynamic Rainbow Color
final Color primaryColor =
HSVColor.fromAHSV(1.0, _colorController.value * 360, 0.8, 1.0)
.toColor();
final Color secondaryColor =
HSVColor.fromAHSV(1.0, (_colorController.value * 360 + 180) % 360, 0.8, 1.0)
.toColor();
// Matrix for 3D Tilt
final matrix = Matrix4.identity()
..setEntry(3, 2, 0.001) // Perspective
..rotateX(isHovering ? -y * 0.5 : 0) // Tilt X
..rotateY(isHovering ? x * 0.5 : 0); // Tilt Y
return Transform(
transform: matrix,
alignment: Alignment.center,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
width: 350,
height: 200,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.8),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isHovering ? primaryColor : Colors.white24,
width: isHovering ? 3 : 1,
),
boxShadow: [
BoxShadow(
color: primaryColor.withOpacity(isHovering ? 0.6 : 0),
blurRadius: isHovering ? 30 : 0,
spreadRadius: isHovering ? 5 : 0,
offset: Offset(x * 20, y * 20),
)
],
),
child: Stack(
children: [
// Animated Background shapes
Positioned(
top: -50,
right: -50,
child: Transform.rotate(
angle: _colorController.value * 2 * math.pi,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(colors: [primaryColor, secondaryColor])
),
),
),
),
// Main Content
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.check_circle_outline,
color: secondaryColor,
size: isHovering ? 50 : 40
),
const SizedBox(height: 20),
WavyText(
text: "Remember to do\nyour Checkins!",
color: Colors.white,
animValue: _wobbleController.value,
isHovering: isHovering,
),
],
),
),
],
),
),
);
},
),
);
}
}
// Helper Widget to split text into characters and animate them
class WavyText extends StatelessWidget {
final String text;
final Color color;
final double animValue;
final bool isHovering;
const WavyText({
super.key,
required this.text,
required this.color,
required this.animValue,
required this.isHovering,
});
@override
Widget build(BuildContext context) {
// Split text into lines to handle newlines
List<Widget> lines = [];
List<String> textLines = text.split('\n');
int globalCharIndex = 0;
for (var line in textLines) {
List<Widget> chars = [];
for (int i = 0; i < line.length; i++) {
String char = line[i];
// Math for the wave effect
// We use sine to create the up/down motion
double offset = math.sin((animValue * 2 * math.pi) + (globalCharIndex * 0.4)) * 6;
// If hovering, we make the wave bigger!
double effectiveOffset = isHovering ? offset : offset * 0.3;
chars.add(
Transform.translate(
offset: Offset(0, effectiveOffset),
child: Text(
char,
style: TextStyle(
color: color,
fontSize: 24,
fontWeight: FontWeight.bold,
fontFamily: 'Courier',
letterSpacing: 2,
shadows: [
Shadow(
blurRadius: 10,
color: color.withOpacity(0.5),
offset: const Offset(0, 5),
)
]
),
),
),
);
globalCharIndex++;
}
lines.add(Row(mainAxisAlignment: MainAxisAlignment.center, children: chars));
}
return Column(
mainAxisSize: MainAxisSize.min,
children: lines,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment