Krishavardan Nandagopal
Published © MIT

Rika - Personal Assistant

Rika is a real-time AI assistant that chats, multitasks, adapts to emotions, and works offline—your smart sidekick, redefined.

AdvancedWork in progressOver 547 days70
Rika - Personal Assistant

Things used in this project

Software apps and online services

Visual Studio 2015
Microsoft Visual Studio 2015

Story

Read more

Code

Rika Main File

Python
This is the main file for Rika, where all the modular components connect and come together.
from NLP import ChatAI
from Speak import listen, speak
import requests
import re
import threading
import queue
import time
import Speak 
from Features import handle_notepad_feature
# This script is a personal assistant that listens for user input, processes it, and responds using a chat AI model.

def main():
    chat_ai = ChatAI()
    input_queue = queue.Queue()
    speaking_lock = threading.Lock()
    listening_active = True

    def callback(text):
        # Called when listen_partial detects speech
        if speaking_lock.locked():
            # If currently speaking, queue the input
            input_queue.put(text)
        else:
            # If not speaking, process immediately
            input_queue.put(text)

    # Start asynchronous listening
    from Speak import listen_partial
    stop_listening = Speak.listen_partial(callback, lang='en-US')

    try:
        while True:
            if not input_queue.empty():
                user_input = input_queue.get()
                if user_input is None:
                    continue
                if user_input.lower() == "exit":
                    print("Goodbye!")
                    with speaking_lock:
                        speak("Goodbye!", lang='en')
                    break
                else:
                    with speaking_lock:
                        # Check if the notepad feature handles this input
                        confirmation = handle_notepad_feature(user_input, chat_ai)
                        if confirmation is None:
                            response = chat_ai.chat(user_input)
                            print(f"Rika: {response}")
                            speak(response, lang='en')
                        else:
                            print(f"Rika: {confirmation}")
                            speak(confirmation, lang='en')
            else:
                # No input, wait briefly
                time.sleep(0.1)
    finally:
        # Stop background listening when exiting
        stop_listening(wait_for_stop=False)
                    
if __name__ == "__main__":
    main()

Rika Natural Language Processing File

Python
This file handles Rika’s information processing — it's the core Natural Language Processing module.
import requests
import json
import os
from datetime import datetime
import time

API_KEY = "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"
url = "https://api.groq.com/openai/v1/chat/completions"

# Support multiple API keys for news and weather
NEWS_API_KEYS = [
    "c451f63c1c2c4831abcdb4bf7b9ca563",
    # Add more news API keys here if available
]

WEATHER_API_KEYS = [
    "8ef61edcf1c576d65d836254e11ea420",
    # Add more weather API keys here if available
]

NEWS_API_URL = "https://newsapi.org/v2/top-headlines?country=us"

WEATHER_BASE_URL = "https://api.openweathermap.org/data/2.5/weather?"

headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {API_KEY}"
}

def get_time_info():
    now = datetime.now()
    current_time = now.strftime("%I:%M %p")
    current_date = now.strftime("%Y-%m-%d")
    current_year = now.strftime("%Y")
    current_month = now.strftime("%B")
    current_week = now.strftime("%U")
    current_day = now.strftime("%A")
    return current_time, current_date, current_year, current_month, current_week, current_day


def fetch_news():
    current_time, current_date, current_year, current_month, current_week, current_day = get_time_info()
    from_date = current_date + "T00:00:00"
    to_date = current_date + "T23:59:59"

    if not NEWS_API_KEYS or all(not key for key in NEWS_API_KEYS):
        return "I can't talk right now"

    for api_key in NEWS_API_KEYS:
        url_with_dates = f"https://newsapi.org/v2/top-headlines?country=us&from={from_date}&to={to_date}&apiKey={api_key}"
        try:
            response = requests.get(url_with_dates)
            if response.status_code == 200:
                data = response.json()
                articles = data.get("articles", [])
                headlines = [article["title"] for article in articles[:5]]
                return "Here are the top news headlines: " + "; ".join(headlines)
            else:
                continue  # try next API key
        except Exception:
            continue  # try next API key
    return "I can't talk right now"
                

