# Project export: PoetRate

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: Cal Hacks 11.0
- Tagline: Get inspired to write poems! Write a poem based off of a random image, challenge Gemini to a poetry contest, and gain insights on how to improve your poetry skills!
- Devpost: https://devpost.com/software/poetrate
- GitHub: https://github.com/Hu-Maxwell/PoetryAI
- Video: https://www.youtube.com/embed/Agoyj-bavzM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — dewe biag (68 commits), callistabudiman16 (66 commits), anjanetttee (15 commits)

## Devpost submission (written by the team)

### Inspiration

Poetry can be challenging. Traditionally, it's taught through topics such as love or mortality, and ultimately, might have conditioned us students to think that poetry is boring! However, that couldn't be further from the truth. Thus, we decided to create a website that not only inspires users to write poetry, but also help them improve and hone their skills. Our goal is to spread the joy of writing poetry by making it more accessible and enjoyable for everyone!

### What it does

This tool takes a random image from our own curated list of poetry-inspiring catalogue of images, and challenges you to write a poem based off of it. The image is also sent to the Gemini API, which generates its own poem. These poems are both evaluated by Gemini, which outputs a score, and a quick word of advice on how to improve your poem by learning from the AI's example.

### How we built it

We integrated the Gemini API to rate and review the poems, limiting its' prompt to a simple JSON file + a bit of advice. The backend was developed using using Node.js. The frontend was designed simply with HTML and CSS.

### Challenges we ran into

This was our first time using Node.js, and we had to learn how to manage communication between the frontend and backend. It was also some of our first times coordinating progress among team members to avoid git conflicts and keep our workflow focused.

### Accomplishments we're proud of

We're proud of the aesthetically pleasing UI how quickly we learned how to use Node.js. Initially, we had modest expectations, but this hackathon pushed us to our limits and forced us to widen our programming skills.

### What we learned

We gained experience in server management, UI development, version control with Git, and effective team communication. Additionally, we improved our understanding of the web design process, full-stack development, Node.js, and CSS animations.

### What's next

Future plans include adding difficulty levels, more detailed feedback for each poetry category, and analysis across multiple inputs. We aim to introduce different poem formats (quartets, haikus, sonnets), a timer feature, and both speech-to-text and text-to-speech capabilities.

## README (from the GitHub repository)

