diff --git a/src/Shoebox.Web/Pages/Pool/Gallery.cshtml b/src/Shoebox.Web/Pages/Pool/Gallery.cshtml
index 0c687c7..2c029b8 100644
--- a/src/Shoebox.Web/Pages/Pool/Gallery.cshtml
+++ b/src/Shoebox.Web/Pages/Pool/Gallery.cshtml
@@ -28,9 +28,19 @@
ยท from @Model.Uploaders.Count @(Model.Uploaders.Count == 1 ? "person" : "people")
}
- @if (Model.ExpiresSoon)
+ @if (Model.Pool.ExpiresAt.HasValue)
{
- This box clears out on @(Model.Pool.ExpiresAt?.ToString("d MMM yyyy")). Grab what you want to keep.
+
+ @(Model.ExpiryUrgency == "critical" ? "โ ๏ธ" : "๐")
+
+ This box deletes on @Model.Pool.ExpiresAt.Value.ToString("d MMM yyyy")
+ (@Model.ExpiryLabel)
+ @if (Model.ExpiryUrgency == "critical")
+ {
+ โ save anything you want to keep now.
+ }
+
+
}
diff --git a/src/Shoebox.Web/Pages/Pool/Gallery.cshtml.cs b/src/Shoebox.Web/Pages/Pool/Gallery.cshtml.cs
index ac91804..585f057 100644
--- a/src/Shoebox.Web/Pages/Pool/Gallery.cshtml.cs
+++ b/src/Shoebox.Web/Pages/Pool/Gallery.cshtml.cs
@@ -25,6 +25,8 @@ public class GalleryModel(
public string ShareUrl { get; set; } = "";
public bool IsAdmin { get; set; }
public bool ExpiresSoon { get; set; }
+ public string ExpiryLabel { get; set; } = "";
+ public string ExpiryUrgency { get; set; } = "";
// Whether the box holds any photos the current visitor did not upload, so the
// "everyone else's" download only offers itself when it would actually return files.
@@ -75,7 +77,35 @@ public async Task OnGetAsync(string code)
UploaderName = identity.GetName(HttpContext) ?? "";
ShareUrl = links.PoolUrl(HttpContext, pool);
IsAdmin = access.IsAdmin(HttpContext, pool.Id);
- ExpiresSoon = pool.ExpiresAt is { } exp && exp < DateTime.UtcNow.AddDays(7);
+ if (pool.ExpiresAt is { } exp)
+ {
+ ExpiresSoon = exp < DateTime.UtcNow.AddDays(7);
+ ExpiryLabel = BuildExpiryLabel(exp);
+ ExpiryUrgency = BuildExpiryUrgency(exp);
+ }
return Page();
}
+
+ private static string BuildExpiryLabel(DateTime expiresAt)
+ {
+ var diff = expiresAt - DateTime.UtcNow;
+ if (diff.TotalHours < 24) return "today";
+ if (diff.TotalDays < 2) return "tomorrow";
+ if (diff.TotalDays < 7) return $"in {(int)diff.TotalDays} day{((int)diff.TotalDays == 1 ? "" : "s")}";
+ if (diff.TotalDays < 14) return "in 1 week";
+ if (diff.TotalDays < 30) return $"in {(int)(diff.TotalDays / 7)} weeks";
+ if (diff.TotalDays < 60) return "in 1 month";
+ if (diff.TotalDays < 365) return $"in {(int)(diff.TotalDays / 30)} months";
+ if (diff.TotalDays < 730) return "in 1 year";
+ return $"in {(int)(diff.TotalDays / 365)} years";
+ }
+
+ private static string BuildExpiryUrgency(DateTime expiresAt)
+ {
+ var days = (expiresAt - DateTime.UtcNow).TotalDays;
+ if (days <= 7) return "critical";
+ if (days <= 30) return "warning";
+ if (days <= 90) return "notice";
+ return "info";
+ }
}
diff --git a/src/Shoebox.Web/wwwroot/css/site.css b/src/Shoebox.Web/wwwroot/css/site.css
index 6a10ab8..84b42b8 100644
--- a/src/Shoebox.Web/wwwroot/css/site.css
+++ b/src/Shoebox.Web/wwwroot/css/site.css
@@ -439,6 +439,23 @@ input[type="checkbox"] { accent-color: var(--ink); }
:root[data-theme="dark"] .banner.warn { background: #2a2410; border-color: #6b5a1e; color: #ecd9a2; }
:root[data-theme="dark"] .banner.success { background: #142319; border-color: #2f5b3d; color: #a9d9b6; }
+/* Expiry banner urgency levels */
+.expiry-banner { display: flex; align-items: baseline; gap: 0.5rem; }
+.expiry-icon { font-size: 1.1em; flex-shrink: 0; }
+.expiry-text { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem; line-height: 1.4; }
+.expiry-relative { font-size: 0.9em; opacity: 0.85; }
+.expiry-action { font-size: 0.9em; }
+
+.banner.expiry-critical { background: #fff0f0; border-color: #e03a3a; color: #7a0000; font-weight: 600; }
+.banner.expiry-warning { background: #fff7ed; border-color: #d97706; color: #7c3500; }
+.banner.expiry-notice { background: #fdf6e3; border-color: #d9b23a; color: #5a4a12; }
+.banner.expiry-info { background: #f0f6ff; border-color: #6b9fd4; color: #1a3a5c; }
+
+:root[data-theme="dark"] .banner.expiry-critical { background: #2d0a0a; border-color: #c03030; color: #ffb3b3; }
+:root[data-theme="dark"] .banner.expiry-warning { background: #2a1800; border-color: #c07a00; color: #ffd080; }
+:root[data-theme="dark"] .banner.expiry-notice { background: #2a2410; border-color: #6b5a1e; color: #ecd9a2; }
+:root[data-theme="dark"] .banner.expiry-info { background: #0d1e30; border-color: #3a6a9f; color: #a8c8e8; }
+
.admin-link { margin-top: 0.55rem; font-family: var(--mono); font-size: 0.8rem; }
.banner-cta { margin-top: 0.85rem; }