Improve pet manager filters
This commit is contained in:
parent
534f3cf3bb
commit
ddee2628bc
4 changed files with 127 additions and 46 deletions
|
|
@ -38,6 +38,8 @@ let activePetManagerSelection = "";
|
|||
let activePetManagerFilter = "all";
|
||||
let activePetManagerItems = [];
|
||||
let activePetManagerDefaultId = "";
|
||||
let petGalleryInstance = 0;
|
||||
const remoteCatalogFilters = new Set(["original", "western", "asian"]);
|
||||
|
||||
contextBridge.exposeInMainWorld("openPets", api);
|
||||
contextBridge.exposeInMainWorld("openpetsAgentSetup", agentSetupApi);
|
||||
|
|
@ -649,6 +651,7 @@ async function renderPetManager(state) {
|
|||
}
|
||||
|
||||
function renderPetGallery(catalogState, codexState, state, defaultPetId) {
|
||||
const instance = ++petGalleryInstance;
|
||||
const status = requireElement("catalog-status");
|
||||
const search = requireInput("catalog-search");
|
||||
const grid = requireElement("catalog-pets");
|
||||
|
|
@ -671,8 +674,7 @@ function renderPetGallery(catalogState, codexState, state, defaultPetId) {
|
|||
const catalogPageCount = catalogState.pageCount || 1;
|
||||
let catalogSearchState = null;
|
||||
let remoteResultLimit = 100;
|
||||
let activeRemoteResultIds = null;
|
||||
let activeRemoteHasMore = false;
|
||||
let renderGeneration = 0;
|
||||
let pets = createPetManagerItems({ ...catalogState, pets: catalogPets }, codexState, state, defaultPetId, defaultThumbnailSrc);
|
||||
activePetManagerItems = pets;
|
||||
activePetManagerDefaultId = defaultPetId;
|
||||
|
|
@ -680,18 +682,33 @@ function renderPetGallery(catalogState, codexState, state, defaultPetId) {
|
|||
activePetManagerSelection = defaultPetId || pets[0]?.id || "";
|
||||
}
|
||||
|
||||
const resetPetGalleryViewport = () => {
|
||||
grid.scrollTop = 0;
|
||||
document.querySelector(".pm-gallery-pane")?.scrollTo?.({ top: 0, behavior: "instant" });
|
||||
};
|
||||
|
||||
const isSupportedFilter = (filterName) => {
|
||||
if ((filterName === "western" || filterName === "asian") && !catalogState.supportsCategories) return false;
|
||||
if (filterName === "original" && typeof catalogState.originalsCount !== "number") return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
for (const filter of document.querySelectorAll("[data-pet-filter]")) {
|
||||
if ((filter.dataset.petFilter === "western" || filter.dataset.petFilter === "asian") && !catalogState.supportsCategories) {
|
||||
const filterName = filter.dataset.petFilter || "all";
|
||||
if (!isSupportedFilter(filterName)) {
|
||||
filter.hidden = true;
|
||||
if (activePetManagerFilter === filter.dataset.petFilter) activePetManagerFilter = "all";
|
||||
if (activePetManagerFilter === filterName) activePetManagerFilter = "all";
|
||||
} else {
|
||||
filter.hidden = false;
|
||||
}
|
||||
filter.classList.toggle("active", filter.dataset.petFilter === activePetManagerFilter);
|
||||
filter.setAttribute("aria-pressed", filter.dataset.petFilter === activePetManagerFilter ? "true" : "false");
|
||||
filter.onclick = () => {
|
||||
activePetManagerFilter = filter.dataset.petFilter || "all";
|
||||
const nextFilter = filter.dataset.petFilter || "all";
|
||||
if (nextFilter === activePetManagerFilter) return;
|
||||
activePetManagerFilter = nextFilter;
|
||||
remoteResultLimit = 100;
|
||||
resetPetGalleryViewport();
|
||||
void render();
|
||||
};
|
||||
}
|
||||
|
|
@ -717,46 +734,56 @@ function renderPetGallery(catalogState, codexState, state, defaultPetId) {
|
|||
return false;
|
||||
};
|
||||
|
||||
const shouldUseRemoteResults = (query) => catalogState.version === 3 && (Boolean(query) || activePetManagerFilter === "western" || activePetManagerFilter === "asian");
|
||||
const shouldUseRemoteResults = (filterName, query) => catalogState.version === 3 && ((filterName === "all" && Boolean(query)) || remoteCatalogFilters.has(filterName));
|
||||
|
||||
const ensureRemoteResultsLoaded = async (query) => {
|
||||
activeRemoteResultIds = null;
|
||||
activeRemoteHasMore = false;
|
||||
if (!shouldUseRemoteResults(query)) return;
|
||||
const getRemoteResults = async (filterName, query) => {
|
||||
if (!shouldUseRemoteResults(filterName, query)) return { ids: null, hasMore: false };
|
||||
catalogSearchState ||= await api.getCatalogSearch();
|
||||
if (!isCatalogSearchUiState(catalogSearchState) || catalogSearchState.source === "error") throw new Error(catalogSearchState?.error || "Catalog search unavailable.");
|
||||
const matches = catalogSearchState.pets.filter((pet) => {
|
||||
if (activePetManagerFilter !== "all" && activePetManagerFilter !== "installed" && activePetManagerFilter !== "codex" && pet.category !== activePetManagerFilter) return false;
|
||||
if (filterName === "western" || filterName === "asian") {
|
||||
if (pet.category !== filterName) return false;
|
||||
} else if (filterName === "original" && !pet.original) {
|
||||
return false;
|
||||
}
|
||||
return !query || pet.searchText.includes(query);
|
||||
});
|
||||
const visibleMatches = matches.slice(0, remoteResultLimit);
|
||||
activeRemoteResultIds = new Set(visibleMatches.map((pet) => pet.id));
|
||||
activeRemoteHasMore = matches.length > visibleMatches.length;
|
||||
const pages = new Set(visibleMatches.map((pet) => pet.catalogPage));
|
||||
await Promise.all([...pages].map((page) => loadCatalogPage(page)));
|
||||
return { ids: new Set(visibleMatches.map((pet) => pet.id)), hasMore: matches.length > visibleMatches.length };
|
||||
};
|
||||
|
||||
const render = async () => {
|
||||
const generation = ++renderGeneration;
|
||||
const filterName = activePetManagerFilter;
|
||||
const query = search.value.trim().toLowerCase();
|
||||
const isStale = () => instance !== petGalleryInstance || generation !== renderGeneration || filterName !== activePetManagerFilter || query !== search.value.trim().toLowerCase();
|
||||
for (const filter of document.querySelectorAll("[data-pet-filter]")) {
|
||||
filter.classList.toggle("active", filter.dataset.petFilter === activePetManagerFilter);
|
||||
filter.setAttribute("aria-pressed", filter.dataset.petFilter === activePetManagerFilter ? "true" : "false");
|
||||
filter.classList.toggle("active", filter.dataset.petFilter === filterName);
|
||||
filter.setAttribute("aria-pressed", filter.dataset.petFilter === filterName ? "true" : "false");
|
||||
}
|
||||
|
||||
const query = search.value.trim().toLowerCase();
|
||||
if (shouldUseRemoteResults(query)) {
|
||||
try {
|
||||
await ensureRemoteResultsLoaded(query);
|
||||
} catch (error) {
|
||||
renderCaughtError(error);
|
||||
}
|
||||
let remoteResults = { ids: null, hasMore: false };
|
||||
let remoteError = null;
|
||||
try {
|
||||
remoteResults = await getRemoteResults(filterName, query);
|
||||
} catch (error) {
|
||||
remoteError = error;
|
||||
}
|
||||
if (isStale()) {
|
||||
return;
|
||||
}
|
||||
if (remoteError) renderCaughtError(remoteError);
|
||||
|
||||
const visiblePets = pets.filter((pet) => {
|
||||
if (activePetManagerFilter === "installed" && !pet.installed) return false;
|
||||
if (activePetManagerFilter === "codex" && !pet.codexPet && !pet.codexImported) return false;
|
||||
if ((activePetManagerFilter === "western" || activePetManagerFilter === "asian") && pet.category !== activePetManagerFilter) return false;
|
||||
if (filterName === "installed" && !pet.installed) return false;
|
||||
if (filterName === "codex" && !pet.codexPet && !pet.codexImported) return false;
|
||||
if (filterName === "original" && !pet.original) return false;
|
||||
if ((filterName === "western" || filterName === "asian") && pet.category !== filterName) return false;
|
||||
const haystack = `${pet.id} ${pet.displayName} ${pet.description}`.toLowerCase();
|
||||
if (activeRemoteResultIds) {
|
||||
const remoteMatch = Boolean(pet.catalogPet && activeRemoteResultIds.has(pet.id));
|
||||
if (remoteResults.ids) {
|
||||
const remoteMatch = Boolean(pet.catalogPet && remoteResults.ids.has(pet.id));
|
||||
return query ? remoteMatch || haystack.includes(query) : remoteMatch;
|
||||
}
|
||||
return haystack.includes(query);
|
||||
|
|
@ -771,12 +798,12 @@ function renderPetGallery(catalogState, codexState, state, defaultPetId) {
|
|||
if (visiblePets.length === 0) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "pm-empty-state";
|
||||
empty.textContent = activePetManagerFilter === "installed" ? "No installed pets match your search." : activePetManagerFilter === "codex" ? "No Codex pets match your search." : activePetManagerFilter === "western" || activePetManagerFilter === "asian" ? `No ${activePetManagerFilter} pets match your search.` : "No pets match your search.";
|
||||
empty.textContent = createEmptyPetGalleryMessage(filterName);
|
||||
grid.append(empty);
|
||||
}
|
||||
|
||||
const hasMoreCatalogPages = catalogState.version === 3 && !query && !shouldUseRemoteResults(query) && loadedCatalogPages.size < catalogPageCount;
|
||||
const hasMoreRemoteResults = activeRemoteHasMore;
|
||||
const hasMoreCatalogPages = filterName === "all" && catalogState.version === 3 && !query && loadedCatalogPages.size < catalogPageCount;
|
||||
const hasMoreRemoteResults = remoteResults.hasMore;
|
||||
if (hasMoreCatalogPages || hasMoreRemoteResults) {
|
||||
const loadMore = document.createElement("button");
|
||||
loadMore.className = "pm-load-more";
|
||||
|
|
@ -812,6 +839,7 @@ function renderPetGallery(catalogState, codexState, state, defaultPetId) {
|
|||
|
||||
search.oninput = () => {
|
||||
remoteResultLimit = 100;
|
||||
resetPetGalleryViewport();
|
||||
void render();
|
||||
};
|
||||
void render();
|
||||
|
|
@ -856,6 +884,8 @@ function createPetManagerItem(id, displayName, description, installed, catalogPe
|
|||
displayName,
|
||||
description,
|
||||
category: catalogPet?.category || "",
|
||||
original: Boolean(catalogPet?.original),
|
||||
featured: Boolean(catalogPet?.featured),
|
||||
installed,
|
||||
catalogPet,
|
||||
codexPet,
|
||||
|
|
@ -871,6 +901,15 @@ function createPetManagerItem(id, displayName, description, installed, catalogPe
|
|||
};
|
||||
}
|
||||
|
||||
function createEmptyPetGalleryMessage(filterName) {
|
||||
if (filterName === "installed") return "No installed pets match your search.";
|
||||
if (filterName === "codex") return "No Codex pets match your search.";
|
||||
if (filterName === "original") return "No OpenPets originals match your search.";
|
||||
if (filterName === "western") return "No Western pets match your search.";
|
||||
if (filterName === "asian") return "No Asian pets match your search.";
|
||||
return "No pets match your search.";
|
||||
}
|
||||
|
||||
function createPetGalleryCard(pet, defaultPetId, onSelect) {
|
||||
const card = document.createElement("article");
|
||||
card.className = pet.id === activePetManagerSelection ? "pm-pet-card active" : "pm-pet-card";
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ export interface CatalogPetV2 {
|
|||
readonly spritesheet?: string;
|
||||
readonly category?: "western" | "asian";
|
||||
readonly subcategory?: string;
|
||||
readonly original?: boolean;
|
||||
readonly featured?: boolean;
|
||||
}
|
||||
|
||||
export interface CatalogV3Index {
|
||||
|
|
@ -23,6 +25,8 @@ export interface CatalogV3Index {
|
|||
readonly search: string;
|
||||
readonly filters: {
|
||||
readonly categories: readonly CatalogV3Category[];
|
||||
readonly originalsCount?: number;
|
||||
readonly featuredCount?: number;
|
||||
};
|
||||
readonly pages: readonly string[];
|
||||
}
|
||||
|
|
@ -61,6 +65,8 @@ export interface CatalogV3SearchPet {
|
|||
readonly searchText: string;
|
||||
readonly category: "western" | "asian";
|
||||
readonly catalogPage: number;
|
||||
readonly original?: boolean;
|
||||
readonly featured?: boolean;
|
||||
}
|
||||
|
||||
export interface CatalogPetV3 {
|
||||
|
|
@ -72,6 +78,8 @@ export interface CatalogPetV3 {
|
|||
readonly zip: string;
|
||||
readonly category: "western" | "asian";
|
||||
readonly subcategory?: string;
|
||||
readonly original?: boolean;
|
||||
readonly featured?: boolean;
|
||||
}
|
||||
|
||||
export function validateCatalogV2(value: unknown): CatalogV2 {
|
||||
|
|
@ -111,7 +119,11 @@ export function validateCatalogV3Index(value: unknown): CatalogV3Index {
|
|||
total,
|
||||
pageSize,
|
||||
search,
|
||||
filters: { categories },
|
||||
filters: {
|
||||
categories,
|
||||
...(value.filters.originalsCount === undefined ? {} : { originalsCount: validateCount(value.filters.originalsCount) }),
|
||||
...(value.filters.featuredCount === undefined ? {} : { featuredCount: validateCount(value.filters.featuredCount) }),
|
||||
},
|
||||
pages,
|
||||
};
|
||||
}
|
||||
|
|
@ -198,8 +210,8 @@ function validateCatalogV3Pet(value: unknown, ids: Set<string>): CatalogPetV3 {
|
|||
zip: validateCatalogUrl(value.zip, "zip"),
|
||||
category,
|
||||
};
|
||||
if (value.subcategory !== undefined) return { ...entry, subcategory: validateString(value.subcategory, "subcategory", 80) };
|
||||
return entry;
|
||||
const withSubcategory = value.subcategory === undefined ? entry : { ...entry, subcategory: validateString(value.subcategory, "subcategory", 80) };
|
||||
return withCatalogMeta(withSubcategory, value);
|
||||
}
|
||||
|
||||
function validateCatalogV3Category(value: unknown): CatalogV3Category {
|
||||
|
|
@ -214,15 +226,28 @@ function validateCatalogV3Category(value: unknown): CatalogV3Category {
|
|||
function validateCatalogV3SearchPet(value: unknown, catalogPageCount: number): CatalogV3SearchPet {
|
||||
if (!isRecord(value)) throw new Error("Catalog v3 search pet must be an object.");
|
||||
const catalogPage = validateInteger(value.catalogPage, "Catalog v3 search catalogPage", 0, Math.max(0, catalogPageCount - 1));
|
||||
return {
|
||||
return withCatalogMeta({
|
||||
id: validateId(value.id),
|
||||
displayName: validateString(value.displayName, "displayName", 120),
|
||||
searchText: validateString(value.searchText, "searchText", 400),
|
||||
category: validateCategory(value.category),
|
||||
catalogPage,
|
||||
}, value);
|
||||
}
|
||||
|
||||
function withCatalogMeta<T extends object>(entry: T, value: Record<string, unknown>): T & { readonly original?: boolean; readonly featured?: boolean } {
|
||||
return {
|
||||
...entry,
|
||||
...(value.original === undefined ? {} : { original: validateBoolean(value.original, "original") }),
|
||||
...(value.featured === undefined ? {} : { featured: validateBoolean(value.featured, "featured") }),
|
||||
};
|
||||
}
|
||||
|
||||
function validateBoolean(value: unknown, field: string): boolean {
|
||||
if (typeof value !== "boolean") throw new Error(`Catalog pet ${field} must be a boolean.`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function validateCategory(value: unknown): "western" | "asian" {
|
||||
if (value === "western" || value === "asian") return value;
|
||||
throw new Error(`Invalid catalog category: ${String(value)}`);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export interface CatalogUiState {
|
|||
readonly page?: number;
|
||||
readonly pageCount?: number;
|
||||
readonly supportsCategories?: boolean;
|
||||
readonly originalsCount?: number;
|
||||
readonly featuredCount?: number;
|
||||
}
|
||||
|
||||
export interface CatalogSearchUiState {
|
||||
|
|
@ -53,6 +55,8 @@ export async function getCatalogUiState(): Promise<CatalogUiState> {
|
|||
page: 0,
|
||||
pageCount: remoteV3.index.pages.length,
|
||||
supportsCategories: true,
|
||||
originalsCount: remoteV3.index.filters.originalsCount,
|
||||
featuredCount: remoteV3.index.filters.featuredCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +81,8 @@ export async function getCatalogPageUiState(page: number): Promise<CatalogUiStat
|
|||
page,
|
||||
pageCount: remoteV3.index.pages.length,
|
||||
supportsCategories: true,
|
||||
originalsCount: remoteV3.index.filters.originalsCount,
|
||||
featuredCount: remoteV3.index.filters.featuredCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +201,7 @@ async function getRemoteCatalogV3Search(index: CatalogV3Index): Promise<readonly
|
|||
return await v3SearchPromise;
|
||||
}
|
||||
|
||||
function toCatalogPetV2Compat(pet: { readonly id: string; readonly displayName: string; readonly description: string; readonly thumbnail: string; readonly spritesheet: string; readonly zip: string; readonly category: "western" | "asian"; readonly subcategory?: string }): CatalogPetV2 {
|
||||
function toCatalogPetV2Compat(pet: { readonly id: string; readonly displayName: string; readonly description: string; readonly thumbnail: string; readonly spritesheet: string; readonly zip: string; readonly category: "western" | "asian"; readonly subcategory?: string; readonly original?: boolean; readonly featured?: boolean }): CatalogPetV2 {
|
||||
const entry: CatalogPetV2 = {
|
||||
id: pet.id,
|
||||
displayName: pet.displayName,
|
||||
|
|
@ -205,8 +211,12 @@ function toCatalogPetV2Compat(pet: { readonly id: string; readonly displayName:
|
|||
zip: pet.zip,
|
||||
category: pet.category,
|
||||
};
|
||||
if (pet.subcategory) return { ...entry, subcategory: pet.subcategory };
|
||||
return entry;
|
||||
return {
|
||||
...entry,
|
||||
...(pet.subcategory ? { subcategory: pet.subcategory } : {}),
|
||||
...(pet.original === undefined ? {} : { original: pet.original }),
|
||||
...(pet.featured === undefined ? {} : { featured: pet.featured }),
|
||||
};
|
||||
}
|
||||
|
||||
async function tryLoadRemoteCatalog(): Promise<{ readonly ok: true; readonly catalog: CatalogV2 } | { readonly ok: false; readonly error: string }> {
|
||||
|
|
|
|||
|
|
@ -380,20 +380,23 @@ function createPetManagerHtml(definition: TaskWindowDefinition): string {
|
|||
<header class="pm-header">
|
||||
<h1 id="pm-title">Install a Pet</h1>
|
||||
<p class="lede">Pick a companion for your terminal.</p>
|
||||
<span id="catalog-status" class="pm-status-pill">Loading…</span>
|
||||
</header>
|
||||
<label class="pm-search-wrap" for="catalog-search">
|
||||
<span class="pm-search-icon" aria-hidden="true"></span>
|
||||
<input id="catalog-search" type="search" placeholder="Search pets…" />
|
||||
</label>
|
||||
<div class="pm-filters" role="group" aria-label="Pet filters">
|
||||
<div class="pm-filter-buttons">
|
||||
<div class="pm-filter-buttons primary">
|
||||
<button id="pm-filter-all" class="pm-filter active" type="button" data-pet-filter="all" aria-pressed="true">All</button>
|
||||
<button id="pm-filter-western" class="pm-filter" type="button" data-pet-filter="western" aria-pressed="false">Western</button>
|
||||
<button id="pm-filter-asian" class="pm-filter" type="button" data-pet-filter="asian" aria-pressed="false">Asian</button>
|
||||
<button id="pm-filter-codex" class="pm-filter" type="button" data-pet-filter="codex" aria-pressed="false">Codex</button>
|
||||
<button id="pm-filter-installed" class="pm-filter" type="button" data-pet-filter="installed" aria-pressed="false">Installed</button>
|
||||
</div>
|
||||
<span id="catalog-status" class="pm-status-pill">Loading…</span>
|
||||
<div class="pm-filter-buttons secondary">
|
||||
<button id="pm-filter-original" class="pm-filter" type="button" data-pet-filter="original" aria-pressed="false">OpenPets</button>
|
||||
<button id="pm-filter-western" class="pm-filter" type="button" data-pet-filter="western" aria-pressed="false">Western</button>
|
||||
<button id="pm-filter-asian" class="pm-filter" type="button" data-pet-filter="asian" aria-pressed="false">Asian</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="catalog-pets" class="pm-pet-grid" aria-label="Pets"></div>
|
||||
</section>
|
||||
|
|
@ -982,10 +985,10 @@ function createTaskWindowStyles(): string {
|
|||
body[data-openpets-view="pet-manager"] .pm-shell { width: min(1160px, calc(100vw - 36px)); height: calc(100vh - 28px); display: grid; grid-template-columns: minmax(330px, 0.78fr) minmax(510px, 1.22fr); gap: 32px; align-items: stretch; padding: 10px 0 18px; overflow: hidden; }
|
||||
body[data-openpets-view="pet-manager"] .pm-gallery-pane { position: relative; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; padding-bottom: 28px; }
|
||||
body[data-openpets-view="pet-manager"] .pm-logo { width: min(218px, 72%); flex: 0 0 auto; display: block; margin: -2px auto 0; filter: drop-shadow(0 10px 14px rgba(42, 80, 138, 0.12)); }
|
||||
body[data-openpets-view="pet-manager"] .pm-header { flex: 0 0 auto; display: grid; align-items: center; row-gap: 0; margin-bottom: 6px; }
|
||||
body[data-openpets-view="pet-manager"] .pm-header { flex: 0 0 auto; position: relative; display: grid; align-items: center; row-gap: 0; margin-bottom: 6px; padding-right: 108px; }
|
||||
body[data-openpets-view="pet-manager"] .pm-header h1 { margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 24px; line-height: 1.08; letter-spacing: -0.03em; color: #102149; text-shadow: 0 1px 0 rgba(255,255,255,0.9); }
|
||||
body[data-openpets-view="pet-manager"] .pm-header .lede { grid-column: 1 / -1; margin: 0; color: #63708f; line-height: 1.25; }
|
||||
body[data-openpets-view="pet-manager"] .pm-status-pill { position: absolute; right: 8px; bottom: 0; height: 22px; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; border: 1px solid rgba(126, 161, 210, 0.28); border-radius: 8px; padding: 0 8px; color: #526483; background: rgba(255, 255, 255, 0.58); font-size: 9px; line-height: 1; font-weight: 850; font-variant-numeric: tabular-nums; white-space: nowrap; box-shadow: 0 8px 18px rgba(61, 99, 160, 0.05), inset 0 1px 0 rgba(255,255,255,0.85); }
|
||||
body[data-openpets-view="pet-manager"] .pm-status-pill { position: absolute; right: 0; top: 2px; height: 22px; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; border: 1px solid rgba(126, 161, 210, 0.28); border-radius: 8px; padding: 0 8px; color: #526483; background: rgba(255, 255, 255, 0.58); font-size: 9px; line-height: 1; font-weight: 850; font-variant-numeric: tabular-nums; white-space: nowrap; box-shadow: 0 8px 18px rgba(61, 99, 160, 0.05), inset 0 1px 0 rgba(255,255,255,0.85); }
|
||||
body[data-openpets-view="pet-manager"] .pm-status-pill.success { color: #047857; background: rgba(236, 253, 245, 0.82); border-color: rgba(16, 185, 129, 0.26); }
|
||||
body[data-openpets-view="pet-manager"] .pm-status-pill.error { color: #b91c1c; background: rgba(254, 242, 242, 0.86); border-color: rgba(248, 113, 113, 0.28); }
|
||||
body[data-openpets-view="pet-manager"] .pm-search-wrap { height: 40px; flex: 0 0 auto; display: flex; align-items: center; gap: 11px; box-sizing: border-box; margin: 0 0 8px; padding: 0 13px; border: 1px solid rgba(126, 161, 210, 0.54); border-radius: 12px; background: rgba(255,255,255,0.82); box-shadow: inset 0 1px 0 rgba(255,255,255,0.92), 0 10px 24px rgba(61, 99, 160, 0.08); color: #526483; }
|
||||
|
|
@ -994,10 +997,14 @@ function createTaskWindowStyles(): string {
|
|||
body[data-openpets-view="pet-manager"] .pm-search-icon::after { content: ""; width: 7px; height: 2px; border-radius: 999px; background: #5d6e8e; position: absolute; right: -5px; bottom: -2px; transform: rotate(45deg); transform-origin: center; }
|
||||
body[data-openpets-view="pet-manager"] #catalog-search { width: 100%; border: 0; outline: 0; background: transparent; color: #17284f; font: inherit; font-size: 15px; }
|
||||
body[data-openpets-view="pet-manager"] #catalog-search::placeholder { color: #8290aa; }
|
||||
body[data-openpets-view="pet-manager"] .pm-filters { flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-start; gap: 10px; margin-bottom: 9px; }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter-buttons { display: flex; align-items: center; justify-content: flex-start; gap: 9px; min-width: 0; }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter { min-width: 64px; min-height: 30px; padding: 5px 12px; border: 1px solid rgba(126, 161, 210, 0.46); border-radius: 10px; background: rgba(255,255,255,0.76); color: #526483; font-weight: 850; box-shadow: 0 8px 20px rgba(61, 99, 160, 0.06); }
|
||||
body[data-openpets-view="pet-manager"] .pm-filters { flex: 0 0 auto; position: relative; display: grid; gap: 7px; margin-bottom: 9px; }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter-buttons { display: grid; align-items: center; justify-content: stretch; gap: 8px; min-width: 0; }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter-buttons.primary { grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 315px; }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter-buttons.secondary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter { min-width: 0; min-height: 30px; padding: 5px 12px; border: 1px solid rgba(126, 161, 210, 0.46); border-radius: 10px; background: rgba(255,255,255,0.76); color: #526483; font-weight: 850; box-shadow: 0 8px 20px rgba(61, 99, 160, 0.06); display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter[data-pet-filter="original"] { border-color: rgba(217, 119, 6, 0.36); background: linear-gradient(180deg, rgba(255, 251, 235, 0.96), rgba(254, 243, 199, 0.82)); color: #9a5a08; box-shadow: 0 9px 20px rgba(217, 119, 6, 0.09), inset 0 1px 0 rgba(255,255,255,0.9); }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter.active { border-color: rgba(29, 113, 255, 0.5); background: linear-gradient(180deg, #53a3ff, #176df2); color: #fff; box-shadow: 0 10px 22px rgba(37, 99, 235, 0.25), inset 0 1px 0 rgba(255,255,255,0.34); }
|
||||
body[data-openpets-view="pet-manager"] .pm-filter[data-pet-filter="original"].active { border-color: rgba(180, 83, 9, 0.58); background: linear-gradient(180deg, #fbbf24, #d97706); color: #fff7ed; box-shadow: 0 12px 24px rgba(217, 119, 6, 0.24), inset 0 1px 0 rgba(255,255,255,0.34); }
|
||||
body[data-openpets-view="pet-manager"] .pm-pet-grid { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 2px 8px 14px 2px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; scrollbar-color: rgba(96, 165, 250, 0.42) transparent; }
|
||||
body[data-openpets-view="pet-manager"] .pm-pet-card { min-height: 164px; display: grid; grid-template-rows: 58px 40px 31px; gap: 7px; justify-items: center; box-sizing: border-box; padding: 11px 10px 12px; border: 1px solid rgba(126, 161, 210, 0.45); border-radius: 14px; background: rgba(255,255,255,0.74); color: #14264d; box-shadow: 0 12px 28px rgba(61, 99, 160, 0.1), inset 0 1px 0 rgba(255,255,255,0.92); transition: box-shadow 140ms ease, border-color 140ms ease, background-color 140ms ease; }
|
||||
body[data-openpets-view="pet-manager"] .pm-pet-card:hover { border-color: rgba(37, 99, 235, 0.44); background: rgba(255,255,255,0.9); box-shadow: 0 13px 28px rgba(61, 99, 160, 0.13), inset 0 1px 0 rgba(255,255,255,0.94); }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue