Revert "Merge: t-paliad-133 — Fristenrechner v3 (Pathway A/B fork + B1 decision tree + B2 forum filter + retire legacy tabs)"
This reverts commitf7d72ff1d3, reversing changes made to1ea983f0c7.
This commit is contained in:
@@ -1278,14 +1278,7 @@ async function runSearch() {
|
||||
const seq = ++searchSeq;
|
||||
let resp: Response;
|
||||
try {
|
||||
{
|
||||
const searchURL = new URL("/api/tools/fristenrechner/search", window.location.origin);
|
||||
searchURL.searchParams.set("q", q);
|
||||
searchURL.searchParams.set("limit", "12");
|
||||
const forums = getActiveForumsParam();
|
||||
if (forums) searchURL.searchParams.set("forum", forums);
|
||||
resp = await fetch(searchURL.toString(), { credentials: "same-origin" });
|
||||
}
|
||||
resp = await fetch(`/api/tools/fristenrechner/search?q=${encodeURIComponent(q)}&limit=12`, { credentials: "same-origin" });
|
||||
} catch {
|
||||
if (seq !== searchSeq) return;
|
||||
results.classList.remove("is-loading");
|
||||
@@ -1455,14 +1448,9 @@ function drillToProceeding(procCode: string, focusCode: string | null) {
|
||||
}
|
||||
|
||||
function drillToTrigger(triggerId: number) {
|
||||
// v3 (Phase E): legacy tabs are gone. Show the event panel directly.
|
||||
// Triggered from concept-card pill clicks; routes via Pathway A so the
|
||||
// Verfahrensablauf user surface stays consistent.
|
||||
const procedurePanel = document.getElementById("mode-procedure-panel");
|
||||
const eventPanel = document.getElementById("mode-event-panel");
|
||||
if (procedurePanel) procedurePanel.hidden = true;
|
||||
if (eventPanel) eventPanel.hidden = false;
|
||||
// Defer a tick so the panel swap has rendered before we touch state.
|
||||
const eventTab = document.getElementById("mode-event-tab");
|
||||
if (eventTab) eventTab.click();
|
||||
// Defer a tick so the tab activation has run before we touch event-mode state.
|
||||
window.setTimeout(() => {
|
||||
selectTriggerEvent(triggerId);
|
||||
document.getElementById("event-step-2")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
@@ -1566,550 +1554,3 @@ function initSearch() {
|
||||
// Wire on DOM ready (the existing DOMContentLoaded handler is already busy;
|
||||
// add a lightweight follow-up listener to keep the diff small).
|
||||
document.addEventListener("DOMContentLoaded", initSearch);
|
||||
|
||||
// ============================================================================
|
||||
// v3 pathway fork (t-paliad-133)
|
||||
// ============================================================================
|
||||
// Three-state landing surface: fork (default), Pathway A (Verfahrensablauf —
|
||||
// existing wizard), Pathway B (Frist eintragen — search/B1/B2). URL ?path=
|
||||
// drives visibility; localStorage remembers the last-used pathway for soft
|
||||
// re-entry. ?legacy=1 keeps the pre-v3 layout (no fork) for parity testing
|
||||
// during the rollout window.
|
||||
|
||||
type Pathway = "fork" | "a" | "b";
|
||||
type BMode = "tree" | "filter";
|
||||
|
||||
const PATHWAY_STORAGE_KEY = "paliad.fristen.pathway";
|
||||
|
||||
function readPathwayFromURL(): Pathway {
|
||||
const sp = new URLSearchParams(window.location.search);
|
||||
const p = sp.get("path");
|
||||
if (p === "a" || p === "b") return p;
|
||||
return "fork";
|
||||
}
|
||||
|
||||
function readBModeFromURL(): BMode {
|
||||
const sp = new URLSearchParams(window.location.search);
|
||||
const m = sp.get("mode");
|
||||
if (m === "tree" || m === "filter") return m;
|
||||
// Default: tree mode (B1 cascade is the discovery surface; the
|
||||
// free-text/filter B2 mode is for power users who already know what
|
||||
// they want).
|
||||
return "tree";
|
||||
}
|
||||
|
||||
function setPathwayURL(path: Pathway, mode?: BMode, replace = false) {
|
||||
const url = new URL(window.location.href);
|
||||
if (path === "fork") {
|
||||
url.searchParams.delete("path");
|
||||
url.searchParams.delete("mode");
|
||||
url.searchParams.delete("b1");
|
||||
} else {
|
||||
url.searchParams.set("path", path);
|
||||
if (path === "b" && mode) {
|
||||
url.searchParams.set("mode", mode);
|
||||
} else {
|
||||
url.searchParams.delete("mode");
|
||||
}
|
||||
}
|
||||
if (replace) {
|
||||
window.history.replaceState({}, "", url.toString());
|
||||
} else {
|
||||
window.history.pushState({}, "", url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
function showPathway(path: Pathway, mode?: BMode) {
|
||||
const fork = document.getElementById("fristen-pathway-fork");
|
||||
const a = document.getElementById("fristen-pathway-a");
|
||||
const b = document.getElementById("fristen-pathway-b");
|
||||
if (!fork || !a || !b) return;
|
||||
|
||||
fork.hidden = path !== "fork";
|
||||
a.hidden = path !== "a";
|
||||
b.hidden = path !== "b";
|
||||
|
||||
if (path === "b") {
|
||||
showBMode(mode || readBModeFromURL());
|
||||
}
|
||||
}
|
||||
|
||||
function showBMode(mode: BMode) {
|
||||
const tree = document.getElementById("fristen-b1-panel");
|
||||
const filter = document.getElementById("fristen-b2-panel");
|
||||
const treeRadio = document.getElementById("fristen-b-mode-tree") as HTMLInputElement | null;
|
||||
const filterRadio = document.getElementById("fristen-b-mode-filter") as HTMLInputElement | null;
|
||||
if (!tree || !filter) return;
|
||||
tree.hidden = mode !== "tree";
|
||||
filter.hidden = mode !== "filter";
|
||||
if (treeRadio) treeRadio.checked = mode === "tree";
|
||||
if (filterRadio) filterRadio.checked = mode === "filter";
|
||||
|
||||
// Phase B B1 stub — will be replaced by the real cascade in Phase C.
|
||||
if (mode === "tree") {
|
||||
const cascade = document.getElementById("fristen-b1-cascade");
|
||||
if (cascade && cascade.childElementCount === 0) {
|
||||
cascade.innerHTML =
|
||||
`<div class="fristen-b1-stub">${escHtml(t("deadlines.pathway.b.tree.coming_soon"))}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function navigateToPathway(path: Pathway, mode?: BMode) {
|
||||
setPathwayURL(path, mode);
|
||||
showPathway(path, mode);
|
||||
if (path !== "fork") {
|
||||
try {
|
||||
localStorage.setItem(PATHWAY_STORAGE_KEY, path);
|
||||
} catch { /* private mode */ }
|
||||
}
|
||||
}
|
||||
|
||||
function initPathwayFork() {
|
||||
// Initial render from URL (or saved preference if URL is bare).
|
||||
const initial = readPathwayFromURL();
|
||||
const initialMode = readBModeFromURL();
|
||||
showPathway(initial, initialMode);
|
||||
|
||||
// Persist initial choice from URL.
|
||||
if (initial !== "fork") {
|
||||
try { localStorage.setItem(PATHWAY_STORAGE_KEY, initial); } catch { /* */ }
|
||||
}
|
||||
|
||||
// Click handlers on the two fork cards.
|
||||
document.getElementById("fristen-pathway-a-cta")?.addEventListener("click", () => {
|
||||
navigateToPathway("a");
|
||||
});
|
||||
document.getElementById("fristen-pathway-b-cta")?.addEventListener("click", () => {
|
||||
// Default to tree mode on first entry to Pathway B.
|
||||
navigateToPathway("b", "tree");
|
||||
});
|
||||
|
||||
// Back-to-fork buttons inside each pathway shell.
|
||||
document.getElementById("fristen-pathway-a-back")?.addEventListener("click", () => {
|
||||
navigateToPathway("fork");
|
||||
});
|
||||
document.getElementById("fristen-pathway-b-back")?.addEventListener("click", () => {
|
||||
navigateToPathway("fork");
|
||||
});
|
||||
|
||||
// B1/B2 mode toggle inside Pathway B.
|
||||
const bModeRadios = document.querySelectorAll<HTMLInputElement>("input[name='fristen-b-mode']");
|
||||
bModeRadios.forEach((r) => {
|
||||
r.addEventListener("change", () => {
|
||||
if (!r.checked) return;
|
||||
const mode: BMode = r.value === "tree" ? "tree" : "filter";
|
||||
setPathwayURL("b", mode);
|
||||
showBMode(mode);
|
||||
});
|
||||
});
|
||||
|
||||
// Quick-pick chips on the fork shortcut row → jump straight to Pathway B + filter mode + prefilled query.
|
||||
document.querySelectorAll<HTMLButtonElement>("#fristen-fork-chips .fristen-search-chip").forEach((chip) => {
|
||||
chip.addEventListener("click", () => {
|
||||
const q = chip.dataset.q || "";
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("path", "b");
|
||||
url.searchParams.set("mode", "filter");
|
||||
if (q) url.searchParams.set("q", q);
|
||||
window.history.pushState({}, "", url.toString());
|
||||
showPathway("b", "filter");
|
||||
// initSearch listens for popstate, but we used pushState; sync the
|
||||
// search input directly.
|
||||
const input = document.getElementById("fristen-search-input") as HTMLInputElement | null;
|
||||
if (input && q) {
|
||||
input.value = q;
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Browser back/forward should restore pathway state.
|
||||
window.addEventListener("popstate", () => {
|
||||
const path = readPathwayFromURL();
|
||||
const mode = readBModeFromURL();
|
||||
showPathway(path, mode);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initPathwayFork);
|
||||
|
||||
// ============================================================================
|
||||
// v3 B1 decision tree (t-paliad-133 Phase C)
|
||||
// ============================================================================
|
||||
// Data-driven cascade: fetch the event-categories tree from
|
||||
// GET /api/tools/fristenrechner/event-categories, render the current
|
||||
// step's button set, walk down on click, show breadcrumb + reset.
|
||||
// Result cards below come from /api/tools/fristenrechner/search with
|
||||
// ?event_category_slug= narrowing.
|
||||
|
||||
interface EventCategoryNode {
|
||||
id: string;
|
||||
slug: string;
|
||||
label_de: string;
|
||||
label_en: string;
|
||||
description_de?: string;
|
||||
description_en?: string;
|
||||
step_question_de?: string;
|
||||
step_question_en?: string;
|
||||
icon?: string;
|
||||
sort_order: number;
|
||||
is_leaf: boolean;
|
||||
children?: EventCategoryNode[];
|
||||
}
|
||||
|
||||
let eventCategoryTree: EventCategoryNode[] | null = null;
|
||||
let eventCategoryFetchInflight: Promise<EventCategoryNode[]> | null = null;
|
||||
|
||||
async function loadEventCategoryTree(): Promise<EventCategoryNode[]> {
|
||||
if (eventCategoryTree) return eventCategoryTree;
|
||||
if (eventCategoryFetchInflight) return eventCategoryFetchInflight;
|
||||
eventCategoryFetchInflight = (async () => {
|
||||
try {
|
||||
const r = await fetch("/api/tools/fristenrechner/event-categories");
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const data = await r.json();
|
||||
eventCategoryTree = (data.tree || []) as EventCategoryNode[];
|
||||
return eventCategoryTree;
|
||||
} finally {
|
||||
eventCategoryFetchInflight = null;
|
||||
}
|
||||
})();
|
||||
return eventCategoryFetchInflight;
|
||||
}
|
||||
|
||||
function readB1PathFromURL(): string {
|
||||
return new URLSearchParams(window.location.search).get("b1") || "";
|
||||
}
|
||||
|
||||
function setB1PathInURL(slug: string, replace = false) {
|
||||
const url = new URL(window.location.href);
|
||||
if (slug) {
|
||||
url.searchParams.set("b1", slug);
|
||||
} else {
|
||||
url.searchParams.delete("b1");
|
||||
}
|
||||
if (replace) {
|
||||
window.history.replaceState({}, "", url.toString());
|
||||
} else {
|
||||
window.history.pushState({}, "", url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
function findNodeBySlug(roots: EventCategoryNode[], slug: string): EventCategoryNode | null {
|
||||
for (const root of roots) {
|
||||
if (root.slug === slug) return root;
|
||||
if (root.children) {
|
||||
const inner = findNodeBySlug(root.children, slug);
|
||||
if (inner) return inner;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildBreadcrumb(roots: EventCategoryNode[], slug: string): EventCategoryNode[] {
|
||||
// Slug is dot-separated; walk down each segment.
|
||||
if (!slug) return [];
|
||||
const parts = slug.split(".");
|
||||
const trail: EventCategoryNode[] = [];
|
||||
let scope = roots;
|
||||
let cumulative = "";
|
||||
for (const seg of parts) {
|
||||
cumulative = cumulative ? `${cumulative}.${seg}` : seg;
|
||||
const node = scope.find((n) => n.slug === cumulative);
|
||||
if (!node) break;
|
||||
trail.push(node);
|
||||
scope = node.children || [];
|
||||
}
|
||||
return trail;
|
||||
}
|
||||
|
||||
function nodeLabel(n: EventCategoryNode): string {
|
||||
return getLang() === "de" ? n.label_de : n.label_en;
|
||||
}
|
||||
|
||||
function nodeStepQuestion(n: EventCategoryNode): string {
|
||||
return getLang() === "de"
|
||||
? (n.step_question_de || "")
|
||||
: (n.step_question_en || n.step_question_de || "");
|
||||
}
|
||||
|
||||
function renderB1Cascade(currentSlug: string) {
|
||||
const cascade = document.getElementById("fristen-b1-cascade");
|
||||
if (!cascade || !eventCategoryTree) return;
|
||||
|
||||
const trail = buildBreadcrumb(eventCategoryTree, currentSlug);
|
||||
const node = trail.length > 0 ? trail[trail.length - 1] : null;
|
||||
const childScope = node ? (node.children || []) : eventCategoryTree;
|
||||
|
||||
const breadcrumbHtml = trail.length === 0
|
||||
? ""
|
||||
: `<nav class="fristen-b1-breadcrumb" aria-label="Pfad">
|
||||
<button type="button" class="fristen-b1-crumb fristen-b1-crumb--root" data-slug="">
|
||||
${escHtml(t("deadlines.pathway.b.tree.reset"))}
|
||||
</button>
|
||||
${trail.map((c, i) =>
|
||||
`<span class="fristen-b1-crumb-sep" aria-hidden="true">›</span>
|
||||
<button type="button" class="fristen-b1-crumb${i === trail.length - 1 ? " fristen-b1-crumb--current" : ""}" data-slug="${escAttr(c.slug)}">
|
||||
${c.icon ? `<span class="fristen-b1-crumb-icon" aria-hidden="true">${escHtml(c.icon)}</span> ` : ""}${escHtml(nodeLabel(c))}
|
||||
</button>`).join("")}
|
||||
</nav>`;
|
||||
|
||||
const question = node && node.step_question_de
|
||||
? `<p class="fristen-b1-question">${escHtml(nodeStepQuestion(node))}</p>`
|
||||
: trail.length === 0
|
||||
? `<p class="fristen-b1-question">${escHtml(t("deadlines.pathway.b.tree.start_question") || "Was ist passiert?")}</p>`
|
||||
: "";
|
||||
|
||||
let buttonsHtml = "";
|
||||
if (childScope.length > 0) {
|
||||
buttonsHtml = `<div class="fristen-b1-buttons">${
|
||||
childScope.map((c) =>
|
||||
`<button type="button" class="fristen-b1-button${c.is_leaf ? " fristen-b1-button--leaf" : ""}" data-slug="${escAttr(c.slug)}">
|
||||
${c.icon ? `<span class="fristen-b1-button-icon" aria-hidden="true">${escHtml(c.icon)}</span>` : ""}
|
||||
<span class="fristen-b1-button-label">${escHtml(nodeLabel(c))}</span>
|
||||
</button>`).join("")
|
||||
}</div>`;
|
||||
}
|
||||
|
||||
// Skip-step affordance on non-leaf intermediate nodes.
|
||||
let skipHtml = "";
|
||||
if (node && !node.is_leaf && childScope.length > 0) {
|
||||
skipHtml = `<button type="button" class="fristen-b1-skip" data-action="skip">
|
||||
${escHtml(t("deadlines.pathway.b.tree.skip"))}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
// Step-back affordance on any non-root state.
|
||||
let backHtml = "";
|
||||
if (trail.length > 0) {
|
||||
const parentSlug = trail.length > 1 ? trail[trail.length - 2].slug : "";
|
||||
backHtml = `<button type="button" class="fristen-b1-step-back" data-slug="${escAttr(parentSlug)}">
|
||||
← ${escHtml(t("deadlines.pathway.b.tree.step.back"))}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
cascade.innerHTML = `${breadcrumbHtml}${question}${buttonsHtml}${skipHtml}${backHtml}`;
|
||||
|
||||
// Wire button clicks.
|
||||
cascade.querySelectorAll<HTMLButtonElement>(".fristen-b1-button, .fristen-b1-crumb, .fristen-b1-step-back").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const slug = btn.dataset.slug || "";
|
||||
navigateB1(slug);
|
||||
});
|
||||
});
|
||||
|
||||
// Skip-step clicks the deepest path with current slug as the anchor —
|
||||
// it just means "search at this node level without deeper narrowing".
|
||||
cascade.querySelectorAll<HTMLButtonElement>(".fristen-b1-skip").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
runB1Search(currentSlug);
|
||||
});
|
||||
});
|
||||
|
||||
runB1Search(currentSlug);
|
||||
}
|
||||
|
||||
async function runB1Search(slug: string) {
|
||||
const results = document.getElementById("fristen-search-results");
|
||||
if (!results) return;
|
||||
if (!slug) {
|
||||
// Root state: empty results until user picks a step.
|
||||
results.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
results.innerHTML = `<div class="fristen-search-status">${escHtml(t("deadlines.search.loading"))}</div>`;
|
||||
try {
|
||||
const url = new URL("/api/tools/fristenrechner/search", window.location.origin);
|
||||
url.searchParams.set("event_category_slug", slug);
|
||||
url.searchParams.set("limit", "30");
|
||||
const forums = getActiveForumsParam();
|
||||
if (forums) url.searchParams.set("forum", forums);
|
||||
const r = await fetch(url.toString());
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const data = await r.json();
|
||||
if (!data.cards || data.cards.length === 0) {
|
||||
results.innerHTML = `<div class="fristen-search-status fristen-search-error">
|
||||
${escHtml(t("deadlines.pathway.b.tree.empty"))}
|
||||
<button type="button" class="fristen-b1-loosen-link" data-action="loosen">
|
||||
${escHtml(t("deadlines.pathway.b.tree.step.back"))}
|
||||
</button>
|
||||
</div>`;
|
||||
results.querySelector<HTMLButtonElement>(".fristen-b1-loosen-link")?.addEventListener("click", () => {
|
||||
const trail = buildBreadcrumb(eventCategoryTree || [], slug);
|
||||
const parent = trail.length > 1 ? trail[trail.length - 2].slug : "";
|
||||
navigateB1(parent);
|
||||
});
|
||||
return;
|
||||
}
|
||||
renderSearchResults(data);
|
||||
} catch (e) {
|
||||
results.innerHTML = `<div class="fristen-search-status fristen-search-error">
|
||||
${escHtml(t("deadlines.search.no_hits"))}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function navigateB1(slug: string) {
|
||||
setB1PathInURL(slug);
|
||||
renderB1Cascade(slug);
|
||||
}
|
||||
|
||||
async function initB1Cascade() {
|
||||
const panel = document.getElementById("fristen-b1-panel");
|
||||
if (!panel) return;
|
||||
// Lazy-load the tree the first time the user arrives in tree mode.
|
||||
const loadAndRender = async () => {
|
||||
try {
|
||||
await loadEventCategoryTree();
|
||||
renderB1Cascade(readB1PathFromURL());
|
||||
} catch (e) {
|
||||
const cascade = document.getElementById("fristen-b1-cascade");
|
||||
if (cascade) {
|
||||
cascade.innerHTML = `<div class="fristen-b1-error">${escHtml(t("deadlines.pathway.b.tree.empty"))}</div>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Watch for tree mode becoming visible (Phase B's mode toggle).
|
||||
const treeRadio = document.getElementById("fristen-b-mode-tree") as HTMLInputElement | null;
|
||||
if (treeRadio) {
|
||||
treeRadio.addEventListener("change", () => {
|
||||
if (treeRadio.checked) loadAndRender();
|
||||
});
|
||||
}
|
||||
|
||||
// Initial render if the URL already lands in tree mode.
|
||||
const sp = new URLSearchParams(window.location.search);
|
||||
if (sp.get("path") === "b" && sp.get("mode") === "tree") {
|
||||
loadAndRender();
|
||||
}
|
||||
|
||||
// popstate restores the cascade depth.
|
||||
window.addEventListener("popstate", () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get("path") === "b" && params.get("mode") === "tree" && eventCategoryTree) {
|
||||
renderB1Cascade(params.get("b1") || "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initB1Cascade);
|
||||
|
||||
// ============================================================================
|
||||
// v3 B2 forum filter (t-paliad-133 Phase D)
|
||||
// ============================================================================
|
||||
// 10 forum buckets per m's spec lock §10 Q8. Multi-select chips,
|
||||
// AND-narrowing: each chip click toggles its membership in the active
|
||||
// set; the active set is sent as ?forum=<comma-separated> on every
|
||||
// search. Empty set = no filter.
|
||||
|
||||
const FORUM_BUCKETS: { slug: string; i18nKey: string }[] = [
|
||||
{ slug: "upc_cfi", i18nKey: "deadlines.filter.forum.upc_cfi" },
|
||||
{ slug: "upc_coa", i18nKey: "deadlines.filter.forum.upc_coa" },
|
||||
{ slug: "de_lg", i18nKey: "deadlines.filter.forum.de_lg" },
|
||||
{ slug: "de_olg", i18nKey: "deadlines.filter.forum.de_olg" },
|
||||
{ slug: "de_bgh", i18nKey: "deadlines.filter.forum.de_bgh" },
|
||||
{ slug: "de_bpatg", i18nKey: "deadlines.filter.forum.de_bpatg" },
|
||||
{ slug: "epa_grant", i18nKey: "deadlines.filter.forum.epa_grant" },
|
||||
{ slug: "epa_opp", i18nKey: "deadlines.filter.forum.epa_opp" },
|
||||
{ slug: "epa_appeal", i18nKey: "deadlines.filter.forum.epa_appeal" },
|
||||
{ slug: "dpma", i18nKey: "deadlines.filter.forum.dpma" },
|
||||
];
|
||||
|
||||
const activeForums = new Set<string>();
|
||||
|
||||
function readForumsFromURL(): string[] {
|
||||
const sp = new URLSearchParams(window.location.search);
|
||||
const raw = sp.get("forum");
|
||||
if (!raw) return [];
|
||||
return raw.split(",").map((s) => s.trim()).filter((s) => FORUM_BUCKETS.some((b) => b.slug === s));
|
||||
}
|
||||
|
||||
function writeForumsToURL(replace = false) {
|
||||
const url = new URL(window.location.href);
|
||||
if (activeForums.size === 0) {
|
||||
url.searchParams.delete("forum");
|
||||
} else {
|
||||
url.searchParams.set("forum", Array.from(activeForums).sort().join(","));
|
||||
}
|
||||
if (replace) {
|
||||
window.history.replaceState({}, "", url.toString());
|
||||
} else {
|
||||
window.history.pushState({}, "", url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
function renderForumChips() {
|
||||
const container = document.getElementById("fristen-forum-chips");
|
||||
const wrapper = document.getElementById("fristen-forum-filter");
|
||||
if (!container || !wrapper) return;
|
||||
wrapper.hidden = false;
|
||||
container.innerHTML = FORUM_BUCKETS.map((b) => {
|
||||
const active = activeForums.has(b.slug);
|
||||
return `<button type="button" class="fristen-forum-chip${active ? " fristen-forum-chip--active" : ""}"
|
||||
data-forum="${escAttr(b.slug)}"
|
||||
aria-pressed="${active ? "true" : "false"}">
|
||||
${escHtml(t(b.i18nKey))}
|
||||
</button>`;
|
||||
}).join("");
|
||||
container.querySelectorAll<HTMLButtonElement>(".fristen-forum-chip").forEach((chip) => {
|
||||
chip.addEventListener("click", () => {
|
||||
const slug = chip.dataset.forum || "";
|
||||
if (!slug) return;
|
||||
if (activeForums.has(slug)) {
|
||||
activeForums.delete(slug);
|
||||
} else {
|
||||
activeForums.add(slug);
|
||||
}
|
||||
writeForumsToURL();
|
||||
renderForumChips();
|
||||
reissueSearchWithCurrentFilters();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function reissueSearchWithCurrentFilters() {
|
||||
// If we're in B1 mode, refresh the current cascade slug's results.
|
||||
const sp = new URLSearchParams(window.location.search);
|
||||
if (sp.get("mode") === "tree") {
|
||||
const slug = sp.get("b1") || "";
|
||||
if (slug) {
|
||||
runB1Search(slug);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Otherwise re-trigger the B2 search input handler.
|
||||
const input = document.getElementById("fristen-search-input") as HTMLInputElement | null;
|
||||
if (input && input.value.trim() !== "") {
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveForumsParam(): string {
|
||||
if (activeForums.size === 0) return "";
|
||||
return Array.from(activeForums).sort().join(",");
|
||||
}
|
||||
|
||||
function initForumFilter() {
|
||||
// Hydrate from URL on first load.
|
||||
for (const slug of readForumsFromURL()) {
|
||||
activeForums.add(slug);
|
||||
}
|
||||
renderForumChips();
|
||||
|
||||
// Restore on browser nav.
|
||||
window.addEventListener("popstate", () => {
|
||||
activeForums.clear();
|
||||
for (const slug of readForumsFromURL()) {
|
||||
activeForums.add(slug);
|
||||
}
|
||||
renderForumChips();
|
||||
});
|
||||
|
||||
// Re-render labels on language change.
|
||||
onLangChange(() => renderForumChips());
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initForumFilter);
|
||||
|
||||
|
||||
@@ -284,36 +284,6 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
"deadlines.search.results.count": "{n} Treffer",
|
||||
"deadlines.search.results.count_one": "1 Treffer",
|
||||
"deadlines.search.clear": "Suche leeren",
|
||||
"deadlines.pathway.fork.heading": "Was möchten Sie tun?",
|
||||
"deadlines.pathway.a.title": "Verfahrensablauf informieren",
|
||||
"deadlines.pathway.a.desc": "Verfahrenstyp wählen und alle dazugehörigen Fristen auf einer Zeitleiste sehen.",
|
||||
"deadlines.pathway.b.title": "Frist eintragen aufgrund Ereignis",
|
||||
"deadlines.pathway.b.desc": "Ein Ereignis ist eingetreten — ich brauche die richtige Frist für meine Akte.",
|
||||
"deadlines.pathway.shortcut.label": "oder direkt zu einer Frist springen:",
|
||||
"deadlines.pathway.back": "zurück zur Auswahl",
|
||||
"deadlines.pathway.b.mode.tree": "Schritt-für-Schritt (Entscheidungsbaum)",
|
||||
"deadlines.pathway.b.mode.filter": "Filter / Suche",
|
||||
"deadlines.pathway.b.tree.coming_soon": "Der Entscheidungsbaum ist in Vorbereitung. Wechseln Sie zu „Filter / Suche\" oder kehren Sie zur Auswahl zurück.",
|
||||
"deadlines.pathway.b.tree.step.back": "Schritt zurück",
|
||||
"deadlines.pathway.b.tree.empty": "Keine Treffer für diesen Pfad.",
|
||||
"deadlines.pathway.b.tree.reset": "Neu starten",
|
||||
"deadlines.pathway.b.tree.skip": "Diesen Schritt überspringen",
|
||||
"deadlines.pathway.b.tree.start_question": "Was ist passiert?",
|
||||
"deadlines.filter.forum.label": "Gericht / System:",
|
||||
"deadlines.filter.forum.upc_cfi": "UPC CFI",
|
||||
"deadlines.filter.forum.upc_coa": "UPC CoA",
|
||||
"deadlines.filter.forum.de_lg": "DE LG",
|
||||
"deadlines.filter.forum.de_olg": "DE OLG",
|
||||
"deadlines.filter.forum.de_bgh": "DE BGH",
|
||||
"deadlines.filter.forum.de_bpatg": "DE BPatG",
|
||||
"deadlines.filter.forum.epa_grant": "EPA Erteilung",
|
||||
"deadlines.filter.forum.epa_opp": "EPA Einspruchsabt.",
|
||||
"deadlines.filter.forum.epa_appeal": "EPA Beschwerdek.",
|
||||
"deadlines.filter.forum.dpma": "DPMA",
|
||||
"deadlines.perspective.label": "Ich vertrete:",
|
||||
"deadlines.perspective.claimant": "Klägerseite (Proactive)",
|
||||
"deadlines.perspective.defendant": "Beklagtenseite (Reactive)",
|
||||
"deadlines.perspective.appeal_filed_by.label": "Berufung eingelegt durch:",
|
||||
"deadlines.event.composite.label": "Zusammengesetzt:",
|
||||
"deadlines.event.unit.days.one": "Tag",
|
||||
"deadlines.event.unit.days.many": "Tage",
|
||||
@@ -1868,36 +1838,6 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
"deadlines.search.results.count": "{n} hits",
|
||||
"deadlines.search.results.count_one": "1 hit",
|
||||
"deadlines.search.clear": "Clear search",
|
||||
"deadlines.pathway.fork.heading": "What would you like to do?",
|
||||
"deadlines.pathway.a.title": "Browse a proceeding",
|
||||
"deadlines.pathway.a.desc": "Pick a proceeding type and see all its deadlines on a single timeline.",
|
||||
"deadlines.pathway.b.title": "File a deadline based on an event",
|
||||
"deadlines.pathway.b.desc": "Something happened — find the right deadline for the matter.",
|
||||
"deadlines.pathway.shortcut.label": "or jump straight to a deadline:",
|
||||
"deadlines.pathway.back": "back to selection",
|
||||
"deadlines.pathway.b.mode.tree": "Step-by-step (decision tree)",
|
||||
"deadlines.pathway.b.mode.filter": "Filter / Search",
|
||||
"deadlines.pathway.b.tree.coming_soon": "The decision tree is coming soon. Switch to \"Filter / Search\" or return to selection.",
|
||||
"deadlines.pathway.b.tree.step.back": "step back",
|
||||
"deadlines.pathway.b.tree.empty": "No matches for this path.",
|
||||
"deadlines.pathway.b.tree.reset": "Restart",
|
||||
"deadlines.pathway.b.tree.skip": "Skip this step",
|
||||
"deadlines.pathway.b.tree.start_question": "What happened?",
|
||||
"deadlines.filter.forum.label": "Forum / System:",
|
||||
"deadlines.filter.forum.upc_cfi": "UPC CFI",
|
||||
"deadlines.filter.forum.upc_coa": "UPC CoA",
|
||||
"deadlines.filter.forum.de_lg": "DE LG",
|
||||
"deadlines.filter.forum.de_olg": "DE OLG",
|
||||
"deadlines.filter.forum.de_bgh": "DE BGH",
|
||||
"deadlines.filter.forum.de_bpatg": "DE BPatG",
|
||||
"deadlines.filter.forum.epa_grant": "EPO Examining",
|
||||
"deadlines.filter.forum.epa_opp": "EPO Opposition",
|
||||
"deadlines.filter.forum.epa_appeal": "EPO Board of Appeal",
|
||||
"deadlines.filter.forum.dpma": "DPMA",
|
||||
"deadlines.perspective.label": "I represent:",
|
||||
"deadlines.perspective.claimant": "Claimant side (Proactive)",
|
||||
"deadlines.perspective.defendant": "Defendant side (Reactive)",
|
||||
"deadlines.perspective.appeal_filed_by.label": "Appeal filed by:",
|
||||
"deadlines.event.composite.label": "Composite:",
|
||||
"deadlines.event.unit.days.one": "day",
|
||||
"deadlines.event.unit.days.many": "days",
|
||||
|
||||
@@ -78,133 +78,46 @@ export function renderFristenrechner(): string {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* v3 landing fork (t-paliad-133) — visible by default, hidden once
|
||||
the user picks a pathway. URL ?path= drives visibility. */}
|
||||
<div className="fristen-pathway-fork" id="fristen-pathway-fork" role="group" aria-label="Pathway selector">
|
||||
<h2 className="fristen-pathway-fork-heading" data-i18n="deadlines.pathway.fork.heading">Was möchten Sie tun?</h2>
|
||||
<div className="fristen-pathway-fork-cards">
|
||||
<button type="button" className="fristen-pathway-card" data-path="a" id="fristen-pathway-a-cta">
|
||||
<span className="fristen-pathway-card-icon" aria-hidden="true">📖</span>
|
||||
<span className="fristen-pathway-card-title" data-i18n="deadlines.pathway.a.title">Verfahrensablauf informieren</span>
|
||||
<span className="fristen-pathway-card-desc" data-i18n="deadlines.pathway.a.desc">
|
||||
Verfahrenstyp wählen und alle dazugehörigen Fristen auf einer Zeitleiste sehen.
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" className="fristen-pathway-card" data-path="b" id="fristen-pathway-b-cta">
|
||||
<span className="fristen-pathway-card-icon" aria-hidden="true">📅</span>
|
||||
<span className="fristen-pathway-card-title" data-i18n="deadlines.pathway.b.title">Frist eintragen aufgrund Ereignis</span>
|
||||
<span className="fristen-pathway-card-desc" data-i18n="deadlines.pathway.b.desc">
|
||||
Ein Ereignis ist eingetreten — ich brauche die richtige Frist für meine Akte.
|
||||
</span>
|
||||
<div className="fristen-search">
|
||||
<label htmlFor="fristen-search-input" className="visually-hidden" data-i18n="deadlines.search.label">Frist suchen</label>
|
||||
<div className="fristen-search-row">
|
||||
<svg className="fristen-search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
id="fristen-search-input"
|
||||
className="fristen-search-input"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-i18n-placeholder="deadlines.search.placeholder"
|
||||
placeholder="Klageerwiderung, RoP 23, § 82, Wiedereinsetzung…"
|
||||
/>
|
||||
<button type="button" id="fristen-search-clear" className="fristen-search-clear" aria-label="Suche leeren" data-i18n-aria-label="deadlines.search.clear" hidden>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="fristen-pathway-fork-shortcut">
|
||||
<div className="fristen-pathway-fork-shortcut-label" data-i18n="deadlines.pathway.shortcut.label">
|
||||
oder direkt zu einer Frist springen:
|
||||
</div>
|
||||
<div className="fristen-search-chips" id="fristen-fork-chips" role="group" aria-label="Schnellzugriff">
|
||||
<button type="button" className="fristen-search-chip" data-q="Klageerwiderung">Klageerwiderung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Berufung">Berufung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Einspruch">Einspruch</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Replik">Replik</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Beschwerde">Beschwerde</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Wiedereinsetzung">Wiedereinsetzung</button>
|
||||
</div>
|
||||
<div className="fristen-search-chips" id="fristen-search-chips" role="group" aria-label="Schnellzugriff">
|
||||
<span className="fristen-search-chips-label" data-i18n="deadlines.search.chips.label">Schnellzugriff:</span>
|
||||
<button type="button" className="fristen-search-chip" data-q="Klageerwiderung">Klageerwiderung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Berufung">Berufung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Einspruch">Einspruch</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Replik">Replik</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Beschwerde">Beschwerde</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Statement of Defence">Statement of Defence</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Schadensbemessung">Schadensbemessung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Wiedereinsetzung">Wiedereinsetzung</button>
|
||||
</div>
|
||||
<div id="fristen-search-results" className="fristen-search-results" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
{/* Pathway B container — search bar relocates here from the page top.
|
||||
Mode toggle (B1 tree / B2 filter) sits above the panels.
|
||||
Hidden until ?path=b. */}
|
||||
<div className="fristen-pathway-shell" id="fristen-pathway-b" data-path="b" hidden>
|
||||
<button type="button" className="fristen-pathway-back" id="fristen-pathway-b-back">
|
||||
<span aria-hidden="true">←</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.back">zurück zur Auswahl</span>
|
||||
</button>
|
||||
<h2 className="fristen-pathway-heading">
|
||||
<span aria-hidden="true">📅</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.b.title">Frist eintragen aufgrund Ereignis</span>
|
||||
</h2>
|
||||
|
||||
<div className="fristen-mode-toggle" role="radiogroup" aria-label="B1/B2 mode">
|
||||
<label className="fristen-mode-toggle-option">
|
||||
<input type="radio" name="fristen-b-mode" value="tree" id="fristen-b-mode-tree" />
|
||||
<span data-i18n="deadlines.pathway.b.mode.tree">Schritt-für-Schritt (Entscheidungsbaum)</span>
|
||||
</label>
|
||||
<label className="fristen-mode-toggle-option">
|
||||
<input type="radio" name="fristen-b-mode" value="filter" id="fristen-b-mode-filter" />
|
||||
<span data-i18n="deadlines.pathway.b.mode.filter">Filter / Suche</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* B1 panel — populated by fristenrechner-tree.ts in Phase C. */}
|
||||
<div className="fristen-b1-panel" id="fristen-b1-panel" data-mode="tree" hidden>
|
||||
<div className="fristen-b1-cascade" id="fristen-b1-cascade"></div>
|
||||
</div>
|
||||
|
||||
{/* B2 panel — search bar + chips + concept-card results.
|
||||
The search input + chips + results host live here so
|
||||
fristenrechner.ts can drive both Phase D (today) and the
|
||||
B1↔B2 state-share in Phase D (forum filter). */}
|
||||
<div className="fristen-b2-panel" id="fristen-b2-panel" data-mode="filter">
|
||||
<div className="fristen-search">
|
||||
<label htmlFor="fristen-search-input" className="visually-hidden" data-i18n="deadlines.search.label">Frist suchen</label>
|
||||
<div className="fristen-search-row">
|
||||
<svg className="fristen-search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
id="fristen-search-input"
|
||||
className="fristen-search-input"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-i18n-placeholder="deadlines.search.placeholder"
|
||||
placeholder="Klageerwiderung, RoP 23, § 82, Wiedereinsetzung…"
|
||||
/>
|
||||
<button type="button" id="fristen-search-clear" className="fristen-search-clear" aria-label="Suche leeren" data-i18n-aria-label="deadlines.search.clear" hidden>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="fristen-search-chips" id="fristen-search-chips" role="group" aria-label="Schnellzugriff">
|
||||
<span className="fristen-search-chips-label" data-i18n="deadlines.search.chips.label">Schnellzugriff:</span>
|
||||
<button type="button" className="fristen-search-chip" data-q="Klageerwiderung">Klageerwiderung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Berufung">Berufung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Einspruch">Einspruch</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Replik">Replik</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Beschwerde">Beschwerde</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Statement of Defence">Statement of Defence</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Schadensbemessung">Schadensbemessung</button>
|
||||
<button type="button" className="fristen-search-chip" data-q="Wiedereinsetzung">Wiedereinsetzung</button>
|
||||
</div>
|
||||
{/* Forum filter row — populated by Phase D. */}
|
||||
<div className="fristen-forum-filter" id="fristen-forum-filter" hidden>
|
||||
<span className="fristen-forum-filter-label" data-i18n="deadlines.filter.forum.label">Gericht / System:</span>
|
||||
<div className="fristen-forum-chips" id="fristen-forum-chips"></div>
|
||||
</div>
|
||||
<div id="fristen-search-results" className="fristen-search-results" aria-live="polite"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fristen-mode-tabs" role="tablist">
|
||||
<button type="button" id="mode-procedure-tab" className="mode-tab is-active" role="tab" aria-selected="true" data-mode="procedure" data-i18n="deadlines.mode.procedure">Verfahrensablauf</button>
|
||||
<button type="button" id="mode-event-tab" className="mode-tab" role="tab" aria-selected="false" data-mode="event" data-i18n="deadlines.mode.event">Was kommt nach…</button>
|
||||
</div>
|
||||
|
||||
{/* Pathway A container — wraps the existing wizard.
|
||||
Hidden until ?path=a. */}
|
||||
<div className="fristen-pathway-shell" id="fristen-pathway-a" data-path="a" hidden>
|
||||
<button type="button" className="fristen-pathway-back" id="fristen-pathway-a-back">
|
||||
<span aria-hidden="true">←</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.back">zurück zur Auswahl</span>
|
||||
</button>
|
||||
<h2 className="fristen-pathway-heading">
|
||||
<span aria-hidden="true">📖</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.a.title">Verfahrensablauf informieren</span>
|
||||
</h2>
|
||||
|
||||
{/* v3: legacy mode tabs retired (m's spec lock §10 Q1, 2026-05-05).
|
||||
Pathway A is Verfahrensablauf-only; trigger-event drill-in
|
||||
surfaces via concept-card pills with ?path=a&trigger=N URL,
|
||||
which resurfaces mode-event-panel programmatically below. */}
|
||||
<div className="fristen-wizard mode-panel" id="mode-procedure-panel" data-mode="procedure" role="tabpanel">
|
||||
<div className="fristen-wizard mode-panel" id="mode-procedure-panel" data-mode="procedure" role="tabpanel" aria-labelledby="mode-procedure-tab">
|
||||
<div className="wizard-step" id="step-1">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">1</span>
|
||||
@@ -330,7 +243,7 @@ export function renderFristenrechner(): string {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="fristen-wizard mode-panel" id="mode-event-panel" data-mode="event" role="tabpanel" hidden>
|
||||
<div className="fristen-wizard mode-panel" id="mode-event-panel" data-mode="event" role="tabpanel" aria-labelledby="mode-event-tab" hidden>
|
||||
<div className="wizard-step" id="event-step-1">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">1</span>
|
||||
@@ -395,7 +308,6 @@ export function renderFristenrechner(): string {
|
||||
← Neu berechnen
|
||||
</button>
|
||||
</div>
|
||||
</div>{/* /pathway-a */}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -637,17 +637,6 @@ export type I18nKey =
|
||||
| "deadlines.filter.all"
|
||||
| "deadlines.filter.completed"
|
||||
| "deadlines.filter.event_type"
|
||||
| "deadlines.filter.forum.de_bgh"
|
||||
| "deadlines.filter.forum.de_bpatg"
|
||||
| "deadlines.filter.forum.de_lg"
|
||||
| "deadlines.filter.forum.de_olg"
|
||||
| "deadlines.filter.forum.dpma"
|
||||
| "deadlines.filter.forum.epa_appeal"
|
||||
| "deadlines.filter.forum.epa_grant"
|
||||
| "deadlines.filter.forum.epa_opp"
|
||||
| "deadlines.filter.forum.label"
|
||||
| "deadlines.filter.forum.upc_cfi"
|
||||
| "deadlines.filter.forum.upc_coa"
|
||||
| "deadlines.filter.later"
|
||||
| "deadlines.filter.nextweek"
|
||||
| "deadlines.filter.overdue"
|
||||
@@ -684,25 +673,6 @@ export type I18nKey =
|
||||
| "deadlines.party.claimant"
|
||||
| "deadlines.party.court"
|
||||
| "deadlines.party.defendant"
|
||||
| "deadlines.pathway.a.desc"
|
||||
| "deadlines.pathway.a.title"
|
||||
| "deadlines.pathway.b.desc"
|
||||
| "deadlines.pathway.b.mode.filter"
|
||||
| "deadlines.pathway.b.mode.tree"
|
||||
| "deadlines.pathway.b.title"
|
||||
| "deadlines.pathway.b.tree.coming_soon"
|
||||
| "deadlines.pathway.b.tree.empty"
|
||||
| "deadlines.pathway.b.tree.reset"
|
||||
| "deadlines.pathway.b.tree.skip"
|
||||
| "deadlines.pathway.b.tree.start_question"
|
||||
| "deadlines.pathway.b.tree.step.back"
|
||||
| "deadlines.pathway.back"
|
||||
| "deadlines.pathway.fork.heading"
|
||||
| "deadlines.pathway.shortcut.label"
|
||||
| "deadlines.perspective.appeal_filed_by.label"
|
||||
| "deadlines.perspective.claimant"
|
||||
| "deadlines.perspective.defendant"
|
||||
| "deadlines.perspective.label"
|
||||
| "deadlines.print"
|
||||
| "deadlines.priority.date"
|
||||
| "deadlines.reset"
|
||||
|
||||
@@ -1533,321 +1533,6 @@ input[type="range"]::-moz-range-thumb {
|
||||
|
||||
/* --- Fristenrechner --- */
|
||||
|
||||
/* Fristenrechner v3 (t-paliad-133) — landing fork + pathway shells.
|
||||
The fork is the default landing surface. Each pathway is a peer
|
||||
container that slides in once chosen; back-button returns to fork. */
|
||||
|
||||
.fristen-pathway-fork {
|
||||
margin: 2rem 0 2.5rem;
|
||||
padding: 1.75rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 12px;
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.fristen-pathway-fork-heading {
|
||||
margin: 0 0 1.25rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.fristen-pathway-fork-cards {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.fristen-pathway-fork-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fristen-pathway-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 10px;
|
||||
background: var(--color-bg);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: border-color 120ms, box-shadow 120ms, transform 60ms;
|
||||
}
|
||||
|
||||
.fristen-pathway-card:hover {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.fristen-pathway-card:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.fristen-pathway-card:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.fristen-pathway-card-icon {
|
||||
font-size: 1.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.fristen-pathway-card-title {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.fristen-pathway-card-desc {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.fristen-pathway-fork-shortcut {
|
||||
border-top: 1px dashed var(--color-border);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.fristen-pathway-fork-shortcut-label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.fristen-pathway-shell {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.fristen-pathway-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fristen-pathway-back:hover {
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg-muted);
|
||||
}
|
||||
|
||||
.fristen-pathway-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0 0 1.5rem;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.fristen-mode-toggle {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.fristen-mode-toggle-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.fristen-b1-stub {
|
||||
padding: 1.5rem;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* B1 cascade — Phase C decision-tree UI (t-paliad-133) */
|
||||
|
||||
.fristen-b1-breadcrumb {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.fristen-b1-crumb {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
background: var(--color-bg-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 999px;
|
||||
padding: 0.2rem 0.65rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
transition: background 120ms;
|
||||
}
|
||||
|
||||
.fristen-b1-crumb:hover {
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.fristen-b1-crumb--current {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent-text, #000);
|
||||
font-weight: 500;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.fristen-b1-crumb--root {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.fristen-b1-crumb-sep {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.fristen-b1-question {
|
||||
margin: 0.5rem 0 1rem;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.fristen-b1-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 0.6rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.fristen-b1-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.85rem 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
background: var(--color-bg);
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
text-align: left;
|
||||
color: var(--color-text);
|
||||
transition: border-color 120ms, background 120ms;
|
||||
}
|
||||
|
||||
.fristen-b1-button:hover {
|
||||
border-color: var(--color-accent);
|
||||
background: var(--color-bg-muted);
|
||||
}
|
||||
|
||||
.fristen-b1-button:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.fristen-b1-button--leaf {
|
||||
/* Leaf nodes get a subtle marker so users sense they'll see results,
|
||||
not deeper buttons. */
|
||||
border-left: 3px solid var(--color-accent);
|
||||
}
|
||||
|
||||
.fristen-b1-button-icon {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.fristen-b1-button-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fristen-b1-skip,
|
||||
.fristen-b1-step-back,
|
||||
.fristen-b1-loosen-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.fristen-b1-skip:hover,
|
||||
.fristen-b1-step-back:hover,
|
||||
.fristen-b1-loosen-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.fristen-b1-error {
|
||||
padding: 1rem;
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.fristen-forum-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
margin: 0.75rem 0 1rem;
|
||||
}
|
||||
|
||||
.fristen-forum-filter-label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.fristen-forum-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.fristen-forum-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.65rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 999px;
|
||||
background: var(--color-bg);
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text);
|
||||
transition: background 120ms, border-color 120ms;
|
||||
}
|
||||
|
||||
.fristen-forum-chip:hover {
|
||||
background: var(--color-bg-muted);
|
||||
}
|
||||
|
||||
.fristen-forum-chip--active {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent-text, #000);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Phase D search (t-paliad-131) — augments the proceeding tile grid by
|
||||
letting the user type a phrase and drill into the right calculator
|
||||
from a ranked card list. Sits above the mode tabs. */
|
||||
|
||||
Reference in New Issue
Block a user