parent
2f458c2267
commit
6b4769e15b
@ -1,11 +1,17 @@
|
||||
# Reading the Docs
|
||||
|
||||
## Instructions
|
||||
|
||||
There are many tools that a web developer may need that are on the [MDN documentation for client-side tooling](https://developer.mozilla.org/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview). Select 3 tools not covered in the lesson, explain why a web developer would use it, and search for a tool that falls under this category and share its documentation. Do not use the same tool example on MDN docs.
|
||||
There are many tools that a web developer may need that are listed on the [MDN documentation for client-side tooling](https://developer.mozilla.org/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview). Select **three tools** that are **not covered in this lesson** (excluding [list specific tools or refer to lesson content]), explain **why** a web developer would use each tool, and find a tool that fits each category. For each, share a link to its official documentation (not the example used on MDN).
|
||||
|
||||
**Format:**
|
||||
- Tool name
|
||||
- Why a web developer would use it (2-3 sentences)
|
||||
- Link to documentation
|
||||
|
||||
**Length:**
|
||||
- Each explanation should be 2-3 sentences.
|
||||
|
||||
## Rubric
|
||||
|
||||
Exemplary | Adequate | Needs Improvement
|
||||
--- | --- | -- |
|
||||
|Explained why web developer would use tool| Explained how, but not why developer would use tool| Did not mention how or why a developer would use tool |
|
||||
Explained why web developer would use tool | Explained how, but not why developer would use tool | Did not mention how or why a developer would use tool |
|
||||
@ -1,11 +1,27 @@
|
||||
# CSS Refactoring
|
||||
# CSS Refactoring Assignment
|
||||
|
||||
## Objective
|
||||
|
||||
Refactor the terrarium project to use **Flexbox** or **CSS Grid** for layout. Update the HTML and CSS as needed to achieve a modern, responsive design. You do not need to implement draggable elements—focus on layout and styling only.
|
||||
|
||||
## Instructions
|
||||
|
||||
Restyle the terrarium using either Flexbox or CSS Grid, and take screenshots to show that you have tested it on several browsers. You might need to change the markup so create a new version of the app with the art in place for your refactor. Don't worry about making the elements draggable; only refactor the HTML and CSS for now.
|
||||
1. **Create a new version** of the terrarium app. Update the markup and CSS to use Flexbox or CSS Grid for layout.
|
||||
2. **Ensure the art and elements are in place** as in the original version.
|
||||
3. **Test your design** in at least two different browsers (e.g., Chrome, Firefox, Edge).
|
||||
4. **Take screenshots** of your terrarium in each browser to demonstrate cross-browser compatibility.
|
||||
5. **Submit** your updated code and screenshots.
|
||||
|
||||
## Rubric
|
||||
|
||||
| Criteria | Exemplary | Adequate | Needs Improvement |
|
||||
| -------- | ----------------------------------------------------------------- | ----------------------------- | ------------------------------------ |
|
||||
| | Present a completely restyled terrarium using Flexbox or CSS Grid | Restyle a few of the elements | Fail to restyle the terrarium at all |
|
||||
| Criteria | Exemplary | Adequate | Needs Improvement |
|
||||
|------------|--------------------------------------------------------------------------|---------------------------------------|----------------------------------------|
|
||||
| Layout | Fully refactored using Flexbox or CSS Grid; visually appealing and responsive | Some elements refactored; partial use of Flexbox or Grid | Little or no use of Flexbox or Grid; layout unchanged |
|
||||
| Cross-Browser | Screenshots provided for multiple browsers; consistent appearance | Screenshots for one browser; minor inconsistencies | No screenshots or major inconsistencies |
|
||||
| Code Quality | Clean, well-organized HTML/CSS; clear comments | Some organization; few comments | Disorganized code; lacks comments |
|
||||
|
||||
## Tips
|
||||
|
||||
- Review [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) and [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) guides.
|
||||
- Use browser developer tools to test responsiveness.
|
||||
- Comment your code for clarity.
|
||||
@ -1,76 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Welcome to my Virtual Terrarium</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- import the webpage's stylesheet -->
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<!-- import the webpage's JavaScript file -->
|
||||
<script src="./script.js" defer></script>
|
||||
</head>
|
||||
<head>
|
||||
<title>Welcome to my Virtual Terrarium</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Font Awesome CDN for icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" />
|
||||
<!-- import the webpage's stylesheet -->
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<!-- import the webpage's JavaScript file -->
|
||||
<script src="./script.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
<!-- Navigation Bar -->
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#"><i class="fas fa-home"></i> Home</a></li>
|
||||
<li><a href="#"><i class="fas fa-leaf"></i> Plants</a></li>
|
||||
<li><a href="#"><i class="fas fa-seedling"></i> Care Tips</a></li>
|
||||
<li><a href="#"><i class="fas fa-info-circle"></i> About</a></li>
|
||||
<li><a href="#"><i class="fas fa-envelope"></i> Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<body>
|
||||
<div id="page">
|
||||
<h1>My Terrarium</h1>
|
||||
<header>
|
||||
<h1><i class="fas fa-jar"></i> My Terrarium</h1>
|
||||
<p>Welcome to your virtual plant paradise! <i class="fas fa-smile-beam"></i></p>
|
||||
</header>
|
||||
|
||||
<div id="left-container" class="container">
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant1" src="./images/plant1.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant2" src="./images/plant2.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant3" src="./images/plant3.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant4" src="./images/plant4.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant5" src="./images/plant5.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant6" src="./images/plant6.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant7" src="./images/plant7.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="right-container" class="container">
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant8" src="./images/plant8.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant9" src="./images/plant9.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant10" src="./images/plant10.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant11" src="./images/plant11.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant12" src="./images/plant12.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant13" src="./images/plant13.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant14" src="./images/plant14.png" />
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
<section id="plant-selection">
|
||||
<div id="left-container" class="container">
|
||||
<!-- Left-side plants -->
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant1" src="./images/plant1.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant2" src="./images/plant2.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant3" src="./images/plant3.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant4" src="./images/plant4.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant5" src="./images/plant5.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant6" src="./images/plant6.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant7" src="./images/plant7.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="right-container" class="container">
|
||||
<!-- Right-side plants -->
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant8" src="./images/plant8.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant9" src="./images/plant9.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant10" src="./images/plant10.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant11" src="./images/plant11.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant12" src="./images/plant12.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant13" src="./images/plant13.png" />
|
||||
</div>
|
||||
<div class="plant-holder">
|
||||
<img class="plant" alt="plant" id="plant14" src="./images/plant14.png" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="terrarium">
|
||||
<div class="jar-top"></div>
|
||||
<div class="jar-walls">
|
||||
<div class="jar-glossy-long"></div>
|
||||
<div class="jar-glossy-short"></div>
|
||||
</div>
|
||||
<div class="dirt"></div>
|
||||
<div class="jar-bottom"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<section id="terrarium-section">
|
||||
<div id="terrarium">
|
||||
<div class="jar-top"></div>
|
||||
<div class="jar-walls">
|
||||
<div class="jar-glossy-long"></div>
|
||||
<div class="jar-glossy-short"></div>
|
||||
</div>
|
||||
<div class="dirt"></div>
|
||||
<div class="jar-bottom"></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
<i class="fas fa-copyright"></i> 2025 My Terrarium. All rights reserved.
|
||||
|
|
||||
<a href="https://github.com/" target="_blank"><i class="fab fa-github"></i> GitHub</a>
|
||||
|
|
||||
<a href="mailto:info@myterrarium.com"><i class="fas fa-envelope"></i> Email</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,23 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Typing</title>
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Typing</title>
|
||||
<!-- Font Awesome CDN for icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" />
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
<style>
|
||||
|
||||
|
||||
#datetime {
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
color: #2d7c6f;
|
||||
}
|
||||
|
||||
#datetime .fas {
|
||||
margin-right: 0.3rem;
|
||||
color: #3a241d;
|
||||
font-size: 1.1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Hide label visually but keep for accessibility */
|
||||
.hidden-label {
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ...existing code... */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Practice your typing</h1>
|
||||
<div>Click start to have a quote displayed. Type the quote as fast as you can!</div>
|
||||
<body>
|
||||
<h1>
|
||||
<i class="fas fa-keyboard"></i> Practice your typing
|
||||
</h1>
|
||||
<div>
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Click start to have a quote displayed. Type the quote as fast as you can!
|
||||
</div>
|
||||
|
||||
<p id="quote"></p>
|
||||
<p id="message"></p>
|
||||
<div>
|
||||
<input type="text" aria-label="current word" id="typed-value" />
|
||||
</div>
|
||||
<div>
|
||||
<button id="start" type="button">Start</button>
|
||||
</div>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
<p id="quote"></p>
|
||||
<p id="message"></p>
|
||||
<div>
|
||||
<label for="typed-value" class="hidden-label">Current word</label>
|
||||
<input type="text" aria-label="current word" id="typed-value" placeholder="Type here..." />
|
||||
<i class="fas fa-pen"></i>
|
||||
</div>
|
||||
<div>
|
||||
<button id="start" type="button">
|
||||
<i class="fas fa-play"></i> Start
|
||||
</button>
|
||||
</div>
|
||||
<script src="./index.js"></script>
|
||||
<!-- ...existing code... -->
|
||||
<body>
|
||||
<h1>
|
||||
<i class="fas fa-keyboard"></i> Practice your typing
|
||||
</h1>
|
||||
<div id="datetime">
|
||||
<i class="fas fa-calendar-day"></i>
|
||||
<span id="current-date"></span>
|
||||
|
|
||||
<i class="fas fa-clock"></i>
|
||||
<span id="current-time"></span>
|
||||
</div>
|
||||
<!-- ...existing code... -->
|
||||
</body>
|
||||
<!-- ...existing code... -->
|
||||
<script>
|
||||
// ...existing code...
|
||||
|
||||
function updateDateTime() {
|
||||
const now = new Date();
|
||||
|
||||
// Format date as DD/MM/YYYY
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const year = now.getFullYear();
|
||||
const formattedDate = `${day}/${month}/${year}`;
|
||||
|
||||
// Format time as HH:MM AM/PM (12-hour)
|
||||
let hours = now.getHours();
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
hours = hours % 12;
|
||||
hours = hours ? hours : 12; // 0 should be 12
|
||||
const formattedTime = `${hours}:${minutes} ${ampm}`;
|
||||
|
||||
document.getElementById('current-date').textContent = formattedDate;
|
||||
document.getElementById('current-time').textContent = formattedTime;
|
||||
}
|
||||
|
||||
// Initial call and update every minute
|
||||
updateDateTime();
|
||||
setInterval(updateDateTime, 60000);
|
||||
|
||||
// ...existing code...
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@ -1,94 +1,94 @@
|
||||
// Typing Game - Modern ES6+ Version
|
||||
|
||||
// Quotes pool
|
||||
const quotes = [
|
||||
'When you have eliminated the impossible, whatever remains, however improbable, must be the truth.',
|
||||
'There is nothing more deceptive than an obvious fact.',
|
||||
'I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.',
|
||||
'I never make exceptions. An exception disproves the rule.',
|
||||
'What one man can invent another can discover.',
|
||||
'Nothing clears up a case so much as stating it to another person.',
|
||||
'Education never ends, Watson. It is a series of lessons, with the greatest for the last.',
|
||||
"When you have eliminated the impossible, whatever remains, however improbable, must be the truth.",
|
||||
"There is nothing more deceptive than an obvious fact.",
|
||||
"I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.",
|
||||
"I never make exceptions. An exception disproves the rule.",
|
||||
"What one man can invent another can discover.",
|
||||
"Nothing clears up a case so much as stating it to another person.",
|
||||
"Education never ends, Watson. It is a series of lessons, with the greatest for the last."
|
||||
];
|
||||
|
||||
// array for storing the words of the current challenge
|
||||
// State
|
||||
let words = [];
|
||||
// stores the index of the word the player is currently typing
|
||||
let wordIndex = 0;
|
||||
// default value for startTime (will be set on start)
|
||||
let startTime = Date.now();
|
||||
let startTime = 0;
|
||||
|
||||
// UI Elements
|
||||
const quoteElement = document.querySelector("#quote");
|
||||
const messageElement = document.querySelector("#message");
|
||||
const typedValueElement = document.querySelector("#typed-value");
|
||||
const startButton = document.querySelector("#start");
|
||||
|
||||
// Messages system
|
||||
const messages = {
|
||||
success: (seconds) => `🎉 Congratulations! You finished in ${seconds.toFixed(2)} seconds.`,
|
||||
error: "⚠️ Oops! There's a mistake.",
|
||||
start: "⌨️ Start typing to begin the game."
|
||||
};
|
||||
|
||||
// grab UI items
|
||||
const quoteElement = document.getElementById('quote');
|
||||
const messageElement = document.getElementById('message')
|
||||
const typedValueElement = document.getElementById('typed-value');
|
||||
// Utility: pick random quote
|
||||
const getRandomQuote = () => quotes[Math.floor(Math.random() * quotes.length)];
|
||||
|
||||
document.getElementById('start').addEventListener('click', function () {
|
||||
// get a quote
|
||||
const quoteIndex = Math.floor(Math.random() * quotes.length);
|
||||
const quote = quotes[quoteIndex];
|
||||
// Put the quote into an array of words
|
||||
words = quote.split(' ');
|
||||
// reset the word index for tracking
|
||||
wordIndex = 0;
|
||||
// Utility: render quote as spans
|
||||
const renderQuote = (quote) => {
|
||||
quoteElement.innerHTML = quote
|
||||
.split(" ")
|
||||
.map((word, i) => `<span ${i === 0 ? 'class="highlight"' : ""}>${word}</span>`)
|
||||
.join(" ");
|
||||
};
|
||||
|
||||
// UI updates
|
||||
// Create an array of span elements so we can set a class
|
||||
const spanWords = words.map(function (word) { return `<span>${word} </span>` });
|
||||
// Convert into string and set as innerHTML on quote display
|
||||
quoteElement.innerHTML = spanWords.join('');
|
||||
// Highlight the first word
|
||||
quoteElement.childNodes[0].className = 'highlight';
|
||||
// Clear any prior messages
|
||||
messageElement.innerText = '';
|
||||
// Utility: highlight current word
|
||||
const highlightWord = (index) => {
|
||||
[...quoteElement.children].forEach((el, i) => {
|
||||
el.classList.toggle("highlight", i === index);
|
||||
});
|
||||
};
|
||||
|
||||
// Setup the textbox
|
||||
// Clear the textbox
|
||||
typedValueElement.value = '';
|
||||
// set focus
|
||||
typedValueElement.focus();
|
||||
// set the event handler
|
||||
// Game start
|
||||
const startGame = () => {
|
||||
const quote = getRandomQuote();
|
||||
words = quote.split(" ");
|
||||
wordIndex = 0;
|
||||
renderQuote(quote);
|
||||
|
||||
// Start the timer
|
||||
startTime = new Date().getTime();
|
||||
});
|
||||
messageElement.textContent = messages.start;
|
||||
typedValueElement.value = "";
|
||||
typedValueElement.focus();
|
||||
|
||||
typedValueElement.addEventListener('input', (e) => {
|
||||
// Get the current word
|
||||
const currentWord = words[wordIndex];
|
||||
// get the current value
|
||||
const typedValue = typedValueElement.value;
|
||||
startTime = Date.now();
|
||||
};
|
||||
|
||||
if (typedValue === currentWord && wordIndex === words.length - 1) {
|
||||
// end of quote
|
||||
// Display success
|
||||
const elapsedTime = new Date().getTime() - startTime;
|
||||
const message = `CONGRATULATIONS! You finished in ${elapsedTime / 1000} seconds.`;
|
||||
messageElement.innerText = message;
|
||||
} else if (typedValue.endsWith(' ') && typedValue.trim() === currentWord) {
|
||||
// end of word
|
||||
// clear the typedValueElement for the new word
|
||||
typedValueElement.value = '';
|
||||
// move to the next word
|
||||
wordIndex++;
|
||||
// reset the class name for all elements in quote
|
||||
for (const wordElement of quoteElement.childNodes) {
|
||||
wordElement.className = '';
|
||||
}
|
||||
// highlight the new word
|
||||
quoteElement.childNodes[wordIndex].className = 'highlight';
|
||||
} else if (currentWord.startsWith(typedValue)) {
|
||||
// currently correct
|
||||
// highlight the next word
|
||||
typedValueElement.className = '';
|
||||
} else {
|
||||
// error state
|
||||
typedValueElement.className = 'error';
|
||||
}
|
||||
});
|
||||
// Typing logic
|
||||
const handleTyping = () => {
|
||||
const currentWord = words[wordIndex];
|
||||
const typedValue = typedValueElement.value;
|
||||
|
||||
// Add this at the end of the file
|
||||
const messages = {
|
||||
success: "CONGRATULATIONS! You finished in {seconds} seconds.",
|
||||
error: "Oops! There's a mistake.",
|
||||
start: "Start typing to begin the game."
|
||||
if (typedValue === currentWord && wordIndex === words.length - 1) {
|
||||
// Game finished
|
||||
const elapsedTime = (Date.now() - startTime) / 1000;
|
||||
messageElement.textContent = messages.success(elapsedTime);
|
||||
typedValueElement.disabled = true;
|
||||
} else if (typedValue.endsWith(" ") && typedValue.trim() === currentWord) {
|
||||
// Word completed
|
||||
typedValueElement.value = "";
|
||||
wordIndex++;
|
||||
highlightWord(wordIndex);
|
||||
} else if (currentWord.startsWith(typedValue)) {
|
||||
// Correct so far
|
||||
typedValueElement.classList.remove("error");
|
||||
} else {
|
||||
// Error
|
||||
typedValueElement.classList.add("error");
|
||||
messageElement.textContent = messages.error;
|
||||
}
|
||||
};
|
||||
|
||||
export default messages;
|
||||
// Event Listeners
|
||||
startButton.addEventListener("click", startGame);
|
||||
typedValueElement.addEventListener("input", handleTyping);
|
||||
|
||||
// Default state
|
||||
messageElement.textContent = "👉 Click Start to begin!";
|
||||
|
||||
@ -1,30 +1,56 @@
|
||||
# api
|
||||
|
||||
from flask import Flask, request, jsonify
|
||||
from llm import call_llm
|
||||
from flask_cors import CORS
|
||||
import logging
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app) # * example.com
|
||||
|
||||
# Configure CORS (allow all origins for development; restrict in production)
|
||||
CORS(app, resources={r"/*": {"origins": "*"}})
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
@app.route("/", methods=["GET"])
|
||||
def index():
|
||||
return "Welcome to this lesson"
|
||||
"""Root endpoint for API status."""
|
||||
return jsonify({
|
||||
"status": "ok",
|
||||
"message": "Welcome to the Chat Project API"
|
||||
})
|
||||
|
||||
@app.route("/health", methods=["GET"])
|
||||
def health():
|
||||
"""Health check endpoint."""
|
||||
return jsonify({"status": "healthy"}), 200
|
||||
|
||||
@app.route("/test", methods=["GET"])
|
||||
def test():
|
||||
return "Test"
|
||||
"""Simple test endpoint."""
|
||||
return jsonify({"result": "Test successful"}), 200
|
||||
|
||||
@app.route("/hello", methods=["POST"])
|
||||
def hello():
|
||||
# get message from request body { "message": "do this taks for me" }
|
||||
data = request.get_json()
|
||||
message = data.get("message", "")
|
||||
"""
|
||||
Chat endpoint.
|
||||
Expects JSON: { "message": "your message" }
|
||||
Returns: { "response": "LLM response" }
|
||||
"""
|
||||
try:
|
||||
data = request.get_json(force=True)
|
||||
message = data.get("message", "").strip()
|
||||
if not message:
|
||||
logging.warning("No message provided in request.")
|
||||
return jsonify({"error": "No message provided."}), 400
|
||||
|
||||
response = call_llm(message, "You are a helpful assistant.")
|
||||
return jsonify({
|
||||
"response": response
|
||||
})
|
||||
logging.info(f"Received message: {message}")
|
||||
response = call_llm(message, "You are a helpful assistant.")
|
||||
return jsonify({"response": response}), 200
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error in /hello endpoint: {e}")
|
||||
return jsonify({"error": "Internal server error."}), 500
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000)
|
||||
# Run the app with debug mode for development
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
@ -1,29 +1,53 @@
|
||||
import os
|
||||
import logging
|
||||
from openai import OpenAI
|
||||
|
||||
# To authenticate with the model you will need to generate a personal access token (PAT) in your GitHub settings.
|
||||
# Create your PAT token by following instructions here: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Environment variable check for GitHub token
|
||||
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
|
||||
if not GITHUB_TOKEN:
|
||||
logger.error("GITHUB_TOKEN environment variable not set.")
|
||||
raise EnvironmentError("GITHUB_TOKEN environment variable not set.")
|
||||
|
||||
# Model and endpoint configuration
|
||||
MODEL_NAME = os.environ.get("LLM_MODEL", "openai/gpt-4o-mini")
|
||||
BASE_URL = os.environ.get("LLM_BASE_URL", "https://models.github.ai/inference")
|
||||
|
||||
# Initialize OpenAI client
|
||||
client = OpenAI(
|
||||
base_url="https://models.github.ai/inference",
|
||||
api_key=os.environ["GITHUB_TOKEN"],
|
||||
base_url=BASE_URL,
|
||||
api_key=GITHUB_TOKEN,
|
||||
)
|
||||
|
||||
def call_llm(prompt: str, system_message: str):
|
||||
response = client.chat.completions.create(
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_message,
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt,
|
||||
}
|
||||
],
|
||||
model="openai/gpt-4o-mini",
|
||||
temperature=1,
|
||||
max_tokens=4096,
|
||||
top_p=1
|
||||
)
|
||||
def call_llm(prompt: str, system_message: str, temperature: float = 1.0, max_tokens: int = 4096, top_p: float = 1.0) -> str:
|
||||
"""
|
||||
Calls the LLM with the given prompt and system message.
|
||||
Returns the model's response as a string.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Calling LLM model '{MODEL_NAME}' with prompt: {prompt}")
|
||||
response = client.chat.completions.create(
|
||||
messages=[
|
||||
{"role": "system", "content": system_message},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
model=MODEL_NAME,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
top_p=top_p
|
||||
)
|
||||
content = response.choices[0].message.content
|
||||
logger.info("LLM response received successfully.")
|
||||
return content
|
||||
except Exception as e:
|
||||
logger.error(f"Error calling LLM: {e}")
|
||||
return "Sorry, there was an error processing your request."
|
||||
|
||||
return response.choices[0].message.content
|
||||
# Example usage (for testing)
|
||||
if __name__ == "__main__":
|
||||
test_prompt = "Hello, how are you?"
|
||||
test_system = "You are a friendly assistant."
|
||||
print(call_llm(test_prompt, test_system))
|
||||
@ -1,3 +1,108 @@
|
||||
.app-nav ul li {
|
||||
width: max-content;
|
||||
}
|
||||
body {
|
||||
font-family: 'Inter', Arial, sans-serif;
|
||||
}
|
||||
/* Body and Typography */
|
||||
body {
|
||||
font-family: 'Inter', Arial, sans-serif;
|
||||
background: #f7f8fa;
|
||||
color: #222;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Header Styling */
|
||||
h1, h2, h3 {
|
||||
font-weight: 700;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
h1 i, h2 i, h3 i {
|
||||
color: #0078d4;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/* Navigation Bar */
|
||||
.app-nav {
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||
padding: 1em 2em;
|
||||
}
|
||||
|
||||
.app-nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.app-nav ul li {
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.app-nav a {
|
||||
text-decoration: none;
|
||||
color: #0078d4;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.app-nav a:hover {
|
||||
color: #005fa3;
|
||||
}
|
||||
|
||||
/* Card Component */
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.07);
|
||||
padding: 2em;
|
||||
margin: 2em 0;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.button {
|
||||
background: #0078d4;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.75em 1.5em;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #005fa3;
|
||||
}
|
||||
|
||||
/* Font Awesome Icon Styling */
|
||||
.fa {
|
||||
margin-right: 0.5em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Responsive Layout */
|
||||
@media (max-width: 600px) {
|
||||
.app-nav ul {
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
.card {
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue