Adjusted functionality and style of class/background/species screens

This commit is contained in:
boris committed 2026-02-26 20:12:45 +00:00
1 parent 18734acaad
commit 4491e22573
3 files changed
+186 -172

No files matched your search

+40 -7
View File
@@ -350,7 +350,7 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
addCharactersMenu(); addCharactersMenu();
addResetOnboardingMenu(); addResetOnboardingMenu();
bindNativeNavbarSync(); bindNativeNavbarSync();
postNativeNavbarModel(); queueNativeNavbarModelPost({ isForce: true });
}; };
const NAVBAR_ACTION_ATTR = "data-5etd-nav-action-id"; const NAVBAR_ACTION_ATTR = "data-5etd-nav-action-id";
@@ -593,17 +593,50 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
return groups; return groups;
}; };
const postNativeNavbarModel = () => { let navbarModelLastSignature = "";
let isNavbarModelPostQueued = false;
const postNativeNavbarModel = ({ isForce = false } = {}) => {
if (!window.parent || window.parent === window) return; if (!window.parent || window.parent === window) return;
const groups = buildNativeNavbarGroups();
let nextSignature = "";
try {
nextSignature = JSON.stringify(groups);
} catch {
nextSignature = \`groups:\${groups.length}:\${Date.now()}\`;
}
if (!isForce && nextSignature === navbarModelLastSignature) {
return;
}
navbarModelLastSignature = nextSignature;
window.parent.postMessage( window.parent.postMessage(
{ {
type: "5etd:native-navbar-data", type: "5etd:native-navbar-data",
groups: buildNativeNavbarGroups() groups
}, },
"*" "*"
); );
}; };
const queueNativeNavbarModelPost = ({ isForce = false } = {}) => {
if (isForce) {
isNavbarModelPostQueued = false;
postNativeNavbarModel({ isForce: true });
return;
}
if (isNavbarModelPostQueued) {
return;
}
isNavbarModelPostQueued = true;
window.setTimeout(() => {
isNavbarModelPostQueued = false;
postNativeNavbarModel();
}, 80);
};
let navbarSyncObserver = null; let navbarSyncObserver = null;
const bindNativeNavbarSync = () => { const bindNativeNavbarSync = () => {
const navbar = document.getElementById("navbar"); const navbar = document.getElementById("navbar");
@@ -614,9 +647,9 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
navbar.dataset.desktopNavSyncBound = "1"; navbar.dataset.desktopNavSyncBound = "1";
navbarSyncObserver?.disconnect?.(); navbarSyncObserver?.disconnect?.();
navbarSyncObserver = new MutationObserver(() => { navbarSyncObserver = new MutationObserver(() => {
window.requestAnimationFrame(postNativeNavbarModel); queueNativeNavbarModelPost();
}); });
navbarSyncObserver.observe(navbar, { childList: true, subtree: true, attributes: true, characterData: true }); navbarSyncObserver.observe(navbar, { childList: true, subtree: true });
}; };
const runNativeNavbarAction = (actionId) => { const runNativeNavbarAction = (actionId) => {
@@ -649,7 +682,7 @@ function getDesktopReskinBootstrap(externalImageRoot: string | null): string {
if (data.type === "5etd:native-navbar-request") { if (data.type === "5etd:native-navbar-request") {
bindNativeNavbarSync(); bindNativeNavbarSync();
postNativeNavbarModel(); queueNativeNavbarModelPost({ isForce: true });
return; return;
} }
@@ -693,7 +726,7 @@ html.desktop-reskin .page__header {
border: 1px solid rgba(109, 164, 223, 0.32) !important; border: 1px solid rgba(109, 164, 223, 0.32) !important;
border-radius: 18px; border-radius: 18px;
background: linear-gradient(115deg, rgba(17, 29, 47, 0.98), rgba(24, 38, 58, 0.96) 64%, rgba(87, 58, 32, 0.88)); background: linear-gradient(115deg, rgba(17, 29, 47, 0.98), rgba(24, 38, 58, 0.96) 64%, rgba(87, 58, 32, 0.88));
backdrop-filter: blur(14px); // backdrop-filter: blur(14px);
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
} }
+116 -152
View File
@@ -1,7 +1,6 @@
import { FormEvent, JSX, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FormEvent, JSX, useCallback, useEffect, useMemo, useRef, useState } from "react";
const DEFAULT_PAGE = "/5etools.html"; const DEFAULT_PAGE = "/5etools.html";
const CARD_PAGE_SIZE = 8;
const APP_CHARACTERS_MENU_ID = "__characters"; const APP_CHARACTERS_MENU_ID = "__characters";
const CHARACTER_STEPS = [ const CHARACTER_STEPS = [
@@ -82,6 +81,10 @@ const FIVEETOOLS_RENDER_STYLESHEET_PATHS = ["css/bootstrap.css", "css/main.css"]
type CharacterModalMode = "closed" | "list" | "editor"; type CharacterModalMode = "closed" | "list" | "editor";
type EntityPopoverKind = "class" | "species" | "background"; type EntityPopoverKind = "class" | "species" | "background";
type PendingEntitySelection = {
kind: EntityPopoverKind;
option: CharacterSelectionOption;
};
type RendererHandle = { type RendererHandle = {
setBaseUrl: (value: string) => RendererHandle; setBaseUrl: (value: string) => RendererHandle;
@@ -206,10 +209,6 @@ function getPathnameFromHrefLoose(hrefRaw: string): string {
} }
} }
function areTopbarGroupsEquivalent(a: TopbarMenuGroup[], b: TopbarMenuGroup[]): boolean {
return JSON.stringify(a) === JSON.stringify(b);
}
function parseNativeTopbarMenuList( function parseNativeTopbarMenuList(
list: Element, list: Element,
registerAction: (ele: HTMLElement) => string, registerAction: (ele: HTMLElement) => string,
@@ -755,17 +754,6 @@ function filterBySourceBuckets(options: CharacterSelectionOption[], sourceBucket
return options.filter((option) => selected.has(option.sourceBucket)); return options.filter((option) => selected.has(option.sourceBucket));
} }
function clampPage(page: number, totalItems: number): number {
const totalPages = Math.max(1, Math.ceil(totalItems / CARD_PAGE_SIZE));
return Math.max(1, Math.min(totalPages, page));
}
function paginateOptions(options: CharacterSelectionOption[], page: number): CharacterSelectionOption[] {
const safePage = clampPage(page, options.length);
const start = (safePage - 1) * CARD_PAGE_SIZE;
return options.slice(start, start + CARD_PAGE_SIZE);
}
function toEntityRef(option: CharacterSelectionOption): CharacterEntityRef { function toEntityRef(option: CharacterSelectionOption): CharacterEntityRef {
return { return {
entityType: option.entityType, entityType: option.entityType,
@@ -2001,6 +1989,7 @@ function WikiView() {
const [topbarGroups, setTopbarGroups] = useState<TopbarMenuGroup[]>([]); const [topbarGroups, setTopbarGroups] = useState<TopbarMenuGroup[]>([]);
const frameRef = useRef<HTMLIFrameElement | null>(null); const frameRef = useRef<HTMLIFrameElement | null>(null);
const topbarRef = useRef<HTMLElement | null>(null); const topbarRef = useRef<HTMLElement | null>(null);
const topbarGroupsSignatureRef = useRef<string>("");
const [characterModalMode, setCharacterModalMode] = useState<CharacterModalMode>("closed"); const [characterModalMode, setCharacterModalMode] = useState<CharacterModalMode>("closed");
const [characterStep, setCharacterStep] = useState<number>(0); const [characterStep, setCharacterStep] = useState<number>(0);
@@ -2019,10 +2008,8 @@ function WikiView() {
const [spellSearch, setSpellSearch] = useState(""); const [spellSearch, setSpellSearch] = useState("");
const [equipmentItemInput, setEquipmentItemInput] = useState(""); const [equipmentItemInput, setEquipmentItemInput] = useState("");
const [classPage, setClassPage] = useState(1);
const [speciesPage, setSpeciesPage] = useState(1);
const [backgroundPage, setBackgroundPage] = useState(1);
const [entityPopover, setEntityPopover] = useState<{ kind: EntityPopoverKind; entityRef: string } | null>(null); const [entityPopover, setEntityPopover] = useState<{ kind: EntityPopoverKind; entityRef: string } | null>(null);
const [pendingEntitySelection, setPendingEntitySelection] = useState<PendingEntitySelection | null>(null);
const [featCatalog, setFeatCatalog] = useState<FeatCatalogItem[] | null>(null); const [featCatalog, setFeatCatalog] = useState<FeatCatalogItem[] | null>(null);
const [spellCatalog, setSpellCatalog] = useState<SpellCatalogItem[] | null>(null); const [spellCatalog, setSpellCatalog] = useState<SpellCatalogItem[] | null>(null);
const [itemCatalog, setItemCatalog] = useState<string[]>([]); const [itemCatalog, setItemCatalog] = useState<string[]>([]);
@@ -2081,6 +2068,60 @@ function WikiView() {
[ensureOptionDetail] [ensureOptionDetail]
); );
const closeEntityPopover = useCallback(() => {
setEntityPopover(null);
setPendingEntitySelection(null);
}, []);
const confirmPendingEntitySelection = useCallback(() => {
if (!pendingEntitySelection) {
closeEntityPopover();
return;
}
const { kind, option } = pendingEntitySelection;
updateDraft((draft) => {
if (kind === "class") {
return {
...draft,
selections: {
...draft.selections,
class: toEntityRef(option),
subclass: null
}
};
}
if (kind === "species") {
return {
...draft,
selections: {
...draft.selections,
species: toEntityRef(option)
}
};
}
return {
...draft,
selections: {
...draft.selections,
background: toEntityRef(option)
},
steps: {
...draft.steps,
abilityScores: {
...draft.steps.abilityScores,
backgroundBonusModeIndex: 0,
backgroundBonusSelections: []
}
}
};
});
closeEntityPopover();
}, [closeEntityPopover, pendingEntitySelection, updateDraft]);
const refreshCharacterSummaries = useCallback(async () => { const refreshCharacterSummaries = useCallback(async () => {
const list = await window.desktopApi.listCharacters(); const list = await window.desktopApi.listCharacters();
setCharacterSummaries(list); setCharacterSummaries(list);
@@ -2093,9 +2134,6 @@ function WikiView() {
setFeatSearch(""); setFeatSearch("");
setSpellSearch(""); setSpellSearch("");
setEquipmentItemInput(""); setEquipmentItemInput("");
setClassPage(1);
setSpeciesPage(1);
setBackgroundPage(1);
setCharacterStep(0); setCharacterStep(0);
}, []); }, []);
@@ -2348,9 +2386,9 @@ function WikiView() {
}; };
}; };
const pLoadAll = runtime.DataUtil?.spell?.pLoadAll; const spellDataUtil = runtime.DataUtil?.spell;
const getCombinedClasses = runtime.Renderer?.spell?.getCombinedClasses; const getCombinedClasses = runtime.Renderer?.spell?.getCombinedClasses;
if (!pLoadAll) { if (!spellDataUtil?.pLoadAll) {
if (!isCancelled) { if (!isCancelled) {
setSpellCatalog([]); setSpellCatalog([]);
} }
@@ -2367,7 +2405,7 @@ function WikiView() {
sourceLookup = null; sourceLookup = null;
} }
const spellsRaw = await pLoadAll(); const spellsRaw = await spellDataUtil.pLoadAll();
const next = (Array.isArray(spellsRaw) ? spellsRaw : []) const next = (Array.isArray(spellsRaw) ? spellsRaw : [])
.map((spellRaw): SpellCatalogItem | null => { .map((spellRaw): SpellCatalogItem | null => {
if (!isRecord(spellRaw)) { if (!isRecord(spellRaw)) {
@@ -2471,7 +2509,20 @@ function WikiView() {
return; return;
} }
setTopbarGroups((current) => (areTopbarGroupsEquivalent(current, data.groups as TopbarMenuGroup[]) ? current : (data.groups as TopbarMenuGroup[]))); const nextGroups = data.groups as TopbarMenuGroup[];
let nextSignature = "";
try {
nextSignature = JSON.stringify(nextGroups);
} catch {
nextSignature = `groups:${nextGroups.length}:${Date.now()}`;
}
if (topbarGroupsSignatureRef.current === nextSignature) {
return;
}
topbarGroupsSignatureRef.current = nextSignature;
setTopbarGroups(nextGroups);
return; return;
} }
@@ -2829,39 +2880,6 @@ function WikiView() {
[characterOptions?.backgrounds, sourceBuckets, backgroundSearch] [characterOptions?.backgrounds, sourceBuckets, backgroundSearch]
); );
const classTotalPages = Math.max(1, Math.ceil(filteredClassOptions.length / CARD_PAGE_SIZE));
const speciesTotalPages = Math.max(1, Math.ceil(filteredSpeciesOptions.length / CARD_PAGE_SIZE));
const backgroundTotalPages = Math.max(1, Math.ceil(filteredBackgroundOptions.length / CARD_PAGE_SIZE));
const classPageSafe = clampPage(classPage, filteredClassOptions.length);
const speciesPageSafe = clampPage(speciesPage, filteredSpeciesOptions.length);
const backgroundPageSafe = clampPage(backgroundPage, filteredBackgroundOptions.length);
const classPageOptions = useMemo(
() => paginateOptions(filteredClassOptions, classPageSafe),
[filteredClassOptions, classPageSafe]
);
const speciesPageOptions = useMemo(
() => paginateOptions(filteredSpeciesOptions, speciesPageSafe),
[filteredSpeciesOptions, speciesPageSafe]
);
const backgroundPageOptions = useMemo(
() => paginateOptions(filteredBackgroundOptions, backgroundPageSafe),
[filteredBackgroundOptions, backgroundPageSafe]
);
useEffect(() => {
setClassPage((current) => clampPage(current, filteredClassOptions.length));
}, [filteredClassOptions.length]);
useEffect(() => {
setSpeciesPage((current) => clampPage(current, filteredSpeciesOptions.length));
}, [filteredSpeciesOptions.length]);
useEffect(() => {
setBackgroundPage((current) => clampPage(current, filteredBackgroundOptions.length));
}, [filteredBackgroundOptions.length]);
useEffect(() => { useEffect(() => {
const selectedRefs = [ const selectedRefs = [
characterDraft?.selections.class?.entityRef, characterDraft?.selections.class?.entityRef,
@@ -2883,6 +2901,12 @@ function WikiView() {
: null; : null;
const entityPopoverDetail = entityPopover ? detailCache[entityPopover.entityRef] ?? null : null; const entityPopoverDetail = entityPopover ? detailCache[entityPopover.entityRef] ?? null : null;
useEffect(() => {
if (characterModalMode !== "editor") {
closeEntityPopover();
}
}, [characterModalMode, closeEntityPopover]);
useEffect(() => { useEffect(() => {
if (characterModalMode !== "editor" || !entityPopover) { if (characterModalMode !== "editor" || !entityPopover) {
return; return;
@@ -2890,22 +2914,22 @@ function WikiView() {
const onKeyDown = (event: KeyboardEvent) => { const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") { if (event.key === "Escape") {
setEntityPopover(null); closeEntityPopover();
} }
}; };
window.addEventListener("keydown", onKeyDown); window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown);
}, [characterModalMode, entityPopover]); }, [characterModalMode, closeEntityPopover, entityPopover]);
useEffect(() => { useEffect(() => {
if (characterModalMode !== "editor" || !entityPopover) { if (characterModalMode !== "editor" || !entityPopover) {
return; return;
} }
if (characterStep < 0 || characterStep > 2) { if (characterStep < 0 || characterStep > 2) {
setEntityPopover(null); closeEntityPopover();
} }
}, [characterModalMode, characterStep, entityPopover]); }, [characterModalMode, characterStep, closeEntityPopover, entityPopover]);
const classSkillData = useMemo( const classSkillData = useMemo(
() => parseClassSkills(selectedClassDetail), () => parseClassSkills(selectedClassDetail),
@@ -4168,7 +4192,7 @@ function WikiView() {
<div className="entity-layout"> <div className="entity-layout">
<div className="entity-list-panel entity-list-panel--solo"> <div className="entity-list-panel entity-list-panel--solo">
<div className="entity-card-grid"> <div className="entity-card-grid">
{classPageOptions.map((option) => { {filteredClassOptions.map((option) => {
const isSelected = characterDraft.selections.class?.entityRef === option.entityRef; const isSelected = characterDraft.selections.class?.entityRef === option.entityRef;
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath); const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition); const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
@@ -4179,14 +4203,7 @@ function WikiView() {
key={option.entityRef} key={option.entityRef}
className={isSelected ? "entity-tile is-selected" : "entity-tile"} className={isSelected ? "entity-tile is-selected" : "entity-tile"}
onClick={() => { onClick={() => {
updateDraft((draft) => ({ setPendingEntitySelection({ kind: "class", option });
...draft,
selections: {
...draft.selections,
class: toEntityRef(option),
subclass: null
}
}));
openEntityPopover("class", option.entityRef); openEntityPopover("class", option.entityRef);
}} }}
> >
@@ -4205,22 +4222,6 @@ function WikiView() {
{filteredClassOptions.length === 0 ? <p className="character-empty">No classes match the selected source buckets.</p> : null} {filteredClassOptions.length === 0 ? <p className="character-empty">No classes match the selected source buckets.</p> : null}
<div className="entity-pagination">
<button type="button" onClick={() => setClassPage((it) => Math.max(1, it - 1))} disabled={classPageSafe <= 1}>
Previous
</button>
<span>
Page {classPageSafe}/{classTotalPages} ({filteredClassOptions.length} total)
</span>
<button
type="button"
onClick={() => setClassPage((it) => Math.min(classTotalPages, it + 1))}
disabled={classPageSafe >= classTotalPages}
>
Next
</button>
</div>
<div className="entity-list-actions"> <div className="entity-list-actions">
<button <button
type="button" type="button"
@@ -4229,6 +4230,7 @@ function WikiView() {
if (!characterDraft.selections.class) { if (!characterDraft.selections.class) {
return; return;
} }
setPendingEntitySelection(null);
openEntityPopover("class", characterDraft.selections.class.entityRef); openEntityPopover("class", characterDraft.selections.class.entityRef);
}} }}
disabled={!characterDraft.selections.class} disabled={!characterDraft.selections.class}
@@ -4252,7 +4254,7 @@ function WikiView() {
<div className="entity-layout"> <div className="entity-layout">
<div className="entity-list-panel entity-list-panel--solo"> <div className="entity-list-panel entity-list-panel--solo">
<div className="entity-card-grid"> <div className="entity-card-grid">
{speciesPageOptions.map((option) => { {filteredSpeciesOptions.map((option) => {
const isSelected = characterDraft.selections.species?.entityRef === option.entityRef; const isSelected = characterDraft.selections.species?.entityRef === option.entityRef;
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath); const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition); const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
@@ -4263,13 +4265,7 @@ function WikiView() {
key={option.entityRef} key={option.entityRef}
className={isSelected ? "entity-tile is-selected" : "entity-tile"} className={isSelected ? "entity-tile is-selected" : "entity-tile"}
onClick={() => { onClick={() => {
updateDraft((draft) => ({ setPendingEntitySelection({ kind: "species", option });
...draft,
selections: {
...draft.selections,
species: toEntityRef(option)
}
}));
openEntityPopover("species", option.entityRef); openEntityPopover("species", option.entityRef);
}} }}
> >
@@ -4288,22 +4284,6 @@ function WikiView() {
{filteredSpeciesOptions.length === 0 ? <p className="character-empty">No species match the selected source buckets.</p> : null} {filteredSpeciesOptions.length === 0 ? <p className="character-empty">No species match the selected source buckets.</p> : null}
<div className="entity-pagination">
<button type="button" onClick={() => setSpeciesPage((it) => Math.max(1, it - 1))} disabled={speciesPageSafe <= 1}>
Previous
</button>
<span>
Page {speciesPageSafe}/{speciesTotalPages} ({filteredSpeciesOptions.length} total)
</span>
<button
type="button"
onClick={() => setSpeciesPage((it) => Math.min(speciesTotalPages, it + 1))}
disabled={speciesPageSafe >= speciesTotalPages}
>
Next
</button>
</div>
<div className="entity-list-actions"> <div className="entity-list-actions">
<button <button
type="button" type="button"
@@ -4312,6 +4292,7 @@ function WikiView() {
if (!characterDraft.selections.species) { if (!characterDraft.selections.species) {
return; return;
} }
setPendingEntitySelection(null);
openEntityPopover("species", characterDraft.selections.species.entityRef); openEntityPopover("species", characterDraft.selections.species.entityRef);
}} }}
disabled={!characterDraft.selections.species} disabled={!characterDraft.selections.species}
@@ -4337,7 +4318,7 @@ function WikiView() {
<div className="entity-layout"> <div className="entity-layout">
<div className="entity-list-panel entity-list-panel--solo"> <div className="entity-list-panel entity-list-panel--solo">
<div className="entity-card-grid"> <div className="entity-card-grid">
{backgroundPageOptions.map((option) => { {filteredBackgroundOptions.map((option) => {
const isSelected = characterDraft.selections.background?.entityRef === option.entityRef; const isSelected = characterDraft.selections.background?.entityRef === option.entityRef;
const imageUrl = buildAssetUrl(wikiUrl, option.imagePath); const imageUrl = buildAssetUrl(wikiUrl, option.imagePath);
const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition); const sourceBadge = getSourceBadgeText(option.source, option.sourceEdition);
@@ -4348,21 +4329,7 @@ function WikiView() {
key={option.entityRef} key={option.entityRef}
className={isSelected ? "entity-tile is-selected" : "entity-tile"} className={isSelected ? "entity-tile is-selected" : "entity-tile"}
onClick={() => { onClick={() => {
updateDraft((draft) => ({ setPendingEntitySelection({ kind: "background", option });
...draft,
selections: {
...draft.selections,
background: toEntityRef(option)
},
steps: {
...draft.steps,
abilityScores: {
...draft.steps.abilityScores,
backgroundBonusModeIndex: 0,
backgroundBonusSelections: []
}
}
}));
openEntityPopover("background", option.entityRef); openEntityPopover("background", option.entityRef);
}} }}
> >
@@ -4383,26 +4350,6 @@ function WikiView() {
<p className="character-empty">No backgrounds match the selected source buckets.</p> <p className="character-empty">No backgrounds match the selected source buckets.</p>
) : null} ) : null}
<div className="entity-pagination">
<button
type="button"
onClick={() => setBackgroundPage((it) => Math.max(1, it - 1))}
disabled={backgroundPageSafe <= 1}
>
Previous
</button>
<span>
Page {backgroundPageSafe}/{backgroundTotalPages} ({filteredBackgroundOptions.length} total)
</span>
<button
type="button"
onClick={() => setBackgroundPage((it) => Math.min(backgroundTotalPages, it + 1))}
disabled={backgroundPageSafe >= backgroundTotalPages}
>
Next
</button>
</div>
<div className="entity-list-actions"> <div className="entity-list-actions">
<button <button
type="button" type="button"
@@ -4411,6 +4358,7 @@ function WikiView() {
if (!characterDraft.selections.background) { if (!characterDraft.selections.background) {
return; return;
} }
setPendingEntitySelection(null);
openEntityPopover("background", characterDraft.selections.background.entityRef); openEntityPopover("background", characterDraft.selections.background.entityRef);
}} }}
disabled={!characterDraft.selections.background} disabled={!characterDraft.selections.background}
@@ -5973,7 +5921,7 @@ function WikiView() {
<div <div
className="entity-popover-backdrop" className="entity-popover-backdrop"
role="presentation" role="presentation"
onClick={() => setEntityPopover(null)} onClick={closeEntityPopover}
> >
<section <section
className="entity-popover" className="entity-popover"
@@ -5990,11 +5938,27 @@ function WikiView() {
? "Species details" ? "Species details"
: "Background details"} : "Background details"}
</h4> </h4>
<button type="button" className="is-subtle" onClick={() => setEntityPopover(null)}> <button type="button" className="is-subtle" onClick={closeEntityPopover}>
Close {pendingEntitySelection ? "Cancel" : "Close"}
</button> </button>
</header> </header>
{renderEntityDetailContent(entityPopoverDetail, entityPopover.kind)} <div className="entity-popover-body">{renderEntityDetailContent(entityPopoverDetail, entityPopover.kind)}</div>
<footer className="entity-popover-footer">
{pendingEntitySelection ? (
<>
<button type="button" className="is-subtle" onClick={closeEntityPopover}>
Cancel
</button>
<button type="button" onClick={confirmPendingEntitySelection}>
Add {pendingEntitySelection.kind}
</button>
</>
) : (
<button type="button" className="is-subtle" onClick={closeEntityPopover}>
Close
</button>
)}
</footer>
</section> </section>
</div> </div>
) : null} ) : null}
+30 -13
View File
@@ -69,7 +69,7 @@ body {
radial-gradient(circle at 16% 0%, rgba(71, 131, 206, 0.34), transparent 34%), radial-gradient(circle at 16% 0%, rgba(71, 131, 206, 0.34), transparent 34%),
radial-gradient(circle at 88% -8%, rgba(234, 163, 78, 0.28), transparent 40%), radial-gradient(circle at 88% -8%, rgba(234, 163, 78, 0.28), transparent 40%),
linear-gradient(180deg, rgba(14, 24, 40, 0.96), rgba(9, 16, 28, 0.97)); linear-gradient(180deg, rgba(14, 24, 40, 0.96), rgba(9, 16, 28, 0.97));
backdrop-filter: blur(12px); /* backdrop-filter: blur(12px); */
position: relative; position: relative;
overflow: visible; overflow: visible;
isolation: isolate; isolation: isolate;
@@ -516,9 +516,9 @@ body {
border-radius: 16px; border-radius: 16px;
background: linear-gradient(180deg, rgba(14, 22, 35, 0.98), rgba(8, 14, 24, 0.99)); background: linear-gradient(180deg, rgba(14, 22, 35, 0.98), rgba(8, 14, 24, 0.99));
box-shadow: var(--shadow); box-shadow: var(--shadow);
display: grid; /* display: grid; */
grid-template-rows: auto auto 1fr; /* grid-template-rows: auto auto 1fr; */
overflow: hidden; overflow: scroll;
} }
.character-modal button, .character-modal button,
@@ -1529,6 +1529,7 @@ body {
.entity-card-grid { .entity-card-grid {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
max-height: clamp(260px, 44vh, 560px);
overflow: auto; overflow: auto;
padding-right: 4px; padding-right: 4px;
gap: 10px; gap: 10px;
@@ -1737,7 +1738,7 @@ body {
color: #edf4ff; color: #edf4ff;
overflow: hidden; overflow: hidden;
text-align: left; text-align: left;
padding: 0; padding: 0 !important;
display: block; display: block;
min-height: 0; min-height: 0;
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
@@ -1837,17 +1838,18 @@ body {
place-items: center; place-items: center;
padding: 18px; padding: 18px;
background: rgba(2, 6, 12, 0.74); background: rgba(2, 6, 12, 0.74);
backdrop-filter: blur(2px); /* backdrop-filter: blur(2px); */
} }
.entity-popover { .entity-popover {
width: 70%; width: 70%;
max-height: calc(70vh - 36px); max-height: calc(70vh - 36px);
overflow: auto; overflow: hidden;
display: flex;
flex-direction: column;
border: 1px solid var(--line-strong); border: 1px solid var(--line-strong);
border-radius: 14px; border-radius: 14px;
background: rgba(8, 12, 20, 0.98); background: rgba(8, 12, 20, 0.98);
padding: 12px;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45); box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);
} }
@@ -1856,9 +1858,8 @@ body {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 10px; gap: 10px;
padding-bottom: 10px; padding: 12px;
border-bottom: 1px solid var(--line); border-bottom: 1px solid var(--line);
margin-bottom: 10px;
} }
.entity-popover-header h4 { .entity-popover-header h4 {
@@ -1867,6 +1868,22 @@ body {
font-size: 1.05rem; font-size: 1.05rem;
} }
.entity-popover-body {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
padding: 12px;
}
.entity-popover-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 12px;
border-top: 1px solid var(--line);
background: rgba(5, 9, 16, 0.94);
}
.entity-detail--popover { .entity-detail--popover {
min-height: 0; min-height: 0;
} }
@@ -1886,6 +1903,7 @@ body {
.chooser-layout { .chooser-layout {
min-height: 0; min-height: 0;
max-height: 62vh;
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 12px; gap: 12px;
@@ -2249,10 +2267,10 @@ body {
width: 100%; width: 100%;
} }
.ability-score-strip, /* .ability-score-strip,
.ability-assignment-grid--ability-step { .ability-assignment-grid--ability-step {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} } */
.chooser-layout { .chooser-layout {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -2305,7 +2323,6 @@ body {
width: calc(100vw - 20px); width: calc(100vw - 20px);
max-height: calc(100vh - 20px); max-height: calc(100vh - 20px);
border-radius: 12px; border-radius: 12px;
padding: 10px;
} }
.entity-popover-header { .entity-popover-header {