Updated design
This commit is contained in:
+1
-1
@@ -6,5 +6,5 @@
|
||||
<File Path="midrandbooks-uat.yml" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="MidrandBookshop/MidrandBookshop.csproj" />
|
||||
<Project Path="MidrandBooks/MidrandBooks.csproj" />
|
||||
</Solution>
|
||||
|
||||
+29
-3
@@ -1,12 +1,38 @@
|
||||
@namespace MidrandBooks.Components
|
||||
|
||||
<div id="animated-bg" class="fixed inset-0 -z-50 overflow-hidden bg-[#FDFCFB]">
|
||||
<style>
|
||||
.mesh-bg {
|
||||
background-image: radial-gradient(at 0% 0%, rgba(245, 158, 11, 0.08) 0, transparent 60%),
|
||||
radial-gradient(at 50% 0%, rgba(20, 184, 166, 0.06) 0, transparent 60%),
|
||||
radial-gradient(at 100% 0%, rgba(249, 115, 22, 0.08) 0, transparent 60%);
|
||||
background-color: #030712;
|
||||
}
|
||||
|
||||
@@keyframes float-up {
|
||||
0% {
|
||||
transform: translateY(100vh) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: var(--float-opacity, 0.2);
|
||||
}
|
||||
90% {
|
||||
opacity: var(--float-opacity, 0.2);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-20vh) rotate(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="animated-bg" class="fixed inset-0 -z-50 overflow-hidden mesh-bg">
|
||||
<!-- Abstract Elegant Soft Warm Gradients -->
|
||||
<div class="absolute -top-[30%] -left-[10%] h-[80%] w-[60%] rounded-full bg-amber-500/5 blur-[120px] animate-pulse"
|
||||
style="animation-duration: 14s;" />
|
||||
<div class="absolute -bottom-[20%] -right-[5%] h-[80%] w-[50%] rounded-full bg-violet-500/5 blur-[150px] animate-pulse"
|
||||
<div class="absolute -bottom-[20%] -right-[5%] h-[80%] w-[50%] rounded-full bg-sky-500/4 blur-[150px] animate-pulse"
|
||||
style="animation-duration: 20s;" />
|
||||
<div class="absolute top-[40%] left-[30%] h-[300px] w-[300px] rounded-full bg-indigo-500/3 blur-[100px] animate-pulse"
|
||||
<div class="absolute top-[40%] left-[30%] h-[300px] w-[300px] rounded-full bg-orange-400/4 blur-[100px] animate-pulse"
|
||||
style="animation-duration: 16s;" />
|
||||
|
||||
<!-- Grid Pattern Overlay -->
|
||||
+2
-2
@@ -117,7 +117,7 @@
|
||||
|
||||
<!-- Seek Bar -->
|
||||
<div class="space-y-1">
|
||||
<input type="range" min="0" max="@Duration" value="@Progress" @oninput="SeekChanged" class="w-full h-1 bg-white/10 rounded-lg appearance-none cursor-pointer" />
|
||||
<input type="range" min="0" max="@Duration" value="@Progress" @oninput="SeekChanged" class="w-full h-1.5 bg-slate-700 accent-amber-500 rounded-lg appearance-none cursor-pointer transition-colors" />
|
||||
<div class="flex justify-between text-[10px] font-mono text-slate-400">
|
||||
<span>@FormatSeconds(Progress)</span>
|
||||
<span>@FormatSeconds(Duration)</span>
|
||||
@@ -171,7 +171,7 @@
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
<input type="range" min="0" max="1" step="0.05" @bind="Volume" class="w-20 h-1 bg-white/10 rounded-lg appearance-none cursor-pointer" />
|
||||
<input type="range" min="0" max="1" step="0.05" @bind="Volume" class="w-20 h-1.5 bg-slate-700 accent-amber-500 rounded-lg appearance-none cursor-pointer transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using MidrandBooks.Models
|
||||
|
||||
<style>
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="razor-book-grid" class="max-w-7xl mx-auto px-4 sm:px-6 py-6 space-y-6">
|
||||
<!-- Filters Rail -->
|
||||
<div class="flex flex-col md:flex-row gap-4 items-stretch md:items-center justify-between border-b border-slate-200/50 pb-5">
|
||||
+17
-5
@@ -2,6 +2,18 @@
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using MidrandBooks.Models
|
||||
|
||||
<style>
|
||||
.reader-text-serif {
|
||||
font-family: "Playfair Display", serif;
|
||||
}
|
||||
.reader-text-sans {
|
||||
font-family: "Poppins", sans-serif;
|
||||
}
|
||||
.reader-text-mono {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="fixed inset-0 z-50 flex flex-col h-screen overflow-hidden @ThemeBgClass()">
|
||||
|
||||
<!-- Header Controls -->
|
||||
@@ -47,7 +59,7 @@
|
||||
<label class="text-[10px] font-mono font-bold uppercase block opacity-70">Theme</label>
|
||||
<div class="flex bg-black/10 dark:bg-white/10 p-1 rounded-xl">
|
||||
<button @onclick='() => SetTheme("sepia")' class="flex-1 py-1 text-xs font-bold rounded-lg @(Theme == "sepia" ? "bg-amber-100 text-amber-950" : "text-slate-500")">Sepia</button>
|
||||
<button @onclick='() => SetTheme("dark")' class="flex-1 py-1 text-xs font-bold rounded-lg @(Theme == "dark" ? "bg-slate-850 text-white" : "text-slate-500")">Dark</button>
|
||||
<button @onclick='() => SetTheme("dark")' class="flex-1 py-1 text-xs font-bold rounded-lg @(Theme == "dark" ? "bg-slate-800 text-white" : "text-slate-500")">Dark</button>
|
||||
<button @onclick='() => SetTheme("light")' class="flex-1 py-1 text-xs font-bold rounded-lg @(Theme == "light" ? "bg-white text-slate-800" : "text-slate-500")">Light</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +80,7 @@
|
||||
<span>Font Size</span>
|
||||
<span>@(FontSize)px</span>
|
||||
</div>
|
||||
<input type="range" min="12" max="24" @bind="FontSize" class="w-full h-1 bg-slate-200 rounded-lg appearance-none cursor-pointer" />
|
||||
<input type="range" min="12" max="24" @bind="FontSize" class="w-full h-1.5 bg-slate-300 dark:bg-slate-700 accent-amber-500 rounded-lg appearance-none cursor-pointer transition-colors" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -192,9 +204,9 @@
|
||||
{
|
||||
return Font switch
|
||||
{
|
||||
"sans" => "font-sans",
|
||||
"mono" => "font-mono",
|
||||
_ => "font-serif"
|
||||
"sans" => "reader-text-sans",
|
||||
"mono" => "reader-text-mono",
|
||||
_ => "reader-text-serif"
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
var audioInCart = Cart.Any(i => i.Book.Id == book.Id && i.Format == "audiobook");
|
||||
|
||||
<!-- Main Carousel Card -->
|
||||
<div class="relative overflow-hidden rounded-3xl border border-slate-800/40 bg-slate-900 shadow-2xl min-h-[480px] md:min-h-[380px] flex items-center">
|
||||
<div class="relative overflow-hidden rounded-3xl border border-slate-800/80 bg-slate-900/95 backdrop-blur-md shadow-lg shadow-slate-950/25 min-h-[480px] md:min-h-[380px] flex items-center">
|
||||
|
||||
<!-- Dynamic Background Glow -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br @book.CoverColor opacity-25 blur-2xl scale-125 transition-all duration-1000 -z-10" />
|
||||
+14
-14
@@ -1,5 +1,5 @@
|
||||
@namespace MidrandBooks.Components.Layout
|
||||
@using MidrandBooks.Components.Pages
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
@if (ShowBanner || ShowPrivacyModal)
|
||||
@@ -7,13 +7,13 @@
|
||||
@if (ShowBanner)
|
||||
{
|
||||
<div id="cookie-acceptance-banner"
|
||||
class="fixed bottom-4 left-4 z-40 max-w-sm w-[calc(100vw-2rem)] sm:w-[380px] bg-slate-900/95 backdrop-blur-md border border-white/10 rounded-2xl p-4 shadow-2xl flex flex-col gap-3 animate-in slide-in-from-bottom-8 fade-in duration-500">
|
||||
class="fixed bottom-4 left-4 z-40 max-w-sm w-[calc(100vw-2rem)] sm:w-[380px] bg-white/90 backdrop-blur-xl border border-slate-200/80 rounded-2xl p-4 shadow-xl flex flex-col gap-3 animate-in slide-in-from-bottom-8 fade-in duration-500 hover:shadow-slate-200/50 transition-all">
|
||||
<!-- Accent Line -->
|
||||
<div class="absolute top-0 left-6 right-6 h-0.5 bg-gradient-to-r from-violet-500/0 via-violet-500/50 to-violet-500/0"></div>
|
||||
<div class="absolute top-0 left-6 right-6 h-0.5 bg-gradient-to-r from-amber-500/0 via-amber-500/60 to-amber-500/0"></div>
|
||||
|
||||
<div class="flex items-start gap-3">
|
||||
<!-- Visual Icon -->
|
||||
<div class="h-9 w-9 rounded-xl bg-violet-500/10 border border-violet-500/20 text-violet-400 flex items-center justify-center flex-shrink-0">
|
||||
<div class="h-9 w-9 rounded-xl bg-amber-50 border border-amber-200/60 text-amber-600 flex items-center justify-center flex-shrink-0">
|
||||
<svg class="h-5 w-5 animate-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a10 10 0 1 0 10 10 4 4 0 0 1-5-5 4 4 0 0 1-5-5" />
|
||||
@@ -26,16 +26,16 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<h4 class="text-xs font-mono font-bold uppercase tracking-wider text-white">
|
||||
<h4 class="text-xs font-sans font-bold text-slate-800 tracking-tight">
|
||||
Cookie & POPIA Protocol
|
||||
</h4>
|
||||
<p class="text-[11px] text-slate-300 leading-relaxed font-medium">
|
||||
We utilize essential identifiers to facilitate secure electronic catalog licensing, sandboxed media playbacks, and cryptographic PayFast gateway handshake handoffs under <strong class="text-violet-300">Lite Charms (Pty) Ltd</strong>.
|
||||
<p class="text-[11px] text-slate-600 leading-relaxed font-medium">
|
||||
We utilize essential identifiers to facilitate secure electronic catalog licensing, sandboxed media playbacks, and cryptographic PayFast gateway handshake handoffs under <strong class="text-amber-600 font-bold">Lite Charms (Pty) Ltd</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button @onclick="DismissBanner"
|
||||
class="text-slate-400 hover:text-white transition-colors cursor-pointer p-0.5"
|
||||
class="text-slate-400 hover:text-slate-600 transition-colors cursor-pointer p-0.5"
|
||||
aria-label="Dismiss banner">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -44,9 +44,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Action Links & Buttons -->
|
||||
<div class="flex items-center justify-between mt-1 pt-2.5 border-t border-white/5">
|
||||
<div class="flex items-center justify-between mt-1 pt-2.5 border-t border-slate-100">
|
||||
<button @onclick="OpenPrivacyModal"
|
||||
class="text-[11px] font-semibold text-violet-400 hover:text-violet-300 underline cursor-pointer flex items-center gap-1 focus:outline-none">
|
||||
class="text-[11px] font-semibold text-amber-600 hover:text-amber-700 underline cursor-pointer flex items-center gap-1 focus:outline-none">
|
||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button @onclick="AcceptCookies"
|
||||
class="bg-violet-600 hover:bg-violet-500 text-white rounded-xl py-1.5 px-3.5 text-[10px] font-mono font-bold uppercase tracking-wider transition-all cursor-pointer shadow-lg shadow-violet-600/15 hover:scale-[1.02] active:scale-[0.98]">
|
||||
class="bg-slate-900 hover:bg-slate-800 text-white rounded-xl py-1.5 px-3.5 text-[10px] font-mono font-bold uppercase tracking-wider transition-all cursor-pointer shadow shadow-slate-900/10 hover:scale-[1.02] active:scale-[0.98]">
|
||||
Accept All
|
||||
</button>
|
||||
</div>
|
||||
@@ -70,12 +70,12 @@
|
||||
<div id="cookie-privacy-modal-content"
|
||||
class="relative w-full max-w-5xl bg-slate-950 border border-white/10 rounded-3xl shadow-2xl overflow-hidden max-h-[90vh] flex flex-col animate-in zoom-in-95 duration-200">
|
||||
<!-- Upper Highlight Bar -->
|
||||
<div class="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-violet-500 via-indigo-500 to-violet-500" />
|
||||
<div class="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-amber-500 via-amber-400 to-amber-600" />
|
||||
|
||||
<!-- Modal Header -->
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-white/10 bg-slate-900/40 relative z-10 flex-shrink-0">
|
||||
<div class="flex items-center gap-2 text-white">
|
||||
<svg class="h-5 w-5 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<svg class="h-5 w-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
<span class="font-mono text-xs font-bold uppercase tracking-wider text-slate-300">
|
||||
@@ -94,7 +94,7 @@
|
||||
<!-- Modal Scrollable Body -->
|
||||
<div class="flex-1 overflow-y-auto bg-slate-950/50 p-4 sm:p-6 md:p-8">
|
||||
<div class="max-w-4xl mx-auto -my-10">
|
||||
<Privacy />
|
||||
<MidrandBooks.Components.Privacy />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
-1
@@ -2,7 +2,6 @@
|
||||
@namespace MidrandBooks.Components.Layout
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using MidrandBooks.Components
|
||||
@using MidrandBooks.Components.Pages
|
||||
@using MidrandBooks.Models
|
||||
|
||||
<div class="relative min-h-screen flex flex-col justify-between selection:bg-violet-500/10 selection:text-violet-700">
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@namespace MidrandBooks.Components.Layout
|
||||
@namespace MidrandBooks.Components
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using MidrandBooks.Models
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
@namespace MidrandBooks.Components.Layout
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<dialog id="components-reconnect-modal" data-nosnippet class="fixed inset-0 w-screen h-screen m-0 p-0 border-0 bg-transparent z-[99999] overflow-hidden flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-300">
|
||||
<!-- Dark Frosted Jacket Layer -->
|
||||
<div class="absolute inset-0 bg-slate-950/60 backdrop-blur-md cursor-wait z-[1] animate-in fade-in duration-300"></div>
|
||||
|
||||
<!-- Centered Content Wrapper -->
|
||||
<div class="reconnect-content relative z-[2] flex flex-col items-center gap-6 text-center text-slate-100 max-w-sm px-6">
|
||||
|
||||
<!-- Waveform Animation -->
|
||||
<svg class="waveform-svg text-violet-400" viewBox="0 0 54 50" width="60" height="60" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect class="bar bar-1" x="2" y="15" width="4" height="20" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-2" x="14" y="5" width="4" height="40" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-3" x="26" y="20" width="4" height="10" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-4" x="38" y="10" width="4" height="30" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-5" x="50" y="18" width="4" height="14" rx="2" fill="currentColor" />
|
||||
</svg>
|
||||
|
||||
<!-- Message Body -->
|
||||
<div class="sync-message-body font-mono uppercase text-xs tracking-widest text-slate-300 space-y-2">
|
||||
<span class="components-reconnect-first-attempt-visible hidden">
|
||||
Losing signal...
|
||||
</span>
|
||||
|
||||
<span class="components-reconnect-repeated-attempt-visible hidden">
|
||||
reconnecting in <span id="components-seconds-to-next-attempt" class="font-bold text-violet-400">0</span>s...
|
||||
</span>
|
||||
|
||||
<span class="components-reconnect-failed-visible text-rose-400 font-semibold hidden">
|
||||
Playback disconnected.
|
||||
</span>
|
||||
|
||||
<span class="components-pause-visible hidden">
|
||||
Reading layout paused.
|
||||
</span>
|
||||
|
||||
<span class="components-resume-failed-visible text-rose-400 font-semibold hidden">
|
||||
State alignment broken.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Glass Action Buttons -->
|
||||
<div class="sync-action-node flex items-center justify-center gap-3">
|
||||
<button id="components-reconnect-button" class="btn-glass-action hidden px-4 py-2.5 bg-white/5 border border-white/10 hover:bg-white/10 text-white font-mono uppercase text-[10px] tracking-widest rounded-xl transition-all items-center gap-2 cursor-pointer shadow-lg hover:scale-102 hover:border-violet-500/30">
|
||||
<span>Retry Sync</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="components-resume-button" class="btn-glass-action hidden px-4 py-2.5 bg-white/5 border border-white/10 hover:bg-white/10 text-white font-mono uppercase text-[10px] tracking-widest rounded-xl transition-all items-center gap-2 cursor-pointer shadow-lg hover:scale-102 hover:border-violet-500/30">
|
||||
<span>Reload Session</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="23 4 23 10 17 10"></polyline>
|
||||
<polyline points="1 20 1 14 7 14"></polyline>
|
||||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
/* --- Show the modal smoothly when Blazor attaches state classes --- */
|
||||
#components-reconnect-modal.components-reconnect-show,
|
||||
#components-reconnect-modal.components-reconnect-failed,
|
||||
#components-reconnect-modal.components-reconnect-rejected {
|
||||
opacity: 1 !important;
|
||||
pointer-events: all !important;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* --- SVG Waveform Animation --- */
|
||||
#components-reconnect-modal .bar {
|
||||
transform-origin: center;
|
||||
animation: reconnectWavePulse 1.2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
#components-reconnect-modal .bar-1 { animation-delay: 0.0s; }
|
||||
#components-reconnect-modal .bar-2 { animation-delay: 0.3s; }
|
||||
#components-reconnect-modal .bar-3 { animation-delay: 0.6s; }
|
||||
#components-reconnect-modal .bar-4 { animation-delay: 0.2s; }
|
||||
#components-reconnect-modal .bar-5 { animation-delay: 0.5s; }
|
||||
|
||||
@@keyframes reconnectWavePulse {
|
||||
0% {
|
||||
transform: scaleY(0.3);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Stop the waveform if the connection completely fails */
|
||||
#components-reconnect-modal.components-reconnect-failed .bar,
|
||||
#components-reconnect-modal.components-reconnect-rejected .bar,
|
||||
#components-reconnect-modal.components-reconnect-paused .bar {
|
||||
animation: none !important;
|
||||
transform: scaleY(0.1) !important;
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
/* --- Display Mechanics Matrix Controllers --- */
|
||||
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-failed .btn-glass-action,
|
||||
#components-reconnect-modal.components-reconnect-paused .btn-glass-action,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .btn-glass-action {
|
||||
display: inline-flex !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function initReconnectModal() {
|
||||
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||
if (!reconnectModal) {
|
||||
setTimeout(initReconnectModal, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||
|
||||
const retryButton = document.getElementById("components-reconnect-button");
|
||||
if (retryButton) {
|
||||
retryButton.addEventListener("click", retry);
|
||||
}
|
||||
|
||||
const resumeButton = document.getElementById("components-resume-button");
|
||||
if (resumeButton) {
|
||||
resumeButton.addEventListener("click", resume);
|
||||
}
|
||||
|
||||
function handleReconnectStateChanged(event) {
|
||||
if (event.detail.state === "show") {
|
||||
reconnectModal.showModal();
|
||||
} else if (event.detail.state === "hide") {
|
||||
reconnectModal.close();
|
||||
} else if (event.detail.state === "failed") {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
} else if (event.detail.state === "rejected") {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retry() {
|
||||
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
|
||||
try {
|
||||
if (window.Blazor) {
|
||||
const successful = await window.Blazor.reconnect();
|
||||
if (!successful) {
|
||||
const resumeSuccessful = await window.Blazor.resumeCircuit();
|
||||
if (!resumeSuccessful) {
|
||||
location.reload();
|
||||
} else {
|
||||
reconnectModal.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
}
|
||||
}
|
||||
|
||||
async function resume() {
|
||||
try {
|
||||
if (window.Blazor) {
|
||||
const successful = await window.Blazor.resumeCircuit();
|
||||
if (!successful) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function retryWhenDocumentBecomesVisible() {
|
||||
if (document.visibilityState === "visible") {
|
||||
await retry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initReconnectModal);
|
||||
} else {
|
||||
initReconnectModal();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@namespace MidrandBooks.Components.Pages
|
||||
@namespace MidrandBooks.Components
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using MidrandBooks.Models
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@namespace MidrandBooks.Components.Pages
|
||||
@namespace MidrandBooks.Components
|
||||
@using Microsoft.AspNetCore.Components
|
||||
|
||||
<div id="error-razor" class="w-full max-w-xl mx-auto px-4 py-16 text-center space-y-6">
|
||||
+3
-2
@@ -22,8 +22,9 @@
|
||||
<div class="flex bg-slate-100 border border-slate-200/60 p-1 rounded-xl self-start sm:self-auto">
|
||||
@foreach (var format in Formats)
|
||||
{
|
||||
<button @onclick="() => FilterByFormat(format)"
|
||||
class="px-4 py-1.5 rounded-lg text-xs font-semibold uppercase tracking-wider transition-all cursor-pointer @(ActiveFormat == format ? "bg-white text-slate-900 shadow-sm border border-slate-200/50" : "text-slate-500 hover:text-slate-800")">
|
||||
<button
|
||||
@onclick="() => FilterByFormat(format)"
|
||||
class="px-4 py-1.5 rounded-lg text-xs font-semibold uppercase tracking-wider transition-all cursor-pointer @(ActiveFormat == format ? "bg-white text-slate-900 shadow-sm border border-slate-200/50" : "text-slate-500 hover:text-slate-800")">
|
||||
@format
|
||||
</button>
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@namespace MidrandBooks.Components.Pages
|
||||
@namespace MidrandBooks.Components
|
||||
@using Microsoft.AspNetCore.Components
|
||||
|
||||
<div id="notfound-razor" class="w-full max-w-xl mx-auto px-4 py-16 text-center space-y-6">
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
@page "/privacy"
|
||||
@namespace MidrandBooks.Components.Pages
|
||||
@namespace MidrandBooks.Components
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
@page "/terms"
|
||||
@namespace MidrandBooks.Components.Pages
|
||||
@namespace MidrandBooks.Components
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||
--color-red-600: oklch(57.7% 0.245 27.325);
|
||||
--color-red-800: oklch(44.4% 0.177 26.899);
|
||||
--color-orange-400: oklch(75% 0.183 55.934);
|
||||
--color-orange-900: oklch(40.8% 0.123 38.172);
|
||||
--color-amber-50: oklch(98.7% 0.022 95.277);
|
||||
--color-amber-100: oklch(96.2% 0.059 95.617);
|
||||
@@ -34,11 +35,11 @@
|
||||
--color-emerald-700: oklch(50.8% 0.118 165.612);
|
||||
--color-emerald-950: oklch(26.2% 0.051 172.552);
|
||||
--color-teal-950: oklch(27.7% 0.046 192.524);
|
||||
--color-sky-500: oklch(68.5% 0.169 237.323);
|
||||
--color-blue-50: oklch(97% 0.014 254.604);
|
||||
--color-blue-100: oklch(93.2% 0.032 255.585);
|
||||
--color-blue-700: oklch(48.8% 0.243 264.376);
|
||||
--color-indigo-50: oklch(96.2% 0.018 272.314);
|
||||
--color-indigo-500: oklch(58.5% 0.233 277.117);
|
||||
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
||||
--color-indigo-700: oklch(45.7% 0.24 277.023);
|
||||
--color-indigo-900: oklch(35.9% 0.144 278.697);
|
||||
@@ -1035,6 +1036,12 @@
|
||||
border-color: color-mix(in oklab, var(--color-amber-200) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.border-amber-200\/60 {
|
||||
border-color: color-mix(in srgb, oklch(92.4% 0.12 95.746) 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-amber-200) 60%, transparent);
|
||||
}
|
||||
}
|
||||
.border-amber-400 {
|
||||
border-color: var(--color-amber-400);
|
||||
}
|
||||
@@ -1125,6 +1132,12 @@
|
||||
border-color: color-mix(in oklab, var(--color-slate-800) 40%, transparent);
|
||||
}
|
||||
}
|
||||
.border-slate-800\/80 {
|
||||
border-color: color-mix(in srgb, oklch(27.9% 0.041 260.031) 80%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-slate-800) 80%, transparent);
|
||||
}
|
||||
}
|
||||
.border-slate-900\/10 {
|
||||
border-color: color-mix(in srgb, oklch(20.8% 0.042 265.755) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1149,12 +1162,6 @@
|
||||
border-color: color-mix(in oklab, var(--color-violet-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.border-violet-500\/20 {
|
||||
border-color: color-mix(in srgb, oklch(60.6% 0.25 292.717) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-violet-500) 20%, transparent);
|
||||
}
|
||||
}
|
||||
.border-violet-500\/30 {
|
||||
border-color: color-mix(in srgb, oklch(60.6% 0.25 292.717) 30%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1188,9 +1195,6 @@
|
||||
.border-t-violet-600 {
|
||||
border-top-color: var(--color-violet-600);
|
||||
}
|
||||
.bg-\[\#FDFCFB\] {
|
||||
background-color: #FDFCFB;
|
||||
}
|
||||
.bg-amber-50 {
|
||||
background-color: var(--color-amber-50);
|
||||
}
|
||||
@@ -1248,18 +1252,18 @@
|
||||
.bg-indigo-50 {
|
||||
background-color: var(--color-indigo-50);
|
||||
}
|
||||
.bg-indigo-500\/3 {
|
||||
background-color: color-mix(in srgb, oklch(58.5% 0.233 277.117) 3%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-indigo-500) 3%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-indigo-600\/10 {
|
||||
background-color: color-mix(in srgb, oklch(51.1% 0.262 276.966) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-indigo-600) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-orange-400\/4 {
|
||||
background-color: color-mix(in srgb, oklch(75% 0.183 55.934) 4%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-orange-400) 4%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-red-100 {
|
||||
background-color: var(--color-red-100);
|
||||
}
|
||||
@@ -1281,6 +1285,12 @@
|
||||
.bg-rose-600 {
|
||||
background-color: var(--color-rose-600);
|
||||
}
|
||||
.bg-sky-500\/4 {
|
||||
background-color: color-mix(in srgb, oklch(68.5% 0.169 237.323) 4%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-sky-500) 4%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-slate-50 {
|
||||
background-color: var(--color-slate-50);
|
||||
}
|
||||
@@ -1302,6 +1312,15 @@
|
||||
.bg-slate-200 {
|
||||
background-color: var(--color-slate-200);
|
||||
}
|
||||
.bg-slate-300 {
|
||||
background-color: var(--color-slate-300);
|
||||
}
|
||||
.bg-slate-700 {
|
||||
background-color: var(--color-slate-700);
|
||||
}
|
||||
.bg-slate-800 {
|
||||
background-color: var(--color-slate-800);
|
||||
}
|
||||
.bg-slate-800\/60 {
|
||||
background-color: color-mix(in srgb, oklch(27.9% 0.041 260.031) 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1371,18 +1390,6 @@
|
||||
.bg-violet-100 {
|
||||
background-color: var(--color-violet-100);
|
||||
}
|
||||
.bg-violet-500\/5 {
|
||||
background-color: color-mix(in srgb, oklch(60.6% 0.25 292.717) 5%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-violet-500) 5%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-violet-500\/10 {
|
||||
background-color: color-mix(in srgb, oklch(60.6% 0.25 292.717) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-violet-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-violet-500\/20 {
|
||||
background-color: color-mix(in srgb, oklch(60.6% 0.25 292.717) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1437,6 +1444,12 @@
|
||||
background-color: color-mix(in oklab, var(--color-white) 70%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-white\/90 {
|
||||
background-color: color-mix(in srgb, #fff 90%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-white) 90%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-white\/95 {
|
||||
background-color: color-mix(in srgb, #fff 95%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1455,6 +1468,17 @@
|
||||
--tw-gradient-position: to top right in oklab;
|
||||
background-image: linear-gradient(var(--tw-gradient-stops));
|
||||
}
|
||||
.from-amber-500 {
|
||||
--tw-gradient-from: var(--color-amber-500);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.from-amber-500\/0 {
|
||||
--tw-gradient-from: color-mix(in srgb, oklch(76.9% 0.188 70.08) 0%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-gradient-from: color-mix(in oklab, var(--color-amber-500) 0%, transparent);
|
||||
}
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.from-amber-950 {
|
||||
--tw-gradient-from: var(--color-amber-950);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
@@ -1492,16 +1516,18 @@
|
||||
--tw-gradient-from: var(--color-slate-900);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.from-violet-500 {
|
||||
--tw-gradient-from: var(--color-violet-500);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
.via-amber-400 {
|
||||
--tw-gradient-via: var(--color-amber-400);
|
||||
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops);
|
||||
}
|
||||
.from-violet-500\/0 {
|
||||
--tw-gradient-from: color-mix(in srgb, oklch(60.6% 0.25 292.717) 0%, transparent);
|
||||
.via-amber-500\/60 {
|
||||
--tw-gradient-via: color-mix(in srgb, oklch(76.9% 0.188 70.08) 60%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-gradient-from: color-mix(in oklab, var(--color-violet-500) 0%, transparent);
|
||||
--tw-gradient-via: color-mix(in oklab, var(--color-amber-500) 60%, transparent);
|
||||
}
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops);
|
||||
}
|
||||
.via-black\/10 {
|
||||
--tw-gradient-via: color-mix(in srgb, #000 10%, transparent);
|
||||
@@ -1511,18 +1537,16 @@
|
||||
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops);
|
||||
}
|
||||
.via-indigo-500 {
|
||||
--tw-gradient-via: var(--color-indigo-500);
|
||||
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops);
|
||||
}
|
||||
.via-violet-500\/50 {
|
||||
--tw-gradient-via: color-mix(in srgb, oklch(60.6% 0.25 292.717) 50%, transparent);
|
||||
.to-amber-500\/0 {
|
||||
--tw-gradient-to: color-mix(in srgb, oklch(76.9% 0.188 70.08) 0%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-gradient-via: color-mix(in oklab, var(--color-violet-500) 50%, transparent);
|
||||
--tw-gradient-to: color-mix(in oklab, var(--color-amber-500) 0%, transparent);
|
||||
}
|
||||
--tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-amber-600 {
|
||||
--tw-gradient-to: var(--color-amber-600);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-emerald-950 {
|
||||
--tw-gradient-to: var(--color-emerald-950);
|
||||
@@ -1544,17 +1568,6 @@
|
||||
--tw-gradient-to: transparent;
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-violet-500 {
|
||||
--tw-gradient-to: var(--color-violet-500);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-violet-500\/0 {
|
||||
--tw-gradient-to: color-mix(in srgb, oklch(60.6% 0.25 292.717) 0%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-gradient-to: color-mix(in oklab, var(--color-violet-500) 0%, transparent);
|
||||
}
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.fill-amber-400 {
|
||||
fill: var(--color-amber-400);
|
||||
}
|
||||
@@ -1997,6 +2010,9 @@
|
||||
.placeholder-slate-400::placeholder {
|
||||
color: var(--color-slate-400);
|
||||
}
|
||||
.accent-amber-500 {
|
||||
accent-color: var(--color-amber-500);
|
||||
}
|
||||
.opacity-0 {
|
||||
opacity: 0%;
|
||||
}
|
||||
@@ -2036,18 +2052,24 @@
|
||||
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-slate-900\/10 {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(20.8% 0.042 265.755) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-slate-900) 10%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
.shadow-slate-950\/25 {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(12.9% 0.042 264.695) 25%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-slate-950) 25%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
.shadow-violet-500\/15 {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(60.6% 0.25 292.717) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-violet-500) 15%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
.shadow-violet-600\/15 {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(54.1% 0.281 293.009) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-violet-600) 15%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
.blur {
|
||||
--tw-blur: blur(8px);
|
||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||
@@ -2107,6 +2129,11 @@
|
||||
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
}
|
||||
.backdrop-blur-xl {
|
||||
--tw-backdrop-blur: blur(var(--blur-xl));
|
||||
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
}
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -2313,6 +2340,9 @@
|
||||
.hover\:text-amber-400:hover {
|
||||
color: var(--color-amber-400);
|
||||
}
|
||||
.hover\:text-amber-700:hover {
|
||||
color: var(--color-amber-700);
|
||||
}
|
||||
.hover\:text-red-400:hover {
|
||||
color: var(--color-red-400);
|
||||
}
|
||||
@@ -2350,6 +2380,14 @@
|
||||
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.hover\:shadow-slate-200\/50:hover {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(92.9% 0.013 255.508) 50%, transparent);
|
||||
}
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
.hover\:shadow-slate-200\/50:hover {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-slate-200) 50%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
.focus\:border-violet-500:focus {
|
||||
border-color: var(--color-violet-500);
|
||||
@@ -2583,6 +2621,9 @@
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:bg-slate-700 {
|
||||
background-color: var(--color-slate-700);
|
||||
}
|
||||
.dark\:bg-white\/5 {
|
||||
background-color: color-mix(in srgb, #fff 5%, transparent);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,219 +0,0 @@
|
||||
@namespace MidrandBooks.Components.Layout
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<dialog id="components-reconnect-modal" data-nosnippet class="fixed inset-0 w-screen h-screen m-0 p-0 border-0 bg-transparent z-[99999] overflow-hidden flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-300">
|
||||
<!-- Dark Frosted Jacket Layer -->
|
||||
<div class="absolute inset-0 bg-slate-950/60 backdrop-blur-md cursor-wait z-[1] animate-in fade-in duration-300"></div>
|
||||
|
||||
<!-- Centered Content Wrapper -->
|
||||
<div class="reconnect-content relative z-[2] flex flex-col items-center gap-6 text-center text-slate-100 max-w-sm px-6">
|
||||
|
||||
<!-- Waveform Animation -->
|
||||
<svg class="waveform-svg text-violet-400" viewBox="0 0 54 50" width="60" height="60" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect class="bar bar-1" x="2" y="15" width="4" height="20" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-2" x="14" y="5" width="4" height="40" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-3" x="26" y="20" width="4" height="10" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-4" x="38" y="10" width="4" height="30" rx="2" fill="currentColor" />
|
||||
<rect class="bar bar-5" x="50" y="18" width="4" height="14" rx="2" fill="currentColor" />
|
||||
</svg>
|
||||
|
||||
<!-- Message Body -->
|
||||
<div class="sync-message-body font-mono uppercase text-xs tracking-widest text-slate-300 space-y-2">
|
||||
<span class="components-reconnect-first-attempt-visible hidden">
|
||||
Losing signal...
|
||||
</span>
|
||||
|
||||
<span class="components-reconnect-repeated-attempt-visible hidden">
|
||||
reconnecting in <span id="components-seconds-to-next-attempt" class="font-bold text-violet-400">0</span>s...
|
||||
</span>
|
||||
|
||||
<span class="components-reconnect-failed-visible text-rose-400 font-semibold hidden">
|
||||
Playback disconnected.
|
||||
</span>
|
||||
|
||||
<span class="components-pause-visible hidden">
|
||||
Reading layout paused.
|
||||
</span>
|
||||
|
||||
<span class="components-resume-failed-visible text-rose-400 font-semibold hidden">
|
||||
State alignment broken.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Glass Action Buttons -->
|
||||
<div class="sync-action-node flex items-center justify-center gap-3">
|
||||
<button id="components-reconnect-button" class="btn-glass-action hidden px-4 py-2.5 bg-white/5 border border-white/10 hover:bg-white/10 text-white font-mono uppercase text-[10px] tracking-widest rounded-xl transition-all items-center gap-2 cursor-pointer shadow-lg hover:scale-102 hover:border-violet-500/30">
|
||||
<span>Retry Sync</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="components-resume-button" class="btn-glass-action hidden px-4 py-2.5 bg-white/5 border border-white/10 hover:bg-white/10 text-white font-mono uppercase text-[10px] tracking-widest rounded-xl transition-all items-center gap-2 cursor-pointer shadow-lg hover:scale-102 hover:border-violet-500/30">
|
||||
<span>Reload Session</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="23 4 23 10 17 10"></polyline>
|
||||
<polyline points="1 20 1 14 7 14"></polyline>
|
||||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
/* --- Show the modal smoothly when Blazor attaches state classes --- */
|
||||
#components-reconnect-modal.components-reconnect-show,
|
||||
#components-reconnect-modal.components-reconnect-failed,
|
||||
#components-reconnect-modal.components-reconnect-rejected {
|
||||
opacity: 1 !important;
|
||||
pointer-events: all !important;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* --- SVG Waveform Animation --- */
|
||||
#components-reconnect-modal .bar {
|
||||
transform-origin: center;
|
||||
animation: reconnectWavePulse 1.2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
#components-reconnect-modal .bar-1 {
|
||||
animation-delay: 0.0s;
|
||||
}
|
||||
|
||||
#components-reconnect-modal .bar-2 {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
#components-reconnect-modal .bar-3 {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
#components-reconnect-modal .bar-4 {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
#components-reconnect-modal .bar-5 {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
@@keyframes reconnectWavePulse {
|
||||
0% {
|
||||
transform: scaleY(0.3);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Stop the waveform if the connection completely fails */
|
||||
#components-reconnect-modal.components-reconnect-failed .bar,
|
||||
#components-reconnect-modal.components-reconnect-rejected .bar,
|
||||
#components-reconnect-modal.components-reconnect-paused .bar {
|
||||
animation: none !important;
|
||||
transform: scaleY(0.1) !important;
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
/* --- Display Mechanics Matrix Controllers --- */
|
||||
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-failed .btn-glass-action,
|
||||
#components-reconnect-modal.components-reconnect-paused .btn-glass-action,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .btn-glass-action {
|
||||
display: inline-flex !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
function initReconnectModal() {
|
||||
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||
if (!reconnectModal) {
|
||||
setTimeout(initReconnectModal, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||
|
||||
const retryButton = document.getElementById("components-reconnect-button");
|
||||
if (retryButton) {
|
||||
retryButton.addEventListener("click", retry);
|
||||
}
|
||||
|
||||
const resumeButton = document.getElementById("components-resume-button");
|
||||
if (resumeButton) {
|
||||
resumeButton.addEventListener("click", resume);
|
||||
}
|
||||
|
||||
function handleReconnectStateChanged(event) {
|
||||
if (event.detail.state === "show") {
|
||||
reconnectModal.showModal();
|
||||
} else if (event.detail.state === "hide") {
|
||||
reconnectModal.close();
|
||||
} else if (event.detail.state === "failed") {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
} else if (event.detail.state === "rejected") {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retry() {
|
||||
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
|
||||
try {
|
||||
if (window.Blazor) {
|
||||
const successful = await window.Blazor.reconnect();
|
||||
if (!successful) {
|
||||
const resumeSuccessful = await window.Blazor.resumeCircuit();
|
||||
if (!resumeSuccessful) {
|
||||
location.reload();
|
||||
} else {
|
||||
reconnectModal.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
}
|
||||
}
|
||||
|
||||
async function resume() {
|
||||
try {
|
||||
if (window.Blazor) {
|
||||
const successful = await window.Blazor.resumeCircuit();
|
||||
if (!successful) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function retryWhenDocumentBecomesVisible() {
|
||||
if (document.visibilityState === "visible") {
|
||||
await retry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initReconnectModal);
|
||||
} else {
|
||||
initReconnectModal();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user