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>