5et Initial React Skin

This commit is contained in:
boris committed 2026-02-24 19:58:53 +00:00
1 parent b236390afe
commit 0eb340e1c2
17 files changed
+5079

No files matched your search

+91
View File
@@ -0,0 +1,91 @@
import { useEffect, useMemo, useState } from "react";
import { WIKI_NAV_SECTIONS } from "./wikiNav";
const DEFAULT_PAGE = "/5etools.html";
export default function App() {
const [wikiUrl, setWikiUrl] = useState<string>("");
const [error, setError] = useState<string>("");
const [currentPath, setCurrentPath] = useState<string>(DEFAULT_PAGE);
useEffect(() => {
window.desktopApi
.getWikiUrl()
.then((url) => setWikiUrl(url))
.catch((err: unknown) => {
const message = err instanceof Error ? err.message : "Unknown error";
setError(message);
});
}, []);
const frameSrc = useMemo(() => {
if (!wikiUrl) {
return "";
}
return new URL(currentPath, `${wikiUrl}/`).toString();
}, [currentPath, wikiUrl]);
if (error) {
return (
<main className="status-wrap">
<section className="status-panel">
<h1>5etools Desktop failed to start</h1>
<p>{error}</p>
</section>
</main>
);
}
if (!wikiUrl) {
return (
<main className="status-wrap">
<section className="status-panel">
<h1>Starting 5etools Desktop</h1>
<p>Loading local 5etools wiki...</p>
</section>
</main>
);
}
return (
<main className="shell" aria-label="5etools Desktop Wiki Shell">
<aside className="sidebar">
<header className="sidebar-brand">
<p className="sidebar-kicker">5etools Desktop</p>
<h1 className="sidebar-title">Compendium</h1>
<p className="sidebar-copy">5etools Desktop by <b>notBoris</b>.</p>
<p className="sidebar-copy-subtitle">This application is not affiliated with the 5etools Project. 5etools® is a Registered Trademark of The Norr Group Ltd. All rights reserved.</p>
</header>
<nav className="sidebar-nav" aria-label="Wiki Sections">
{WIKI_NAV_SECTIONS.map((section) => (
<section key={section.title} className="nav-section">
<h2>{section.title}</h2>
<ul>
{section.items.map((item) => (
<li key={item.path}>
<button
type="button"
className={item.path === currentPath ? "nav-item is-active" : "nav-item"}
onClick={() => setCurrentPath(item.path)}
>
<span className="nav-item-label">{item.label}</span>
<span className="nav-item-desc">{item.description}</span>
</button>
</li>
))}
</ul>
</section>
))}
</nav>
</aside>
<section className="workspace">
<section className="frame-shell frame-shell--full">
<iframe className="wiki-frame" src={frameSrc} title="5etools Wiki" />
</section>
</section>
</main>
);
}
+10
View File
@@ -0,0 +1,10 @@
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./styles.css";
createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
+250
View File
@@ -0,0 +1,250 @@
:root {
--bg-0: #070d15;
--bg-1: #0f1a2c;
--bg-2: #17243a;
--panel-0: rgba(10, 16, 28, 0.86);
--panel-1: rgba(23, 36, 58, 0.88);
--line: rgba(130, 178, 228, 0.24);
--line-strong: rgba(130, 178, 228, 0.5);
--text-0: #f4f8fd;
--text-1: #c2d0e4;
--text-2: #90a3bf;
--accent: #60c4ff;
--accent-strong: #8fd8ff;
--accent-warm: #e8b273;
--shadow: 0 16px 42px rgba(0, 0, 0, 0.42);
color: var(--text-0);
background: var(--bg-0);
font-family: "Avenir Next", "Trebuchet MS", "Segoe UI", sans-serif;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body,
#root {
width: 100%;
height: 100%;
margin: 0;
}
body {
overflow: hidden;
background:
radial-gradient(circle at 15% -5%, rgba(62, 117, 194, 0.45), transparent 42%),
radial-gradient(circle at 86% 0%, rgba(235, 167, 86, 0.4), transparent 40%),
linear-gradient(165deg, var(--bg-0) 0%, #0b1322 100%);
}
.shell {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 320px 1fr;
}
.sidebar {
border-right: 1px solid var(--line);
background: linear-gradient(180deg, var(--panel-0) 0%, rgba(9, 14, 24, 0.88) 100%);
backdrop-filter: blur(18px);
display: flex;
flex-direction: column;
min-height: 0;
}
.sidebar-brand {
padding: 24px 20px 16px;
border-bottom: 1px solid var(--line);
}
.sidebar-kicker {
margin: 0 0 6px;
color: var(--accent-warm);
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.sidebar-title {
margin: 0;
font-size: 1.45rem;
letter-spacing: 0.01em;
}
.sidebar-copy {
margin: 10px 0 0;
color: var(--text-1);
font-size: 0.9rem;
line-height: 1.4;
}
.sidebar-copy-subtitle {
margin: 5px 0 0;
color: var(--text-1);
font-size: 0.6rem;
line-height: 1.4;
}
.sidebar-nav {
padding: 16px 14px 24px;
overflow: auto;
}
.nav-section + .nav-section {
margin-top: 18px;
}
.nav-section h2 {
margin: 0 0 10px;
padding: 0 8px;
color: var(--text-2);
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.14em;
font-weight: 700;
}
.nav-section ul {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 6px;
}
.nav-item {
width: 100%;
border: 1px solid transparent;
background: rgba(24, 40, 63, 0.45);
border-radius: 12px;
padding: 10px 12px;
color: var(--text-1);
text-align: left;
cursor: pointer;
transition:
transform 120ms ease,
border-color 120ms ease,
background-color 120ms ease;
}
.nav-item:hover {
transform: translateX(2px);
border-color: var(--line-strong);
background: rgba(31, 54, 83, 0.75);
}
.nav-item.is-active {
border-color: rgba(120, 205, 255, 0.75);
background: linear-gradient(140deg, rgba(63, 132, 207, 0.38), rgba(36, 75, 116, 0.82));
}
.nav-item-label {
display: block;
font-size: 0.92rem;
font-weight: 650;
color: var(--text-0);
}
.nav-item-desc {
margin-top: 3px;
display: block;
font-size: 0.76rem;
color: var(--text-2);
}
.workspace {
min-width: 0;
min-height: 0;
padding: 14px;
display: grid;
grid-template-rows: 1fr;
gap: 0;
}
.frame-shell {
min-height: 0;
border: 1px solid var(--line);
border-radius: 16px;
overflow: hidden;
box-shadow: var(--shadow);
background: rgba(0, 0, 0, 0.35);
animation: frame-enter 220ms ease;
}
.frame-shell--full {
height: 100%;
}
.wiki-frame {
width: 100%;
height: 100%;
border: 0;
display: block;
}
.status-wrap {
width: 100%;
height: 100%;
display: grid;
place-items: center;
padding: 24px;
}
.status-panel {
width: min(680px, 100%);
border: 1px solid var(--line-strong);
border-radius: 12px;
padding: 20px;
background: linear-gradient(145deg, #18273f 0%, #101726 100%);
box-shadow: var(--shadow);
}
.status-panel h1 {
margin: 0 0 8px;
font-size: 1.15rem;
}
.status-panel p {
margin: 0;
color: var(--text-1);
}
@keyframes frame-enter {
from {
opacity: 0.5;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (width <= 1200px) {
.shell {
grid-template-columns: 268px 1fr;
}
.nav-item-desc {
display: none;
}
}
@media (width <= 900px) {
.shell {
grid-template-columns: 1fr;
}
.sidebar {
display: none;
}
.workspace {
padding: 12px;
}
}
+9
View File
@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
interface DesktopApi {
getWikiUrl: () => Promise<string>;
}
interface Window {
desktopApi: DesktopApi;
}
+41
View File
@@ -0,0 +1,41 @@
export interface WikiNavItem {
label: string;
path: string;
description: string;
}
export interface WikiNavSection {
title: string;
items: WikiNavItem[];
}
export const WIKI_NAV_SECTIONS: WikiNavSection[] = [
{
title: "Core",
items: [
{ label: "Home", path: "/5etools.html", description: "Main dashboard and tools" },
{ label: "Search", path: "/search.html", description: "Global content search" },
{ label: "Quick Reference", path: "/quickreference.html", description: "Fast rules lookup" }
]
},
{
title: "Character",
items: [
{ label: "Classes", path: "/classes.html", description: "Class and subclass details" },
{ label: "Backgrounds", path: "/backgrounds.html", description: "Origin and backstory options" },
{ label: "Species", path: "/races.html", description: "Species and lineages" },
{ label: "Feats", path: "/feats.html", description: "Character feat selection" },
{ label: "Optional Features", path: "/optionalfeatures.html", description: "Expanded build options" }
]
},
{
title: "Compendium",
items: [
{ label: "Spells", path: "/spells.html", description: "Spell catalog and filters" },
{ label: "Items", path: "/items.html", description: "Equipment and magic items" },
{ label: "Bestiary", path: "/bestiary.html", description: "Creatures and stat blocks" },
{ label: "Rules", path: "/variantrules.html", description: "System and optional rules" },
{ label: "Tables", path: "/tables.html", description: "Reference tables and charts" }
]
}
];