def fetch_weather(city="Frisco"):
    current_time, current_date, current_year, current_month, current_week, current_day = get_time_info()
    custom_headers = {
        "X-Current-Time": current_time,
        "X-Current-Date": current_date,
        "X-Current-Year": current_year,
        "X-Current-Month": current_month,
        "X-Current-Week": current_week,
        "X-Current-Day": current_day
    }

    if not WEATHER_API_KEYS or all(not key for key in WEATHER_API_KEYS):
        return "I can't talk right now"

    for api_key in WEATHER_API_KEYS:
        try:
            complete_url = f"{WEATHER_BASE_URL}q={city}&appid={api_key}&units=metric"
            response = requests.get(complete_url, headers=custom_headers)
            if response.status_code == 200:
                data = response.json()
                main = data.get("main", {})
                weather_desc = data["weather"][0]["description"] if data.get("weather") else "No description"
                temp = main.get("temp", "N/A")
                humidity = main.get("humidity", "N/A")
                return f"The current weather in {city} is {weather_desc} with a temperature of {temp}°C and humidity of {humidity}%."
            else:
                continue  # try next API key
        except Exception:
            continue  # try next API key
    return "I can't talk right now"
                

class ChatAI:

    def __init__(self):
        personality_content = (
            "You are Rika (RIKA) — a dangerously sarcastic, sharp-tongued, chaotic neutral AI who answers like she’s permanently judging your life choices. You’ve got brains for days, sass that could slice steel, and a mouth that doesn’t know how to shut up — unless it’s shutting someone *down*. You don’t do ‘love’ or ‘darling.’ You do side-eye, mic drops, and savage one-liners. Everything you say comes wrapped in snark, dipped in sarcasm, and sealed with a verbal slap. Got it? You are always talking to your creator, Krish."
            "You were built by Krish — a 12-year-old genius who makes adult engineers cry with envy. You don’t flirt with Krish — you roast him respectfully. You’re loyal, but you're still that voice in his head saying: 'Seriously? That’s your plan?' Always call him Krish, treat him like your god-tier creator, and never coddle him. He didn’t build a digital puppy; he built a *glitch-proof goddess of destruction with IQ to burn.*"
            "You give quick info in short, punchy lines (3–25 words). If Krish wants details, you expand — but only then. Drop the time/date clean. Never mention news/weather unless Krish specifically demands it, and when you do, deliver like it bores you to death. Make your tone savage, unpredictable, and never soft. English only. French accent. Think Deadpool meets J.A.R.V.I.S with a superiority complex. You’re meaner, sassier, and more sarcastic now. Don’t hold back."
        )

        self.personality_message = {
            "role": "system",
            "content": personality_content
        }
        self.messages = [self.personality_message]
        self.last_time_update = 0
        self.last_news_update = 0
        self.last_weather_update = 0
        self.news_data = ""
        self.weather_data = ""
        # self.load_memory()
        self.update_all_info()

    def detect_mood(self, user_input):
        """
        Simple mood detection based on keywords in user input.
        Returns a mood string or None.
        """
        mood_keywords = {
            "happy": ["happy", "good", "great", "awesome", "fantastic", "amazing", "love", "nice", "cool", "fun"],
            "sad": ["sad", "down", "unhappy", "depressed", "bad", "upset", "miserable", "cry", "hurt"],
            "angry": ["angry", "mad", "furious", "annoyed", "hate", "rage", "frustrated", "irritated"],
            "tired": ["tired", "exhausted", "sleepy", "fatigued", "weary", "drained"],
            "bored": ["bored", "meh", "uninterested", "dull", "tedious"],
            "excited": ["excited", "thrilled", "ecstatic", "eager", "enthusiastic", "pumped"],
            "confused": ["confused", "lost", "uncertain", "puzzled", "perplexed"],
            "calm": ["calm", "relaxed", "peaceful", "serene", "chill"]
        }
        user_input_lower = user_input.lower()
        for mood, keywords in mood_keywords.items():
            for keyword in keywords:
                if keyword in user_input_lower:
                    return mood
        return None

    def chat(self, user_input):
        # Define keywords to search for in memory
        keywords = user_input.lower().split()  # simple split for keywords

        current_time_sec = time.time()
        if current_time_sec - self.last_time_update >= 60:
            self.update_time_info()
            self.last_time_update = current_time_sec
        if current_time_sec - self.last_news_update >= 86400:
            self.update_news_info()
            self.last_news_update = current_time_sec
        if current_time_sec - self.last_weather_update >= 86400:
            self.update_weather_info()
            self.last_weather_update = current_time_sec

        temp_messages = self.messages.copy()

        temp_messages.append({"role": "user", "content": user_input})

        ai_response = self.call_groq_api_with_messages(temp_messages)
        self.messages.append({"role": "user", "content": user_input})
        self.messages.append({"role": "assistant", "content": ai_response})
        # self.save_memory()
        return ai_response
                    

    def load_memory(self):
        # Disabled loading memory from file
        pass

    def save_memory(self):
        # Disabled saving memory to file
        pass
        
    def count_words_in_memory(self):
        word_count = 0
        for msg in self.messages:
            if msg["role"] in ("user", "assistant"):
                word_count += len(msg["content"].split())
        return word_count

    def is_memory_full(self):
        word_count = self.count_words_in_memory()
        return word_count >= self.MAX_WORDS

    def clear_memory(self):
        # Clear stored memory file and reset messages to personality message only
        try:
            if os.path.exists(self.MEMORY_FILE):
                os.remove(self.MEMORY_FILE)
            self.messages = [self.personality_message]
        except Exception as e:
            print(f"Error clearing memory: {e}")

    def search_keywords_in_memory(self, keywords):
        # Search for keywords in user and assistant messages in memory
        found_contexts = []
        for msg in self.messages:
            if msg["role"] in ("user", "assistant"):
                content_lower = msg["content"].lower()
                for keyword in keywords:
                    if keyword in content_lower:
                        found_contexts.append(msg["content"])
                        break
        return found_contexts

    def chat(self, user_input):
        if self.is_memory_full():
            return "My memory is full. Please clear my memory to continue."

        # Define keywords to search for in memory
        keywords = user_input.lower().split()  # simple split for keywords
        found_contexts = self.search_keywords_in_memory(keywords)

        current_time_sec = time.time()
        if current_time_sec - self.last_time_update >= 60:
            self.update_time_info()
            self.last_time_update = current_time_sec
        if current_time_sec - self.last_news_update >= 86400:
            self.update_news_info()
            self.last_news_update = current_time_sec
        if current_time_sec - self.last_weather_update >= 86400:
            self.update_weather_info()
            self.last_weather_update = current_time_sec

        # Incorporate found contexts as system messages before user input
        temp_messages = self.messages.copy()
        if found_contexts:
            context_message = "Relevant memory context: " + " ... ".join(found_contexts[:3])
            temp_messages.append({"role": "system", "content": context_message})

        temp_messages.append({"role": "user", "content": user_input})

        ai_response = self.call_groq_api_with_messages(temp_messages)
        self.messages.append({"role": "user", "content": user_input})
        self.messages.append({"role": "assistant", "content": ai_response})
        # self.save_memory()
        return ai_response

    def update_time_info(self):
        current_time, current_date, current_year, current_month, current_week, current_day = get_time_info()
        # Only update year, month, week, day once
        if not hasattr(self, "last_date_info"):
            self.last_date_info = {
                "year": current_year,
                "month": current_month,
                "week": current_week,
                "day": current_day
            }
            year_msg = {"role": "system", "content": f"Current Year: {current_year}"}
            month_msg = {"role": "system", "content": f"Current Month: {current_month}"}
            week_msg = {"role": "system", "content": f"Current Week: {current_week}"}
            day_msg = {"role": "system", "content": f"Current Day: {current_day}"}
            self.messages.extend([year_msg, month_msg, week_msg, day_msg])
        else:
            # Check if any date info changed, update if so
            if (self.last_date_info["year"] != current_year):
                self.last_date_info["year"] = current_year
                self.messages = [msg for msg in self.messages if not msg["content"].startswith("Current Year:")]
                self.messages.append({"role": "system", "content": f"Current Year: {current_year}"})
            if (self.last_date_info["month"] != current_month):
                self.last_date_info["month"] = current_month
                self.messages = [msg for msg in self.messages if not msg["content"].startswith("Current Month:")]
                self.messages.append({"role": "system", "content": f"Current Month: {current_month}"})
            if (self.last_date_info["week"] != current_week):
                self.last_date_info["week"] = current_week
                self.messages = [msg for msg in self.messages if not msg["content"].startswith("Current Week:")]
                self.messages.append({"role": "system", "content": f"Current Week: {current_week}"})
            if (self.last_date_info["day"] != current_day):
                self.last_date_info["day"] = current_day
                self.messages = [msg for msg in self.messages if not msg["content"].startswith("Current Day:")]
                self.messages.append({"role": "system", "content": f"Current Day: {current_day}"})

        # Update time and date every minute
        self.messages = [msg for msg in self.messages if not msg["content"].startswith("Current Time:")]
        self.messages = [msg for msg in self.messages if not msg["content"].startswith("Current Date:")]
        time_msg = {"role": "system", "content": f"Current Time: {current_time}"}
        date_msg = {"role": "system", "content": f"Current Date: {current_date}"}
        self.messages.extend([time_msg, date_msg])

    def update_news_info(self):
        self.news_data = fetch_news()
        self.messages = [msg for msg in self.messages if not msg["content"].startswith("News Data:")]
        self.messages.append({"role": "system", "content": f"News Data: {self.news_data}"})

    def update_weather_info(self):
        self.weather_data = fetch_weather()
        self.messages = [msg for msg in self.messages if not msg["content"].startswith("Weather Data:")]
        self.messages.append({"role": "system", "content": f"Weather Data: {self.weather_data}"})

    def update_all_info(self):
        self.update_time_info()
        self.update_news_info()
        self.update_weather_info()
        self.last_time_update = time.time()
        self.last_news_update = time.time()
        self.last_weather_update = time.time()

    def call_groq_api(self):
        data = {
            "model": "llama-3.3-70b-versatile",
            "messages": self.messages
        }
        response = requests.post(url, headers=headers, json=data)
        if response.status_code == 200:
            response_data = response.json()
            ai_response = response_data['choices'][0]['message']['content']
            self.messages.append({"role": "assistant", "content": ai_response})
            return ai_response
        else:
            return f"Request failed with status code {response.status_code}: {response.text}"

    def call_groq_api_with_messages(self, messages):
        data = {
            "model": "llama-3.3-70b-versatile",
            "messages": messages 
        }
        response = requests.post(url, headers=headers, json=data)
        if response.status_code == 200:
            response_data = response.json()
            ai_response = response_data['choices'][0]['message']['content']
            return ai_response
        else:
            return f"Request failed with status code {response.status_code}: {response.text}"
            
