+
@dropdownStatusOption(animeID, animeTitle, animeTitleEnglish, animeTitleJapanese, animeImage, "watching", currentStatus, airing)
@dropdownStatusOption(animeID, animeTitle, animeTitleEnglish, animeTitleJapanese, animeImage, "completed", currentStatus, airing)
@dropdownStatusOption(animeID, animeTitle, animeTitleEnglish, animeTitleJapanese, animeImage, "on_hold", currentStatus, airing)
diff --git a/internal/templates/discovery.templ b/internal/templates/discovery.templ
index 51730c4..4e24613 100644
--- a/internal/templates/discovery.templ
+++ b/internal/templates/discovery.templ
@@ -19,6 +19,8 @@ templ Discover() {
hx-target="#discover-content"
hx-trigger="click"
data-tab-trigger
+ data-tab-active-classes="bg-[var(--surface-tab-active)] text-[var(--accent)]"
+ data-tab-inactive-classes="bg-[var(--panel-soft)] text-[var(--text-muted)]"
>
airing now
@@ -29,6 +31,8 @@ templ Discover() {
hx-target="#discover-content"
hx-trigger="click"
data-tab-trigger
+ data-tab-active-classes="bg-[var(--surface-tab-active)] text-[var(--accent)]"
+ data-tab-inactive-classes="bg-[var(--panel-soft)] text-[var(--text-muted)]"
>
upcoming
diff --git a/internal/templates/layout.templ b/internal/templates/layout.templ
index 59ae8f0..a45d890 100644
--- a/internal/templates/layout.templ
+++ b/internal/templates/layout.templ
@@ -36,7 +36,7 @@ templ Layout(title string, showHeader bool) {
diff --git a/static/js/anime.js b/static/js/anime.js
index 50530b6..aba9a52 100644
--- a/static/js/anime.js
+++ b/static/js/anime.js
@@ -1,15 +1,32 @@
// static/js/anime.ts
(() => {
+ const parseClassList = (value) => {
+ if (!value) {
+ return [];
+ }
+ return value.split(" ").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
+ };
+ const setMenuState = (menu, isOpen) => {
+ const openClasses = parseClassList(menu.getAttribute("data-dropdown-open-classes"));
+ const closedClasses = parseClassList(menu.getAttribute("data-dropdown-closed-classes"));
+ if (isOpen) {
+ menu.classList.remove(...closedClasses);
+ menu.classList.add(...openClasses);
+ return;
+ }
+ menu.classList.remove(...openClasses);
+ menu.classList.add(...closedClasses);
+ };
const toggleDropdown = () => {
const dropdown = document.getElementById("watchlist-dropdown");
if (!dropdown) {
return;
}
- dropdown.classList.toggle("open");
+ const isOpen = !dropdown.classList.contains("open");
+ dropdown.classList.toggle("open", isOpen);
const menu = dropdown.querySelector("[data-dropdown-menu]");
if (menu instanceof HTMLElement) {
- menu.classList.toggle("invisible");
- menu.classList.toggle("opacity-0");
+ setMenuState(menu, isOpen);
}
};
window.toggleDropdown = toggleDropdown;
@@ -26,8 +43,7 @@
dropdown.classList.remove("open");
const menu = dropdown.querySelector("[data-dropdown-menu]");
if (menu instanceof HTMLElement) {
- menu.classList.add("invisible");
- menu.classList.add("opacity-0");
+ setMenuState(menu, false);
}
}
});
diff --git a/static/js/anime.ts b/static/js/anime.ts
index 3048696..3693c09 100644
--- a/static/js/anime.ts
+++ b/static/js/anime.ts
@@ -1,15 +1,40 @@
((): void => {
+ const parseClassList = (value: string | null): string[] => {
+ if (!value) {
+ return []
+ }
+
+ return value
+ .split(' ')
+ .map((entry: string): string => entry.trim())
+ .filter((entry: string): boolean => entry.length > 0)
+ }
+
+ const setMenuState = (menu: HTMLElement, isOpen: boolean): void => {
+ const openClasses = parseClassList(menu.getAttribute('data-dropdown-open-classes'))
+ const closedClasses = parseClassList(menu.getAttribute('data-dropdown-closed-classes'))
+
+ if (isOpen) {
+ menu.classList.remove(...closedClasses)
+ menu.classList.add(...openClasses)
+ return
+ }
+
+ menu.classList.remove(...openClasses)
+ menu.classList.add(...closedClasses)
+ }
+
const toggleDropdown = (): void => {
const dropdown = document.getElementById('watchlist-dropdown')
if (!dropdown) {
return
}
- dropdown.classList.toggle('open')
+ const isOpen = !dropdown.classList.contains('open')
+ dropdown.classList.toggle('open', isOpen)
const menu = dropdown.querySelector('[data-dropdown-menu]')
if (menu instanceof HTMLElement) {
- menu.classList.toggle('invisible')
- menu.classList.toggle('opacity-0')
+ setMenuState(menu, isOpen)
}
}
@@ -30,8 +55,7 @@
dropdown.classList.remove('open')
const menu = dropdown.querySelector('[data-dropdown-menu]')
if (menu instanceof HTMLElement) {
- menu.classList.add('invisible')
- menu.classList.add('opacity-0')
+ setMenuState(menu, false)
}
}
})
diff --git a/static/js/discover.js b/static/js/discover.js
index e595634..1751bef 100644
--- a/static/js/discover.js
+++ b/static/js/discover.js
@@ -1,5 +1,11 @@
// static/js/discover.ts
(() => {
+ const parseClassList = (value) => {
+ if (!value) {
+ return [];
+ }
+ return value.split(" ").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
+ };
const setActiveTab = (clickedTab) => {
const group = clickedTab.closest('[data-tab-group="discover"]');
if (!group) {
@@ -7,13 +13,15 @@
}
const triggers = group.querySelectorAll("[data-tab-trigger]");
triggers.forEach((tab) => {
- tab.classList.add("tab-trigger");
- tab.classList.remove("bg-[var(--surface-tab-active)]", "text-[var(--accent)]");
- tab.classList.add("bg-[var(--panel-soft)]", "text-[var(--text-muted)]");
+ const activeClasses2 = parseClassList(tab.getAttribute("data-tab-active-classes"));
+ const inactiveClasses2 = parseClassList(tab.getAttribute("data-tab-inactive-classes"));
+ tab.classList.remove(...activeClasses2);
+ tab.classList.add(...inactiveClasses2);
});
- clickedTab.classList.add("tab-trigger");
- clickedTab.classList.remove("bg-[var(--panel-soft)]", "text-[var(--text-muted)]");
- clickedTab.classList.add("bg-[var(--surface-tab-active)]", "text-[var(--accent)]");
+ const activeClasses = parseClassList(clickedTab.getAttribute("data-tab-active-classes"));
+ const inactiveClasses = parseClassList(clickedTab.getAttribute("data-tab-inactive-classes"));
+ clickedTab.classList.remove(...inactiveClasses);
+ clickedTab.classList.add(...activeClasses);
};
document.addEventListener("click", (event) => {
const target = event.target;
diff --git a/static/js/discover.ts b/static/js/discover.ts
index 622e03c..93ece01 100644
--- a/static/js/discover.ts
+++ b/static/js/discover.ts
@@ -1,4 +1,15 @@
((): void => {
+ const parseClassList = (value: string | null): string[] => {
+ if (!value) {
+ return []
+ }
+
+ return value
+ .split(' ')
+ .map((entry: string): string => entry.trim())
+ .filter((entry: string): boolean => entry.length > 0)
+ }
+
const setActiveTab = (clickedTab: Element): void => {
const group = clickedTab.closest('[data-tab-group="discover"]')
if (!group) {
@@ -7,13 +18,16 @@
const triggers = group.querySelectorAll('[data-tab-trigger]')
triggers.forEach((tab: Element): void => {
- tab.classList.add('tab-trigger')
- tab.classList.remove('bg-[var(--surface-tab-active)]', 'text-[var(--accent)]')
- tab.classList.add('bg-[var(--panel-soft)]', 'text-[var(--text-muted)]')
+ const activeClasses = parseClassList(tab.getAttribute('data-tab-active-classes'))
+ const inactiveClasses = parseClassList(tab.getAttribute('data-tab-inactive-classes'))
+ tab.classList.remove(...activeClasses)
+ tab.classList.add(...inactiveClasses)
})
- clickedTab.classList.add('tab-trigger')
- clickedTab.classList.remove('bg-[var(--panel-soft)]', 'text-[var(--text-muted)]')
- clickedTab.classList.add('bg-[var(--surface-tab-active)]', 'text-[var(--accent)]')
+
+ const activeClasses = parseClassList(clickedTab.getAttribute('data-tab-active-classes'))
+ const inactiveClasses = parseClassList(clickedTab.getAttribute('data-tab-inactive-classes'))
+ clickedTab.classList.remove(...inactiveClasses)
+ clickedTab.classList.add(...activeClasses)
}
document.addEventListener('click', (event: MouseEvent): void => {
diff --git a/static/js/search.js b/static/js/search.js
index c71d9a6..9b5e3f1 100644
--- a/static/js/search.js
+++ b/static/js/search.js
@@ -7,7 +7,7 @@
globalWindow.searchInitialized = true;
let searchTimeout;
const searchInput = document.getElementById("search-input");
- const searchDropdown = document.getElementById("search-dropdown");
+ const searchDropdown = document.querySelector("[data-search-results-container]");
if (!searchInput || !searchDropdown) {
return;
}
@@ -31,42 +31,42 @@
return;
}
const searchResults = document.createElement("div");
- searchResults.className = "search-results";
+ searchResults.className = "grid";
const title = document.createElement("div");
- title.className = "search-results-title";
+ title.className = "px-3 py-2 text-[0.68rem] text-[var(--text-faint)]";
title.textContent = "Anime";
searchResults.appendChild(title);
results.forEach((result) => {
const item = document.createElement("a");
- item.className = "search-result-item";
+ item.className = "flex items-start gap-3 px-3 py-2 text-inherit no-underline hover:bg-[var(--panel-soft)] hover:no-underline";
item.setAttribute("href", "/anime/" + encodeURIComponent(String(result.id || "")));
if (isSafeImageUrl(result.image)) {
const img = document.createElement("img");
- img.className = "search-result-thumb";
+ img.className = "aspect-[2/3] w-[42px] shrink-0 object-cover bg-[var(--surface-thumb)]";
img.setAttribute("src", result.image || "");
img.setAttribute("alt", String(result.title || ""));
item.appendChild(img);
} else {
const noImage = document.createElement("div");
- noImage.className = "search-result-no-image";
+ noImage.className = "aspect-[2/3] w-[42px] shrink-0 bg-[var(--surface-thumb)] text-[0] text-transparent";
noImage.textContent = "no image";
item.appendChild(noImage);
}
const info = document.createElement("div");
- info.className = "search-result-info";
+ info.className = "grid min-w-0 gap-px";
const itemTitle = document.createElement("div");
- itemTitle.className = "search-result-title";
+ itemTitle.className = "line-clamp-1 text-[0.86rem] leading-[1.3] text-[var(--text)]";
itemTitle.textContent = String(result.title || "");
info.appendChild(itemTitle);
const itemType = document.createElement("div");
- itemType.className = "search-result-type";
+ itemType.className = "text-[0.67rem] text-[var(--text-faint)]";
itemType.textContent = String(result.type || "");
info.appendChild(itemType);
item.appendChild(info);
searchResults.appendChild(item);
});
const viewAll = document.createElement("a");
- viewAll.className = "search-result-view-all";
+ viewAll.className = "bg-[var(--surface-search-view-all)] px-3 py-2 text-center text-[0.8rem] text-[var(--text-muted)] no-underline hover:bg-[var(--panel-soft)] hover:text-[var(--text)] hover:no-underline";
viewAll.setAttribute("href", "/search?q=" + encodeURIComponent(query));
viewAll.textContent = "View all results for " + query;
searchResults.appendChild(viewAll);
diff --git a/static/js/search.ts b/static/js/search.ts
index 075e05f..e0123f5 100644
--- a/static/js/search.ts
+++ b/static/js/search.ts
@@ -7,7 +7,7 @@
let searchTimeout: number | undefined
const searchInput = document.getElementById('search-input') as HTMLInputElement | null
- const searchDropdown = document.getElementById('search-dropdown')
+ const searchDropdown = document.querySelector('[data-search-results-container]') as HTMLElement | null
if (!searchInput || !searchDropdown) {
return
@@ -39,41 +39,41 @@
}
const searchResults = document.createElement('div')
- searchResults.className = 'search-results'
+ searchResults.className = 'grid'
const title = document.createElement('div')
- title.className = 'search-results-title'
+ title.className = 'px-3 py-2 text-[0.68rem] text-[var(--text-faint)]'
title.textContent = 'Anime'
searchResults.appendChild(title)
results.forEach((result): void => {
const item = document.createElement('a')
- item.className = 'search-result-item'
+ item.className = 'flex items-start gap-3 px-3 py-2 text-inherit no-underline hover:bg-[var(--panel-soft)] hover:no-underline'
item.setAttribute('href', '/anime/' + encodeURIComponent(String(result.id || '')))
if (isSafeImageUrl(result.image)) {
const img = document.createElement('img')
- img.className = 'search-result-thumb'
+ img.className = 'aspect-[2/3] w-[42px] shrink-0 object-cover bg-[var(--surface-thumb)]'
img.setAttribute('src', result.image || '')
img.setAttribute('alt', String(result.title || ''))
item.appendChild(img)
} else {
const noImage = document.createElement('div')
- noImage.className = 'search-result-no-image'
+ noImage.className = 'aspect-[2/3] w-[42px] shrink-0 bg-[var(--surface-thumb)] text-[0] text-transparent'
noImage.textContent = 'no image'
item.appendChild(noImage)
}
const info = document.createElement('div')
- info.className = 'search-result-info'
+ info.className = 'grid min-w-0 gap-px'
const itemTitle = document.createElement('div')
- itemTitle.className = 'search-result-title'
+ itemTitle.className = 'line-clamp-1 text-[0.86rem] leading-[1.3] text-[var(--text)]'
itemTitle.textContent = String(result.title || '')
info.appendChild(itemTitle)
const itemType = document.createElement('div')
- itemType.className = 'search-result-type'
+ itemType.className = 'text-[0.67rem] text-[var(--text-faint)]'
itemType.textContent = String(result.type || '')
info.appendChild(itemType)
@@ -82,7 +82,7 @@
})
const viewAll = document.createElement('a')
- viewAll.className = 'search-result-view-all'
+ viewAll.className = 'bg-[var(--surface-search-view-all)] px-3 py-2 text-center text-[0.8rem] text-[var(--text-muted)] no-underline hover:bg-[var(--panel-soft)] hover:text-[var(--text)] hover:no-underline'
viewAll.setAttribute('href', '/search?q=' + encodeURIComponent(query))
viewAll.textContent = 'View all results for ' + query
searchResults.appendChild(viewAll)