# Project export: Verify

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: Instantly verify tweets with our seamless web extension, ensuring understanding in a world riddled with misinformation. From political stories to fake sports trades, know the truth automatically
- Devpost: https://devpost.com/software/factchex
- GitHub: https://github.com/jcub-gold/TreeHacks_25_Project
- Video: https://www.youtube.com/embed/kRgA_4Bvj5o?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Perplexity: Best Search Hack ($250 Gift Card per team member + HQ visit))
- Team: 1 GitHub contributor(s) — Jonas Pao (10 commits)

## Devpost submission (written by the team)

### Inspiration

As young, first-time voters, almost all of our political awareness and understanding comes from our engagement with social media. Although information access is easier than ever, attaining unbiased information has become nearly impossible in today's highly divisive political environment. From charged language that misrepresents reality to outright lies, a solution that could provide an accurate and unbiased assessment of online content could remove this misinformation fog currently clouding social media.

### What it does

Verify is a Chrome web extension that can analyze a tweet with just a click of a button, presenting an unbiased assessment of the post's subject matter, while citing key sources and presenting an accuracy score. Although intended primarily for political awareness, the tool can be used to assess the accuracy of any tweet, from debunking false sports trades to fact-checking history fun facts. We were motivated to add this on X specifically, as the platform only has community notes as a check against misinformation, which is not available on most tweets.

### How we built it

Our team built Verify by building a Chrome-integrated UI with HTML and CSS, and a JavaScript backend that connected to Perplexity's API for accurate searching and tweet language processing. Our team segmented work between front and back-end development based on our personal interests and experience. At a high level, Connor built our front end, learning and implementing key web design principles on the fly. Meanwhile, Jacob built out our back end in JavaScript, ensuring accurate tweet processing and API calls. Jonas handled complexities related to API calls and ensured well-formatted, balanced, and customized prompt responses with Perplexity. Kevin focused on connecting all the pieces, and integrating the back and front end while supporting UI/UX development.

### Challenges we ran into

The biggest challenge we faced was developing the backend, particularly the messaging between service workers and Chrome extensions. To resolve this problem, we all got hands on deck to resolve it and pushed through several hours of debugging line-by-line, learning a ton about threading and multiprocessing.

### Accomplishments we're proud of

We're incredibly proud that we were able to come up with and implement a tool that we would personally use in our day-to-day lives. As first-time hackers, we loved seeing our work turn from just an idea into a product that we believe can make a genuine impact on misinformation. We're also very satisfied with the technical progress we achieved.

### What we learned

We all gained significant technical experience, specifically improving our ability to develop in a full-stack environment and to integrate API calls to leverage the immense impact of LLMs. We also learned how severe the misinformation gap is on social media while implementing our product. When testing our tweets on popular creators on both sides of the political spectrum and even on history fun fact pages, we realized how many social media users digest misinformation without thinking deeply about whether the information they're reading is verified.

### What's next

We are all-in on this project idea and its impact. We will expand upon Verify to integrate the product on other social media platforms like Reddit, Instagram, and YouTube. We believe the biggest opportunity will be to analyze and summarize video content on platforms like YouTube, reducing both misinformation and time spent. Ideally, we would also be able to implement Verify on iOS and Android to further expand its reach by integrating it within apps.

## README (from the GitHub repository)

# Verify
Connor Lee, Jacob Goldberg, Jonas Pao, Kevin Pan TreeHacks 2025 project

https://devpost.com/software/factchex?_gl=1*mf4hk*_gcl_au*MTMyNTM2ODM5Mi4xNzM5NjAyMTM0*_ga*MTY4ODgwOTYzNy4xNzM5NjAyMTM0*_ga_0YHJK3Y10M*MTczOTg0ODE3My41LjEuMTczOTg0ODE4MS4wLjAuMA..

## Inspiration
As young, first-time voters, almost all of our political awareness and understanding comes from our engagement with social media. Although information access is easier than ever, attaining unbiased information has become nearly impossible in today's highly divisive political environment. From charged language that misrepresents reality to outright lies, a solution that could provide an accurate and unbiased assessment of online content could remove this misinformation fog currently clouding social media.