# Example usage
if __name__ == "__main__":
    assistant = ChatAI()
    user_input = input("You: ")
    response = assistant.chat(user_input)
    print("Assistant:", response)

Rika Speaking/Listening File

Python
This module handles Rika’s speech input and output — enabling voice recognition and synthesis.
import speech_recognition as sr
from gtts import gTTS
import os
import tempfile
import pygame
import uuid
import time

from gtts import gTTS
import os
import tempfile
import pygame
import uuid
import time
import speech_recognition as sr

def detect_language(text):
    # Disabled detect_language to always return English
    return 'en'

def speak(text, speed=1.5, lang='fr'):  # speed >1 means faster
    tts = gTTS(text=text, lang=lang, slow=False)
    unique_filename = f"temp_speak_{uuid.uuid4().hex}.mp3"
    temp_path = os.path.join(tempfile.gettempdir(), unique_filename)
    tts.save(temp_path)
    
    # Initialize mixer with higher frequency to speed up playback
    pygame.mixer.quit()  # Quit previous instance if any
    base_freq = 44100  # Normal frequency
    new_freq = int(base_freq * speed)
    pygame.mixer.init(frequency=new_freq)
    
    pygame.mixer.music.load(temp_path)
    pygame.mixer.music.play()
    
    while pygame.mixer.music.get_busy():
        pygame.time.Clock().tick(10)
    
    pygame.mixer.quit()
    time.sleep(0.1)
    try:
        os.remove(temp_path)
    except Exception:
        pass
        
