diff --git a/.env.example b/.env.example
index 70172a5..882e9cd 100644
--- a/.env.example
+++ b/.env.example
@@ -4,7 +4,7 @@ JWT_SECRET=
# Generate with: openssl rand -hex 16
MEILI_MASTER_KEY=
-# Secure cookies (true in production, false in development)
+# Secure cookies (default true; set to false only when serving over plain HTTP)
COOKIE_SECURE=
# App port
diff --git a/README.md b/README.md
index 442e7f7..0dfec8c 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@ The app reads secrets from `.env` (created by `setup.sh`).
|---|---|---|
| `JWT_SECRET` | Yes | Secret for signing auth tokens (min 32 chars). |
| `MEILI_MASTER_KEY` | Yes | Meilisearch master key. |
-| `COOKIE_SECURE` | Yes | Use secure cookies (HTTPS). |
+| `COOKIE_SECURE` | No | Use secure cookies (HTTPS). Defaults to `true`; set to `false` when serving over plain HTTP. |
| `PORT` | Yes | Host port for the app. Default `3000`. |
| `DATABASE_PATH` | Yes | SQLite database file path. |
| `FILES_PATH` | Yes | Attachments storage path. |
diff --git a/app/assets/css/main.css b/app/assets/css/main.css
index d00ccac..faf738c 100644
--- a/app/assets/css/main.css
+++ b/app/assets/css/main.css
@@ -154,3 +154,11 @@
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background-color: var(--nord-ice); border-radius: 9999px; }
.dark ::-webkit-scrollbar-thumb { background-color: var(--nord-graphite); }
+
+/* Global so it reaches both the PIN input component and the mobile PIN dots */
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 20%, 60% { transform: translateX(-6px); }
+ 40%, 80% { transform: translateX(6px); }
+}
+.animate-shake { animation: shake 0.5s ease-in-out; }
diff --git a/app/components/attachment/ImageGrid.vue b/app/components/attachment/ImageGrid.vue
index db2c813..b52b796 100644
--- a/app/components/attachment/ImageGrid.vue
+++ b/app/components/attachment/ImageGrid.vue
@@ -11,7 +11,7 @@
class="w-full object-cover cursor-zoom-in"
:class="visibleAttachments.length === 1 ? 'max-h-56' : 'h-32'"
loading="lazy"
- @error="(e) => (e.target as HTMLImageElement).src = `/api/files/${att.id}`"
+ @error="(e) => fallbackToOriginal(e, att.id)"
@click.stop="openLightbox(idx)"
>
@@ -65,6 +65,14 @@