## What it does
Verify is a Chrome web extension that can analyze a tweet with just a click of a button, presenting an unbiased assessment of the post's subject matter, while citing key sources and presenting an accuracy score. Although intended primarily for political awareness, the tool can be used to assess the accuracy of any tweet, from debunking false sports trades to fact-checking history fun facts. We were motivated to add this on X specifically, as the platform only has community notes as a check against misinformation, which is not available on most tweets.

## How we built it
Our team built Verify by building a Chrome-integrated UI with HTML and CSS, and a JavaScript backend that connected to Perplexity's API for accurate searching and tweet language processing. Our team segmented work between front and back-end development based on our personal interests and experience. At a high level, Connor built our front end, learning and implementing key web design principles on the fly. Meanwhile, Jacob built out our back end in JavaScript, ensuring accurate tweet processing and API calls. Jonas handled complexities related to API calls and ensured well-formatted, balanced, and customized prompt responses with Perplexity. Kevin focused on connecting all the pieces, and integrating the back and front end while supporting UI/UX development.

## Challenges we ran into
The biggest challenge we faced was developing the backend, particularly the messaging between service workers and Chrome extensions. To resolve this problem, we all got hands on deck to resolve it and pushed through several hours of debugging line-by-line, learning a ton about threading and multiprocessing.

## Accomplishments that we're proud of
We're incredibly proud that we were able to come up with and implement a tool that we would personally use in our day-to-day lives. As first-time hackers, we loved seeing our work turn from just an idea into a product that we believe can make a genuine impact on misinformation. We're also very satisfied with the technical progress we achieved.

## What we learned
We all gained significant technical experience, specifically improving our ability to develop in a full-stack environment and to integrate API calls to leverage the immense impact of LLMs. We also learned how severe the misinformation gap is on social media while implementing our product. When testing our tweets on popular creators on both sides of the political spectrum and even on history fun fact pages, we realized how many social media users digest misinformation without thinking deeply about whether the information they're reading is verified.

## What's next for Verify
We are all-in on this project idea and its impact. We will expand upon Verify to integrate the product on other social media platforms like Reddit, Instagram, and YouTube. We believe the biggest opportunity will be to analyze and summarize video content on platforms like YouTube, reducing both misinformation and time spent. Ideally, we would also be able to implement Verify on iOS and Android to further expand its reach by integrating it within apps.


## Detected evidence (automated analysis)

Indexed codebase: 12 recognized source files, 38 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (18 of 18)

```
.DS_Store
.gitignore
icons/Readme.txt
LICENSE
manifest.json
README.md
src/.DS_Store
src/background/background.js
src/background/index.js
src/popup/initial.html
src/popup/initial.js
src/popup/loading_styles.css
src/popup/loading.html
src/popup/loading.js
src/popup/main_styles.css
src/popup/popup.html
src/popup/popup.js
src/scripts/x.js
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Update README.md
- Update README.md
- Lil jit and jdizz W
- new loading page
- final image fix
- Merge pull request #4 from jcub-gold/JonasUI
- Merge remote-tracking branch 'origin' into JonasUI
- Merge pull request #3 from jcub-gold/JonasUI
- One must imagine sisyphus happy
- fixed all frontend
- fuck you jdizzle
- Merge pull request #2 from jcub-gold/jacob/dev
- update git ignore and working button
- working tweet extraction button
- working foundation for extracting first tweet
- Merge pull request #1 from jcub-gold/Jonas-branch
- Added all piplines of info, created background.js, made readme and updated manifest.json, created basic UIUX
- Read tweets from page
- Chatgpt code -- trying to just have a popup show up when you visit the X site (first step)
- Adding directory structure

## Key source files (fetched from GitHub, selected and truncated for size)

### src/popup/initial.js

```javascript
document.addEventListener("DOMContentLoaded", function () {
    document.getElementById("verifyIcon").src = chrome.runtime.getURL("icons/verifyicon.png");
});


