Stop being the product.
Become the owner.
or
sign uplog in
What is wrong with this sliding menu setup?

I've found the solution to this issue today by changing my approach, but I'm still unclear what the problem was with the original setup so I wanted to ask the hive mind. Take this page for example.

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Test Page</title>
        <style>
            body {
                display: grid;
                grid-template-rows: 75px 1fr 75px;
                height: 100vh;
                padding: 0;
                margin: 0;
                overflow: hidden;
            }
            #slidingMenu {
                width: 200px;
                height: 100vh;
                background-color: #333 ;
                position: absolute;
                top: 0;
                right: -300px; /* Start hidden */
                transition: right 0.3s ease; /* Smooth transition */
            }
            #slidingMenu slidingMenu.open {
                right: 0; /* Slide in */
            }
            #top {
                background-color: red;
            }
            #middle {
                background-color: green;
            }
            #bottom {
                background-color: blue;
            }
        </style>
    </head>
    <body>
        <div id="top">
            <div id="slidingMenu"></div>
        </div>
        <div id="middle">
            <button id="toggleMenu" onClick='slidingMenu.classList.toggle("open")''>Toggle Menu</button>
        </div>
        <div id="bottom"></div>
    </body>
</html>

When you view this page on a tablet/mobile screen, the page scrolls beyond the bottom of the <body> element. If you open dev tools and enable device preview mode and then resize the viewport, all hell breaks loose. This only happens when the menu is closed. The moment you open the menu, everything sorts itself out.

https://preview.redd.it/lcj5ycfy345h1.png?width=1918&format=png&auto=webp&s=ad3a2ddb0813c4f8f7da28c7b018498be1c5ff26

https://preview.redd.it/xrtiuuro445h1.png?width=1917&format=png&auto=webp&s=f705b27e2c2dc7635e1ff1f783c917dd0c334f28

What am I missing?
#dev #programming #technology
source
A simple meme generator that lets users upload a picture can be built with a lightweight tech stack and minimal infrastructure. Here's how you could approach it:

🧰 Core Features

Image Upload
Users can drag-and-drop or select an image from their device.
Use HTML5 <input type="file"> or a drag-and-drop library like Dropzone.js..

Text Overlay
Add top and bottom text (classic meme style).
Font color: Black or white
Fonts: Arial, Impact, Comic Sans, Times New Roman
Live Preview : Real-time rendering of the meme.


Preview & Download
Show a live preview of the meme.
Let users download the final image as PNG or JPEG and/or share on social media (creates a post on tangled)

🧑‍💻 Suggested Tech Stack

Frontend
HTML, CSS, JavaScript (React or Vanilla JS)

Image Editing
HTML5 Canvas API or Fabric.js

Backend (optional)
Node.js , Flask, or serverless (e.g., Vercel Functions)

Hosting
GitHub Pages, Netlify, or Vercel

🗃️ Do You Need a Database?
No, not necessarily. If you're not storing user data or memes long-term, you can skip a database entirely.
Temporary storage: Use in-browser memory or session storage.
Cloud upload (optional): If you want to let users share memes, you could integrate:
Imgur API (free image hosting)
Cloudinary (image hosting + transformations)

Html
1. Image Upload
<input type="file" accept="image/*" id="upload" />

Html
2. Text Input & Styling
<input type="text" placeholder="Top Text" id="topText" />
<input type="text" placeholder="Bottom Text" id="bottomText" />
<select id="fontSelect">
<option value="Impact">Impact</option>
<option value="Arial">Arial</option>
<option value="Comic Sans MS">Comic Sans</option>
</select>
<select id="colorSelect">
<option value="white">White</option>
<option value="black">Black</option>
</select>

javascript
3. Canvas Rendering
const canvas = document.getElementById( 'memeCanvas');
const ctx = canvas.getContext( '2d');
// draw image, then draw text with selected font and color

javascript
4. Download Button
const link = document.createElement( 'a');
link.download = 'meme.png';
link.href = canvas.toDataURL();
link.click();

5. Social Sharing
Use platform-specific share URLs:
Twitter : https://twitter.com/intent/tweet?text=Check _out_my_meme!&url=YOUR_IMAGE_URL
Facebook : https://www.facebook.com/sharer/sharer.php?u=YOUR_IMAGE_URL
Tangled : Embedded post creation/redirection to post creation with the image loaded

Meme Generator Demo Template:

