diff --git a/.gitignore b/.gitignore index 1507d31..1d91665 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ +.env node_modules/ public/uploads/ data/ *.log .DS_Store +.aider* diff --git a/README.md b/README.md new file mode 100644 index 0000000..e9abb2a --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# imgur-clone + +Imgur-style 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 +- **Frontend**: vanilla HTML/CSS/JS, CSS masonry grid, no build step +- **Storage**: local `public/uploads/` (in-memory + file persistence in `data/`) +- **Image processing**: any upload is re-encoded to JPEG q92, downscaled to max 3840px (4K) while preserving aspect ratio + +## API +| Method | Path | Description | +|---|---|---| +| GET | `/api/health` | health + counts | +| GET | `/api/posts?sort=new\|viral\|user\|top` | list posts | +| GET | `/api/posts/:id` | single post | +| POST | `/api/upload` | multipart (`image`, `title`, `tags`, `author`) | +| POST | `/api/posts/:id/vote` | JSON `{direction, userId}` | +| GET | `/api/comments/:postId` | list comments (flat, with parentId) | +| POST | `/api/comments/:postId` | JSON `{author, text, parentId?}` | + +## Frontend +- Masonry gallery with staggered card entrance animation +- Filter tabs: New, Most Viral, User Submitted, Top Today +- Upload via: file picker, drag-and-drop, or **Ctrl+V paste** (opens modal with preview, auto-submits) +- Floating action button (FAB) as the persistent upload CTA +- New posts scroll into view with a highlight pulse +- Post detail modal: voting, views, tags, threaded comments +- Focus-visible states and reduced-motion support + +## Run locally +```bash +npm ci +node server.js # http://localhost:3000 +``` + +## Docker +```bash +docker build -t imgur-clone:latest . +docker run -d --name imgur-clone --restart unless-stopped -p 3001:3000 imgur-clone:latest +``` + +## Security +- Runtime image strips npm/corepack (only `node server.js` runs at runtime) +- `apk upgrade` on every build for base-image CVE coverage +- Verified with Trivy: 0 vulnerabilities across all OS + node packages + +## Production +Deployed on Unraid (192.168.0.123), Caddy reverse proxy with auto-TLS → **https://imgur.krisforbes.ca** diff --git a/public/css/style.css b/public/css/style.css index cc3e205..034edcf 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -6,57 +6,114 @@ } :root { + /* Spacing scale (4px base) */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-7: 32px; + --space-8: 40px; + --space-9: 48px; + --space-10: 64px; + + /* Colors */ --bg-primary: #14141c; --bg-secondary: #1b1b2f; --bg-tertiary: #252540; --accent-green: #1bb76e; --accent-green-hover: #15995d; + --accent-green-glow: rgba(27, 183, 110, 0.25); --accent-red: #e74c3c; --accent-red-hover: #c0392b; --text-primary: #ffffff; --text-secondary: #a0a0b8; --text-muted: #6c6c85; --border-color: #2a2a45; - --shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5); + --border-subtle: rgba(42, 42, 69, 0.6); + + /* Shadows */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2); + --shadow: 0 2px 8px rgba(0, 0, 0, 0.25); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4); + --shadow-glow: 0 0 20px var(--accent-green-glow); + + /* Radii */ --radius: 8px; --radius-lg: 12px; - --transition: 0.2s ease; + --radius-xl: 16px; + --radius-full: 9999px; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition: 200ms ease; + --transition-slow: 300ms ease; + + /* Typography */ + --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --text-xs: 11px; + --text-sm: 13px; + --text-base: 14px; + --text-md: 15px; + --text-lg: 16px; + --text-xl: 18px; + --text-2xl: 20px; + --text-3xl: 24px; } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-family: var(--font-family); background: var(--bg-primary); color: var(--text-primary); min-height: 100vh; line-height: 1.6; + font-size: var(--text-base); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ===== Focus Visible ===== */ +:focus-visible { + outline: 2px solid var(--accent-green); + outline-offset: 2px; + border-radius: var(--radius); +} + +:focus:not(:focus-visible) { + outline: none; } /* ===== Header ===== */ .header { background: var(--bg-secondary); - border-bottom: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-subtle); position: sticky; top: 0; z-index: 100; - box-shadow: var(--shadow); + box-shadow: var(--shadow-sm); } .header-inner { max-width: 1400px; margin: 0 auto; - padding: 12px 24px; + padding: var(--space-3) var(--space-6); display: flex; align-items: center; - gap: 24px; + gap: var(--space-6); } .logo { display: flex; align-items: center; - gap: 8px; + gap: var(--space-2); cursor: pointer; flex-shrink: 0; + transition: opacity var(--transition-fast); +} + +.logo:hover { + opacity: 0.85; } .logo-icon { @@ -64,9 +121,10 @@ body { } .logo-text { - font-size: 20px; + font-size: var(--text-xl); font-weight: 700; color: var(--accent-green); + letter-spacing: -0.02em; } .search-bar { @@ -77,19 +135,21 @@ body { .search-bar input { flex: 1; - padding: 10px 16px; + padding: var(--space-2) var(--space-4); background: var(--bg-tertiary); border: 1px solid var(--border-color); border-right: none; border-radius: var(--radius) 0 0 var(--radius); color: var(--text-primary); - font-size: 14px; + font-size: var(--text-base); + font-family: var(--font-family); outline: none; - transition: border-color var(--transition); + transition: border-color var(--transition), box-shadow var(--transition); } .search-bar input:focus { border-color: var(--accent-green); + box-shadow: 0 0 0 3px var(--accent-green-glow); } .search-bar input::placeholder { @@ -97,36 +157,50 @@ body { } .search-bar button { - padding: 10px 16px; + padding: var(--space-2) var(--space-4); background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 0 var(--radius) var(--radius) 0; cursor: pointer; - font-size: 14px; + font-size: var(--text-base); + transition: background var(--transition-fast); +} + +.search-bar button:hover { + background: var(--bg-secondary); } .btn-new-post { - padding: 10px 20px; - background: var(--accent-green); + padding: var(--space-2) var(--space-5); + background: linear-gradient(135deg, var(--accent-green) 0%, #17a35e 100%); color: white; border: none; border-radius: var(--radius); font-weight: 600; - font-size: 14px; + font-size: var(--text-base); + font-family: var(--font-family); cursor: pointer; - transition: background var(--transition); + transition: all var(--transition); flex-shrink: 0; + box-shadow: var(--shadow-sm); } .btn-new-post:hover { - background: var(--accent-green-hover); + background: linear-gradient(135deg, var(--accent-green-hover) 0%, #128a50 100%); + box-shadow: var(--shadow-glow); + transform: translateY(-1px); +} + +.btn-new-post:active { + transform: translateY(0); + box-shadow: var(--shadow-sm); } /* ===== Filter Bar ===== */ .filter-bar { max-width: 1400px; margin: 0 auto; - padding: 16px 24px; + padding: var(--space-4) var(--space-6); display: flex; align-items: center; justify-content: space-between; @@ -134,48 +208,51 @@ body { .filter-tabs { display: flex; - gap: 8px; + gap: var(--space-2); } .filter-tab { - padding: 8px 16px; + padding: var(--space-2) var(--space-4); background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius); color: var(--text-secondary); cursor: pointer; - font-size: 13px; + font-size: var(--text-sm); font-weight: 500; - transition: all var(--transition); + font-family: var(--font-family); + transition: all var(--transition-fast); } .filter-tab:hover { border-color: var(--accent-green); color: var(--text-primary); + background: var(--bg-tertiary); } .filter-tab.active { background: var(--accent-green); border-color: var(--accent-green); color: white; + box-shadow: var(--shadow-glow); } .post-count { color: var(--text-muted); - font-size: 13px; + font-size: var(--text-sm); } /* ===== Gallery Grid ===== */ .gallery-container { max-width: 1400px; margin: 0 auto; - padding: 0 24px 48px; + padding: 0 var(--space-6) var(--space-9); } .gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 16px; + gap: var(--space-4); } /* ===== Post Card ===== */ @@ -184,13 +261,45 @@ body { border-radius: var(--radius-lg); overflow: hidden; cursor: pointer; - transition: transform var(--transition), box-shadow var(--transition); - border: 1px solid var(--border-color); + transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition); + border: 1px solid var(--border-subtle); + animation: cardEntrance 300ms ease both; +} + +@keyframes cardEntrance { + from { + opacity: 0; + transform: translateY(12px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } } .post-card:hover { - transform: translateY(-2px); + transform: translateY(-3px); box-shadow: var(--shadow-lg); + border-color: var(--border-color); +} + +.post-card.highlight-new { + animation: highlightPulse 2s ease; +} + +@keyframes highlightPulse { + 0% { + box-shadow: 0 0 0 3px var(--accent-green), var(--shadow-lg); + border-color: var(--accent-green); + } + 50% { + box-shadow: 0 0 0 6px var(--accent-green-glow), var(--shadow-lg); + border-color: var(--accent-green); + } + 100% { + box-shadow: var(--shadow); + border-color: var(--border-subtle); + } } .post-card-image { @@ -202,24 +311,26 @@ body { } .post-card-body { - padding: 12px 16px; + padding: var(--space-3) var(--space-4); } .post-card-title { - font-size: 14px; + font-size: var(--text-base); font-weight: 600; - margin-bottom: 8px; + margin-bottom: var(--space-2); + line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; + letter-spacing: -0.01em; } .post-card-stats { display: flex; align-items: center; - gap: 12px; - font-size: 12px; + gap: var(--space-3); + font-size: var(--text-xs); color: var(--text-secondary); } @@ -231,22 +342,23 @@ body { .post-card-tags { display: flex; flex-wrap: wrap; - gap: 4px; - margin-top: 8px; + gap: var(--space-1); + margin-top: var(--space-2); } .tag-pill { - padding: 2px 8px; + padding: 2px var(--space-2); background: var(--bg-tertiary); - border-radius: 12px; - font-size: 11px; + border-radius: var(--radius-full); + font-size: var(--text-xs); color: var(--text-muted); + border: 1px solid var(--border-subtle); } /* ===== Loading ===== */ .loading { text-align: center; - padding: 48px; + padding: var(--space-9); color: var(--text-muted); } @@ -257,7 +369,14 @@ body { border-top-color: var(--accent-green); border-radius: 50%; animation: spin 0.8s linear infinite; - margin: 0 auto 16px; + margin: 0 auto var(--space-4); +} + +.spinner-sm { + width: 28px; + height: 28px; + border-width: 2px; + margin: 0 auto var(--space-3); } @keyframes spin { @@ -273,10 +392,11 @@ body { right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(4px); z-index: 1000; align-items: center; justify-content: center; - padding: 24px; + padding: var(--space-6); } .modal-overlay.active { @@ -285,13 +405,25 @@ body { .modal { background: var(--bg-secondary); - border-radius: var(--radius-lg); + border-radius: var(--radius-xl); width: 100%; max-width: 560px; max-height: 90vh; overflow-y: auto; border: 1px solid var(--border-color); box-shadow: var(--shadow-lg); + animation: modalIn 200ms ease; +} + +@keyframes modalIn { + from { + opacity: 0; + transform: scale(0.95) translateY(10px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } } .modal-large { @@ -302,24 +434,26 @@ body { display: flex; align-items: center; justify-content: space-between; - padding: 16px 24px; - border-bottom: 1px solid var(--border-color); + padding: var(--space-4) var(--space-6); + border-bottom: 1px solid var(--border-subtle); } .modal-header h2 { - font-size: 18px; - font-weight: 600; + font-size: var(--text-xl); + font-weight: 700; + letter-spacing: -0.02em; } .modal-close { background: none; border: none; color: var(--text-secondary); - font-size: 18px; + font-size: var(--text-lg); cursor: pointer; - padding: 4px 8px; - border-radius: 4px; - transition: all var(--transition); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius); + transition: all var(--transition-fast); + line-height: 1; } .modal-close:hover { @@ -328,45 +462,67 @@ body { } .modal-body { - padding: 24px; + padding: var(--space-6); } /* ===== Upload Zone ===== */ .upload-zone { border: 2px dashed var(--border-color); border-radius: var(--radius-lg); - padding: 48px 24px; + padding: var(--space-9) var(--space-6); text-align: center; cursor: pointer; - transition: border-color var(--transition), background var(--transition); - margin-bottom: 24px; + transition: border-color var(--transition), background var(--transition), box-shadow var(--transition); + margin-bottom: var(--space-6); + position: relative; } .upload-zone:hover, .upload-zone.dragover { border-color: var(--accent-green); - background: rgba(27, 183, 110, 0.05); + background: rgba(27, 183, 110, 0.04); + box-shadow: inset 0 0 30px rgba(27, 183, 110, 0.05); } .upload-icon { font-size: 48px; - margin-bottom: 16px; + margin-bottom: var(--space-4); } -.upload-prompt p { - color: var(--text-secondary); - margin-bottom: 4px; +.upload-main-text { + color: var(--text-primary); + font-size: var(--text-md); + font-weight: 500; + margin-bottom: var(--space-1); } .upload-hint { - font-size: 13px; + font-size: var(--text-sm); color: var(--text-muted); + margin-bottom: var(--space-2); +} + +.upload-paste-hint { + font-size: var(--text-sm); + color: var(--text-secondary); + margin-bottom: var(--space-2); +} + +.upload-paste-hint kbd { + display: inline-block; + padding: 1px 6px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: var(--text-xs); + font-family: var(--font-family); + color: var(--text-primary); } .upload-formats { - font-size: 12px; + font-size: var(--text-xs); color: var(--text-muted); - margin-top: 8px; + margin-top: var(--space-2); } .upload-preview { @@ -378,49 +534,66 @@ body { max-width: 100%; max-height: 300px; border-radius: var(--radius); + box-shadow: var(--shadow); } .btn-remove { position: absolute; - top: 8px; - right: 8px; - background: rgba(0, 0, 0, 0.7); + top: var(--space-2); + right: var(--space-2); + background: rgba(0, 0, 0, 0.75); color: white; border: none; border-radius: 50%; width: 32px; height: 32px; cursor: pointer; - font-size: 14px; + font-size: var(--text-base); + display: flex; + align-items: center; + justify-content: center; + transition: background var(--transition-fast); +} + +.btn-remove:hover { + background: var(--accent-red); +} + +.upload-processing { + text-align: center; + color: var(--text-secondary); + font-size: var(--text-sm); } /* ===== Form ===== */ .form-group { - margin-bottom: 20px; + margin-bottom: var(--space-5); } .form-group label { display: block; - margin-bottom: 6px; - font-size: 13px; + margin-bottom: var(--space-1); + font-size: var(--text-sm); font-weight: 500; color: var(--text-secondary); } .form-group input { width: 100%; - padding: 10px 14px; + padding: var(--space-2) var(--space-3); background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: var(--radius); color: var(--text-primary); - font-size: 14px; + font-size: var(--text-base); + font-family: var(--font-family); outline: none; - transition: border-color var(--transition); + transition: border-color var(--transition), box-shadow var(--transition); } .form-group input:focus { border-color: var(--accent-green); + box-shadow: 0 0 0 3px var(--accent-green-glow); } .form-group input::placeholder { @@ -429,72 +602,84 @@ body { .btn-submit { width: 100%; - padding: 12px; - background: var(--accent-green); + padding: var(--space-3); + background: linear-gradient(135deg, var(--accent-green) 0%, #17a35e 100%); color: white; border: none; border-radius: var(--radius); - font-size: 15px; + font-size: var(--text-md); font-weight: 600; + font-family: var(--font-family); cursor: pointer; - transition: background var(--transition); + transition: all var(--transition); + box-shadow: var(--shadow-sm); } .btn-submit:hover { - background: var(--accent-green-hover); + background: linear-gradient(135deg, var(--accent-green-hover) 0%, #128a50 100%); + box-shadow: var(--shadow-glow); + transform: translateY(-1px); +} + +.btn-submit:active { + transform: translateY(0); } .btn-submit:disabled { opacity: 0.5; cursor: not-allowed; + transform: none; + box-shadow: none; } .error-message { background: rgba(231, 76, 60, 0.1); border: 1px solid var(--accent-red); color: var(--accent-red); - padding: 10px 14px; + padding: var(--space-2) var(--space-3); border-radius: var(--radius); - margin-bottom: 16px; - font-size: 13px; + margin-bottom: var(--space-4); + font-size: var(--text-sm); } /* ===== Post Detail ===== */ .post-detail-grid { display: grid; grid-template-columns: 1fr 280px; - gap: 24px; - margin-bottom: 24px; + gap: var(--space-6); + margin-bottom: var(--space-6); } .post-image-container img { width: 100%; border-radius: var(--radius); cursor: zoom-in; + box-shadow: var(--shadow); } .vote-box { display: flex; align-items: center; - gap: 8px; - margin-bottom: 16px; + gap: var(--space-2); + margin-bottom: var(--space-4); } .vote-btn { - padding: 8px 16px; + padding: var(--space-2) var(--space-4); background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: var(--radius); color: var(--text-secondary); cursor: pointer; - font-size: 16px; + font-size: var(--text-lg); font-weight: 700; - transition: all var(--transition); + transition: all var(--transition-fast); } .vote-btn:hover { border-color: var(--accent-green); color: var(--accent-green); + background: rgba(27, 183, 110, 0.08); } .vote-btn.active-up { @@ -510,35 +695,35 @@ body { } .vote-count { - font-size: 20px; + font-size: var(--text-2xl); font-weight: 700; color: var(--accent-green); } .post-meta { display: flex; - gap: 16px; - margin-bottom: 16px; - font-size: 13px; + gap: var(--space-4); + margin-bottom: var(--space-4); + font-size: var(--text-sm); color: var(--text-secondary); } .post-tags { display: flex; flex-wrap: wrap; - gap: 6px; - margin-bottom: 16px; + gap: var(--space-1); + margin-bottom: var(--space-4); } .post-tags .tag-pill { - padding: 4px 10px; - font-size: 12px; + padding: var(--space-1) var(--space-2); + font-size: var(--text-sm); } .post-author { - font-size: 13px; + font-size: var(--text-sm); color: var(--text-secondary); - margin-bottom: 4px; + margin-bottom: var(--space-1); } .post-author strong { @@ -546,60 +731,67 @@ body { } .post-date { - font-size: 12px; + font-size: var(--text-xs); color: var(--text-muted); } /* ===== Comments ===== */ .comments-section { - border-top: 1px solid var(--border-color); - padding-top: 24px; + border-top: 1px solid var(--border-subtle); + padding-top: var(--space-6); } .comments-section h3 { - font-size: 16px; - margin-bottom: 16px; + font-size: var(--text-lg); + font-weight: 600; + margin-bottom: var(--space-4); } .comment-form { display: flex; - gap: 12px; - margin-bottom: 24px; + gap: var(--space-3); + margin-bottom: var(--space-6); } .comment-form input { flex: 1; - padding: 10px 14px; + padding: var(--space-2) var(--space-3); background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: var(--radius); color: var(--text-primary); - font-size: 14px; + font-size: var(--text-base); + font-family: var(--font-family); outline: none; + transition: border-color var(--transition), box-shadow var(--transition); } .comment-form input:focus { border-color: var(--accent-green); + box-shadow: 0 0 0 3px var(--accent-green-glow); } .btn-comment { - padding: 10px 20px; + padding: var(--space-2) var(--space-5); background: var(--accent-green); color: white; border: none; border-radius: var(--radius); font-weight: 600; + font-size: var(--text-base); + font-family: var(--font-family); cursor: pointer; - transition: background var(--transition); + transition: all var(--transition-fast); } .btn-comment:hover { background: var(--accent-green-hover); + box-shadow: var(--shadow-glow); } .comment-item { - padding: 12px 0; - border-bottom: 1px solid var(--border-color); + padding: var(--space-3) 0; + border-bottom: 1px solid var(--border-subtle); } .comment-item:last-child { @@ -607,27 +799,28 @@ body { } .comment-item.reply { - margin-left: 32px; + margin-left: var(--space-8); border-left: 2px solid var(--border-color); - padding-left: 16px; + padding-left: var(--space-4); } .comment-author { - font-size: 13px; + font-size: var(--text-sm); font-weight: 600; color: var(--accent-green); - margin-bottom: 4px; + margin-bottom: var(--space-1); } .comment-text { - font-size: 14px; + font-size: var(--text-base); color: var(--text-secondary); + line-height: 1.5; } .comment-time { - font-size: 11px; + font-size: var(--text-xs); color: var(--text-muted); - margin-top: 4px; + margin-top: var(--space-1); } /* ===== Fullscreen ===== */ @@ -653,28 +846,59 @@ body { max-width: 90%; max-height: 90%; object-fit: contain; + border-radius: var(--radius); +} + +/* ===== Floating Action Button ===== */ +.fab { + position: fixed; + bottom: var(--space-6); + right: var(--space-6); + width: 56px; + height: 56px; + border-radius: 50%; + background: linear-gradient(135deg, var(--accent-green) 0%, #17a35e 100%); + color: white; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--shadow-lg), var(--shadow-glow); + transition: all var(--transition); + z-index: 500; +} + +.fab:hover { + transform: scale(1.1); + box-shadow: var(--shadow-lg), 0 0 30px var(--accent-green-glow); +} + +.fab:active { + transform: scale(0.95); } /* ===== Toast ===== */ .toast-container { position: fixed; - bottom: 24px; - right: 24px; + bottom: var(--space-6); + right: var(--space-6); z-index: 3000; display: flex; flex-direction: column; - gap: 8px; + gap: var(--space-2); } .toast { - padding: 12px 20px; + padding: var(--space-3) var(--space-5); background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius); color: var(--text-primary); - font-size: 14px; + font-size: var(--text-base); + font-family: var(--font-family); box-shadow: var(--shadow-lg); - animation: slideIn 0.3s ease; + animation: slideIn 250ms ease; } .toast.success { @@ -712,8 +936,8 @@ body { @media (max-width: 768px) { .header-inner { flex-wrap: wrap; - gap: 12px; - padding: 12px 16px; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); } .search-bar { @@ -723,37 +947,45 @@ body { } .filter-bar { - padding: 12px 16px; + padding: var(--space-3) var(--space-4); } .filter-tabs { overflow-x: auto; - gap: 6px; + gap: var(--space-1); + -webkit-overflow-scrolling: touch; } .filter-tab { - padding: 6px 12px; - font-size: 12px; + padding: var(--space-1) var(--space-3); + font-size: var(--text-xs); white-space: nowrap; } .gallery-container { - padding: 0 16px 32px; + padding: 0 var(--space-4) var(--space-8); } .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 12px; + gap: var(--space-3); } .modal { max-width: 100%; - margin: 16px; + margin: var(--space-4); } .modal-large { max-width: 100%; } + + .fab { + bottom: var(--space-4); + right: var(--space-4); + width: 48px; + height: 48px; + } } @media (max-width: 375px) { @@ -766,7 +998,7 @@ body { } .btn-new-post { - padding: 8px 14px; - font-size: 13px; + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); } } diff --git a/public/index.html b/public/index.html index f289c51..0665632 100644 --- a/public/index.html +++ b/public/index.html @@ -5,7 +5,7 @@