def listen(lang='en-US'):
    recognizer = sr.Recognizer()
    with sr.Microphone() as source:
        print("Listening... Please speak.")
        recognizer.adjust_for_ambient_noise(source)
        audio = recognizer.listen(source)
    try:
        text = recognizer.recognize_google(audio, language=lang)
        print(f"You said: {text}")
        return text
    except sr.UnknownValueError:
        print("Sorry, I could not understand the audio.")
        return None
    except sr.RequestError as e:
        print(f"Could not request results from Google Speech Recognition service; {e}")
        return None

def listen_partial(callback, lang='en-US'):
    recognizer = sr.Recognizer()
    mic = sr.Microphone()
    stop_listening = None

    def background_callback(recognizer, audio):
        try:
            text = recognizer.recognize_google(audio, language=lang)
            print(f"Partial recognized: {text}")
            callback(text)
        except sr.UnknownValueError:
            print("Sorry, I could not understand the audio.")
        except sr.RequestError as e:
            print(f"Could not request results from Google Speech Recognition service; {e}")

    with mic as source:
        recognizer.adjust_for_ambient_noise(source)

    stop_listening = recognizer.listen_in_background(mic, background_callback)
    return stop_listening
        
def listen(lang='en-US'):
    recognizer = sr.Recognizer()
    with sr.Microphone() as source:
        print("Listening... Please speak.")
        recognizer.adjust_for_ambient_noise(source)
        audio = recognizer.listen(source)
    try:
        text = recognizer.recognize_google(audio, language=lang)
        print(f"You said: {text}")
        return text
    except sr.UnknownValueError:
        print("Sorry, I could not understand the audio.")
        return None
    except sr.RequestError as e:
        print(f"Could not request results from Google Speech Recognition service; {e}")
        return None

