@leroysheep / MovieWebApp / commits / 9bdb43f076

added js and css

Lee Roy Stevenson committed May 22, 2025 at 00:58 UTC 9bdb43f07668d77ffe0b6f350306bd917fc2c25a
9 files changed +534 -13
app.py
+1 -1
index 3e4c86de12..3da542485d 100644 --- a/app.py +++ b/app.py @@ -211,5 +211,5 @@ def recommendation(user_id): if __name__ == "__main__": - app.run() + app.run(debug=True,host="127.0.0.1",port=5000)
static/action.js
+158
new file mode 100644 index 0000000000..8814fabfa1 --- /dev/null +++ b/static/action.js @@ -0,0 +1,158 @@ +// Wrap all DOM-dependent code inside a DOMContentLoaded event listener +document.addEventListener('DOMContentLoaded', function() { + // Get the canvas element and its 2D rendering context + const canvas = document.getElementById('pixelCanvas'); + const ctx = canvas.getContext('2d'); + + // Text content and properties for the canvas-drawn text + const mainText = "Dynamic Pixelmap Background"; + const subText = "Watch the colors change!"; + const thirdText = "This text has pixel-level contrast!"; + let fontSizeH1 = 48; // px - will be dynamically adjusted + let fontSizeP = 24; // px - will be dynamically adjusted + const fontName = "Inter, sans-serif"; // Ensure font is available or fallback + + /** + * Generates a random hexadecimal color code, biased towards darker shades (slightly brighter than before). + * + * @returns {string} A random hexadecimal color code (e.g., '#RRGGBB'). + */ + function generateRandomColor() { + // Limit random byte generation to a darker range (0-150) - adjusted for brighter background + const getRandomDarkByte = () => Math.floor(Math.random() * 151); // Max value 150 + + const r = getRandomDarkByte(); + const g = getRandomDarkByte(); + const b = getRandomDarkByte(); + + // Helper to convert a number to a two-digit hexadecimal string + const toHex = (c) => { + const hex = c.toString(16); + return hex.length === 1 ? '0' + hex : hex; // Ensure two digits (e.g., 'f' becomes '0f') + }; + + // Combine RGB components into a full hexadecimal color string + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; + } + + // Function to set up canvas dimensions and handle window resizing + function resizeCanvas() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + // Dynamically adjust font sizes based on screen width for responsiveness + if (canvas.width < 600) { + fontSizeH1 = 32; // Smaller font for small screens + fontSizeP = 18; + } else if (canvas.width < 900) { + fontSizeH1 = 40; // Medium font for medium screens + fontSizeP = 22; + } else { + fontSizeH1 = 48; // Original font size for large screens + fontSizeP = 24; + } + // No need to clear here, animate will clear and redraw + } + + // Define parameters for drawing background pixels + const pixelSize = 1; // The size (width and height) of each square pixel in pixels (remains 1) + const pixelsPerFrame = 500; // The number of random background pixels to draw in each animation frame (increased for quicker change) + + /** + * Draws a single randomly colored pixel on the canvas. + * The pixel's position is snapped to a grid defined by `pixelSize`. + */ + function drawRandomBackgroundPixel() { + const x = Math.floor(Math.random() * (canvas.width / pixelSize)) * pixelSize; + const y = Math.floor(Math.random() * (canvas.height / pixelSize)) * pixelSize; + const color = generateRandomColor(); + ctx.fillStyle = color; + ctx.fillRect(x, y, pixelSize, pixelSize); + } + + /** + * Draws text on the canvas with pixel-level contrast to the background. + * This function uses an off-screen canvas to get the text mask. + */ + function drawContrastingText() { + // Create an off-screen canvas for text rendering + const tempCanvas = document.createElement('canvas'); + const tempCtx = tempCanvas.getContext('2d'); + tempCanvas.width = canvas.width; + tempCanvas.height = canvas.height; + + // Set text properties for the off-screen canvas + tempCtx.font = `${fontSizeH1}px ${fontName}`; + tempCtx.textAlign = 'center'; + tempCtx.textBaseline = 'middle'; + tempCtx.fillStyle = 'black'; // Draw text in black to get a clear mask + + // Calculate text positions (centered) + const centerX = canvas.width / 2; + const centerY = canvas.height / 2; + // Adjust lineHeight based on current fontSizeH1 + const lineHeight = fontSizeH1 * 1.2; // Approximate line height + + // Draw text on the off-screen canvas + tempCtx.fillText(mainText, centerX, centerY - lineHeight); + tempCtx.font = `${fontSizeP}px ${fontName}`; + tempCtx.fillText(subText, centerX, centerY); + tempCtx.fillText(thirdText, centerX, centerY + lineHeight); + + // Get pixel data from the main canvas (current background) + const mainImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const mainPixels = mainImageData.data; + + // Get pixel data from the off-screen text canvas (text mask) + const tempImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height); + const tempPixels = tempImageData.data; + + // Loop through all pixels + for (let i = 0; i < mainPixels.length; i += 4) { + // Check if the pixel on the temporary canvas is part of the text (opaque black) + if (tempPixels[i + 3] > 0) { // If this pixel is part of the text + // Get the background color at this pixel position from main canvas + const bgR = mainPixels[i]; + const bgG = mainPixels[i + 1]; + const bgB = mainPixels[i + 2]; + + // Calculate the inverse color for contrast. + mainPixels[i] = 255 - bgR; + mainPixels[i + 1] = 255 - bgG; + mainPixels[i + 2] = 255 - bgB; + } + } + + // Put the modified pixel data back onto the main canvas + ctx.putImageData(mainImageData, 0, 0); + } + + /** + * The main animation loop. + * It draws a set number of random background pixels per frame and then redraws the contrasting text. + */ + function animate() { + // Draw multiple background pixels in each frame (accumulating effect) + for (let i = 0; i < pixelsPerFrame; i++) { + drawRandomBackgroundPixel(); + } + + // Draw the contrasting text on top of the background + drawContrastingText(); + + // Request the next animation frame, creating a smooth loop + requestAnimationFrame(animate); + } + + // Initial setup and animation start + resizeCanvas(); // Set the initial canvas size and font sizes + animate(); // Start the pixel drawing animation + + // Add an event listener to resize the canvas whenever the window is resized + window.addEventListener('resize', () => { + resizeCanvas(); // Recalculate canvas size and font sizes + // On resize, you might want to clear the canvas and immediately redraw text + // to avoid temporary distortions before the pixels fill in. + // ctx.clearRect(0, 0, canvas.width, canvas.height); // Optional: clear on resize + }); +});
static/homepic.jpg
new file mode 100644 index 0000000000..0a52e59c5a Binary files /dev/null and b/static/homepic.jpg differ
static/style.css
+317
new file mode 100644 index 0000000000..d6622c6421 --- /dev/null +++ b/static/style.css @@ -0,0 +1,317 @@ +body { + font-family: "Inter", sans-serif; + margin: 0; + overflow-x: hidden; /* Prevent horizontal scrollbar on body */ + overflow-y: auto; /* Enable vertical scrolling for the entire page */ + display: flex; /* Use flexbox to center canvas content conceptually */ + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: #000; /* Fallback for body background */ +} + +canvas { + display: block; /* Remove extra space below canvas */ + position: fixed; /* Use fixed to keep it in viewport regardless of scroll */ + top: 0; + left: 0; + z-index: -1; /* Place the canvas behind other content */ + width: 100%; /* Ensure canvas covers full width */ + height: 100%; /* Ensure canvas covers full height */ +} + +/* Styles for the new main content container */ +.main-content-container { + position: relative; /* Ensure content is positioned above the canvas */ + z-index: 1; /* Bring content to the front */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.2); /* Semi-transparent dark background for contrast */ + border-radius: 12px; /* Rounded corners */ + padding: 30px; /* Padding inside the container */ + margin: 20px auto; /* Changed margin to auto for horizontal centering */ + width: 90%; /* Make it more fluid on smaller screens */ + max-width: 700px; /* Limit width for readability on larger screens */ + box-shadow: 0 8px 16px rgba(0,0,0,0.4); /* More prominent shadow */ + color: white; /* Default text color for content */ + text-align: center; + backdrop-filter: blur(3px); /* Optional: subtle blur effect for content behind */ + box-sizing: border-box; /* Include padding in the element's total width */ +} + +.content-title { + font-size: 2.5rem; /* text-4xl equivalent */ + font-weight: 700; /* font-bold equivalent */ + margin-bottom: 1rem; + color: #E0E0E0; /* Slightly off-white for title */ +} + +.content-paragraph { + font-size: 1.1rem; /* text-lg equivalent */ + line-height: 1.6; + margin-bottom: 1rem; + color: #F0F0F0; /* Slightly off-white for paragraph */ +} + +.content-image { + max-width: 200px; /* Ensure image is responsive */ + height: auto; + border-radius: 8px; + margin-top: 1.5rem; + margin-bottom: 1.5rem; + box-shadow: 0 4px 8px rgba(0,0,0,0.2); +} + +/* Navigation */ +.topnav { + background-color: black; + display: flex; + gap: 15px; + padding: 12px 20px; + border-bottom: 2px solid darkred; + justify-content: center; +} + +.topnav a { + color: darkgreen; + text-decoration: none; + padding: 8px 14px; + border-radius: 8px; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.topnav a.active, +.topnav a:hover { + background-color: darkgreen; + color: darkred; +} + +/* Title styling */ +h1.title { + text-align: center; + margin: 20px 0; + color: darkgreen; +} + +/* Status message */ +.status-message p { + text-align: center; + font-weight: bold; + color: darkgreen; + margin-bottom: 20px; +} + +/* Carousel container with horizontal scroll */ +.css-carousel { + display: flex; + overflow-x: auto; /* Enable horizontal scrolling */ + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + padding-bottom: 12px; /* Keep bottom padding for scrollbar */ + scrollbar-width: thin; + scrollbar-color: darkred black; + border: 2px solid darkred; /* Full border for clearer limits */ + scroll-behavior: smooth; + color: darkgreen; + width: 100%; /* Ensure it takes full width of its parent */ + box-sizing: border-box; /* Ensure padding is included in width */ + gap: 20px; /* Space between carousel items */ + + /* New: Dynamic scroll-padding for consistent edge snapping */ + /* This ensures the item snaps to the start, but we add padding to the scroll container itself */ + /* The padding is calculated to center the item based on its flex-basis */ + scroll-padding-inline: calc(50% - (250px / 2)); /* 50% of carousel width - half of default item width */ +} + +/* Scrollbar styling */ +.css-carousel::-webkit-scrollbar { + height: 8px; +} + +.css-carousel::-webkit-scrollbar-track { + background: black; +} + +.css-carousel::-webkit-scrollbar-thumb { + background-color: darkred; + border-radius: 10px; + border: 2px solid black; +} + +/* Scroll instruction text */ +.scroll-instruction { + color: darkgreen; + font-size: 0.9rem; + text-align: center; + margin-top: 6px; + font-style: italic; + user-select: none; +} + +/* Individual movie card */ +.carousel-item { + flex: 0 0 250px; /* Fixed width for items, no shrinking/growing by default */ + scroll-snap-align: center; /* Center the item when snapped */ + background-color: #111; + border: 1px solid darkgreen; + border-radius: 12px; + padding: 15px; + color: darkgreen; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + transition: transform 0.3s ease; +} + +/* Slight scale on hover */ +.carousel-item:hover { + transform: scale(1.05); + border-color: darkred; +} + +/* Movie image */ +.carousel-item img { + border-radius: 10px; + max-width: 100%; + height: auto; /* Ensures aspect ratio is maintained */ + margin-bottom: 10px; + object-fit: contain; /* Ensures entire image is visible without distortion */ +} + +/* Movie info list */ +.carousel-item ul { + list-style: none; + padding-left: 0; + width: 100%; + margin: 0; + color: darkgreen; +} + +.carousel-item ul li { + margin: 5px 0; + font-size: 0.9rem; + color: darkgreen; +} + +.carousel-item ul li a { + color: darkgreen; + text-decoration: none; + transition: color 0.3s ease; +} + +.carousel-item ul li a:hover { + color: darkred; +} + +/* Delete button styling */ +button, +.back_button a{ + background-color: darkred; + color: darkgreen; + border: none; + border-radius: 8px; + padding: 8px 12px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.3s ease, color 0.3s ease; +} + +button:hover, +.back_button a:hover { + background-color: darkgreen; + color: darkred; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 20px; /* space between forms */ + justify-content: center; + margin-bottom: 20px; +} + +.action-form { + background-color: #1a0000; /* very dark red background */ + padding: 15px; + border-radius: 12px; + display: flex; + flex-direction: column; + align-items: center; + min-width: 220px; + box-sizing: border-box; +} + +.action-form label { + margin-bottom: 8px; + color: darkgreen; +} + +.action-form input[type="text"] { + padding: 8px; + border-radius: 10px; + border: none; + margin-bottom: 12px; + width: 100%; + max-width: 200px; +} + +.action-form button { + background-color: darkred; + color: darkgreen; + border: none; + padding: 10px 16px; + border-radius: 12px; + cursor: pointer; + transition: background-color 0.3s ease; + width: 100%; + max-width: 200px; +} + +.action-form button:hover { + background-color: darkgreen; + color: black; +} +/* Responsive adjustments for smaller screens */ +@media (max-width: 768px) { + .main-content-container { + padding: 20px; + margin: 10px auto; /* Adjusted margin for centering */ + width: calc(100% - 40px); /* Adjust width to account for 20px left/right margin */ + } + .content-title { + font-size: 2rem; + } + .content-paragraph { + font-size: 1rem; + } + .carousel-item { + flex-basis: 80vp; /* Adjusted flex-basis for smaller screens */ + padding: 10px; + } + .css-carousel { + scroll-padding-inline: calc(50% - (80vp / 2)); /* Adjust for smaller item size */ + } +} + +/* Responsive tweaks */ +@media (max-width: 600px) { + .carousel-item { + flex-basis: 180px; /* Adjusted flex-basis */ + padding: 10px; + } + .css-carousel { + scroll-padding-inline: calc(50% - (180px / 2)); /* Adjust for smaller item size */ + } +} + +@media (max-width: 400px) { + .carousel-item { + flex-basis: 50vp; /* Adjusted flex-basis for very small screens */ + } + .css-carousel { + scroll-padding-inline: calc(50% - (60vp/ 2)); /* Adjust for smallest item size */ + } +}
templates/base.html
+5 -3
index b15a969065..ee90b6b144 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,9 +4,13 @@ <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{% block title %}{% endblock %}</title> - <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" /> + <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" /> + <script src="{{ url_for('static', filename='action.js') }}"></script> </head> <body> +<canvas id="pixelCanvas"></canvas> + +<main class="main-content-container"> <header class="topnav"> {% set path = request.path %} <a href="/" class="{% if path == '/' %}active{% endif %}">Home</a> @@ -15,8 +19,6 @@ <a href="/users/new" class="{% if path == '/users/new' %}active{% endif %}">New User</a> <a href="/movies/new" class="{% if path == '/movies/new' %}active{% endif %}">New Movie</a> </header> - -<main> {% block content %}{% endblock %} </main> </body>
templates/home.html
+12 -5
index 912760d867..eaa274da77 100644 --- a/templates/home.html +++ b/templates/home.html @@ -2,15 +2,22 @@ {% block title %}Movie - WebApp{% endblock %} {% block content %} <h1 class="home_title">Welcome to the Movie Web App</h1> - <section class="home_section"> - <h2>Features</h2> - <ul> - <li>View all movies</li> + <section class="content-container"> + <h2 class="content-title">Features</h2> + <img src="/static/homepic.jpg" alt="Movie Image" class="content-image"> + + <p class="content-paragraph"> + This Movie Web App has the following features: + </p> + <ul class="content-paragraph"> + <li>View all <a href="/movies">movies</a></li> <li>View a single movie</li> - <li>View all users</li> + <li>View all <a href="/users">users</a></li> <li>View a single user</li> <li>View all user's movies list</li> <li>View a single user's movie</li> </ul> </section> + + {% endblock %} \ No newline at end of file
templates/index.html
+35
new file mode 100644 index 0000000000..1005c8952b --- /dev/null +++ b/templates/index.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Random Pixelmap Background with Content</title> + <link rel="stylesheet" href="static/style.css"> + + + +</head> + +<body> + + <canvas id="pixelCanvas"></canvas> + + <div class="main-content-container"> + <h2 class="content-title">Welcome to the Dynamic World!</h2> + <p class="content-paragraph"> + This is a customizable area where you can add your images and text. + The pixelmap background is active behind this content. + </p> + + <img src="https://m.media-amazon.com/images/M/MV5BNjFkNjdiZjUtNzUzNy00NWM5LWFlNDUtNTRiYmJiZWNiYjkwXkEyXkFqcGc@._V1_SX300.jpg" alt="Placeholder Image" class="content-image"> + + <p class="content-paragraph"> + Feel free to add more images, paragraphs, or any other HTML elements here. + The semi-transparent background ensures good readability. + </p> + </div> + +</body> + +</html> \ No newline at end of file
templates/movies.html
+3 -2
index ad33c0d08f..f86f491163 100644 --- a/templates/movies.html +++ b/templates/movies.html @@ -16,9 +16,10 @@ <div class="css-carousel" tabindex="0" aria-label="Movies carousel"> {% for movie in movies %} <div class="carousel-item" role="group" aria-roledescription="slide" aria-label="{{ loop.index }} of {{ movies|length }}"> - <img src="{{ movie.poster }}" alt="{{ movie.name }}" height="200" /> + <a href="/movies/{{ movie.id }}"> + <img src="{{ movie.poster }}" alt="{{ movie.name }}" height="200"></a> <ul> - <li><a href="/movies/{{ movie.id }}">{{ movie.name }} ({{ movie.year }})</a></li> + <li>{{ movie.name }} ({{ movie.year }})</a></li> <li> <form action="/movies" method="post"> <button type="submit" id="movie_id" name="movie_id" value="{{ movie.id }}">Delete</button>
templates/user_movies.html
+3 -2
index ac73865438..39e8de483a 100644 --- a/templates/user_movies.html +++ b/templates/user_movies.html @@ -35,9 +35,10 @@ <div class="css-carousel" tabindex="0" aria-label="User's movies carousel"> {% for movie in user_movies %} <div class="carousel-item" role="group" aria-roledescription="slide" aria-label="{{ loop.index }} of {{ user_movies|length }}"> - <img src="{{ movie.poster }}" alt="{{ movie.name }}" height="200"> + <a href="/users/{{ user.id }}/{{ movie.id }}"> + <img src="{{ movie.poster }}" alt="{{ movie.name }}" height="200"></a> <ul> - <li><a href="/users/{{ user.id }}/{{ movie.id }}">{{ movie.name }} ({{ movie.year }})</a></li> + <li>{{ movie.name }} ({{ movie.year }})</li> <li>Director: {{ movie.director }}</li> <li>Rating: {{ movie.rating }}</li> <li>User Rating: {{ movie.user_rating }}</li>