document.getElementById('startButton').addEventListener('click', async () => {
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    
    // First, show loading page
    window.location.href = 'loading.html';
    
    // Send message to content script to start extraction
    chrome.tabs.sendMessage(tab.id, { action: "extractTweets" });
}); 
```

### src/popup/loading.js

```javascript
// Listen for analysis results
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "analysisComplete" && message.results) {
        // Calculate reliability percentage
        const reliabilityPercentage = (100 - (message.results.probability_fake * 100)).toFixed(0);
        
        // Store both the results, reliability, and summary
        chrome.storage.local.set({
            'factCheckResults': message.results,
            'reliabilityPercentage': reliabilityPercentage,
            'summary': message.results.summary
        }, () => {
            // After storing results, redirect to popup.html
            window.location.href = 'popup.html';
        });
    }
}); 
```

### src/popup/popup.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Fact-Check Main</title>
  <link rel="stylesheet" href="main_styles.css">
</head>
<body>
  <div class="main-container">
    <div class="main-card">

      
      <!-- Centered Logo + Status + Percentage -->
      <div class="main-header">
        <div class="gauge-container">
          <div class="gauge-background"></div>
          <div class="gauge-fill"></div>
          <div class="gauge-content">
            <span class="truth-status">Analyzing...</span>
            <span class="truth-percent"></span>
          </div>
        </div>
      </div>

      <!-- Sources will be populated from background.js -->
      <div class="main-sources">
        <!-- Dynamic source links will be inserted here -->
      </div>

      <div class="summary-section">
        <!-- Summary will be inserted here -->
      </div>

    </div>
  </div>
  <script src="popup.js"></script>
</body>
</html>

```

### src/popup/loading.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Fact-Check Loading</title>
  <link rel="stylesheet" href="loading_styles.css">
</head>
<body>
  <div class="loading-container">
    <div class="loading-card">

      <!-- Centered Hollow Circular Gauge Placeholder -->
      <div class="loading-header">
        <div class="gauge-container">
          <div class="gauge-ring"></div>
          <div class="gauge-fill"></div>
          <div class="gauge-content">
            <div class="loading-placeholder status-placeholder"></div>
            <div class="loading-placeholder percent-placeholder"></div>
          </div>
        </div>
      </div>

      <!-- Placeholder for Sources -->
      <div class="loading-sources">
        <div class="loading-placeholder source-placeholder"></div>
        <div class="loading-placeholder source-placeholder"></div>
        <div class="loading-placeholder source-placeholder"></div>
      </div>

      <!-- Placeholder for Summary (Increased Vertical Height) -->
      <div class="summary-section">
        <div class="loading-placeholder summary-placeholder"></div>
      </div>

    </div>
  </div>
  <script src="loading.js"></script>
</body>
</html>

```

### src/scripts/x.js

```javascript
console.log("✅ Content script loaded!");

// Function to extract tweets from the Twitter page
function extractTweets() {
    let tweets = []; // Array to store extracted tweet texts
    let tweetElements = document.querySelectorAll("article");

    tweetElements.forEach(tweet => {
        let tweetText = tweet.querySelector("div[lang]")?.innerText; // Extract tweet text
        if (tweetText) {
            tweets.push({ text: tweetText, element: tweet });
        }
    });

    if (tweets.length > 0) {
        console.log("🐦 Extracted Tweets:", tweets.map(t => t.text)); // Debugging log

        // Send tweets to background.js
        console.log("📨 Sending tweets to background.js...");
        chrome.runtime.sendMessage({ 
            action: "tweetsExtracted", 
            tweets: tweets.map(t => t.text),
            firstIndex: tweets[0].text // Send the first tweet text
        }, response => {
            if (chrome.runtime.lastError) {
                console.error("⚠️ Message send failed:", chrome.runtime.lastError);
            } else {
                console.log("📨 Sent tweets to background.js, awaiting API response...");
                console.log("📩 Response from background.js:", response);
            }
        });
    } else {
        console.log("⚠️ No tweets found. Is the page loaded?");
    }
}