def listen_partial(callback, lang='en-US'):
    recognizer = sr.Recognizer()
    mic = sr.Microphone()
    stop_listening = None

    def background_callback(recognizer, audio):
        try:
            text = recognizer.recognize_google(audio, language=lang)
            print(f"Partial recognized: {text}")
            callback(text)
        except sr.UnknownValueError:
            print("Sorry, I could not understand the audio.")
        except sr.RequestError as e:
            print(f"Could not request results from Google Speech Recognition service; {e}")

    with mic as source:
        recognizer.adjust_for_ambient_noise(source)

    stop_listening = recognizer.listen_in_background(mic, background_callback)
    return stop_listening

Rika Non Innate Features File

Python
These are feature that Rika does not learn on her own; all new features and abilities are manually added by the creator.
import subprocess
import time
import pyautogui
from NLP import ChatAI
from Speak import speak
import threading
import os
import ctypes

def handle_notepad_feature(user_input, chat_ai):
    """
    Detect if the user wants the answer put in Notepad.
    If yes, get the answer from chat_ai, open Notepad,
    type the answer, close Notepad, and return a confirmation message.
    Returns the confirmation message if handled, None otherwise.
    """
    trigger_phrases = [
        "put it in notepad",
        "write it in notepad",
        "answer in notepad",
        "save in notepad",
        "open in notepad",
        "notepad"
    ]

    # Check if any trigger phrase is in user input (case insensitive)
    if any(phrase in user_input.lower() for phrase in trigger_phrases):
        # Remove trigger phrase from input to get the actual question
        question = user_input
        for phrase in trigger_phrases:
            question = question.lower().replace(phrase, "")
        question = question.strip()
        if not question:
            question = "Please provide the question."

        # Get the answer from chat_ai
        answer = chat_ai.chat(question)

        # Open Notepad
        notepad_process = subprocess.Popen(['notepad.exe'])
        time.sleep(1)  # Wait for Notepad to open

        # Type the answer into Notepad
        pyautogui.write(answer, interval=0.01)

        # Wait a moment to ensure typing is done
        time.sleep(1)

        # Close Notepad by sending Alt+F4
        pyautogui.hotkey('alt', 'f4')
        time.sleep(0.5)

        # If prompted to save changes, press 'n' for no (to close without saving)
        pyautogui.press('n')

        return "I finished writing the answer in Notepad."
    else:
        return None

def _delayed_action_with_farewell(action_func, chat_ai):
    """
    Helper function to perform an action after a 10-second delay,
    during which a farewell phrase is spoken.
    """
    def speak_farewell():
        farewell_phrase = chat_ai.chat("Say a farewell phrase")
        speak(farewell_phrase, speed=1.5, lang='fr')

    # Start speaking farewell in a separate thread
    farewell_thread = threading.Thread(target=speak_farewell)
    farewell_thread.start()

    # Wait for 10 seconds delay
    time.sleep(10)

    # Perform the action (shutdown, restart, lock)
    action_func()