[![Demo Video](https://img.youtube.com/vi/Agoyj-bavzM/hqdefault.jpg)](https://youtu.be/Agoyj-bavzM)


## Detected evidence (automated analysis)

Indexed codebase: 11 recognized source files, 28 KB.
- CSS (language) — detected in the code
- Google Gemini (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (14 of 14)

```
.gitignore
.vscode/launch.json
backend/package.json
backend/script.js
backend/server.js
frontend/app.js
frontend/help.css
frontend/help.html
frontend/index.html
frontend/start.css
frontend/start.html
frontend/start.js
frontend/style.css
README.md
```

### Dependencies

- backend/package.json: @google/generative-ai@^0.21.0, dotenv@^16.4.5, fs@^0.0.1-security, path@^0.12.7

### Recent commits (newest first)

- Update README.md
- border
- border
- final
- animation fixed
- padding
- padding
- Merge branch 'main' of https://github.com/Hu-Maxwell/PoetryAI
- Updated words
- scoring
- center
- box title
- Working spinner
- Merge branch 'main' of https://github.com/Hu-Maxwell/PoetryAI
- Removed sunflower pic
- spin
- spin
- spinner
- Merge branch 'main' of https://github.com/Hu-Maxwell/PoetryAI
- spin

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

### backend/package.json

```
{
  "dependencies": {
    "@google/generative-ai": "^0.21.0",
    "dotenv": "^16.4.5",
    "fs": "^0.0.1-security",
    "path": "^0.12.7"
  },
  "scripts": {
    "build": "npm run compile",
    "start": "node server.js"
  }
}
```

### frontend/app.js

```javascript

function goToNextPage(){
    window.location.href = 'start.html';
}
const observer = new IntersectionObserver((entries)=>{
    entries.forEach((entry)=>{
        if(entry.isIntersecting){
            entry.target.classList.add('show');
        }else{
            entry.target.classList.remove('show');
        }
    });
});

const hiddenElements = document.querySelectorAll('.hidden');
hiddenElements.forEach((el)=> observer.observe(el));


const faqHeader = document.querySelectorAll(".faq-header");
faqHeader.forEach(faqHeader=>{
    faqHeader.addEventListener("click", event =>{
        faqHeader.classList.toggle("active");
        const faqBody = faqHeader.nextElementSibling;
        if(faqHeader.classList.contains("active")){
            faqBody.style.maxHeight = faqBody.scrollHeight + "px";
        }else{
            faqBody.style.maxHeight = 0;
        }
    });
    
});


```

### backend/server.js

```javascript
const fs = require('fs');
const path = require('path'); 

const { compareUserAIPoem, formatPoemComparison, selectRandomImage } = require('./script');

function serveStaticFile(res, filePath, contentType) {
    fs.readFile(filePath, (err, data) => {
        if (err) {
            res.statusCode = 500;
            res.end("An error occurred.");
        } else {
            res.statusCode = 200;
            res.setHeader('Content-Type', contentType);
            res.end(data);
        }
    });
}

function createServer() {
    const http = require('http');

    const hostname = '127.0.0.1';
    const port = 3000;

    const server = http.createServer(async (req, res) => {
        if (req.url === '/' && req.method === 'GET') {
            // serve index.html
            const indexHtml = path.join(__dirname, '../frontend/index.html');
            serveStaticFile(res, indexHtml, 'text/html');

        } else if (req.url === '/style.css' && req.method === 'GET') {
            // serve style.css
            const indexStyle = path.join(__dirname, '../frontend/style.css');
            serveStaticFile(res, indexStyle, 'text/css');

        } else if (req.url === '/start.html' && req.method === 'GET') {
            // serve start.html 
            const startHtml = path.join(__dirname, '../frontend/start.html');
            serveStaticFile(res, startHtml, 'text/html');

        } else if (req.url === '/start.css' && req.method === 'GET') {
            // serve start.css
            const startStyle = path.join(__dirname, '../frontend/start.css');
            serveStaticFile(res, startStyle, 'text/css');
            
        } else if (req.url === '/sunflower.jpg' && req.method === 'GET') {
            // serve the sunflower image
            const sunflowerImage = path.join(__dirname, '../frontend/sunflower.jpg');
            serveStaticFile(res, sunflowerImage, 'image/jpg');
            
        } else if (req.url === '/start.js' && req.method === 'GET') {
            // serve start.js file
            const startJsPath = path.join(__dirname, '../frontend/start.js');
            serveStaticFile(res, startJsPath, 'application/javascript');

        } else if (req.url === '/help.html' && req.method === 'GET') {
            // serve help.html 
            const helpHtml = path.join(__dirname, '../frontend/help.html');
            serveStaticFile(res, helpHtml, 'text/html');

        } else if (req.url === '/get-comparison' && req.method === 'POST') {
            // returns data comparison data
            let body = '';

            req.on('data', chunk => {
                body += chunk.toString(); 
            });

            req.on('end', async () => {
                console.log("Received body:", body); 

                try {
                    const parsedBody = JSON.parse(body);
                    const userPoem = parsedBody.poem;
                    const imagePath = parsedBody.imagePath;

                    console.log("Received image path:", imagePath);

                    const comparisonResults = await compareUserAIPoem(userPoem, imagePath);

                    // the server gets the json object and AI poem seperately
                    res.statusCode = 200;
                    res.setHeader('Content-Type', 'application/json');
                    res.end(JSON.stringify({
                        formattedData: comparisonResults.comparisonResults,
                        AIPoem: comparisonResults.AIPoem
                    }));
        
                } catch (error) {
                    console.error("An error occurred while processing the comparison:", error);
                    res.statusCode = 500;
                    res.end("An error occurred while processing the comparison.");
                }
            });
        } else if (req.url === '/get-random-image' && req.method === 'GET') {
            // serve randomly generated image
            const { imagePath, clientImagePath } = selectRandomImage();
            res.statusCode = 200;
            res.setHeader('Content-Type', 'application/json');
            res.end(JSON.stringify({ imagePath: clientImagePath }));
        
        } else if (req.url.startsWith('/assets/') && req.method === 'GET') {
            // serve assets folder
            const filePath = path.join(__dirname, '../frontend', req.url);
            const extname = String(path.extname(filePath)).toLowerCase();
            const mimeTypes = {
                '.png': 'image/png',
                '.jpg': 'image/jpg',
            };
            const contentType = mimeTypes[extname] || 'application/octet-stream';
            serveStaticFile(res, filePath, contentType);
        
        } else if (req.url === '/app.js' && req.method === 'GET') {
            // serve app.js file
            const startJsPath = path.join(__dirname, '../frontend/app.js');
            serveStaticFile(res, startJsPath, 'application/javascript');
        
        } else if (req.url === '/help.css' && req.method === 'GET') {
            // serve start.css
            const startStyle = path.join(__dirname, '../frontend/help.css');
            serveStaticFile(res, startStyle, 'text/css');
        
        } else {
            res.statusCode = 404;
            res.end("Page Not Found");
        }
    });

    server.listen(port, hostname, () => {
        console.log(`Server running at http://${hostname}:${port}/`);
    });
}