// Listen for messages from the background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "extractTweets") {
        console.log("🔍 Extracting tweets...");
        extractTweets();
    }
});
```

### src/popup/initial.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=500, initial-scale=1.0">
  <title>Verify Popup</title>
  <style>
    /* RESET & BASE STYLES */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      font-family: "Poppins", sans-serif;
    }
    html, body {
      width: 500px;
      height: 250px; /* Reduced overall height */
      background: #121212;
      color: #fff;
    }
    /* CARD: Fills the entire popup with compact spacing */
    .card {
      width: 100%;
      height: 100%;
      background: #1e1e1e;
      border: 0.5px solid #333;
      box-shadow: 0 4px 10px rgba(0,0,0,0.3);
      display: flex;
      flex-direction: column;
      gap: 2px;  /* Reduced gap between sections */
      padding: 0 10px 10px 10px; /* No top padding */
      position: relative;
    }
    /* HEADER: Extend fully to the top & sides with equal spacing */
    header.top-header {
      width: calc(100% + 20px);
      margin: 0 -10px 2px -10px; /* Top margin is now 0 so header touches the top */
      background: #2a2a2a;
      padding: 10px 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
    }
    header.top-header img {
      width: 50px;
      height: 50px;
      object-fit: contain;
    }
    header.top-header h1 {
      font-size: 1.3rem;
      font-weight: bold;
      color: #fff;
    }
    /* CONTENT: Description and Button with tight spacing */
    .content {
      flex: 1;
      padding: 2px 10px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      gap: 10px;  /* Increased gap between description and button */
      text-align: center;
    }
    .description {
      font-size: 1rem;
      line-height: 1.4;
      color: #ccc;
      max-width: 480px;
      margin-top: 2px;
    }
    /* GREENISH ANIMATED BUTTON (90% width) */
    .action-btn {
      width: 90%;
      padding: 10px;
      border: none;
      border-radius: 5px;
      background: linear-gradient(135deg, #2ecc71, #27ae60);
      color: #fff;
      font-size: 1rem;
      font-weight: bold;
      cursor: pointer;
      text-align: center;
      transition: background 0.3s ease, transform 0.3s ease;
      box-shadow: 0 4px 10px rgba(46,204,113,0.4);
    }
    .action-btn:hover {
      background: linear-gradient(135deg, #27ae60, #2ecc71);
      transform: translateY(-2px);
      box-shadow: 0 6px 12px rgba(46,204,113,0.6);
    }
    /* FINE PRINT: Small, left-aligned */
    .fine-print {
      font-size: 0.65rem;
      color: #aaa;
      text-align: left;
      padding: 5px 10px 10px;
      margin-top: 2px;
    }
  </style>
</head>
<body>
    <div class="card">
      <header class="top-header">
        <img id="verifyIcon" alt="Verify Icon" />
        <h1>Verify</h1>
      </header>
      <div class="content">
        <p class="description">
          Checking the validity of online information in real time.
        </p>
        <button class="action-btn" id="startButton">Start Fact Check</button>
        <script src="initial.js"></script>
      </div>
      <p class="fine-print">
        Disclaimer: This extension offers fact-checking insights, but users should verify information independently.
      </p>
    </div>
</body>
</html>
```

### src/popup/loading_styles.css