Frontend : HTML + CSS + JavaScript
Image Rendering : HTML5 Canvas
Hosting : GitHub Pages (free and easy)

Files:
/meme-generator
│
├── index.html
├── style.css
└── script.js

🧩 Key Features
Upload image
Add top and bottom text
Choose font (Impact, Arial, Comic Sans, Times New Roman)
Choose text color (Black or White)
Download meme
Share to Twitter or Facebook Tangled

🚀 Hosting Instructions (GitHub Pages)
Create a GitHub repo called meme-generator.
Push your files (index.html, style.css , script.js) to the repo.
Go to Settings > Pages and set the source to main branch and /root.
Your meme generator will be live at https://yourusername.github.io/meme-generator.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Meme Generator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Meme Generator</h1>
<input type="file" id="upload" accept="image/*" />
<input type="text" id="topText" placeholder="Top Text" />
<input type="text" id="bottomText" placeholder="Bottom Text" />
<select id="fontSelect">
<option value="Impact">Impact</option>
<option value="Arial">Arial</option>
<option value="Comic Sans MS">Comic Sans</option>
<option value="Times New Roman">Times New Roman</option>
</select>
<select id="colorSelect">
<option value="white">White</option>
<option value="black">Black</option>
</select>
<canvas id="memeCanvas" width="500" height="500"></canvas>
<button id="downloadBtn">Download Meme</button>
<button id="shareTwitter">Share on Twitter</button>
<button id="shareFacebook">Share on Facebook</button>
<script src="script.js"></script>
</body>
</html>

style.css
body {
font-family: sans-serif;
text-align: center;
background: #f0f0f0 ;
padding: 20px;
}

input, select, button {
margin: 10px;
padding: 8px;
font-size: 16px;
}

canvas {
border: 2px solid #333 ;
margin-top: 20px;
}

script.js
const upload = document.getElementById( 'upload');
const topText = document.getElementById( 'topText');
const bottomText = document.getElementById( 'bottomText');
const fontSelect = document.getElementById( 'fontSelect');
const colorSelect = document.getElementById( 'colorSelect');
const canvas = document.getElementById( 'memeCanvas');
const ctx = canvas.getContext( '2d');

let image = new Image();

upload.addEventListener( 'change', (e) => {
const reader = new FileReader();
reader.onload = function () {
image.src = reader.result;
};
reader.readAsDataURL(e.target.files[0]);
});

image.onload = () => drawMeme();
[topText, bottomText, fontSelect, colorSelect].forEach(el =>
el.addEventListener( 'input', drawMeme)
);

function drawMeme() {
ctx.clearRect(0 , 0, canvas.width , canvas.height);
ctx.drawImage(image , 0, 0, canvas.width , canvas.height);
ctx.font = `40px ${fontSelect.value}`;
ctx.fillStyle = colorSelect.value;
ctx.textAlign = 'center';
ctx.lineWidth = 2;
ctx.strokeStyle = colorSelect.value === 'white' ? 'black' : 'white';

ctx.strokeText(topText.value.toUpperCase() , canvas.width / 2, 50);
ctx.fillText(topText.value.toUpperCase() , canvas.width / 2, 50);
ctx.strokeText(bottomText.value.toUpperCase() , canvas.width / 2, canvas.height - 20);
ctx.fillText(bottomText.value.toUpperCase() , canvas.width / 2, canvas.height - 20);
}

document.getElementById( 'downloadBtn').addEventListener('click', () => {
const link = document.createElement( 'a');
link.download = 'meme.png';
link.href = canvas.toDataURL();
link.click();
});

document.getElementById( 'shareTwitter').addEventListener('click', () => {
const tweetText = encodeURIComponent("Check out my meme!");
const tweetUrl = encodeURIComponent(window.location.href);
window.open(`https://twitter.com/intent/tweet?text=${tweetText}&url=${tweetUrl}` , '_blank');
});

document.getElementById( 'shareFacebook').addEventListener('click', () => {
const fbUrl = encodeURIComponent(window.location.href);
window.open(`https://www.facebook.com/sharer/sharer.php?u=${fbUrl}` , '_blank');
});

Once you’ve uploaded these files to your GitHub repo, enable GitHub Pages in the repo settings and your meme generator will be live!
loved
1
shocked
1
import React, { useState, useEffect, useCallback } from 'react';
import './PharaohsTrivia.css'; // For professional styling

