Picta rebrand: remove all clone/Imgur UI strings, wire /logo.svg, simplify upload (Share CTA, full-page drag overlay, body-scroll lock), scrollbar elimination (hidden tracks, overflow-x hidden, break-word, modal internal scroll)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# imgur-clone
|
||||
# Picta
|
||||
|
||||
Imgur-style image sharing app: dark-mode masonry gallery, drag-and-drop / click / **clipboard paste** uploads, 4K re-encoding, voting, and threaded comments.
|
||||
Image sharing app: dark-mode masonry gallery, drag-and-drop / click / **clipboard paste** uploads, 4K re-encoding, voting, and threaded comments.
|
||||
|
||||
## Stack
|
||||
- **Backend**: Node 20 (Alpine), Express, multer (multipart), sharp (image pipeline), cors
|
||||
@@ -40,6 +40,8 @@ docker build -t imgur-clone:latest .
|
||||
docker run -d --name imgur-clone --restart unless-stopped -p 3001:3000 imgur-clone:latest
|
||||
```
|
||||
|
||||
> Note: The Docker image and container names remain `imgur-clone` for deployment compatibility.
|
||||
|
||||
## Security
|
||||
- Runtime image strips npm/corepack (only `node server.js` runs at runtime)
|
||||
- `apk upgrade` on every build for base-image CVE coverage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MCP Deployment & Test Summary Report
|
||||
|
||||
## Imgur Clone — Pipeline Validation Project
|
||||
## Picta — Pipeline Validation Project
|
||||
|
||||
---
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
| **Container Port** | `3001:3000` (host:container) |
|
||||
| **Backend Port** | `3000` (internal) |
|
||||
| **Caddy Reverse Proxy** | `192.168.0.123:2080/2443` → `192.168.0.123:3001` |
|
||||
| **Gitea Repo** | `https://gitea.krisforbes.ca/krisf/imgur-clone` |
|
||||
| **Gitea Repo** | `https://gitea.krisforbes.ca/krisf/imgur-clone` (Picta) |
|
||||
| **Build Engine** | Docker BuildKit (node:20-alpine) |
|
||||
| **Build Time** | ~2s (cached layers) |
|
||||
| **Container Status** | `running` (healthy) |
|
||||
@@ -146,7 +146,7 @@ User → Cloudflare DNS → dyn.krisforbes.ca → Caddy (2443) → imgur-clone:3
|
||||
|
||||
### Files in Gitea Repository
|
||||
```
|
||||
imgur-clone/
|
||||
imgur-clone/ (Picta)
|
||||
├── .gitignore
|
||||
├── Dockerfile
|
||||
├── README.md
|
||||
|
||||
+73
-2
@@ -62,6 +62,10 @@
|
||||
--text-3xl: 24px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
background: var(--bg-primary);
|
||||
@@ -73,6 +77,19 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Hide scrollbar tracks but keep scrollability */
|
||||
* {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
display: none; /* WebKit */
|
||||
}
|
||||
|
||||
/* ===== Focus Visible ===== */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent-green);
|
||||
@@ -116,8 +133,11 @@ body {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 28px;
|
||||
.logo-img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
@@ -304,6 +324,8 @@ body {
|
||||
|
||||
.post-card-image {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 4/3;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
@@ -324,6 +346,8 @@ body {
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
letter-spacing: -0.01em;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.post-card-stats {
|
||||
@@ -410,9 +434,14 @@ body {
|
||||
max-width: 560px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: var(--shadow-lg);
|
||||
animation: modalIn 200ms ease;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.modal::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes modalIn {
|
||||
@@ -652,6 +681,9 @@ body {
|
||||
|
||||
.post-image-container img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
border-radius: var(--radius);
|
||||
cursor: zoom-in;
|
||||
box-shadow: var(--shadow);
|
||||
@@ -815,6 +847,8 @@ body {
|
||||
font-size: var(--text-base);
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.comment-time {
|
||||
@@ -922,6 +956,43 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Full-Page Drag Overlay ===== */
|
||||
.drag-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(20, 20, 28, 0.92);
|
||||
backdrop-filter: blur(6px);
|
||||
z-index: 5000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.drag-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.drag-overlay-inner {
|
||||
text-align: center;
|
||||
padding: var(--space-10) var(--space-9);
|
||||
border: 3px dashed var(--accent-green);
|
||||
border-radius: var(--radius-xl);
|
||||
background: rgba(27, 183, 110, 0.06);
|
||||
}
|
||||
|
||||
.drag-overlay-icon {
|
||||
font-size: 56px;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.drag-overlay-text {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: 700;
|
||||
color: var(--accent-green);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* ===== Responsive ===== */
|
||||
@media (max-width: 1024px) {
|
||||
.post-detail-grid {
|
||||
|
||||
+15
-8
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Imgur Clone - Share Your World</title>
|
||||
<title>Picta — Share Your World</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
@@ -12,8 +12,8 @@
|
||||
<header class="header">
|
||||
<div class="header-inner">
|
||||
<div class="logo" onclick="navigateHome()">
|
||||
<span class="logo-icon">📷</span>
|
||||
<span class="logo-text">Imgur Clone</span>
|
||||
<img src="/logo.svg" class="logo-img" alt="Picta">
|
||||
<span class="logo-text">Picta</span>
|
||||
</div>
|
||||
<div class="search-bar">
|
||||
<input type="text" id="searchInput" placeholder="Search images..." oninput="filterPosts()">
|
||||
@@ -57,16 +57,15 @@
|
||||
<div class="modal-overlay" id="uploadModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h2>Create New Post</h2>
|
||||
<h2>Share</h2>
|
||||
<button class="modal-close" onclick="closeUploadModal()" aria-label="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="upload-zone" id="uploadZone">
|
||||
<div class="upload-prompt" id="uploadPrompt">
|
||||
<div class="upload-icon">📁</div>
|
||||
<p class="upload-main-text">Drag & drop an image here</p>
|
||||
<p class="upload-hint">or click to browse</p>
|
||||
<p class="upload-paste-hint">or paste an image with <kbd>Ctrl</kbd>+<kbd>V</kbd></p>
|
||||
<p class="upload-main-text">Drop an image, click to browse, or paste</p>
|
||||
<p class="upload-paste-hint"><kbd>Ctrl</kbd>+<kbd>V</kbd> works anywhere</p>
|
||||
<p class="upload-formats">JPEG, PNG, GIF, WebP (max 20MB)</p>
|
||||
</div>
|
||||
<input type="file" id="fileInput" accept="image/jpeg,image/png,image/gif,image/webp" hidden>
|
||||
@@ -92,7 +91,7 @@
|
||||
<input type="text" id="postAuthor" placeholder="Anonymous" maxlength="30">
|
||||
</div>
|
||||
<div id="uploadError" class="error-message" style="display:none;"></div>
|
||||
<button class="btn-submit" id="submitBtn" onclick="submitPost()">Upload Post</button>
|
||||
<button class="btn-submit" id="submitBtn" onclick="submitPost()">Share</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,6 +140,14 @@
|
||||
<img id="fullscreenImage" src="" alt="">
|
||||
</div>
|
||||
|
||||
<!-- Full-page Drag Overlay -->
|
||||
<div class="drag-overlay" id="dragOverlay">
|
||||
<div class="drag-overlay-inner">
|
||||
<div class="drag-overlay-icon">📥</div>
|
||||
<p class="drag-overlay-text">Drop to share</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notifications -->
|
||||
<div class="toast-container" id="toastContainer"></div>
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ let currentSort = 'new';
|
||||
let currentPosts = [];
|
||||
let currentPostId = null;
|
||||
let selectedFile = null;
|
||||
let dragCounter = 0;
|
||||
|
||||
// ===== Initialize =====
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadPosts();
|
||||
setupDragDrop();
|
||||
setupPageDrag();
|
||||
setupPasteUpload();
|
||||
});
|
||||
|
||||
@@ -141,6 +143,7 @@ function filterPosts() {
|
||||
// ===== Upload Modal =====
|
||||
function openUploadModal() {
|
||||
document.getElementById('uploadModal').classList.add('active');
|
||||
document.body.classList.add('modal-open');
|
||||
selectedFile = null;
|
||||
document.getElementById('uploadPreview').style.display = 'none';
|
||||
document.getElementById('uploadPrompt').style.display = '';
|
||||
@@ -153,6 +156,7 @@ function openUploadModal() {
|
||||
|
||||
function closeUploadModal() {
|
||||
document.getElementById('uploadModal').classList.remove('active');
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
|
||||
function setupDragDrop() {
|
||||
@@ -191,6 +195,43 @@ function setupDragDrop() {
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Full-Page Drag Overlay =====
|
||||
function setupPageDrag() {
|
||||
const overlay = document.getElementById('dragOverlay');
|
||||
|
||||
document.addEventListener('dragenter', (e) => {
|
||||
e.preventDefault();
|
||||
dragCounter++;
|
||||
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) {
|
||||
overlay.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dragCounter--;
|
||||
if (dragCounter <= 0) {
|
||||
dragCounter = 0;
|
||||
overlay.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dragCounter = 0;
|
||||
overlay.classList.remove('active');
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
openUploadModal();
|
||||
handleFile(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleFile(file) {
|
||||
// Validate
|
||||
const allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
@@ -213,6 +254,8 @@ function handleFile(file) {
|
||||
document.getElementById('previewImage').src = e.target.result;
|
||||
document.getElementById('uploadPreview').style.display = 'block';
|
||||
document.getElementById('uploadPrompt').style.display = 'none';
|
||||
// Auto-focus the Share button so user just presses Enter/clicks
|
||||
document.getElementById('submitBtn').focus();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
@@ -341,6 +384,8 @@ async function handlePasteUpload(blob) {
|
||||
document.getElementById('previewImage').src = e.target.result;
|
||||
document.getElementById('uploadPreview').style.display = 'block';
|
||||
document.getElementById('uploadPrompt').style.display = 'none';
|
||||
// Auto-focus the Share button
|
||||
document.getElementById('submitBtn').focus();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Seed data for Imgur Clone - 12 sample posts with placeholder images
|
||||
// Seed data for Picta — 12 sample posts with placeholder images
|
||||
// Using picsum.photos for realistic placeholder images
|
||||
|
||||
const posts = [
|
||||
|
||||
@@ -268,7 +268,7 @@ app.get('/api/health', (req, res) => {
|
||||
|
||||
// Initialize and start
|
||||
loadSeedData();
|
||||
console.log(`🚀 Imgur Clone running on port ${PORT}`);
|
||||
console.log(`🚀 Picta running on port ${PORT}`);
|
||||
console.log(`📦 ${posts.length} seed posts loaded`);
|
||||
|
||||
const server = app.listen(PORT, () => {
|
||||
|
||||
Reference in New Issue
Block a user