```css
/*=============== RESET & BASE STYLES ===============*/
* {
  margin: 0; 
  padding: 0; 
  box-sizing: border-box;
  font-family: Arial, sans-serif;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background: #121212;
  color: #ffffff;
}

.loading-container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
}

/*=============== LOADING CARD (Rectangular Outer Box) ===============*/
.loading-card {
  width: 500px;
  padding: 20px;
  background: #1e1e1e;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
  gap: 15px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.3);
}

/*=============== HEADER (Centered) ===============*/
.loading-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}

/*=============== GAUGE CONTAINER ===============*/
.gauge-container {
  width: 120px;
  height: 120px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Single gauge ring with shimmering effect */
.gauge-ring {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  border: 10px solid #2a2a2a;
  background: transparent;
  position: absolute;
  overflow: hidden;
}

/* Shimmer effect applied to the gauge ring's border only */
.gauge-ring::before {
  content: "";
  position: absolute;
  top: 0;
  left: -150%;
  width: 300%;
  height: 100%;
  background-image: linear-gradient(
    to right,
    transparent 0%,
    rgba(255, 255, 255, 0.15) 30%,
    transparent 60%
  );
  animation: shimmer-side 1.8s linear infinite;
  /* Mask out the center so only the border (approx. inner 50px radius) shimmers */
  mask: radial-gradient(circle at center, transparent 50px, black 51px);
  -webkit-mask: radial-gradient(circle at center, transparent 50px, black 51px);
}

/* Shimmer animation keyframes */
@keyframes shimmer-side {
  0% { left: -150%; }
  100% { left: 150%; }
}

/* Expanded gauge fill block (inner circle) */
.gauge-fill {
  width: 100px;
  height: 100px;
  background: #1e1e1e;
  border-radius: 50%;
  position: absolute;
}

/*=============== STATUS AND PERCENTAGE PLACEHOLDER ===============*/
.gauge-content {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}

.status-placeholder {
  width: 80px;
  height: 16px;
  background: #2a2a2a;
  border-radius: 5px;
  position: relative;
  overflow: hidden;
}

.percent-placeholder {
  width: 50px;
  height: 14px;
  background: #2a2a2a;
  border-radius: 5px;
  position: relative;
  overflow: hidden;
}

/*=============== LOADING SOURCES PLACEHOLDER ===============*/
.loading-sources {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.source-placeholder {
  width: 100%;
  height: 26px;
  background: #2a2a2a;
  border-radius: 5px;
  position: relative;
  overflow: hidden;
}

/*=============== INCREASED DESCRIPTION HEIGHT PLACEHOLDER ===============*/
.summary-section {
  display: flex;
  justify-content: center;
  margin-top: 10px;
}

.summary-placeholder {
  width: 90%;
  height: 130px; /* Extended vertical height */
  background: #2a2a2a;
  border-radius: 5px;
  position: relative;
  overflow: hidden;
}

/*=============== SHIMMER ANIMATION FOR TEXT & BOXES ===============*/
.loading-placeholder::before,
.source-placeholder::before,
.summary-placeholder::before {
  content: "";
  position: absolute;
  top: 0; 
  left: -150%;
  width: 300%;
  height: 100%;
  background-image: linear-gradient(
    to right,
    #2a2a2a 0%,
    rgba(255,255,255,0.15) 30%,
    #2a2a2a 60%,
    #2a2a2a 100%
  );
  animation: shimmer 1.8s linear infinite;
}

/* Shimmer keyframes */
@keyframes shimmer {
  0% { left: -150%; }
  100% { left: 150%; }
}

```

### src/popup/main_styles.css