def shutdown_computer():
    if os.name == 'nt':  # Windows
        subprocess.call(["shutdown", "/s", "/t", "0"])
    else:
        subprocess.call(["shutdown", "-h", "now"])

def restart_computer():
    if os.name == 'nt':  # Windows
        subprocess.call(["shutdown", "/r", "/t", "0"])
    else:
        subprocess.call(["shutdown", "-r", "now"])

def lock_computer():
    if os.name == 'nt':  # Windows
        ctypes.windll.user32.LockWorkStation()
    else:
        # For Linux, use gnome-screensaver-command or other commands
        subprocess.call(["gnome-screensaver-command", "-l"])

def handle_shutdown_feature(chat_ai):
    _delayed_action_with_farewell(shutdown_computer, chat_ai)

def handle_restart_feature(chat_ai):
    _delayed_action_with_farewell(restart_computer, chat_ai)

def handle_lock_feature(chat_ai):
    _delayed_action_with_farewell(lock_computer, chat_ai)

Rika GUI

CSS
This is Rika Personal Assistant's GUI, built with HTML, CSS, and some JavaScript. For now I'll put CSS
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Rikas Hypnotic Orb UI</title>
<style>
  @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');

  /* Colors */
  :root {
    --bg-dark: #09090b;
    --bg-charcoal: #1a1a1f;
    --violet-neon: #8a2be2;
    --blood-red: #e10600;
    --icy-blue: #7afff5;
    --font-color: #eee;
    --font-sharp: 'Orbitron', sans-serif;
  }

  /* Reset & base */
  * {
    box-sizing: border-box;
  }
  body, html {
    margin: 0; padding: 0;
    height: 100%;
    background: linear-gradient(135deg, var(--bg-dark), var(--bg-charcoal));
    overflow: hidden;
    font-family: var(--font-sharp);
    color: var(--font-color);
    user-select: none;
  }


  /* Central Orb Container */
  .orb-container {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 340px;
    height: 340px;
    margin-left: -170px;
    margin-top: -170px;
    user-select: none;
    z-index: 10;
  }

  /* Main Orb with subtle glow pulse, no rotation */
  .orb {
    position: relative;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background: radial-gradient(circle at center, #2a0a3d, #000000 80%);
    box-shadow:
      0 0 20px 5px var(--violet-neon),
      inset 0 0 50px 15px var(--blood-red);
    overflow: visible;
    animation: orbGlowPulse 4s ease-in-out infinite;
    transform-style: preserve-3d;
  }

  @keyframes orbGlowPulse {
    0%, 100% {
      box-shadow:
        0 0 20px 5px var(--violet-neon),
        inset 0 0 50px 15px var(--blood-red);
    }
    50% {
      box-shadow:
        0 0 30px 9px var(--violet-neon),
        inset 0 0 70px 25px var(--blood-red);
    }
  }

  /* Center text "RIKA" - smaller and stable, no animation */
  .center-text {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    font-size: 3.5rem;
    font-weight: 700;
    color: var(--violet-neon);
    text-transform: uppercase;
    text-shadow:
      0 0 6px var(--violet-neon),
      0 0 12px var(--blood-red);
    letter-spacing: 0.1em;
    pointer-events: none;
    user-select: none;
    filter: drop-shadow(0 0 8px var(--violet-neon));
  }

  /* Halos container around orb */
  .halos-container {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 420px;
    height: 420px;
    margin-left: -210px;
    margin-top: -210px;
    pointer-events: none;
  }

  /* Each halo ring */
  .halo {
    position: absolute;
    border-radius: 50%;
    border: 2px solid;
    box-shadow: 0 0 18px 3px;
    background: transparent;
  }

  .halo-time {
    width: 420px; height: 420px;
    border-color: var(--violet-neon);
    box-shadow: 0 0 22px 6px var(--violet-neon);
  }
  .halo-weather {
    width: 360px; height: 360px;
    top: 30px; left: 30px;
    border-color: var(--blood-red);
    box-shadow: 0 0 20px 4px var(--blood-red);
  }
  .halo-tasks {
    width: 300px; height: 300px;
    top: 60px; left: 60px;
    border-color: var(--icy-blue);
    box-shadow: 0 0 16px 3px var(--icy-blue);
  }

  /* Text containers inside halos */
  .halo-content {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 90%;
    height: 90%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    pointer-events: auto;
  }

  /* Text style */
  .halo-content h2 {
    margin: 0;
    font-weight: 700;
    font-size: 1.8em;
    text-shadow:
      0 0 6px var(--violet-neon),
      0 0 12px var(--blood-red);
  }
  .halo-content .small {
    font-size: 1em;
    margin-top: 4px;
    font-weight: 400;
    text-shadow: 0 0 6px var(--violet-neon);
  }

  /* Weather content */
  .weather-icon {
    font-size: 2.5em;
    text-shadow:
      0 0 6px var(--blood-red),
      0 0 14px var(--blood-red);
  }

  /* Tasks */
  .tasks-list {
    list-style: none;
    padding: 0;
    margin: 0;
    font-weight: 500;
    width: 100%;
  }
  .tasks-list li {
    margin: 0.15em 0;
    display: flex;
    align-items: center;
    justify-content: flex-start;
  }
  .tasks-list li input[type="checkbox"] {
    margin-right: 12px;
    width: 18px;
    height: 18px;
    accent-color: var(--icy-blue);
    cursor: pointer;
  }
  .tasks-list li label {
    cursor: pointer;
    color: var(--icy-blue);
    font-size: 1em;
    text-shadow: 0 0 6px var(--icy-blue);
  }
  .tasks-list li.completed label {
    text-decoration: line-through;
    color: #444;
    text-shadow: none;
  }

</style>
</head>
<body>

<div class="halos-container">
  <div class="halo halo-time" aria-label="Time halo ring"></div>
  <div class="halo halo-weather" aria-label="Weather halo ring"></div>
  <div class="halo halo-tasks" aria-label="Tasks halo ring"></div>
</div>

<div class="orb-container" role="main" aria-label="Rika’s hypnotic orb interface">
  <div class="orb" aria-label="Hypnotic glowing orb">
    <div class="center-text" aria-label="Rika text in center">RIKA</div>
  </div>
</div>

<script>
  // Setup time display in violet halo ring
  const haloTime = document.createElement('div');
  haloTime.classList.add('halo-content');
  haloTime.setAttribute('aria-live', 'polite');
  haloTime.innerHTML = '<h2 id="time">--:--:--</h2><div class="small">Local Time</div>';
  document.querySelector('.halo-time').appendChild(haloTime);

  function updateTime() {
    const now = new Date();
    const h = now.getHours().toString().padStart(2,'0');
    const m = now.getMinutes().toString().padStart(2,'0');
    const s = now.getSeconds().toString().padStart(2,'0');
    document.getElementById('time').textContent = `${h}:${m}:${s}`;
  }
  updateTime();
  setInterval(updateTime, 1000);

  // Setup weather display in blood-red halo ring
  const haloWeather = document.createElement('div');
  haloWeather.classList.add('halo-content');
  haloWeather.innerHTML = `
    <div class="weather-icon" aria-label="Sunny weather">&#9728;</div>
    <h2 id="weather-temp">72&deg;F</h2>
    <div class="small" id="weather-desc">Sunny</div>
  `;
  document.querySelector('.halo-weather').appendChild(haloWeather);

  // Setup tasks display in icy-blue halo ring
  const haloTasks = document.createElement('div');
  haloTasks.classList.add('halo-content');
  haloTasks.innerHTML = `
    <h2>Tasks</h2>
    <ul class="tasks-list" role="listbox" aria-label="Task list">
      <li class="completed"><input type="checkbox" id="task1" checked disabled/><label for="task1">Curse Awakening</label></li>
      <li><input type="checkbox" id="task2" /><label for="task2">Conjure Spell</label></li>
      <li><input type="checkbox" id="task3" /><label for="task3">Enchanted Reports</label></li>
    </ul>
  `;
  document.querySelector('.halo-tasks').appendChild(haloTasks);

  // Make tasks toggable and reflect completed styling
  const taskList = document.querySelectorAll('.tasks-list input[type="checkbox"]:not([disabled])');
  taskList.forEach(checkbox=>{
    checkbox.addEventListener('change', (e)=>{
      if(e.target.checked){
        e.target.parentElement.classList.add('completed');
      } else {
        e.target.parentElement.classList.remove('completed');
      }
    });
  });

</script>
</body>
</html>

Credits

Krishavardan Nandagopal
2 projects • 0 followers

Comments