// Assuming image imports are correctly configured:
import Coin20 from './images/enhanced_1762428323273.jpg';
import Coin50 from './images/enhanced_1762430071978.jpg';
import Coin100 from './images/unnamed (9).jpg';

// --- Game Data: Questions and Levels Configuration ---
const QUIZ_DATA = [
// Level 1: Easy Questions (Reward: 20 EGP)
{
question: "What is the capital city of Egypt?",
options: ["Alexandria", "Luxor", "Cairo", "Aswan"],
answer: "Cairo"
},
{
question: "Which river is often called the 'Lifeblood' of Egypt?",
options: ["Tigris", "Euphrates", "Nile", "Jordan"],
answer: "Nile"
},
];

const LEVEL_CONFIG = [
{
levelId: 1,
minScore: 1, // Need at least 1 correct answer to pass Level 1
reward: { value: 20, image: Coin20, message: "Abu Simbel's Treasure!" }
},
{
levelId: 2,
minScore: 2, // Need at least 2 correct answers to pass Level 2
reward: { value: 50, image: Coin50, message: "Giza Pyramids' Wealth!" }
},
{
levelId: 3,
minScore: 3,
reward: { value: 100, image: Coin100, message: "Grand Museum's Gold!" }
},
];

// --- Main Component ---
const PharaohsTrivia = () => {
const [currentQuestion, setCurrentQuestion] = useState(0);
const [score, setScore] = useState(0);
const [gameState, setGameState] = useState('playing'); // playing, levelComplete, finished
const [levelIndex, setLevelIndex] = useState(0);
const [isAnswerSelected, setIsAnswerSelected] = useState(false);
const [selectedOption, setSelectedOption] = useState(null);

const currentLevelConfig = LEVEL_CONFIG[levelIndex];
const totalQuestions = QUIZ_DATA.length;

// Handles the player selecting an answer
const handleAnswerSelect = useCallback((option) => {
if (isAnswerSelected) return;

setIsAnswerSelected(true);
setSelectedOption(option);

// Check if the answer is correct
if (option === QUIZ_DATA[currentQuestion].answer) {
setScore(prevScore => prevScore + 1);
}

}, [currentQuestion, isAnswerSelected]);

// Handles moving to the next question or checking level completion
const handleNext = useCallback(() => {
if (!isAnswerSelected) return; // Must select an answer first

const nextQuestion = currentQuestion + 1;

if (nextQuestion < totalQuestions) {
// Go to next question
setCurrentQuestion(nextQuestion);
setIsAnswerSelected(false);
setSelectedOption(null);
} else {
// All questions for the current level answered
if (score >= currentLevelConfig.minScore) {
setGameState('levelComplete');
} else {
setGameState('lost'); // Implement a specific loss state if needed
}
}
}, [currentQuestion, totalQuestions, score, currentLevelConfig, isAnswerSelected]);

// Handles moving to the next level
const handleNextLevel = useCallback(() => {
const nextLevelIndex = levelIndex + 1;

if (nextLevelIndex < LEVEL_CONFIG.length) {
// Reset and advance to the next level
setLevelIndex(nextLevelIndex);
setCurrentQuestion(0);
setScore(0);
setGameState('playing');
setIsAnswerSelected(false);
setSelectedOption(null);
} else {
// Game over, all levels finished
setGameState('finished');
}
}, [levelIndex]);

// Helper function for styling options
const getOptionStyle = (option) => {
if (!isAnswerSelected || selectedOption !== option) {
return {}; // Default style
}

// Once an answer is selected
if (option === QUIZ_DATA[currentQuestion].answer) {
return styles.correctOption;
} else {
return styles.incorrectOption;
}
};

// ******************
// UI Rendering Logic
// ******************

// Level Complete Screen (Reward screen)
if (gameState === 'levelComplete') {
const reward = currentLevelConfig.reward;
return (
<div style={styles.container}>
<div style={styles.resultModal}>
<h2 style={styles.winTitle}>Level {currentLevelConfig.levelId} Complete! ✅</h2>
<p style={styles.rewardText}>You earned a **{reward.value} EGP** reward!</p>
<img
src={reward.image}
alt={`Reward ${reward.value} EGP`}
style={styles.rewardImage}
/>
<button onClick={handleNextLevel} style={styles.nextButton}>
{levelIndex === LEVEL_CONFIG.length - 1 ? 'Finish Game' : 'Continue to Next Level'}
</button>
</div>
</div>
);
}

// Game Finished Screen
if (gameState === 'finished') {
return (
<div style={styles.container}>
<div style={{...styles.resultModal, backgroundColor: '#FFD700', color: '#162447'}}>
<h2 style={styles.finishTitle}>🏆 Congratulations! You are a Trivia Master!</h2>
<p>All rewards collected. Thanks for playing!</p>
</div>
</div>
);
}

// Main Playing Screen
return (
<div style={styles.container}>
<h1 style={styles.title}>Pharaoh's Fortune Trivia</h1>

{/* Progress Bar and Stats */}
<div style={styles.statsBar}>
<p>Level: {currentLevelConfig.levelId}</p>
<p>Question: {currentQuestion + 1} / {totalQuestions}</p>
<p>Score: {score}</p>
</div>

<div style={styles.quizArea}>
<p style={styles.questionText}>
{QUIZ_DATA[currentQuestion].question}
</p>

{/* Answer Options */}
<div style={styles.optionsContainer}>
{QUIZ_DATA[currentQuestion].options.map((option, index) => (
<button
key={index}
style={{ ...styles.optionButton, ...getOptionStyle(option) }}
onClick={() => handleAnswerSelect(option)}
disabled={isAnswerSelected}
>
{option}
</button>
))}
</div>

{/* Next Button */}
{isAnswerSelected && (
<button
onClick={handleNext}
style={styles.nextButton}
>
{currentQuestion === totalQuestions - 1 ? 'Check Results' : 'Next Question'}
</button>
)}
</div>
</div>
);
};