```css
/*=============== RESET & BASE STYLES ===============*/
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: Arial, sans-serif;
}

/* Dark background + center layout */
body {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background: #121212;
    color: #ffffff;
}

.main-container {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
}

/*=============== MAIN CARD (Rectangular outer box) ===============*/
.main-card {
    width: 600px; /* Increase the width */
    padding: 20px;
    background: #1e1e1e;
    border: 1px solid #333;
    /* Outer box is purely rectangular (no border-radius) */
    display: flex;
    flex-direction: column;
    gap: 15px;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}

/*=============== HEADER (Centered) ===============*/
.main-header {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
}

/* Larger logo container (70x70) */
.main-logo {
    width: 70px;
    height: 70px;
    background: #2a2a2a;
    border-radius: 50%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}

.status-icon {
    width: 100px; 
    height: 100px;
    object-fit: contain;
}

/* "Likely True" + "85%" text, centered below the logo */
.truth-status {
    font-size: 1.3rem;
    font-weight: bold;
    color: hsl(92, 90%, 68%); /* green color */
}

.truth-percent {
    font-size: 1.1rem;
    font-weight: bold;
    color: #ffffff;
    text-align: center;
}

/*=============== BAR SECTION ===============*/
/* A container 80% wide, centered horizontally, 
   with a static track behind an animated bar */
.bar-container {
    width: 80%;
    height: 10px;
    background: #2a2a2a;
    border-radius: 5px;
    overflow: hidden;
    position: relative;
    margin: 15px auto;
}

/* Static track is 100% of .bar-container, does not move */
.static-track {
    position: absolute;
    width: 100%;
    height: 100%;
    background: #2a2a2a;
}

/* The animated bar starts at width: 0%, expands to 85% from left→right in 3s */
.main-bar {
    position: relative;
    width: 0%;
    height: 100%;
    background: hsl(92, 90%, 68%);
    transition: width 1s ease-in-out, background 1s ease-in-out;
}

/* Single smooth transition from red(0%) → green(85%) in 3s */
@keyframes smoothRedToGreen {
    0% {
        width: 0%;
        background: hsl(7, 89%, 46%);
    }
    100% {
        width: 85%;
        background: hsl(92, 90%, 68%);
    }
}

/* Loading Animation for the bar */
@keyframes loadingPulse {
    0% {
        opacity: 0.6;
    }
    50% {
        opacity: 1;
    }
    100% {
        opacity: 0.6;
    }
}

.loading .main-bar {
    width: 100%;
    background: #444;
    animation: loadingPulse 1.5s infinite;
}

/*=============== 3 SOURCE LINKS ===============*/
.main-sources {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.source-link {
    font-size: 0.8rem;
    text-decoration: none;
    color: #0066cc;
    background: #2a2a2a;
    padding: 5px;
    border-radius: 5px;
    text-align: left; /* Align text to the left */
    height: 26px;
}

/*=============== FOOTER ===============*/
.main-footer {
    display: flex;
    justify-content: center;
    margin-top: 10px;
}

/* Expand More ~100x30 with slight corners */
.expand-btn {
    width: 100px;
    height: 30px;
    background: #2a2a2a;
    color: #fff;
    border: none;
    border-radius: 5px; 
    font-size: 0.9rem;
    cursor: pointer;
    transition: background 0.3s ease;
}
.expand-btn:hover {
    background: #444;
}

/*=============== ADDITIONAL INFORMATION ===============*/
.additional-info {
    margin-top: 10px;
    font-size: 0.9rem;
    color: #cccccc;
    text-align: center;
}

/* Truth Status Colors */
.truth-status.likely-true {
    color: hsl(92, 90%, 68%);
}

.truth-status.uncertain {
    color: hsl(45, 90%, 68%);
}

.truth-status.likely-false {
    color: hsl(7, 89%, 46%);
}

/*=============== GAUGE SECTION ===============*/
.gauge-container {
    position: relative;
    width: 150px;
    height: 150px;
    margin: 20px auto;
}

.gauge-background {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 8px solid #2a2a2a;
    border-radius: 50%;
    z-index: 1;
}

/* Register custom property for the angle (requires modern browser support) */
@property --angle {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0deg;
}

/* The gauge fill now animates from 0deg to a final angle (set via --final-angle) */
/* For example, for 85% fill: --final-angle: 306deg; for 15% fill: --final-angle: calc(15 / 100 * 360deg) (~54deg) */
.gauge-fill {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    mask: radial-gradient(transparent 59%, white 60%);
    -webkit-mask: radial-gradient(transparent 59%, white 60%);
    z-index: 2;
    --final-angle: 0deg;  /* default starting value */
    --gauge-color: hsl(92, 90%, 68%);  /* default color */
    animation: fillGauge 3s ease-in-out forwards;
    background: conic-gradient(var(--gauge-color) var(--angle), transparent var(--angle));
}

@keyframes fillGauge {
    to {
        --angle: var(--final-angle);
    }
}

.gauge-content {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    width: 80%;
    z-index: 3;
}

.truth-status {
    display: block;
    font-size: 1.2rem;
    margin-bottom: 5px;
}

.truth-percent {
    display: block;
    font-size: 1.4rem;
    font-weight: bold;
}

/*=============== SUMMARY SECTION ===============*/
.summary-section {
    padding: 15px;
    margin: 15px;
    background: #2a2a2a;
    border-radius: 5px;
    color: #ffffff;
    font-size: 0.9rem;
    line-height: 1.4;
    max-height: 150px;
    overflow-y: auto;
}

```

### src/background/background.js

