diff --git a/public/encouragements.json b/public/encouragements.json
new file mode 100644
index 0000000..3996d9e
--- /dev/null
+++ b/public/encouragements.json
@@ -0,0 +1,102 @@
+[
+ "Great job! Keep going!",
+ "You're on fire! 🔥",
+ "Consistency is key!",
+ "Amazing streak!",
+ "You crushed it today!",
+ "Small steps, big results!",
+ "Habit hero!",
+ "Progress, not perfection!",
+ "Every dot counts!",
+ "Keep up the momentum!",
+ "You’re building something awesome!",
+ "One step closer to your goal!",
+ "You’re unstoppable!",
+ "Keep the streak alive!",
+ "You’re making it happen!",
+ "Your effort is inspiring!",
+ "You’re a streak superstar!",
+ "Every day matters!",
+ "You’re a habit legend!",
+ "You’re doing fantastic!",
+ "Keep shining!",
+ "You’re a role model!",
+ "You’re a champion!",
+ "You’re making progress!",
+ "You’re a winner!",
+ "You’re a streak master!",
+ "You’re a habit machine!",
+ "You’re a streak builder!",
+ "You’re a streak star!",
+ "You’re a streak hero!",
+ "You’re a streak ninja!",
+ "You’re a streak wizard!",
+ "You’re a streak warrior!",
+ "You’re a streak explorer!",
+ "You’re a streak adventurer!",
+ "You’re a streak conqueror!",
+ "You’re a streak champion!",
+ "You’re a streak genius!",
+ "You’re a streak guru!",
+ "You’re a streak expert!",
+ "You’re a streak pro!",
+ "You’re a streak veteran!",
+ "You’re a streak rookie!",
+ "You’re a streak all-star!",
+ "You’re a streak MVP!",
+ "You’re a streak superstar!",
+ "You’re a streak rockstar!",
+ "You’re a streak dynamo!",
+ "You’re a streak powerhouse!",
+ "You’re a streak inspiration!",
+ "You’re a streak motivator!",
+ "You’re a streak leader!",
+ "You’re a streak innovator!",
+ "You’re a streak creator!",
+ "You’re a streak builder!",
+ "You’re a streak achiever!",
+ "You’re a streak doer!",
+ "You’re a streak finisher!",
+ "You’re a streak starter!",
+ "You’re a streak closer!",
+ "You’re a streak winner!",
+ "You’re a streak believer!",
+ "You’re a streak dreamer!",
+ "You’re a streak thinker!",
+ "You’re a streak planner!",
+ "You’re a streak organizer!",
+ "You’re a streak strategist!",
+ "You’re a streak tactician!",
+ "You’re a streak visionary!",
+ "You’re a streak optimist!",
+ "You’re a streak realist!",
+ "You’re a streak enthusiast!",
+ "You’re a streak supporter!",
+ "You’re a streak encourager!",
+ "You’re a streak helper!",
+ "You’re a streak friend!",
+ "You’re a streak teammate!",
+ "You’re a streak partner!",
+ "You’re a streak ally!",
+ "You’re a streak companion!",
+ "You’re a streak buddy!",
+ "You’re a streak pal!",
+ "You’re a streak mate!",
+ "You’re a streak peer!",
+ "You’re a streak colleague!",
+ "You’re a streak associate!",
+ "You’re a streak collaborator!",
+ "You’re a streak contributor!",
+ "You’re a streak participant!",
+ "You’re a streak member!",
+ "You’re a streak player!",
+ "You’re a streak contender!",
+ "You’re a streak competitor!",
+ "You’re a streak challenger!",
+ "You’re a streak rival!",
+ "You’re a streak victor!",
+ "You’re a streak survivor!",
+ "You’re a streak thriver!",
+ "You’re a streak overcomer!",
+ "You’re a streak achiever!"
+]
diff --git a/src/components/MiniGrid.jsx b/src/components/MiniGrid.jsx
index 2b3d50a..07dfcac 100644
--- a/src/components/MiniGrid.jsx
+++ b/src/components/MiniGrid.jsx
@@ -17,6 +17,7 @@ import { motion } from 'framer-motion';
import { getColorIntensity, isToday, formatDate } from '../lib/utils-habit';
import { getFrozenDays } from '../lib/utils-habit';
import { toggleCompletion } from '../lib/storage';
+import { toast } from './ui/use-toast';
const MiniGrid = ({ habit, onUpdate }) => {
const today = new Date();
@@ -39,10 +40,33 @@ const MiniGrid = ({ habit, onUpdate }) => {
days.push(date);
}
- const handleCellClick = (e, date) => {
+ const handleCellClick = async (e, date) => {
e.stopPropagation();
- toggleCompletion(habit.id, formatDate(date));
+ const dateStr = formatDate(date);
+ const isTodayCell = isToday(date);
+ const wasCompleted = habit.completions.includes(dateStr);
+ toggleCompletion(habit.id, dateStr);
onUpdate();
+ // Only show encouragement toast if validating (adding) today's dot
+ if (isTodayCell && !wasCompleted) {
+ try {
+ const res = await fetch('/encouragements.json');
+ const messages = await res.json();
+ const msg = messages[Math.floor(Math.random() * messages.length)];
+ toast({
+ title: '🎉 Keep Going!',
+ description: msg,
+ duration: 2500,
+ });
+ } catch (err) {
+ // fallback message
+ toast({
+ title: '🎉 Keep Going!',
+ description: 'Great job! Keep up the streak!',
+ duration: 2500,
+ });
+ }
+ }
};
return (
diff --git a/src/components/ui/toast.jsx b/src/components/ui/toast.jsx
index c1b11d2..535e5fe 100644
--- a/src/components/ui/toast.jsx
+++ b/src/components/ui/toast.jsx
@@ -7,19 +7,21 @@ import React from 'react';
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef(({ className, ...props }, ref) => (
-
+
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
- 'data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full',
+ 'data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-2xl border-0 p-6 pr-8 shadow-2xl transition-all bg-white/80 backdrop-blur-lg ring-2 ring-green-300/40 drop-shadow-xl scale-95 animate-toast-in data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full',
{
variants: {
variant: {
@@ -73,20 +75,24 @@ const ToastClose = React.forwardRef(({ className, ...props }, ref) => (
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef(({ className, ...props }, ref) => (
-
+
+ 🎊 {props.children}
+
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef(({ className, ...props }, ref) => (
-
+
+ ✨ {props.children}
+
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
diff --git a/src/index.css b/src/index.css
index c69a07c..3ef95e6 100644
--- a/src/index.css
+++ b/src/index.css
@@ -94,4 +94,21 @@
.dark .grid-scroll::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
+}
+
+/* Toast custom animations */
+@keyframes toast-in {
+ 0% { transform: scale(0.7) translateY(40px); opacity: 0; }
+ 100% { transform: scale(1) translateY(0); opacity: 1; }
+}
+.animate-toast-in {
+ animation: toast-in 0.5s cubic-bezier(.68,-0.55,.27,1.55);
+}
+@keyframes float {
+ 0% { transform: translateY(0); }
+ 50% { transform: translateY(-8px); }
+ 100% { transform: translateY(0); }
+}
+.animate-float {
+ animation: float 2s infinite ease-in-out;
}
\ No newline at end of file