223 lines
9.5 KiB
Plaintext
223 lines
9.5 KiB
Plaintext
@namespace MidrandBooks.Components
|
|
@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 -->
|
|
<header class="flex items-center justify-between px-6 py-4 border-b @HeaderBorderClass() transition-all">
|
|
<div class="flex items-center gap-4">
|
|
<button @onclick="OnClose" class="p-2 rounded-xl hover:bg-black/5 dark:hover:bg-white/5 text-slate-500 hover:text-slate-800 dark:hover:text-slate-100 cursor-pointer">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
</svg>
|
|
</button>
|
|
<div>
|
|
<h2 class="font-serif text-sm font-bold truncate max-w-xs sm:max-w-md">@Book.Title</h2>
|
|
<p class="text-[10px] text-slate-400 font-mono uppercase">By @Book.Author • Chapter Page @(CurrentPage + 1) of @PagesCount()</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3">
|
|
<!-- Bookmark Button -->
|
|
<button @onclick="ToggleBookmark" class="p-2 rounded-xl hover:bg-black/5 dark:hover:bg-white/5 cursor-pointer" title="Toggle Bookmark">
|
|
<svg class="h-5 w-5 @(IsBookmarked ? "text-amber-500 fill-amber-500" : "text-slate-400")" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z" />
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Reader Settings Button -->
|
|
<button @onclick="ToggleSettings" class="p-2 rounded-xl hover:bg-black/5 dark:hover:bg-white/5 text-slate-500 hover:text-slate-800 dark:hover:text-slate-100 cursor-pointer" title="Reader Font / Theme Customization">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Reader Settings Overlay Panel -->
|
|
@if (ShowSettings)
|
|
{
|
|
<div class="p-6 border-b @HeaderBorderClass() bg-black/5 dark:bg-white/5 space-y-4 animate-in slide-in-from-top duration-200">
|
|
<div class="max-w-md mx-auto grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
|
|
<!-- Theme Selectors -->
|
|
<div class="space-y-1.5">
|
|
<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-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>
|
|
|
|
<!-- Font Selectors -->
|
|
<div class="space-y-1.5">
|
|
<label class="text-[10px] font-mono font-bold uppercase block opacity-70">Font Style</label>
|
|
<div class="flex bg-black/10 dark:bg-white/10 p-1 rounded-xl">
|
|
<button @onclick='() => SetFont("serif")' class="flex-1 py-1 text-xs font-bold font-serif rounded-lg @(Font == "serif" ? "bg-white/40 text-current" : "text-slate-500")">Serif</button>
|
|
<button @onclick='() => SetFont("sans")' class="flex-1 py-1 text-xs font-bold font-sans rounded-lg @(Font == "sans" ? "bg-white/40 text-current" : "text-slate-500")">Sans</button>
|
|
<button @onclick='() => SetFont("mono")' class="flex-1 py-1 text-xs font-bold font-mono rounded-lg @(Font == "mono" ? "bg-white/40 text-current" : "text-slate-500")">Mono</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Font Size Config -->
|
|
<div class="sm:col-span-2 space-y-1">
|
|
<div class="flex justify-between text-[10px] font-mono opacity-70">
|
|
<span>Font Size</span>
|
|
<span>@(FontSize)px</span>
|
|
</div>
|
|
<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>
|
|
</div>
|
|
}
|
|
|
|
<!-- Reading Canvas Area -->
|
|
<div class="flex-1 overflow-y-auto px-6 py-12 flex justify-center">
|
|
<div class="max-w-2xl w-full leading-relaxed text-justify @FontClass() @TextContrastClass()" style="font-size: @(FontSize)px;">
|
|
@if (Book.FullText != null && Book.FullText.Count > CurrentPage)
|
|
{
|
|
<p class="whitespace-pre-wrap">@Book.FullText[CurrentPage]</p>
|
|
}
|
|
else
|
|
{
|
|
<p class="italic text-center py-12">
|
|
Digital copy protection sandbox active. Reflowable text formatting is loading.
|
|
</p>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer Controls -->
|
|
<footer class="px-6 py-4 border-t @HeaderBorderClass() flex items-center justify-between">
|
|
<button @onclick="PrevPage" class="px-4 py-2 rounded-xl bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 text-xs font-bold transition-all flex items-center gap-1 cursor-pointer">
|
|
Previous Page
|
|
</button>
|
|
|
|
<span class="text-xs font-mono font-semibold">Page @(CurrentPage + 1) of @PagesCount()</span>
|
|
|
|
<button @onclick="NextPage" class="px-4 py-2 rounded-xl bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 text-xs font-bold transition-all flex items-center gap-1 cursor-pointer">
|
|
Next Page
|
|
</button>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter] public BookModel Book { get; set; } = new();
|
|
[Parameter] public EventCallback OnClose { get; set; }
|
|
[Parameter] public int CurrentPage { get; set; } = 0;
|
|
[Parameter] public EventCallback<int> OnPageChanged { get; set; }
|
|
|
|
private bool ShowSettings { get; set; } = false;
|
|
private string Theme { get; set; } = "sepia";
|
|
private string Font { get; set; } = "serif";
|
|
private int FontSize { get; set; } = 16;
|
|
private bool IsBookmarked { get; set; } = false;
|
|
private List<int> Bookmarks { get; set; } = new();
|
|
|
|
private void ToggleSettings()
|
|
{
|
|
ShowSettings = !ShowSettings;
|
|
}
|
|
|
|
private void ToggleBookmark()
|
|
{
|
|
if (Bookmarks.Contains(CurrentPage))
|
|
{
|
|
Bookmarks.Remove(CurrentPage);
|
|
IsBookmarked = false;
|
|
}
|
|
else
|
|
{
|
|
Bookmarks.Add(CurrentPage);
|
|
IsBookmarked = true;
|
|
}
|
|
}
|
|
|
|
private int PagesCount()
|
|
{
|
|
return Book.FullText?.Count ?? 1;
|
|
}
|
|
|
|
private void NextPage()
|
|
{
|
|
if (Book.FullText != null && CurrentPage < Book.FullText.Count - 1)
|
|
{
|
|
CurrentPage++;
|
|
IsBookmarked = Bookmarks.Contains(CurrentPage);
|
|
OnPageChanged.InvokeAsync(CurrentPage);
|
|
}
|
|
}
|
|
|
|
private void PrevPage()
|
|
{
|
|
if (CurrentPage > 0)
|
|
{
|
|
CurrentPage--;
|
|
IsBookmarked = Bookmarks.Contains(CurrentPage);
|
|
OnPageChanged.InvokeAsync(CurrentPage);
|
|
}
|
|
}
|
|
|
|
private void SetTheme(string theme)
|
|
{
|
|
Theme = theme;
|
|
}
|
|
|
|
private void SetFont(string font)
|
|
{
|
|
Font = font;
|
|
}
|
|
|
|
private string ThemeBgClass()
|
|
{
|
|
return Theme switch
|
|
{
|
|
"sepia" => "bg-amber-50 text-amber-950",
|
|
"dark" => "bg-slate-950 text-slate-100",
|
|
_ => "bg-white text-slate-900"
|
|
};
|
|
}
|
|
|
|
private string HeaderBorderClass()
|
|
{
|
|
return Theme == "dark" ? "border-white/5 bg-slate-900/40" : "border-black/5 bg-slate-100/40";
|
|
}
|
|
|
|
private string FontClass()
|
|
{
|
|
return Font switch
|
|
{
|
|
"sans" => "reader-text-sans",
|
|
"mono" => "reader-text-mono",
|
|
_ => "reader-text-serif"
|
|
};
|
|
}
|
|
|
|
private string TextContrastClass()
|
|
{
|
|
return Theme switch
|
|
{
|
|
"sepia" => "text-amber-900",
|
|
"dark" => "text-slate-200",
|
|
_ => "text-slate-800"
|
|
};
|
|
}
|
|
}
|