```javascript
console.log("📡 Background script loaded!");
const API_KEY = '';

// Listen for messages from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "tweetsExtracted") {
        console.log(`📨 Received ${message.tweets.length} tweets`);

        // Validate that tweets are present
        if (!Array.isArray(message.tweets) || message.tweets.length === 0) {
            console.warn("⚠️ No valid tweets received.");
            sendResponse({ status: "error", message: "No valid tweets received." });
            return;
        }

        // Log the first tweet
        console.log("🐦 First tweet:", message.tweets[0]);

        // Simulate sending tweets to the API for analysis
        analyzeTweets(message.tweets)
            .then(results => {
                console.log("✅ API Response:", results);
                // Send results to popup
                chrome.runtime.sendMessage({
                    action: "analysisComplete",
                    results: results
                });
                sendResponse({ status: "success", results: results });
            })
            .catch(error => {
                console.error("❌ API Error:", error);
                sendResponse({ status: "error", message: error.message });
            });

        return true; // Keeps the messaging channel open for async responses
    }

    if (message.action === "checkConnection") {
        console.log("🔄 Connection check received.");
        sendResponse({ status: "ok" });
    }

    return true; // Keeps the messaging channel open for async responses
});

// Function to call the fact-checking API with a retry mechanism
async function analyzeTweets(tweets) {
    try {
        const options = {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${API_KEY}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                model: "sonar",
                messages: [
                    {
                        role: "system",
                        content: "Analyze the following tweet for misinformation. Respond with a JSON object containing exactly three fields: 1) probability_fake as a number between 0.00 and 1.00 with 2 decimal places (where 1.00 means 100% likely misinformation and 0.00 means 0% likely misinformation) make sure you have 2 siginficant digits and try to not always give the second digit as 0, and look at it with a critical eye, being as true to the sources you can find on it 2) sources as an array of strings containing full URLs to reputable sources related to the topic make sure the URL is fully formed and accurate, and 3) summary as a string containing a brief summary of the tweet content and the reason behind the misinformation score. Do not include any markdown formatting, code blocks, or the word 'json' - just return the raw JSON object."},
                    {
                        role: "user",
                        content: tweets[0]
                    }
                ],
                max_tokens: 500,
                temperature: 0.0,
                top_p: 0.9,
                stream: false,
                presence_penalty: 0,
                frequency_penalty: 1,
                search_domain_filter: ["-x.com", "-twitter.com", "-facebook.com", "-instagram.com", "-reddit.com"]
            })
        };

        const response = await fetch('https://api.perplexity.ai/chat/completions', options);

        if (!response.ok) {
            const errorText = await response.text();
            throw new Error(`API call failed with status ${response.status}: ${errorText}`);
        }

        const data = await response.json();
        console.log("Raw API response:", data); // Log the raw response

        // Parse the content from the response
        let content;
        try {
            // Remove any markdown code block syntax that might be present
            const rawContent = data.choices[0].message.content.trim()
                .replace(/```json\n?/g, '')  // Remove opening code block
                .replace(/```\n?/g, '')      // Remove closing code block
                .trim();
            console.log("Response content:", rawContent); // Log the content before parsing
            content = JSON.parse(rawContent);
        } catch (e) {
            console.error("Failed to parse API response:", e);
            console.log("Unparseable content:", data.choices[0].message.content); // Log the unparseable content
            content = {
                probability_fake: 0.50, // Default to uncertain (50%) with 2 decimal places
                sources: [],
                summary: ""
            };
        }

        // Extract relevant information and validate URLs
        const analysis = {
            probability_fake: typeof content.probability_fake === 'number' ? 
                Number(Math.max(0, Math.min(1, content.probability_fake)).toFixed(2)) : 0.50, // Clamp between 0 and 1 with 2 decimal places
            sources: Array.isArray(content.sources) ? content.sources.filter(url => {
                try {
                    new URL(url);
                    return true;
                } catch {
                    return false;
                }
            }) : [],
            summary: content.summary || ""
        };
        
        console.log("Final analysis:", analysis); // Log the final analysis
        return analysis;

    } catch (error) {
        console.error("Error in analyzeTweets:", error);
        return { 
            error: error.message,
            probability_fake: 0.50, // Default to uncertain (50%) with 2 decimal places
            sources: [],
            summary: ""
        };
    }
}

// Listen for extension button click
chrome.action.onClicked.addListener((tab) => {
    console.log("🔘 Extension button clicked");
    chrome.tabs.sendMessage(tab.id, { action: "extractTweet
[truncated — 10 more characters]
```

### src/popup/popup.js

```javascript
// Wait for DOM to be fully loaded before adding event listeners
document.addEventListener('DOMContentLoaded', () => {
    // Add analyze button click handler
    const analyzeButton = document.getElementById('analyzeButton');
    if (analyzeButton) {
        analyzeButton.addEventListener('click', async () => {
            // Get the current active tab
            const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
            
            // Send message to content script to extract tweets
            chrome.tabs.sendMessage(tab.id, { action: "extractTweets" });
        });
    }
});

// When popup loads, check for stored results
document.addEventListener('DOMContentLoaded', () => {
    // Get the current active tab
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        const currentTab = tabs[0];
        // Request the latest results from background script
        chrome.runtime.sendMessage({ 
            action: "tweetsExtracted",
            tabId: currentTab.id 
        });
    });
});

// Listen for messages from the background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log("Received message in popup:", message); // Debug log
    
    if (message.action === 'showLoading') {
        console.log("📩 Received showLoading message in popup");
        showLoading();
    } else if (message.action === 'displayResults') {
        console.log("📩 Results received in popup:", message.results);
        displayResults(message.results);
    } else if (message.action === 'displayError') {
        console.log("📩 Error received in popup:", message.message);
        displayError(message.message);
    } else if (message.action === "analysisComplete" && message.results) {
        console.log("Sources received:", message.results.sources);
        
        // Get the sources container
        const sourcesContainer = document.querySelector('.main-sources');
        sourcesContainer.innerHTML = ''; // Clear existing content
        
        // Display each source as a formatted link
        message.results.sources.forEach((source, index) => {
            const link = document.createElement('a');
            link.href = source;
            link.className = 'source-link';
            
            // Try to extract and display just the domain name
            try {
                const url = new URL(source);
                const domain = url.hostname.replace('www.', '');
                link.textContent = `🔗 Source ${index + 1}: ${domain}`;
            } catch (e) {
                // If URL parsing fails, show the full source
                link.textContent = `🔗 Source ${index + 1}: ${source}`;
            }
            
            link.style.cssText = `
                display: block;
                color: #ffffff;
                text-decoration: underline;
                padding: 8px;
                margin: 5px 0;
                background: #2a2a2a;
                border-radius: 5px;
            `;
            
            sourcesContainer.appendChild(link);
        });
    }
});

// Function to show loading screen in the popup
function showLoading() {
    document.getElementById('verifyContainer').classList.add('hidden');
    const resultsContainer = document.getElementById('resultsContainer');
    resultsContainer.classList.remove('hidden');
    resultsContainer.innerHTML = `
        <p>Loading...</p>
    `;
}

// Function to display results in the popup
function displayResults(results) {
    document.getElementById('loading').style.display = 'none';
    document.getElementById('results').style.display = 'block';
    
    // Calculate truth percentage (inverse of fake probability)
    const truthPercentage = (100 - (results.probability_fake * 100)).toFixed(0);
    
    // Update status and percentage
    const truthStatus = document.getElementById('truthStatus');
    const truthPercent = document.getElementById('truthPercent');
    
    if (truthPercentage >= 70) {
        truthStatus.textContent = 'Likely True';
        truthStatus.style.color = 'hsl(92, 90%, 68%)';
    } else if (truthPercentage >= 30) {
        truthStatus.textContent = 'Uncertain';
        truthStatus.style.color = 'hsl(45, 90%, 68%)';
    } else {
        truthStatus.textContent = 'Likely False';
        truthStatus.style.color = 'hsl(7, 89%, 46%)';
    }
    
    truthPercent.textContent = `${truthPercentage}%`;

    // Update the main bar width and color
    const mainBar = document.querySelector('.main-bar');
    mainBar.style.width = `${truthPercentage}%`;
    mainBar.style.background = truthPercentage >= 70 ? 'hsl(92, 90%, 68%)' : 
                              truthPercentage >= 30 ? 'hsl(45, 90%, 68%)' : 
                              'hsl(7, 89%, 46%)';

    // Update sources
    const mainSources = document.querySelector('.main-sources');
    mainSources.innerHTML = ''; // Clear existing sources
    
    results.sources.forEach((source, index) => {
        const link = document.createElement('a');
        link.href = source;
        link.className = 'source-link';
        link.textContent = `🔗 Source ${index + 1}: ${source}`;
        link.target = "_blank";
        mainSources.appendChild(link);
    });

    // Add click handler for expand button
    document.querySelector('.expand-btn').addEventListener('click', () => {
        window.location.href = 'expand.html';
    });
}

// Function to display error in the popup
function displayError(message) {
    const resultsContainer = document.getElementById('resultsContainer');
    resultsContainer.innerHTML = `
        <p>Error: ${message}</p>
    `;
}

function setAnalyzingState() {
    document.getElementById('truthStatus').textContent = 'Analyzing...';
    document.getElementById('truthPercent').textContent = '';
    document.getElementById('sourceLinks').innerHTML = '';
    
    // Reset the progress bar
    const mainBar = document.getElementById('mainBar'
[truncated — 4331 more characters]
```