5et Initial React Skin
This commit is contained in:
1 parent
b236390afe
commit
0eb340e1c2
17 files changed
+5079
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface DesktopApi {
|
||||
getWikiUrl: () => Promise<string>;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
desktopApi: DesktopApi;
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
];
|
||||
Reference in new issue
Block a user