createServer(); 
```

### frontend/index.html

```html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>PoetRate</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="style.css">
        
    </head>
    <body>
        <section class = "hidden">
            <div class = "animated-txt">
                POEMS CAN BE <span></span>
            </div>
        </section>
        <section class = "hidden">
            <div class ="main">
                <p>PoetRate</p>
                <button class ="button" type ="button" onclick=goToNextPage()>START</button>
            </div>
        </section>
    
        <script defer src ="app.js"></script>
    </body>
</html>
```

### frontend/help.css

```css
@import url('https://fonts.googleapis.com/css2?family=Baskervville:ital@0;1&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
*{
    margin:0;
    padding:0;
    font-family: 'Montserrat';
    box-sizing: border-box;
}
.helpHeader{
    text-align: center;
    padding:40px;
    color:#FDEFE4;
    font-size: 50px;
}
body{
    background-color: #647e7d;
    color: black;
    font-size: 20px;
}
.faq{
    width:90%;
    max-width: 1000px;
    margin: 2rem auto;
}

.faq-item{
    background-color:  #FDEFE4;
    color: #111;
    margin: 1rem 0;
    border-radius: 0.5rem;
    box-shadow: 0 3px 6px 0 rgba(0,0,0,0,25);
}

.faq-header{
    padding: 1.5rem 3rem 1.5rem 2rem;
    font-weight:bold;
    display:flex;
    align-items: center;
    cursor: pointer;
    position: relative;
}   
.faq-header::after{
    content: "\25BE";
    font-size: 2rem;
    position: absolute;
    right: 1rem;
}
.faq-header.active::after{
    transform: rotate(180deg);
}
.faq-body{
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease-out;
}
.faq-body-content{
    padding: 1rem;
    line-height: 1.5rem;
    border-top: 1px solid;
 }
 


```

### frontend/start.html

```html
<!DOCTYPE html>

<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PoetryAI</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="start.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
    <div class="questionicon">
        <a href="help.html">
        <i class="fa fa-question-circle"></i>
        </a>
    </div>
    <div class="main-container">
    <div class="image-container">
        <img id="random-image" src="" alt="Random Image"/>
        <div class="text_input">
            <!-- <label class="user_poem" for="userpoem">User's poem</label> -->
            <textarea type="text" id="userpoeminput" placeholder="Enter your poem here..." ></textarea>
        </div>

    </div>

    <div class="button_gap">
        
        <button class="submit_button" id="submitbtn" onClick="scrollDown()">Submit</button>
        <button class="startover_button" id="startoverbtn" onclick="startOver()">Start Over</button>
    </div>
    <div class="spinner" style="display:none"></div>

    <!-- temp div to test things--> 

    <script src="start.js"></script>
    </div>

    <div class="result-container">
        <div id="comparison-results"> </div>
    

</div>
</div>

</body>


</html>


```

### frontend/style.css

```css
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Baskervville:ital@0;1&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Playfair:ital,opsz,wght@0,5..1200,300..900;1,5..1200,300..900&display=swap');
body{
    font-family: 'Playfair Display';
    background-color: #647e7d;
    display:grid;
    place-items: center;
    align-content: center;
    min-height: 100vh;
}
.animated-txt{
    color: #fff;
    font-size: 90px;
    font-weight: 500;
    line-height: 90px;
    margin-left: 70px;
    font-style: italic;
}
.animated-txt span{
    position: relative;
}
.animated-txt span::before{
    content: "Simple";
    color:#C38590;  
    animation: words 20s infinite;
}

.animated-txt span::after{
    content: "";
    color:#C38590;  
    position: absolute;
    width: calc(100% + 8px);
    height: 100%;
    background-color: #647e7d;
    border-left: 2px solid #C38590;
    left:103%;
    transform: translateX(-100%); 
    animation: cursor .5s infinite, typing 20s steps(11) infinite;
}

@keyframes cursor{
    to{
        border-left: 2px solid #C38590;
    }
}

@keyframes words{
    0%,20%{
        content: "SIMPLE.";
    }
    21%,40%{
        content: "CATS?";
    }
    41%,60%{
        content: "EASY.";
    }
    61%,80%{
        content: "DIVERSE.";
    }
    81%,100%{
        content: "HUMOROUS.";
    }
}

@keyframes typing {
   10%,15%,30%,35%,50%,55%,70%,75%,90%,95%{
    width: 0;
   }
   5%,20%,25%,40%,45%,60%,65%,80%,85%{
    width:calc(100% + 8px);
   }
}
section{
    display:grid;
    place-items: center;
    align-content: center;
    min-height: 100vh;
}
.main{
    background-color: #647E7D;
    font-size: 150px;
    text-align: center;
    color: #FDEFE4;
    font-weight: bold;
    display: flex;             
    flex-direction: column;    
    justify-content: center;   
    align-items: center;   
    height: 100vh;  
}


.button{
    font-size: 20px;
    color: white;
    font-family: Playfair;
    background-color: #C38590;
    border: none;
    border-radius: 25px;
    padding: 15px 32px;
    text-align: center;
    display: flex;
    justify-content: center;
    cursor: pointer;

}
.button:hover{
    background-color: #FDEFE4;
    color:#C38590;
}
.hidden{
    opacity: 0;
    filter:blue(5px);
    transition: all 3s;
}
@media(prefers-reduced-motion){
    .hidden{
        transition: none;
    }
}

.show{
    opacity: 1;
    filter:blur(0);
    transform: translateX(0);
}
```

### frontend/help.html

```html
<!DOCTYPE html>

<html>
    <header>
        <title>Help/Frequently Asked Questions(FAQ)</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="help.css">
        <script defer src ="app.js"></script>
    </header>
    <body>
       <h1 class ="helpHeader">Frequently Asked Questions</h1>
        <div class = "faq">
            <div class = "faq-item">
                <div class ="faq-header">
                    <p>What does this website do?</p>
                </div>
                <div class = "faq-body">
                    <div class = "faq-body-content">
                        <p>This website allows you to submit your poem and compares it to an AI-generated poem based on a picture. </p>
                    </div>
                </div>
            </div>
        </div>

        <div class = "faq">
            <div class = "faq-item">
                <div class ="faq-header">
                    <p>How does the AI generate a poem from a picture?</p>
                </div>
                <div class = "faq-body">
                    <div class = "faq-body-content">
                    <p>The AI analyzes the visual elements of the picture and uses natural language processing
                        to create a poem inspired by the image. </p>
                </div>
            </div>
            </div>
        </div>
        
        <div class = "faq">
            <div class = "faq-item">
                <div class ="faq-header">
                    <p>How do I submit my poem?</p>
                </div>
                <div class = "faq-body">
                    <div class = "faq-body-content">
                    <p>Just enter your poem in the textbox provided and click on the 'Submit' button.</p>
                </div>
            </div>
            </div>
        </div>

        <div class = "faq">
            <div class = "faq-item">
                <div class ="faq-header">
                    <p>Is there a word limit for the poem I can submit?</p>
                </div>
                <div class = "faq-body">
                    <div class = "faq-body-content">
                    <p>Yes, the maximum word limit is 100 words, and the poem must be at least 20 words long.</p>
                </div>
            </div>
            </div>
        </div>
        <div class = "faq">
            <div class = "faq-item">
                <div class ="faq-header">
                    <p>Do you offer any tools to help me improve my poetry?</p>
                </div>
                <div class = "faq-body">
                    <div class = "faq-body-content">
                    <p>Yes! With the help of AI, it will give advice on ways to improve your poems.</p>
                </div>
            </div>
            </div>
        </div>


    <script>
    </script>
    



    </body>






</html>
```

### frontend/start.css

```css
@import url('https://fonts.googleapis.com/css2?family=Baskervville:ital@0;1&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');

body{
    background-color: #647e7d;
    margin: 0;
    padding: 0;
    font-family: 'Montserrat';
}

.main-container{
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
}


#userpoeminput {
    width: 300px;
    height: 390px;
    background-color: #FDEFE4;
    color: black;
    border: 1px solid #ccc;
    padding: 10px;
    caret-color: black;
    font-size: 16px;
    line-height: 1.5;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    transition: box-shadow 0.3 ease;
    justify-content: flex-start;
    resize: none;
    overflow-y: auto;
}