// --- Styling ---
const styles = {
container: {
fontFamily: 'Roboto, sans-serif',
textAlign: 'center',
padding: '20px',
background: 'linear-gradient(135deg, #4b0082 0%, #8a2be2 100%)', // Purple/Violet Theme
minHeight: '100vh',
color: 'white',
},
title: {
color: '#FFD700', // Gold
marginBottom: '20px',
fontSize: '2.5em',
textShadow: '2px 2px #000 ',
},
statsBar: {
display: 'flex',
justifyContent: 'space-around',
backgroundColor: 'rgba(255, 255, 255, 0.15)',
padding: '10px',
borderRadius: '8px',
marginBottom: '30px',
},
quizArea: {
backgroundColor: 'rgba(255, 255, 255, 0.95)',
padding: '40px',
borderRadius: '15px',
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.5)',
maxWidth: '550px',
margin: '0 auto',
color: '#333',
},
questionText: {
fontSize: '1.5em',
marginBottom: '30px',
color: '#4b0082',
fontWeight: 'bold',
},
optionsContainer: {
display: 'flex',
flexDirection: 'column',
gap: '15px',
marginBottom: '20px',
},
optionButton: {
padding: '15px 20px',
fontSize: '1.1em',
cursor: 'pointer',
backgroundColor: '#9370DB', // Medium Purple
color: 'white',
border: '3px solid #6A5ACD ',
borderRadius: '30px',
fontWeight: 'bold',
transition: 'background-color 0.3s',
},
correctOption: {
backgroundColor: '#4CAF50', // Green
border: '3px solid #2E8B57 ',
},
incorrectOption: {
backgroundColor: '#F44336', // Red
border: '3px solid #B22222 ',
},
nextButton: {
padding: '12px 25px',
fontSize: '1.1em',
cursor: 'pointer',
backgroundColor: '#FFD700', // Gold
color: '#4b0082',
border: 'none',
borderRadius: '25px',
fontWeight: 'bold',
marginTop: '20px',
},
// Result/Reward Modal Styles
resultModal: {
maxWidth: '500px',
margin: '50px auto',
padding: '40px',
borderRadius: '20px',
backgroundColor: '#8A2BE2',
boxShadow: '0 10px 25px rgba(0, 0, 0, 0.7)',
color: 'white',
},
winTitle: {
fontSize: '2.5em',
color: '#EAF2F8',
},
rewardText: {
fontSize: '1.4em',
margin: '15px 0',
fontWeight: 'bold',
color: '#FFD700',
},
rewardImage: {
width: '100%',
maxWidth: '400px',
height: 'auto',
borderRadius: '10px',
border: '5px solid #FFD700 ',
margin: '20px 0',
},
finishTitle: {
fontSize: '2.5em',
}
};

export default PharaohsTrivia;
shocked
1