#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_NeoPixel.h>
#define PIN 5
#define NUM_LEDS 390
Adafruit_NeoPixel strip(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);
RTC_DS3231 rtc;
#define SEG_SIZE 9
//int lastSecond = -1;
int digitStart[6] = {0,63,126,189,252,315};
int segOffset[7] = {0,9,18,27,36,45,54};
int ringOrder[7] = {0,1,2,3,4,5,6};
byte digits[10][7] = {
{1,1,1,1,1,1,0},
{0,1,1,0,0,0,0},
{1,1,0,1,1,0,1},
{1,1,1,1,0,0,1},
{0,1,1,0,0,1,1},
{1,0,1,1,0,1,1},
{1,0,1,1,1,1,1},
{1,1,1,0,0,0,0},
{1,1,1,1,1,1,1},
{1,1,1,1,0,1,1}
};
int currentDigits[6];
int prevDigits[6] = {-1,-1,-1,-1,-1,-1};
int mode = 0;
unsigned long lastModeChange = 0;
unsigned long lastFrame = 0;
int frameDelay = 25;
//------------------------------------------
// ================= SETUP =================
void setup(){
strip.begin();
strip.show();
Wire.begin(21,22);
rtc.begin();
}
// ================= LOOP =================
void loop(){
DateTime now = rtc.now();
currentDigits[0] = now.hour()/10;
currentDigits[1] = now.hour()%10;
currentDigits[2] = now.minute()/10;
currentDigits[3] = now.minute()%10;
currentDigits[4] = now.second()/10;
currentDigits[5] = now.second()%10;
if(millis() - lastModeChange > 8000){
mode++;
if(mode > 1) mode = 0;
lastModeChange = millis();
}
if(millis() - lastFrame < frameDelay) return;
lastFrame = millis();
switch(mode){
//case 0: modeSmooth(); break;
// case 1: modeRGB(); break;
// case 2: modeSnake(); break;
//case 3: modeWave(); break;
//case 4: modeFire(); break;
//case 5: modeColorSweep(); break;
//case 6: modeGlitch(); break;
//case 7: modeFlip(); break;
// case 8: modeMorph(); break;
//case 0: modeShatter(); break;
//case 3: modeNeonTraceSmart(); break;
// case 1: modeNeonTrace(); break;
case 0: modeIce(); break;
//case 0: modeLiquid(); break;
case 1: modeGlitchh(); break;
}
drawColon();
strip.show();
}
// ================= DRAW =================
void drawDigit(int pos, int num, uint32_t color){
int base = digitStart[pos];
for(int s=0;s<7;s++){
for(int i=0;i<SEG_SIZE;i++){
int led = base + segOffset[s] + i;
if(digits[num][s])
strip.setPixelColor(led, color);
else
strip.setPixelColor(led, 0);
}
}
}
// ================= COLON =================
void drawColon(){
int c1[] = {378,379};
int c2[] = {380,381};
for(int i=0;i<2;i++){
strip.setPixelColor(c1[i], strip.Color(150,150,150));
strip.setPixelColor(c2[i], strip.Color(150,150,150));
}
}
// ================= MODE 0 =================
void modeSmooth(){
static int b=50,dir=3;
b+=dir;
if(b>200||b<50) dir=-dir;
for(int i=0;i<6;i++){
drawDigit(i,currentDigits[i],strip.Color(0,b,150));
}
}
// ================= MODE 1 =================
void modeRGB(){
static uint16_t hue=0;
for(int i=0;i<6;i++){
for(int s=0;s<7;s++){
for(int p=0;p<SEG_SIZE;p++){
int led=digitStart[i]+segOffset[s]+p;
if(digits[currentDigits[i]][s]){
strip.setPixelColor(led,strip.ColorHSV(hue+i*3000));
} else strip.setPixelColor(led,0);
}
}
}
hue+=200;
}
// ================= MODE 2 =================
// ================= MODE 6 =================
void modeGlitch(){
for(int d=0;d<6;d++){
drawDigit(d,currentDigits[d],strip.Color(0,180,60));
}
}
void modeFire(){
static byte heat[390];
// 🔻 COOLING (reduced → taller flame)
for(int i=0;i<390;i++){
int cooldown = random(1,4); // less cooling = higher flame
if(heat[i] > cooldown)
heat[i] -= cooldown;
else
heat[i] = 0;
}
// 🔺 HEAT RISE (strong upward movement)
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=SEG_SIZE-1; i>=1; i--){
int led = digitStart[d] + segOffset[s] + i;
int below = digitStart[d] + segOffset[s] + (i-1);
heat[led] = (heat[led] + heat[below]*2) / 3;
}
}
}
// 🔥 STRONG SPARKS (multi-point base)
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
if(digits[currentDigits[d]][s]){
int base = digitStart[d] + segOffset[s];
if(random(100) > 70){ // more sparks = more flame
heat[base] = random(200,255);
}
// extra spark for height boost
if(random(100) > 85){
heat[base + 1] = random(180,240);
}
}
}
}
// 🎨 REAL FIRE COLOR (NO GREEN)
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
if(digits[currentDigits[d]][s]){
byte h = heat[led];
uint8_t r,g,b;
if(h > 200){
// white/yellow tip
r = 255;
g = 200 + (h - 200);
b = 0;
}
else if(h > 120){
// orange
r = 255;
g = (h - 120) * 2; // controlled yellow (NOT green)
b = 0;
}
else{
// deep red
r = h * 2;
g = 0;
b = 0;
}
// flicker
r = constrain(r - random(10), 0, 255);
g = constrain(g - random(10), 0, 255);
strip.setPixelColor(led, strip.Color(r,g,b));
} else {
strip.setPixelColor(led, 0);
}
}
}
}
}
void modeWave(){
static float t = 0;
t += 0.15;
for(int d=0;d<6;d++){
for(int s=0;s<7;s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
if(digits[currentDigits[d]][s]){
float wave = sin(t + i*0.5 + d);
int brightness = (wave * 127) + 128;
strip.setPixelColor(led,
strip.Color(0, brightness, 255 - brightness)
);
} else {
strip.setPixelColor(led, 0);
}
}
}
}
}
void modeSnake(){
static int step = 0;
step++;
if(step >= 63) step = 0;
for(int d=0; d<6; d++){
for(int i=0;i<63;i++){
int led = digitStart[d] + i;
int seg = i / 9;
// only draw valid segments
if(digits[currentDigits[d]][seg]){
if(i < step){
// already formed part
strip.setPixelColor(led, strip.Color(0,150,50));
}
else if(i == step){
// snake head
strip.setPixelColor(led, strip.Color(0,255,150));
}
else{
// not formed yet
strip.setPixelColor(led, 0);
}
} else {
strip.setPixelColor(led, 0);
}
}
}
}
void modeColorSweep(){
static int offset = 0;
offset++;
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
if(digits[currentDigits[d]][s]){
int pos = (i + d*5 + offset) % 255;
strip.setPixelColor(led,
strip.ColorHSV(pos * 256)
);
} else {
strip.setPixelColor(led, 0);
}
}
}
}
}
void modeFlip(){
static float t = 0;
static bool animating = false;
static int oldDigits[6];
// detect change
if(!animating){
for(int d=0; d<6; d++){
if(currentDigits[d] != prevDigits[d]){
animating = true;
t = 0;
for(int i=0;i<6;i++){
oldDigits[i] = prevDigits[i];
}
break;
}
}
}
// animation progress (smooth flip curve)
if(animating){
t += 0.08;
if(t >= 1.0){
t = 1.0;
animating = false;
for(int d=0; d<6; d++){
prevDigits[d] = currentDigits[d];
}
}
}
// FLIP CURVE (IMPORTANT)
float flip = sin(t * 3.1415); // 0 → 1 → 0 feel
// DRAW
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
bool prevOn = digits[oldDigits[d]][s];
bool currOn = digits[currentDigits[d]][s];
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
int brightness = 0;
// OLD DIGIT FLIPS OUT
if(prevOn && !currOn){
brightness = (1.0 - flip) * 200;
// squash effect (flip illusion)
if(i < SEG_SIZE * flip)
brightness = 0;
}
// NEW DIGIT FLIPS IN
else if(!prevOn && currOn){
brightness = flip * 255;
// grow from center illusion
int mid = SEG_SIZE / 2;
if(abs(i - mid) > flip * mid)
brightness = 0;
}
// SAME DIGIT (stable)
else if(prevOn && currOn){
brightness = 180;
}
else{
brightness = 0;
}
// COLOR (nice LED look)
int r = 0;
int g = brightness;
int b = brightness / 2;
strip.setPixelColor(led, strip.Color(r,g,b));
}
}
}
}
void modeMorph(){
static int step = 0;
static bool animating = false;
static int oldDigits[6];
// detect change (ONLY ONCE)
if(!animating){
for(int d=0; d<6; d++){
if(currentDigits[d] != prevDigits[d]){
animating = true;
step = 0;
// store old digits
for(int i=0;i<6;i++){
oldDigits[i] = prevDigits[i];
}
break;
}
}
}
// animation progress
if(animating){
step++;
if(step >= SEG_SIZE){
step = SEG_SIZE;
animating = false;
for(int d=0; d<6; d++){
prevDigits[d] = currentDigits[d];
}
}
}
// DRAW
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
bool prevOn = (oldDigits[d] >= 0) ? digits[oldDigits[d]][s] : 0;
bool currOn = digits[currentDigits[d]][s];
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
// FULL SEGMENT CONTROL (KEY FIX)
if(prevOn && currOn){
strip.setPixelColor(led, strip.Color(0,150,50));
}
else if(prevOn && !currOn){
// SHRINK
int cutoff = SEG_SIZE - step;
if(i < cutoff)
strip.setPixelColor(led, strip.Color(0,150,50));
else
strip.setPixelColor(led, 0);
}
else if(!prevOn && currOn){
// GROW
//int cutoff = step;
// if(i <= cutoff)
int mid = SEG_SIZE / 2;
if(abs(i - mid) <= step/2)
strip.setPixelColor(led, strip.Color(0,150,50));
else
strip.setPixelColor(led, 0);
}
else{
strip.setPixelColor(led, 0);
}
}
}
}
}
void modeShatter(){
static float t = 0;
static bool animating = false;
static int oldDigits[6];
if(!animating){
for(int d=0; d<6; d++){
if(currentDigits[d] != prevDigits[d]){
animating = true;
t = 0;
for(int i=0;i<6;i++) oldDigits[i] = prevDigits[i];
break;
}
}
}
if(animating){
t += 0.08;
if(t >= 1){
t = 1;
animating = false;
for(int i=0;i<6;i++) prevDigits[i] = currentDigits[i];
}
}
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
bool prevOn = digits[oldDigits[d]][s];
bool currOn = digits[currentDigits[d]][s];
float spread = t * 10;
int brightness = 0;
if(prevOn && !currOn){
// explode
brightness = (1.0 - t) * 200;
if(random(10) < spread)
brightness = 0;
}
else if(!prevOn && currOn){
// reform
brightness = t * 255;
}
else if(prevOn && currOn){
brightness = 180;
}
strip.setPixelColor(led, strip.Color(brightness, brightness/2, 0));
}
}
}
}
void modeNeonTrace(){
static int pos = 0;
static bool hold = false;
static unsigned long holdStart = 0;
// HOLD STATE (pause for 1 second)
if(hold){
if(millis() - holdStart >= 1000){
hold = false;
pos = 0; // restart drawing
}
}
else{
pos++;
if(pos >= SEG_SIZE){
pos = SEG_SIZE;
hold = true;
holdStart = millis();
}
}
// DRAW DIGITS
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
if(digits[currentDigits[d]][s]){
if(i < pos){
// already drawn
strip.setPixelColor(led, strip.Color(0,255,180));
}
else if(i == pos && !hold){
// moving bright tip
strip.setPixelColor(led, strip.Color(255,255,255));
}
else{
// dim background glow
strip.setPixelColor(led, strip.Color(0,20,10));
}
} else {
strip.setPixelColor(led, 0);
}
}
}
}
}
void modeIce(){
static float t = 0;
static bool animating = false;
static int oldDigits[6];
if(!animating){
for(int d=0; d<6; d++){
if(currentDigits[d] != prevDigits[d]){
animating = true;
t = 0;
for(int i=0;i<6;i++) oldDigits[i] = prevDigits[i];
break;
}
}
}
if(animating){
t += 0.07;
if(t >= 1){
t = 1;
animating = false;
for(int i=0;i<6;i++) prevDigits[i] = currentDigits[i];
}
}
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
bool prevOn = digits[oldDigits[d]][s];
bool currOn = digits[currentDigits[d]][s];
int brightness = 0;
if(prevOn && !currOn){
brightness = (1.0 - t) * 200;
}
else if(!prevOn && currOn){
brightness = t * 255;
}
else if(prevOn && currOn){
brightness = 180;
}
// icy blue color
strip.setPixelColor(led, strip.Color(0, brightness/2, brightness));
}
}
}
}
void modeNeonTraceSmart(){
static int digitIndex = 0;
static int pos = 0;
static bool animating = false;
static bool hold = false;
static unsigned long holdStart = 0;
static int lastMinute = -1;
DateTime now = rtc.now();
// 🔥 trigger only on minute change
if(now.minute() != lastMinute){
lastMinute = now.minute();
animating = true;
digitIndex = 0;
pos = 0;
hold = false;
}
// HOLD
if(hold){
if(millis() - holdStart > 1000){
hold = false;
animating = false;
}
}
// ANIMATION
if(animating && !hold){
pos++;
if(pos >= SEG_SIZE){
pos = 0;
digitIndex++;
if(digitIndex >= 4){
hold = true;
holdStart = millis();
}
}
}
// DRAW ALL 6 DIGITS
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
if(digits[currentDigits[d]][s]){
uint32_t baseColor;
uint32_t tipColor = strip.Color(255,255,255);
// 🎨 COLOR GROUPS
if(d < 2){ // HOURS
baseColor = strip.Color(0,150,255);
}
else if(d < 4){ // MINUTES
baseColor = strip.Color(0,255,120);
}
else{ // SECONDS
baseColor = strip.Color(255,80,0); // orange
}
// 🟢 SECONDS → ALWAYS STATIC (NO ANIMATION)
if(d >= 4){
strip.setPixelColor(led, baseColor);
}
// 🔵 HH:MM animation
else{
if(!animating || d < digitIndex){
strip.setPixelColor(led, baseColor);
}
else if(d == digitIndex){
if(i < pos){
strip.setPixelColor(led, baseColor);
}
else if(i == pos){
strip.setPixelColor(led, tipColor);
}
else{
strip.setPixelColor(led, strip.Color(0,20,10));
}
} else {
strip.setPixelColor(led, 0);
}
}
} else {
strip.setPixelColor(led, 0);
}
}
}
}
}
void modeLiquid(){
static float t = 1.0;
static bool animating = false;
static int oldDigits[6];
static int lastSecond = -1;
DateTime now = rtc.now();
// 🔥 trigger on second change
if(now.second() != lastSecond){
lastSecond = now.second();
animating = true;
t = 0;
for(int i=0;i<6;i++){
oldDigits[i] = prevDigits[i];
}
}
// animation progress
if(animating){
t += 0.10;
if(t >= 1.0){
t = 1.0;
animating = false;
for(int i=0;i<6;i++){
prevDigits[i] = currentDigits[i];
}
}
}
// DRAW
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
bool prevOn = digits[oldDigits[d]][s];
bool currOn = digits[currentDigits[d]][s];
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
int brightness = 0;
// 💧 OLD DIGIT DRAIN (top → down)
if(prevOn && !currOn){
if(i < SEG_SIZE * (1.0 - t)){
brightness = 150;
}
}
// 💧 NEW DIGIT FILL (bottom → up)
else if(!prevOn && currOn){
if(i < SEG_SIZE * t){
brightness = 255;
}
}
// 🔥 STABLE SEGMENT (DIM, NOT FULL)
else if(prevOn && currOn){
brightness = 60; // 👈 reduced brightness (KEY FIX)
}
else{
brightness = 0;
}
// 💙 smooth liquid color
strip.setPixelColor(led, strip.Color(0, brightness/2, brightness));
}
}
}
}
void modeGlitchh(){
static bool glitch = false;
static unsigned long glitchStart = 0;
// 🔥 trigger random glitch
if(!glitch && random(1000) > 980){ // rare glitch
glitch = true;
glitchStart = millis();
}
// stop glitch after short time
if(glitch && millis() - glitchStart > 120){
glitch = false;
}
for(int d=0; d<6; d++){
for(int s=0; s<7; s++){
for(int i=0;i<SEG_SIZE;i++){
int led = digitStart[d] + segOffset[s] + i;
if(digits[currentDigits[d]][s]){
int r = 0, g = 150, b = 80; // base color
if(glitch){
// ⚡ RGB SHIFT EFFECT
int shift = random(-2, 3);
int newIndex = led + shift;
if(newIndex >= 0 && newIndex < NUM_LEDS){
strip.setPixelColor(newIndex,
strip.Color(random(100,255),0,random(100,255))
);
}
// ⚡ flicker brightness
int flick = random(50,255);
r = flick;
g = random(0,150);
b = flick;
// ⚡ random drop pixels
if(random(1000) > 970){
r = g = b = 0;
}
}
strip.setPixelColor(led, strip.Color(r,g,b));
} else {
// slight background noise during glitch
if(glitch && random(100) > 95){
strip.setPixelColor(led, strip.Color(50,0,50));
} else {
strip.setPixelColor(led, 0);
}
}
}
}
}
}