#userpoeminput:hover{
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

#userpoeminput::placeholder{
    color: gray;
    font-size: 15px;
    line-height: 1.5;
}



div.text_input{
    position: relative;
    width: 300px;
    font-family: 'Montserrat';
}


img.generated_image{
    width: 500px; 
    height: 400px;
    object-fit: cover;
    margin-bottom: 25px;
}


.image-container{
    display:flex;
    justify-content: space-evenly;
    align-items: center;
    gap: 30px;
}

#random-image {
    height: 390px; 
    width: auto;  
}


.questionicon{
    position: absolute;
    top: 10px;
    right: 10px;
    font-size: 45px;
    color: white;
    cursor: pointer;
}
.questionicon a:hover{
    color: white;
}
.questionicon a:visited{
    color: white;
}

button{
    font-size: 17px;
    font-family: 'Montserrat';
    color: white;
    background-color: #C38590;
    text-align: center;
    display: inline-block;
    padding: 10px 25px;
    cursor: pointer;
    border-radius: 10px;
    margin-bottom: 20px;
    gap: 10px;

}

#submitbtn{
    margin-right: 10px;
}
#startoverbtn{
    margin-left: 25px;
}

.button_gap{
    margin-top: 25px;
}

.result-container{
    width: 100%;
    height: 90vh;
    margin-top: 40px;
}

