Upload files to "Examm"
This commit is contained in:
parent
9515197b7b
commit
b15ea71de5
10
Examm/Dockerfile
Normal file
10
Examm/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Use the official nginx image
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy the game files into the nginx html folder
|
||||||
|
COPY index.html /usr/share/nginx/html/
|
||||||
|
COPY style.css /usr/share/nginx/html/
|
||||||
|
COPY script.js /usr/share/nginx/html/
|
||||||
|
|
||||||
|
# Expose port 80
|
||||||
|
EXPOSE 80
|
120
Examm/README.md
Normal file
120
Examm/README.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# Typing Speed & RSI Awareness Game - Kubernetes & Netlify Deployment
|
||||||
|
|
||||||
|
This project showcases a web-based Typing Speed & RSI Awareness Game, deployed in two ways:
|
||||||
|
|
||||||
|
1. Locally on a Kubernetes cluster using best practices and persistent storage.
|
||||||
|
2. Published online using Netlify, making it accessible via a global CDN.
|
||||||
|
|
||||||
|
# Game Overview
|
||||||
|
|
||||||
|
The application is designed to:
|
||||||
|
|
||||||
|
1. Improve typing speed and accuracy.
|
||||||
|
2. Educate users on RSI (Repetitive Strain Injury) prevention.
|
||||||
|
3. Include real-time feedback and typing stats.
|
||||||
|
4. Encourage healthy typing habits with periodic stretch reminders.
|
||||||
|
|
||||||
|
Project Structure
|
||||||
|
|
||||||
|
File/Folder - Description
|
||||||
|
index.html, style.css, script.js - Frontend files for the typing game
|
||||||
|
Dockerfile - Builds an Nginx image to serve the game
|
||||||
|
prepare-app.sh - Prepares Docker image and volume for Kubernetes
|
||||||
|
start-app.sh - Deploys app using Kubernetes objects
|
||||||
|
stop-app.sh - Deletes all Kubernetes resources
|
||||||
|
namespace.yaml - Creates typing-game namespace
|
||||||
|
deployment.yaml - Deploys frontend with Nginx
|
||||||
|
service.yaml
|
||||||
|
pv-pvc.yaml - Persistent Volume and Claim configuration
|
||||||
|
statefulset.yaml
|
||||||
|
|
||||||
|
|
||||||
|
# Namespace
|
||||||
|
Created and referenced typing-game in all YAML files.
|
||||||
|
|
||||||
|
# Deployment
|
||||||
|
Used deployment.yaml for Nginx-based frontend.
|
||||||
|
|
||||||
|
# tatefulSet + Persistent Volume
|
||||||
|
statefulset.yaml logs data persistently via PVC.
|
||||||
|
|
||||||
|
# Service
|
||||||
|
Exposed frontend via NodePort (port 30007).
|
||||||
|
|
||||||
|
# Scripts
|
||||||
|
start-app.sh - to deploy, stop-app.sh to clean up.
|
||||||
|
|
||||||
|
prepare-app.sh - for Docker image and volume setup.
|
||||||
|
|
||||||
|
./stop-app.sh - Which will stop all Kubernetes resources associated with the app.
|
||||||
|
|
||||||
|
Dockerfile - Built custom image serving game with Nginx.
|
||||||
|
|
||||||
|
|
||||||
|
# How to Deploy on Kubernetes
|
||||||
|
|
||||||
|
Step 1: Prepare the Application (All the files and scripts)
|
||||||
|
|
||||||
|
Step 2: ./prepare-app.sh
|
||||||
|
|
||||||
|
|
||||||
|
Step 3: Deploy the App via - ./start-app.sh
|
||||||
|
|
||||||
|
Which Applies:
|
||||||
|
|
||||||
|
A)Namespace
|
||||||
|
B)Deployment
|
||||||
|
C)Service
|
||||||
|
D)StatefulSet
|
||||||
|
E)PersistentVolume and PVC
|
||||||
|
|
||||||
|
Step 4: Access the App (Locally)
|
||||||
|
|
||||||
|
Replace <your-ip> with the IP address of your Kubernetes node.
|
||||||
|
|
||||||
|
Step 5: Clean Up via - ./stop-app.sh
|
||||||
|
|
||||||
|
Which will stop all Kubernetes resources associated with the app.
|
||||||
|
|
||||||
|
# How to Deploy with Netlify (Online)
|
||||||
|
|
||||||
|
You also hosted the Typing Game on Netlify, a popular platform for deploying Games or websites on Public Cloud Environment
|
||||||
|
|
||||||
|
# Setup Steps (Completed)
|
||||||
|
|
||||||
|
1. Installed Netlify CLI using Homebrew:
|
||||||
|
2. brew install netlify-cli
|
||||||
|
3. Logged in and linked your project:
|
||||||
|
4. netlify login
|
||||||
|
5. netlify init # Or `netlify link` if project already exists
|
||||||
|
|
||||||
|
Your game is now live at:
|
||||||
|
|
||||||
|
https://typing-game-app.netlify.app
|
||||||
|
|
||||||
|
# Netlify Publish Directory
|
||||||
|
|
||||||
|
When prompted during deploy, you provided this directory:
|
||||||
|
|
||||||
|
/Users/puneetkhurana/Documents/zkt25/Examm
|
||||||
|
|
||||||
|
|
||||||
|
# CI-Like Workflow with CLI
|
||||||
|
|
||||||
|
To update your live website via CLI:
|
||||||
|
|
||||||
|
Make changes in your local Examm folder.
|
||||||
|
netlify deploy --prod
|
||||||
|
Changes will go live on the same URL.
|
||||||
|
|
||||||
|
This allows you to re-deploy instantly after edits without needing to use the Netlify UI.
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
1. Typing Game promotes speed and RSI awareness.
|
||||||
|
|
||||||
|
2. Deployed on Kubernetes with storage and services.
|
||||||
|
|
||||||
|
3. Published live via Netlify using CLI.
|
||||||
|
|
||||||
|
4. Fully automated with scripts for ease of use and reusability.
|
21
Examm/deployment.yaml
Normal file
21
Examm/deployment.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: typing-game-deployment
|
||||||
|
namespace: typing-game
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: typing-game
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: typing-game
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: typing-game
|
||||||
|
image: typing-game:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
43
Examm/index.html
Normal file
43
Examm/index.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Typing Speed & RSI Awareness Game</title>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Fast Fingers, Healthy Habits</h1>
|
||||||
|
<p style="font-style: italic; margin-top: -5px; color: #555;">
|
||||||
|
Type fast. Type smart. Stay safe.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="game-container">
|
||||||
|
<button id="dark-mode-toggle">🌙 Toggle Dark Mode</button>
|
||||||
|
<div id="session-timer">You've been typing for 0 minutes.</div>
|
||||||
|
<div id="high-score">🏆 Highest Score: 0</div>
|
||||||
|
|
||||||
|
<h1>Typing Speed & RSI Awareness</h1>
|
||||||
|
<div id="word-display">Start</div>
|
||||||
|
<input type="text" id="word-input" placeholder="Type here..." autofocus />
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<span id="score">Score: 0</span>
|
||||||
|
<span id="accuracy">Accuracy: 100%</span>
|
||||||
|
<span id="timer">Time: 0:00</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="rsi-tip" class="tip-box">💡 Keep your wrists straight while typing.</div>
|
||||||
|
<div id="stretch-popup" class="stretch-popup"></div>
|
||||||
|
|
||||||
|
<div class="gif-container">
|
||||||
|
<img id="stretch-gif"
|
||||||
|
src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExZHpoc3hwOTQ4Nm0xcWowN2h3ODhudXdybnYzdWJrZ2JiamNkajU3eCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/395GQQavHm5CPXboEG/giphy.gif"
|
||||||
|
alt="Stretch your hands!"
|
||||||
|
style="display: none; width: 140px; border-radius: 12px;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
4
Examm/namespace.yaml
Normal file
4
Examm/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: typing-game
|
11
Examm/postgres-service.yaml
Normal file
11
Examm/postgres-service.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: typing-game
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
39
Examm/postgres-statefulset.yaml
Normal file
39
Examm/postgres-statefulset.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: typing-game
|
||||||
|
spec:
|
||||||
|
serviceName: "postgres"
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: postgres:13
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: typingdb
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: typinguser
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
value: secret123
|
||||||
|
volumeMounts:
|
||||||
|
- name: postgres-storage
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: postgres-storage
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
13
Examm/prepare-app.sh
Normal file
13
Examm/prepare-app.sh
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Create Docker image
|
||||||
|
echo "Building Docker image: typing-game"
|
||||||
|
docker build -t typing-game .
|
||||||
|
|
||||||
|
# Save Docker image as tar to simulate deployment to remote cluster
|
||||||
|
docker save typing-game > typing-game.tar
|
||||||
|
|
||||||
|
# Dummy command to create persistent volume directory (for demo)
|
||||||
|
mkdir -p ./persistent-data
|
||||||
|
|
||||||
|
echo "Preparation complete."
|
25
Examm/pv-pvc.yaml
Normal file
25
Examm/pv-pvc.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: typing-game-pv
|
||||||
|
namespace: typing-game
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 1Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
hostPath:
|
||||||
|
path: "/mnt/data/typing-game"
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: typing-game-pvc
|
||||||
|
namespace: typing-game
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
114
Examm/script.js
Normal file
114
Examm/script.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
const words = ["apple", "banana", "cherry", "dragon", "elephant", "fish", "grape", "house", "ink", "jungle"];
|
||||||
|
let currentWord = "";
|
||||||
|
let score = 0;
|
||||||
|
let typedWords = 0;
|
||||||
|
let missed = 0;
|
||||||
|
let totalTyped = 0;
|
||||||
|
let startTime = Date.now();
|
||||||
|
let wordStartTime = Date.now();
|
||||||
|
|
||||||
|
const wordDisplay = document.getElementById("word-display");
|
||||||
|
const wordInput = document.getElementById("word-input");
|
||||||
|
const scoreDisplay = document.getElementById("score");
|
||||||
|
const accuracyDisplay = document.getElementById("accuracy");
|
||||||
|
const timerDisplay = document.getElementById("timer");
|
||||||
|
const tipBox = document.getElementById("rsi-tip");
|
||||||
|
const stretchPopup = document.getElementById("stretch-popup");
|
||||||
|
const sessionTimerText = document.getElementById("session-timer");
|
||||||
|
const highScoreDisplay = document.getElementById("high-score");
|
||||||
|
const darkModeToggle = document.getElementById("dark-mode-toggle");
|
||||||
|
|
||||||
|
const tips = [
|
||||||
|
"💡 Keep your wrists straight while typing.",
|
||||||
|
"🪑 Sit with your back supported.",
|
||||||
|
"👁 Adjust your screen to eye level.",
|
||||||
|
"💧 Take a short water break.",
|
||||||
|
"🧍♂️ Stand and stretch for a moment."
|
||||||
|
];
|
||||||
|
|
||||||
|
let highScore = localStorage.getItem("typingHighScore") || 0;
|
||||||
|
highScoreDisplay.textContent = `🏆 Highest Score: ${highScore}`;
|
||||||
|
|
||||||
|
function generateWord() {
|
||||||
|
currentWord = words[Math.floor(Math.random() * words.length)];
|
||||||
|
wordDisplay.textContent = currentWord;
|
||||||
|
wordInput.value = "";
|
||||||
|
wordStartTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateScore() {
|
||||||
|
scoreDisplay.textContent = `Score: ${score}`;
|
||||||
|
const accuracy = totalTyped === 0 ? 100 : Math.round((score / totalTyped) * 100);
|
||||||
|
accuracyDisplay.textContent = `Accuracy: ${accuracy}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimer() {
|
||||||
|
const now = Date.now();
|
||||||
|
const seconds = Math.floor((now - startTime) / 1000);
|
||||||
|
const mins = Math.floor(seconds / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
timerDisplay.textContent = `Time: ${mins}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameOver() {
|
||||||
|
if (score > highScore) {
|
||||||
|
localStorage.setItem("typingHighScore", score);
|
||||||
|
alert(`New High Score: ${score}! 🎉`);
|
||||||
|
} else {
|
||||||
|
alert("Game Over! You missed 3 words.");
|
||||||
|
}
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
wordInput.addEventListener("input", () => {
|
||||||
|
if (wordInput.value.trim() === currentWord) {
|
||||||
|
score++;
|
||||||
|
typedWords++;
|
||||||
|
totalTyped++;
|
||||||
|
generateWord();
|
||||||
|
updateScore();
|
||||||
|
|
||||||
|
if (typedWords % 10 === 0) {
|
||||||
|
stretchPopup.textContent = "🧘♀️ Time to stretch your fingers or roll your shoulders!";
|
||||||
|
document.getElementById("stretch-gif").style.display = "block";
|
||||||
|
setTimeout(() => {
|
||||||
|
stretchPopup.textContent = "";
|
||||||
|
document.getElementById("stretch-gif").style.display = "none";
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
wordInput.addEventListener("blur", () => wordInput.focus());
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
const timeSinceWordShown = Date.now() - wordStartTime;
|
||||||
|
if (timeSinceWordShown >= 8000 && wordInput.value.trim() === "") {
|
||||||
|
missed++;
|
||||||
|
totalTyped++;
|
||||||
|
if (missed >= 3) gameOver();
|
||||||
|
generateWord();
|
||||||
|
updateScore();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
let tipIndex = 0;
|
||||||
|
setInterval(() => {
|
||||||
|
tipBox.textContent = tips[tipIndex];
|
||||||
|
tipIndex = (tipIndex + 1) % tips.length;
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
setInterval(updateTimer, 1000);
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
const minutes = Math.floor((Date.now() - startTime) / 60000);
|
||||||
|
sessionTimerText.textContent = `You've been typing for ${minutes} minute${minutes !== 1 ? 's' : ''}.`;
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
darkModeToggle.addEventListener("click", () => {
|
||||||
|
document.body.classList.toggle("dark-mode");
|
||||||
|
});
|
||||||
|
|
||||||
|
generateWord();
|
||||||
|
updateScore();
|
||||||
|
updateTimer();
|
14
Examm/service.yaml
Normal file
14
Examm/service.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: typing-game-service
|
||||||
|
namespace: typing-game
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: typing-game
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
nodePort: 30007
|
11
Examm/start-app.sh
Normal file
11
Examm/start-app.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Creating Kubernetes resources..."
|
||||||
|
|
||||||
|
kubectl apply -f namespace.yaml
|
||||||
|
kubectl apply -f pv-pvc.yaml
|
||||||
|
kubectl apply -f deployment.yaml
|
||||||
|
kubectl apply -f service.yaml
|
||||||
|
kubectl apply -f statefulset.yaml
|
||||||
|
|
||||||
|
echo "Application deployed under namespace 'typing-game'."
|
31
Examm/statefulset.yaml
Normal file
31
Examm/statefulset.yaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: typing-backend
|
||||||
|
namespace: typing-game
|
||||||
|
spec:
|
||||||
|
serviceName: "backend-service"
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: typing-backend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: typing-backend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
image: alpine
|
||||||
|
command: ["/bin/sh", "-c", "while true; do echo Hello from StatefulSet >> /data/log.txt; sleep 10; done"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: backend-storage
|
||||||
|
mountPath: /data
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: backend-storage
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
11
Examm/stop-app.sh
Normal file
11
Examm/stop-app.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Deleting Kubernetes resources..."
|
||||||
|
|
||||||
|
kubectl delete -f statefulset.yaml
|
||||||
|
kubectl delete -f service.yaml
|
||||||
|
kubectl delete -f deployment.yaml
|
||||||
|
kubectl delete -f pv-pvc.yaml
|
||||||
|
kubectl delete -f namespace.yaml
|
||||||
|
|
||||||
|
echo "All resources deleted."
|
93
Examm/style.css
Normal file
93
Examm/style.css
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: #bbff00;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-container {
|
||||||
|
text-align: center;
|
||||||
|
background: rgb(0, 249, 253);
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dark-mode-toggle {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#session-timer {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#high-score {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gif-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#word-display {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#word-input {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-box {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: #e6f7ff;
|
||||||
|
border-left: 5px solid #1890ff;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stretch-popup {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #d48806;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode {
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .game-container {
|
||||||
|
background: #2c2c2c;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode input {
|
||||||
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: 2px solid #888;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user