#comparison-results{
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 100px;
    background-color: linear-gradient(454deg,#FDEFE4, #C38590 );
}

.hidden{
    opacity: 0;
    transform: translateY(20px);
    transition: opacity 0.5 ease-out, transform 0.5 ease-out
}

.show{
    opacity: 1;
    transform: translateY(0);
}

.bigbox{
    width: 500px;
    height: 600px;
    background-color:#FDEFE4;
    border: 3px solid black;
    border-radius: 10px;
    box-shadow: 0 4px 15 px rgba(0, 0, 0, 0.1);
    white-space: pre-wrap;
    align-content: center;
    line-height: 1.5;
    font-size: 16px;
    padding-left: 10px;
    padding-right: 10px;
    padding-top: 20px;
    display:flex;
    flex-direction: column;
}


p.AIpoem{
    padding-top: 15px;
    text-align: center;
    justify-items: center;
    align-content: center;

}

table.table1{
    padding-top: 15px;
    padding-left: 20px;
    padding-right: 20px;
    text-align: center;
    justify-items: center;
    align-content: center;
}

.spinner{
    border: 8px solid #f3f3f3; 
    border-top: 8px solid #3498db;
    border-radius: 50%;
    width: 50px;
    height: 50px;
    animation: spin 2s linear infinite;
    display: none;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%); 
    z-index: 9999;
  
}

@keyframes spin{
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}


h2.AIspoem{
    justify-content: center;
    text-align: center;
}

h2.scoring{
    justify-content: center;
    text-align: center;
}

td.bold{
    font-weight: bold;
}
```

### frontend/start.js

```javascript
window.onload = function() {
    fetch('http://127.0.0.1:3000/get-random-image')
        .then(response => response.json())
        .then(data => {
            const clientImagePath = data.imagePath;
            const imageElement = document.getElementById('random-image');
            imageElement.src = clientImagePath;
            window.selectedImagePath = clientImagePath;
        })
        .catch(error => {
            console.error('Error fetching random image:', error);
        });
};

let scoreText = {};
let poemText = '';

async function fetchComparisonData() {

    const userInput = document.getElementById("userpoeminput").value;
    const clientImagePath = window.selectedImagePath; 
    const imagePath = clientImagePath.substring(1); 
  
    try {
      const response = await fetch('http://127.0.0.1:3000/get-comparison', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          poem: userInput,
          imagePath: imagePath
        })
      });
  
      const data = await response.json(); 
  
      scoreText = data.formattedData;

      poemText = data.AIPoem;

    } catch (error) {
      console.error("An error occurred while fetching comparison data:", error);
    } 
} 


const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
        if(entry.isIntersecting){
            entry.target.classList.add('show');
        } 
        else{
            entry.target.classList.remove('show');
        }
        });
    });


async function scrollDown(){
    await fetchComparisonData(); 
    let proseScore = scoreText.poem_1.prose; 

    const resultsDiv = document.getElementById("comparison-results");
    resultsDiv.innerHTML = ` 

    <div class="bigbox"> 
            <h2 class="AIspoem">AI-generated Poem</h2>
            <p class="AIpoem">${poemText}</p>
        </div>
        <div class="bigbox">
            <table class="table1">
            <h2 class="scoring">Scoring</h2>
                <thead>
                    <tr>
                        <th></th>
                        <th >Your Poem</th>
                        <th >AI's Poem</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="bold">Creativity</td>
                        <td>${scoreText.poem_1.creativity}</td>
                        <td>${scoreText.poem_2.creativity}</td>
                    </tr>
                    <tr>
                        <td class="bold">Originality</td>
                        <td>${scoreText.poem_1.originality}</td>
                        <td>${scoreText.poem_2.originality}</td>
                    </tr>
                    <tr>
                        <td class="bold">Prose</td>
                        <td>${scoreText.poem_1.prose}</td>
                        <td>${scoreText.poem_2.prose}</td>
                    </tr>
                    <tr>
                        <td class="bold">Personal Meaning</td>
                        <td>${scoreText.poem_1.personal_meaning}</td>
                        <td>${scoreText.poem_2.personal_meaning}</td>
                    </tr>
                    <tr>
                        <td class="bold">Overall</td>
                        <td>${scoreText.poem_1.overall}</td>
                        <td>${scoreText.poem_2.overall}</td>
                    </tr>
                </tbody>
            </table>

            <p>${scoreText.advice}</p>
        </div>

        
    `;
    resultsDiv.scrollIntoView({behavior: 'smooth'});

    setTimeout(function(){
        const hiddenElements = resultsDiv.querySelectorAll('.hidden');
        hiddenElements.forEach((el) => observer.observe(el));
        
    },2000);

}

document.getElementById("submitbtn").addEventListener("click", function(){
    document.querySelector('.spinner').style.display = 'block';

    setTimeout(function(){
        document.querySelector('.spinner').style.display = 'none';

        scrollDown();
    },3000);
    });

function startOver(){
    document.getElementById('userpoeminput').value = '';
    regenerateImage();
    document.getElementById('comparison-results').innerHTML = '';
}

function regenerateImage() {
    fetch('http://127.0.0.1:3000/get-random-image')
        .then(response => response.json())
        .then(data => {
            const clientImagePath = data.imagePath;
            const imageElement = document.getElementById('random-image');
            imageElement.src = clientImagePath;
            window.selectedImagePath = clientImagePath; 
        })
        .catch(error => {
            console.error('Error fetching new image:', error);
        });
}
```

[1 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]