interface improvements
This commit is contained in:
parent
fd65130bac
commit
fcab1485f5
@ -2,10 +2,16 @@
|
|||||||
import { getToolsData } from '../utils/dataService.js';
|
import { getToolsData } from '../utils/dataService.js';
|
||||||
|
|
||||||
const data = await getToolsData();
|
const data = await getToolsData();
|
||||||
|
|
||||||
const domains = data.domains;
|
const domains = data.domains;
|
||||||
const phases = data.phases;
|
const phases = data.phases;
|
||||||
|
|
||||||
|
// Extract unique values dynamically - NO HARD-CODING
|
||||||
|
const skillLevels = [...new Set(data.tools.map(tool => tool.skillLevel))].filter(Boolean).sort();
|
||||||
|
const platforms = [...new Set(data.tools.flatMap(tool => tool.platforms || []))].filter(Boolean).sort();
|
||||||
|
const licenses = [...new Set(data.tools.map(tool => tool.license))].filter(Boolean).sort();
|
||||||
|
const toolTypes = [...new Set(data.tools.map(tool => tool.type))].filter(Boolean).sort();
|
||||||
|
const accessTypes = [...new Set(data.tools.map(tool => tool.accessType))].filter(Boolean).sort();
|
||||||
|
|
||||||
const tagFrequency = data.tools.reduce((acc: Record<string, number>, tool: any) => {
|
const tagFrequency = data.tools.reduce((acc: Record<string, number>, tool: any) => {
|
||||||
tool.tags.forEach((tag: string) => {
|
tool.tags.forEach((tag: string) => {
|
||||||
acc[tag] = (acc[tag] || 0) + 1;
|
acc[tag] = (acc[tag] || 0) + 1;
|
||||||
@ -19,97 +25,243 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
---
|
---
|
||||||
|
|
||||||
<div class="filters-container">
|
<div class="filters-container">
|
||||||
<div style="margin-bottom: 1.5rem;">
|
<!-- Search Section -->
|
||||||
<input
|
<div class="filter-section">
|
||||||
type="text"
|
<div class="filter-card-compact">
|
||||||
id="search-input"
|
<div class="filter-header-compact">
|
||||||
placeholder="Suchfeld: Name der Software, Beschreibung oder Tags..."
|
<h3>🔍 Suche</h3>
|
||||||
style="width: 100%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="domain-phase-container" style="margin-bottom: 1.5rem;">
|
|
||||||
<div class="domain-section">
|
|
||||||
<label for="domain-select" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">
|
|
||||||
Forensische Domäne
|
|
||||||
</label>
|
|
||||||
<select id="domain-select" style="max-width: 300px;">
|
|
||||||
<option value="">Alle Domänen</option>
|
|
||||||
{domains.map((domain: any) => (
|
|
||||||
<option value={domain.id}>{domain.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="phase-section">
|
|
||||||
<label style="display: block; margin-bottom: 0.75rem; font-weight: 500;">
|
|
||||||
Untersuchungsphase
|
|
||||||
</label>
|
|
||||||
<div class="phase-buttons">
|
|
||||||
{phases.map((phase: any) => (
|
|
||||||
<button
|
|
||||||
class="phase-button"
|
|
||||||
data-phase={phase.id}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{phase.name}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="search-wrapper">
|
||||||
</div>
|
<div class="search-icon">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<div style="margin-bottom: 1.5rem;">
|
<circle cx="11" cy="11" r="8"/>
|
||||||
<div class="checkbox-wrapper" style="margin-bottom: 1rem;">
|
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||||
<input type="checkbox" id="include-proprietary" checked />
|
</svg>
|
||||||
<label for="include-proprietary">Proprietäre Software mit einschließen</label>
|
</div>
|
||||||
</div>
|
<input
|
||||||
|
type="text"
|
||||||
<div class="mb-4">
|
id="search-input"
|
||||||
<div class="tag-header">
|
placeholder="Software, Beschreibung oder Tags durchsuchen..."
|
||||||
<label style="font-weight: 500;">
|
class="search-input"
|
||||||
Nach Tags filtern
|
/>
|
||||||
</label>
|
<button id="clear-search" class="search-clear hidden" title="Suche löschen">
|
||||||
<button
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
id="tag-cloud-toggle"
|
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||||
class="btn-tag-toggle"
|
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||||
data-expanded="false"
|
</svg>
|
||||||
>
|
|
||||||
Mehr zeigen
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-cloud" id="tag-cloud">
|
</div>
|
||||||
{sortedTags.map((tag, index) => (
|
</div>
|
||||||
<button
|
|
||||||
class="tag-cloud-item"
|
<!-- Primary Filters Section - ONLY Domain and Phase -->
|
||||||
data-tag={tag}
|
<div class="filter-section">
|
||||||
data-frequency={tagFrequency[tag]}
|
<div class="filter-card-compact">
|
||||||
data-index={index}
|
<div class="filter-header-compact">
|
||||||
>
|
<h3>🎯 Primäre Filter</h3>
|
||||||
{tag}
|
<button class="filter-reset" id="reset-primary" title="Primäre Filter zurücksetzen">
|
||||||
<span class="tag-frequency">({tagFrequency[tag]})</span>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
</button>
|
<polyline points="1 4 1 10 7 10"/>
|
||||||
))}
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-grid-compact">
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Forensische Domäne</label>
|
||||||
|
<select id="domain-select" class="filter-select">
|
||||||
|
<option value="">Alle Domänen</option>
|
||||||
|
{domains.map((domain: any) => (
|
||||||
|
<option value={domain.id}>{domain.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Untersuchungsphase</label>
|
||||||
|
<select id="phase-select" class="filter-select">
|
||||||
|
<option value="">Alle Phasen</option>
|
||||||
|
{phases.map((phase: any) => (
|
||||||
|
<option value={phase.id}>{phase.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; gap: 1rem; margin-bottom: 1.5rem; align-items: center;">
|
<!-- Advanced Filters Section -->
|
||||||
<button class="btn btn-secondary view-toggle active h-12" data-view="grid">Kachelansicht</button>
|
<div class="filter-section">
|
||||||
<button class="btn btn-secondary view-toggle h-12" data-view="matrix">Matrix-Ansicht</button>
|
<div class="filter-card-compact">
|
||||||
|
<div class="filter-header-compact">
|
||||||
<button
|
<h3>⚙️ Erweiterte Filter</h3>
|
||||||
id="ai-view-toggle"
|
<button class="filter-reset" id="reset-advanced" title="Erweiterte Filter zurücksetzen">
|
||||||
class="btn btn-secondary view-toggle"
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
data-view="ai"
|
<polyline points="1 4 1 10 7 10"/>
|
||||||
style="display: none; background-color: var(--color-accent); color: white; border-color: var(--color-accent);"
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
||||||
>
|
</svg>
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; height:25px">
|
</button>
|
||||||
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
|
</div>
|
||||||
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
|
|
||||||
</svg>
|
<div class="advanced-filters-compact">
|
||||||
Forensic-AI
|
<div class="filter-grid-compact">
|
||||||
</button>
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Tool-Typ</label>
|
||||||
|
<select id="type-select" class="filter-select">
|
||||||
|
<option value="">Alle Typen</option>
|
||||||
|
{toolTypes.map((type: string) => (
|
||||||
|
<option value={type}>{type}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Skill Level</label>
|
||||||
|
<select id="skill-select" class="filter-select">
|
||||||
|
<option value="">Alle Level</option>
|
||||||
|
{skillLevels.map((level: string) => (
|
||||||
|
<option value={level}>{level}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Plattform</label>
|
||||||
|
<select id="platform-select" class="filter-select">
|
||||||
|
<option value="">Alle Plattformen</option>
|
||||||
|
{platforms.map((platform: string) => (
|
||||||
|
<option value={platform}>{platform}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Lizenztyp</label>
|
||||||
|
<select id="license-select" class="filter-select">
|
||||||
|
<option value="">Alle Lizenzen</option>
|
||||||
|
{licenses.map((license: string) => (
|
||||||
|
<option value={license}>{license}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label class="filter-label">Zugangsart</label>
|
||||||
|
<select id="access-select" class="filter-select">
|
||||||
|
<option value="">Alle Zugangsarten</option>
|
||||||
|
{accessTypes.map((access: string) => (
|
||||||
|
<option value={access}>{access}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-toggles-compact">
|
||||||
|
<label class="toggle-wrapper">
|
||||||
|
<input type="checkbox" id="hosted-only" />
|
||||||
|
<span class="toggle-label">🟣 Nur CC24-Server Tools</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="toggle-wrapper">
|
||||||
|
<input type="checkbox" id="knowledgebase-only" />
|
||||||
|
<span class="toggle-label">📖 Nur Tools mit Knowledgebase</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tag Filters Section -->
|
||||||
|
<div class="filter-section">
|
||||||
|
<div class="filter-card-compact">
|
||||||
|
<div class="filter-header-compact">
|
||||||
|
<h3>🏷️ Tag-Filter</h3>
|
||||||
|
<div class="tag-controls">
|
||||||
|
<button class="filter-reset" id="reset-tags" title="Tags zurücksetzen">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="1 4 1 10 7 10"/>
|
||||||
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button id="tag-cloud-toggle" class="tag-toggle" data-expanded="false">
|
||||||
|
Mehr zeigen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tag-section">
|
||||||
|
<div class="selected-tags" id="selected-tags"></div>
|
||||||
|
<div class="tag-cloud" id="tag-cloud">
|
||||||
|
{sortedTags.map((tag, index) => (
|
||||||
|
<button
|
||||||
|
class="tag-cloud-item"
|
||||||
|
data-tag={tag}
|
||||||
|
data-frequency={tagFrequency[tag]}
|
||||||
|
data-index={index}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
<span class="tag-frequency">({tagFrequency[tag]})</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- View Controls Section -->
|
||||||
|
<div class="filter-section">
|
||||||
|
<div class="filter-card-compact">
|
||||||
|
<div class="filter-header-compact">
|
||||||
|
<h3>👁️ Ansicht</h3>
|
||||||
|
<div class="results-count">
|
||||||
|
<span id="results-counter">Alle Tools</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="view-controls-compact">
|
||||||
|
<button class="view-toggle active" data-view="grid">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<rect x="3" y="3" width="7" height="7"/>
|
||||||
|
<rect x="14" y="3" width="7" height="7"/>
|
||||||
|
<rect x="14" y="14" width="7" height="7"/>
|
||||||
|
<rect x="3" y="14" width="7" height="7"/>
|
||||||
|
</svg>
|
||||||
|
<span>Kacheln</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="view-toggle" data-view="matrix">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<rect x="3" y="3" width="4" height="4"/>
|
||||||
|
<rect x="10" y="3" width="4" height="4"/>
|
||||||
|
<rect x="17" y="3" width="4" height="4"/>
|
||||||
|
<rect x="3" y="10" width="4" height="4"/>
|
||||||
|
<rect x="10" y="10" width="4" height="4"/>
|
||||||
|
<rect x="17" y="10" width="4" height="4"/>
|
||||||
|
<rect x="3" y="17" width="4" height="4"/>
|
||||||
|
<rect x="10" y="17" width="4" height="4"/>
|
||||||
|
<rect x="17" y="17" width="4" height="4"/>
|
||||||
|
</svg>
|
||||||
|
<span>Matrix</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button id="ai-view-toggle" class="btn btn-accent view-toggle" data-view="ai" style="display: none;">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
|
||||||
|
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
|
||||||
|
</svg>
|
||||||
|
<span>Forensic-AI</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="filter-reset-all" id="reset-all-filters">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="1 4 1 10 7 10"/>
|
||||||
|
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
||||||
|
</svg>
|
||||||
|
<span>Alle Filter zurücksetzen</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -117,31 +269,55 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
window.toolsData = toolsData;
|
window.toolsData = toolsData;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Cache DOM elements once
|
// Cache DOM elements
|
||||||
const elements = {
|
const elements = {
|
||||||
searchInput: document.getElementById('search-input'),
|
searchInput: document.getElementById('search-input'),
|
||||||
|
clearSearch: document.getElementById('clear-search'),
|
||||||
domainSelect: document.getElementById('domain-select'),
|
domainSelect: document.getElementById('domain-select'),
|
||||||
phaseButtons: document.querySelectorAll('.phase-button'),
|
phaseSelect: document.getElementById('phase-select'),
|
||||||
proprietaryCheckbox: document.getElementById('include-proprietary'),
|
typeSelect: document.getElementById('type-select'),
|
||||||
|
skillSelect: document.getElementById('skill-select'),
|
||||||
|
platformSelect: document.getElementById('platform-select'),
|
||||||
|
licenseSelect: document.getElementById('license-select'),
|
||||||
|
accessSelect: document.getElementById('access-select'),
|
||||||
|
hostedOnly: document.getElementById('hosted-only'),
|
||||||
|
knowledgebaseOnly: document.getElementById('knowledgebase-only'),
|
||||||
tagCloudItems: document.querySelectorAll('.tag-cloud-item'),
|
tagCloudItems: document.querySelectorAll('.tag-cloud-item'),
|
||||||
tagCloud: document.getElementById('tag-cloud'),
|
tagCloud: document.getElementById('tag-cloud'),
|
||||||
tagCloudToggle: document.getElementById('tag-cloud-toggle'),
|
tagCloudToggle: document.getElementById('tag-cloud-toggle'),
|
||||||
|
selectedTags: document.getElementById('selected-tags'),
|
||||||
viewToggles: document.querySelectorAll('.view-toggle'),
|
viewToggles: document.querySelectorAll('.view-toggle'),
|
||||||
aiViewToggle: document.getElementById('ai-view-toggle')
|
resultsCounter: document.getElementById('results-counter'),
|
||||||
|
resetButtons: {
|
||||||
|
primary: document.getElementById('reset-primary'),
|
||||||
|
advanced: document.getElementById('reset-advanced'),
|
||||||
|
tags: document.getElementById('reset-tags'),
|
||||||
|
all: document.getElementById('reset-all-filters')
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify critical elements exist
|
// Verify critical elements exist
|
||||||
if (!elements.searchInput || !elements.domainSelect || !elements.proprietaryCheckbox) {
|
if (!elements.searchInput || !elements.domainSelect) {
|
||||||
console.error('Critical filter elements not found');
|
console.error('Critical filter elements not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// State management
|
||||||
let selectedTags = new Set();
|
let selectedTags = new Set();
|
||||||
let selectedPhase = '';
|
let selectedPhase = '';
|
||||||
let isTagCloudExpanded = false;
|
let isTagCloudExpanded = false;
|
||||||
|
|
||||||
|
// Helper function to check if tool is hosted
|
||||||
|
function isToolHosted(tool) {
|
||||||
|
return tool.projectUrl !== undefined &&
|
||||||
|
tool.projectUrl !== null &&
|
||||||
|
tool.projectUrl !== "" &&
|
||||||
|
tool.projectUrl.trim() !== "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize tag cloud
|
||||||
function initTagCloud() {
|
function initTagCloud() {
|
||||||
const visibleCount = 22;
|
const visibleCount = 20;
|
||||||
elements.tagCloudItems.forEach((item, index) => {
|
elements.tagCloudItems.forEach((item, index) => {
|
||||||
if (index >= visibleCount) {
|
if (index >= visibleCount) {
|
||||||
item.style.display = 'none';
|
item.style.display = 'none';
|
||||||
@ -149,9 +325,10 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle tag cloud expansion
|
||||||
function toggleTagCloud() {
|
function toggleTagCloud() {
|
||||||
isTagCloudExpanded = !isTagCloudExpanded;
|
isTagCloudExpanded = !isTagCloudExpanded;
|
||||||
const visibleCount = 22;
|
const visibleCount = 20;
|
||||||
|
|
||||||
if (isTagCloudExpanded) {
|
if (isTagCloudExpanded) {
|
||||||
elements.tagCloud.classList.add('expanded');
|
elements.tagCloud.classList.add('expanded');
|
||||||
@ -181,11 +358,12 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter tag cloud based on search
|
||||||
function filterTagCloud() {
|
function filterTagCloud() {
|
||||||
const searchTerm = elements.searchInput.value.toLowerCase();
|
const searchTerm = elements.searchInput.value.toLowerCase();
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
const maxVisibleWhenCollapsed = 22;
|
const maxVisibleWhenCollapsed = 20;
|
||||||
|
|
||||||
elements.tagCloudItems.forEach(item => {
|
elements.tagCloudItems.forEach(item => {
|
||||||
const tagName = item.getAttribute('data-tag').toLowerCase();
|
const tagName = item.getAttribute('data-tag').toLowerCase();
|
||||||
@ -211,91 +389,132 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
elements.tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
|
elements.tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isToolHosted(tool) {
|
// Update selected tags display
|
||||||
return tool.projectUrl !== undefined &&
|
function updateSelectedTags() {
|
||||||
tool.projectUrl !== null &&
|
if (selectedTags.size === 0) {
|
||||||
tool.projectUrl !== "" &&
|
elements.selectedTags.style.display = 'none';
|
||||||
tool.projectUrl.trim() !== "";
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMethod(tool) {
|
|
||||||
return tool.type === 'method';
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMatrixHighlighting() {
|
|
||||||
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
|
|
||||||
if (currentView !== 'matrix') return;
|
|
||||||
|
|
||||||
const matrixTable = document.querySelector('.matrix-table');
|
elements.selectedTags.style.display = 'block';
|
||||||
if (!matrixTable) return;
|
elements.selectedTags.innerHTML = `
|
||||||
|
<div class="selected-tags-header">Ausgewählte Tags:</div>
|
||||||
|
<div class="selected-tags-list">
|
||||||
|
${Array.from(selectedTags).map(tag => `
|
||||||
|
<span class="selected-tag">
|
||||||
|
${tag}
|
||||||
|
<button class="remove-tag" data-tag="${tag}">×</button>
|
||||||
|
</span>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
matrixTable.querySelectorAll('.highlight-row, .highlight-column').forEach(el => {
|
// Add event listeners for remove buttons
|
||||||
el.classList.remove('highlight-row', 'highlight-column');
|
elements.selectedTags.querySelectorAll('.remove-tag').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const tag = btn.getAttribute('data-tag');
|
||||||
|
removeTag(tag);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedDomain = elements.domainSelect.value;
|
|
||||||
|
|
||||||
if (selectedDomain) {
|
|
||||||
const domainRow = matrixTable.querySelector(`tr[data-domain="${selectedDomain}"]`);
|
|
||||||
if (domainRow) {
|
|
||||||
domainRow.classList.add('highlight-row');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedPhase) {
|
|
||||||
const phaseHeaders = matrixTable.querySelectorAll('thead th[data-phase]');
|
|
||||||
const phaseIndex = Array.from(phaseHeaders).findIndex(th =>
|
|
||||||
th.getAttribute('data-phase') === selectedPhase
|
|
||||||
);
|
|
||||||
|
|
||||||
if (phaseIndex >= 0) {
|
|
||||||
const columnIndex = phaseIndex + 1;
|
|
||||||
matrixTable.querySelectorAll(`tr`).forEach(row => {
|
|
||||||
const cell = row.children[columnIndex];
|
|
||||||
if (cell) {
|
|
||||||
cell.classList.add('highlight-column');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add/remove tags
|
||||||
|
function addTag(tag) {
|
||||||
|
selectedTags.add(tag);
|
||||||
|
document.querySelector(`[data-tag="${tag}"]`).classList.add('active');
|
||||||
|
updateSelectedTags();
|
||||||
|
filterTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTag(tag) {
|
||||||
|
selectedTags.delete(tag);
|
||||||
|
const tagElement = document.querySelector(`[data-tag="${tag}"]`);
|
||||||
|
if (tagElement) tagElement.classList.remove('active');
|
||||||
|
updateSelectedTags();
|
||||||
|
filterTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update results counter
|
||||||
|
function updateResultsCounter(count) {
|
||||||
|
const total = window.toolsData.length;
|
||||||
|
elements.resultsCounter.textContent = count === total
|
||||||
|
? `${total} Tools`
|
||||||
|
: `${count} von ${total} Tools`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main filter function
|
||||||
function filterTools() {
|
function filterTools() {
|
||||||
const searchTerm = elements.searchInput.value.trim();
|
const searchTerm = elements.searchInput.value.trim().toLowerCase();
|
||||||
const selectedDomain = elements.domainSelect.value;
|
const selectedDomain = elements.domainSelect.value;
|
||||||
const includeProprietary = elements.proprietaryCheckbox.checked;
|
const selectedPhaseFromSelect = elements.phaseSelect.value;
|
||||||
|
const selectedType = elements.typeSelect.value;
|
||||||
|
const selectedSkill = elements.skillSelect.value;
|
||||||
|
const selectedPlatform = elements.platformSelect.value;
|
||||||
|
const selectedLicense = elements.licenseSelect.value;
|
||||||
|
const selectedAccess = elements.accessSelect.value;
|
||||||
|
const hostedOnly = elements.hostedOnly.checked;
|
||||||
|
const knowledgebaseOnly = elements.knowledgebaseOnly.checked;
|
||||||
|
|
||||||
|
// Use phase from either dropdown or button selection
|
||||||
|
const activePhase = selectedPhaseFromSelect || selectedPhase;
|
||||||
|
|
||||||
const filtered = window.toolsData.filter(tool => {
|
const filtered = window.toolsData.filter(tool => {
|
||||||
const domains = tool.domains || [];
|
// Search filter
|
||||||
const phases = tool.phases || [];
|
|
||||||
const tags = tool.tags || [];
|
|
||||||
|
|
||||||
// Search filter (keep existing logic)
|
|
||||||
if (searchTerm && !(
|
if (searchTerm && !(
|
||||||
tool.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
tool.name.toLowerCase().includes(searchTerm) ||
|
||||||
tool.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
tool.description.toLowerCase().includes(searchTerm) ||
|
||||||
tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
|
(tool.tags || []).some(tag => tag.toLowerCase().includes(searchTerm))
|
||||||
)) {
|
)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domain filter
|
// Domain filter
|
||||||
if (selectedDomain && !domains.includes(selectedDomain)) {
|
if (selectedDomain && !(tool.domains || []).includes(selectedDomain)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase filter
|
// Phase filter
|
||||||
if (selectedPhase && !phases.includes(selectedPhase)) {
|
if (activePhase && !(tool.phases || []).includes(activePhase)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proprietary filter
|
// Type filter
|
||||||
if (!includeProprietary && !isMethod(tool) && tool.type !== 'concept' && tool.license === 'Proprietary') {
|
if (selectedType && tool.type !== selectedType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skill level filter
|
||||||
|
if (selectedSkill && tool.skillLevel !== selectedSkill) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform filter
|
||||||
|
if (selectedPlatform && !(tool.platforms || []).includes(selectedPlatform)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// License filter - NO MORE HARD-CODED LOGIC
|
||||||
|
if (selectedLicense && tool.license !== selectedLicense) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access type filter
|
||||||
|
if (selectedAccess && tool.accessType !== selectedAccess) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hosted only filter (CC24-Server tools)
|
||||||
|
if (hostedOnly && !isToolHosted(tool)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Knowledgebase only filter
|
||||||
|
if (knowledgebaseOnly && !tool.knowledgebase) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag filter
|
// Tag filter
|
||||||
if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => tags.includes(tag))) {
|
if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => (tool.tags || []).includes(tag))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,97 +526,118 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
? window.prioritizeSearchResults(filtered, searchTerm)
|
? window.prioritizeSearchResults(filtered, searchTerm)
|
||||||
: filtered;
|
: filtered;
|
||||||
|
|
||||||
updateMatrixHighlighting();
|
updateResultsCounter(finalResults.length);
|
||||||
|
|
||||||
|
// Dispatch event for other components
|
||||||
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: finalResults }));
|
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: finalResults }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTagClick(tagItem) {
|
// Reset functions
|
||||||
const tag = tagItem.getAttribute('data-tag');
|
function resetPrimaryFilters() {
|
||||||
|
elements.domainSelect.value = '';
|
||||||
if (selectedTags.has(tag)) {
|
elements.phaseSelect.value = '';
|
||||||
selectedTags.delete(tag);
|
selectedPhase = '';
|
||||||
tagItem.classList.remove('active');
|
|
||||||
} else {
|
|
||||||
selectedTags.add(tag);
|
|
||||||
tagItem.classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
filterTools();
|
filterTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePhaseClick(button) {
|
function resetAdvancedFilters() {
|
||||||
const phase = button.getAttribute('data-phase');
|
elements.typeSelect.value = '';
|
||||||
|
elements.skillSelect.value = '';
|
||||||
if (selectedPhase === phase) {
|
elements.platformSelect.value = '';
|
||||||
selectedPhase = '';
|
elements.licenseSelect.value = '';
|
||||||
button.classList.remove('active');
|
elements.accessSelect.value = '';
|
||||||
} else {
|
elements.hostedOnly.checked = false;
|
||||||
elements.phaseButtons.forEach(btn => btn.classList.remove('active'));
|
elements.knowledgebaseOnly.checked = false;
|
||||||
selectedPhase = phase;
|
|
||||||
button.classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
filterTools();
|
filterTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleViewToggle(view) {
|
function resetTags() {
|
||||||
elements.viewToggles.forEach(btn => {
|
|
||||||
btn.classList.toggle('active', btn.getAttribute('data-view') === view);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('viewChanged', { detail: view }));
|
|
||||||
|
|
||||||
if (view === 'hosted') {
|
|
||||||
const hosted = window.toolsData.filter(tool => isToolHosted(tool));
|
|
||||||
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: hosted }));
|
|
||||||
} else {
|
|
||||||
filterTools();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearTagFilters() {
|
|
||||||
selectedTags.clear();
|
selectedTags.clear();
|
||||||
elements.tagCloudItems.forEach(item => item.classList.remove('active'));
|
elements.tagCloudItems.forEach(item => item.classList.remove('active'));
|
||||||
|
updateSelectedTags();
|
||||||
filterTools();
|
filterTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAllFilters() {
|
function resetAllFilters() {
|
||||||
elements.searchInput.value = '';
|
elements.searchInput.value = '';
|
||||||
elements.domainSelect.value = '';
|
elements.clearSearch.classList.add('hidden');
|
||||||
selectedPhase = '';
|
resetPrimaryFilters();
|
||||||
elements.phaseButtons.forEach(btn => btn.classList.remove('active'));
|
resetAdvancedFilters();
|
||||||
clearTagFilters();
|
resetTags();
|
||||||
filterTagCloud();
|
filterTagCloud();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event listeners using cached elements
|
// Event listeners
|
||||||
elements.searchInput.addEventListener('input', () => {
|
elements.searchInput.addEventListener('input', (e) => {
|
||||||
|
const hasValue = e.target.value.length > 0;
|
||||||
|
elements.clearSearch.classList.toggle('hidden', !hasValue);
|
||||||
filterTagCloud();
|
filterTagCloud();
|
||||||
filterTools();
|
filterTools();
|
||||||
});
|
});
|
||||||
|
|
||||||
elements.domainSelect.addEventListener('change', filterTools);
|
elements.clearSearch.addEventListener('click', () => {
|
||||||
elements.proprietaryCheckbox.addEventListener('change', filterTools);
|
elements.searchInput.value = '';
|
||||||
|
elements.clearSearch.classList.add('hidden');
|
||||||
|
filterTagCloud();
|
||||||
|
filterTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
[elements.domainSelect, elements.phaseSelect, elements.typeSelect, elements.skillSelect,
|
||||||
|
elements.platformSelect, elements.licenseSelect, elements.accessSelect].forEach(select => {
|
||||||
|
select.addEventListener('change', filterTools);
|
||||||
|
});
|
||||||
|
|
||||||
|
[elements.hostedOnly, elements.knowledgebaseOnly].forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', filterTools);
|
||||||
|
});
|
||||||
|
|
||||||
elements.tagCloudToggle.addEventListener('click', toggleTagCloud);
|
elements.tagCloudToggle.addEventListener('click', toggleTagCloud);
|
||||||
|
|
||||||
elements.tagCloudItems.forEach(item => {
|
elements.tagCloudItems.forEach(item => {
|
||||||
item.addEventListener('click', () => handleTagClick(item));
|
item.addEventListener('click', () => {
|
||||||
});
|
const tag = item.getAttribute('data-tag');
|
||||||
|
if (selectedTags.has(tag)) {
|
||||||
elements.phaseButtons.forEach(btn => {
|
removeTag(tag);
|
||||||
btn.addEventListener('click', () => handlePhaseClick(btn));
|
} else {
|
||||||
|
addTag(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
elements.viewToggles.forEach(btn => {
|
elements.viewToggles.forEach(btn => {
|
||||||
btn.addEventListener('click', () => handleViewToggle(btn.getAttribute('data-view')));
|
btn.addEventListener('click', () => {
|
||||||
|
const view = btn.getAttribute('data-view');
|
||||||
|
|
||||||
|
// Simple toggle like the old version
|
||||||
|
elements.viewToggles.forEach(b => {
|
||||||
|
b.classList.toggle('active', b.getAttribute('data-view') === view);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.dispatchEvent(new CustomEvent('viewChanged', { detail: view }));
|
||||||
|
|
||||||
|
if (view === 'hosted') {
|
||||||
|
const hosted = window.toolsData.filter(tool => isToolHosted(tool));
|
||||||
|
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: hosted }));
|
||||||
|
} else {
|
||||||
|
filterTools();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.clearTagFilters = clearTagFilters;
|
// Reset button listeners
|
||||||
window.clearAllFilters = clearAllFilters;
|
elements.resetButtons.primary.addEventListener('click', resetPrimaryFilters);
|
||||||
|
elements.resetButtons.advanced.addEventListener('click', resetAdvancedFilters);
|
||||||
|
elements.resetButtons.tags.addEventListener('click', resetTags);
|
||||||
|
elements.resetButtons.all.addEventListener('click', resetAllFilters);
|
||||||
|
|
||||||
|
// Expose functions globally for backwards compatibility
|
||||||
|
window.clearTagFilters = resetTags;
|
||||||
|
window.clearAllFilters = resetAllFilters;
|
||||||
|
|
||||||
|
// Initialize
|
||||||
initTagCloud();
|
initTagCloud();
|
||||||
filterTagCloud();
|
filterTagCloud();
|
||||||
|
updateSelectedTags();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
filterTools();
|
filterTools();
|
||||||
|
@ -194,8 +194,8 @@ domains.forEach((domain: any) => {
|
|||||||
|
|
||||||
<script define:vars={{ toolsData: tools, domainAgnosticSoftware, domainAgnosticTools }}>
|
<script define:vars={{ toolsData: tools, domainAgnosticSoftware, domainAgnosticTools }}>
|
||||||
function getSelectedPhase() {
|
function getSelectedPhase() {
|
||||||
const activePhaseButton = document.querySelector('.phase-button.active');
|
const activePhaseChip = document.querySelector('.phase-chip.active');
|
||||||
return activePhaseButton ? activePhaseButton.getAttribute('data-phase') : '';
|
return activePhaseChip ? activePhaseChip.getAttribute('data-phase') : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedDomain() {
|
function getSelectedDomain() {
|
||||||
|
@ -302,35 +302,27 @@ const phases = data.phases;
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hideFilterControls() {
|
function hideFilterControls() {
|
||||||
const elements = [
|
const filterSections = document.querySelectorAll('.filter-section');
|
||||||
'.domain-phase-container',
|
filterSections.forEach((section, index) => {
|
||||||
'#search-input',
|
if (index < filterSections.length - 1) {
|
||||||
'.tag-cloud',
|
section.style.display = 'none';
|
||||||
'.tag-header',
|
}
|
||||||
'.checkbox-wrapper'
|
|
||||||
];
|
|
||||||
|
|
||||||
elements.forEach(selector => {
|
|
||||||
const element = document.querySelector(selector);
|
|
||||||
if (element) element.style.display = 'none';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
|
||||||
allInputs.forEach(input => input.style.display = 'none');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showFilterControls() {
|
function showFilterControls() {
|
||||||
const domainPhaseContainer = document.querySelector('.domain-phase-container');
|
const filterSections = document.querySelectorAll('.filter-section');
|
||||||
const searchInput = document.getElementById('search-input');
|
const searchInput = document.getElementById('search-input');
|
||||||
const tagCloud = document.querySelector('.tag-cloud');
|
const tagCloud = document.querySelector('.tag-cloud');
|
||||||
const tagHeader = document.querySelector('.tag-header');
|
const tagControls = document.querySelector('.tag-controls');
|
||||||
const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper');
|
const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper');
|
||||||
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
||||||
|
|
||||||
if (domainPhaseContainer) domainPhaseContainer.style.display = 'grid';
|
filterSections.forEach(section => section.style.display = 'block');
|
||||||
|
|
||||||
if (searchInput) searchInput.style.display = 'block';
|
if (searchInput) searchInput.style.display = 'block';
|
||||||
if (tagCloud) tagCloud.style.display = 'flex';
|
if (tagCloud) tagCloud.style.display = 'flex';
|
||||||
if (tagHeader) tagHeader.style.display = 'flex';
|
if (tagControls) tagControls.style.display = 'flex';
|
||||||
|
|
||||||
allInputs.forEach(input => input.style.display = 'block');
|
allInputs.forEach(input => input.style.display = 'block');
|
||||||
checkboxWrappers.forEach(wrapper => wrapper.style.display = 'flex');
|
checkboxWrappers.forEach(wrapper => wrapper.style.display = 'flex');
|
||||||
|
@ -1217,62 +1217,275 @@ input[type="checkbox"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
15. FILTER AND PHASE CONTROLS (CONSOLIDATED)
|
15. FILTER AND PHASE CONTROLS (CONSOLIDATED & FIXED)
|
||||||
================================================================= */
|
================================================================= */
|
||||||
|
|
||||||
.domain-phase-container {
|
/* Main Filter Container */
|
||||||
display: grid;
|
.filters-container {
|
||||||
grid-template-columns: 1fr 2fr;
|
max-width: 1200px;
|
||||||
gap: 2rem;
|
margin: 0 auto;
|
||||||
align-items: start;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phase-buttons {
|
/* Filter Sections */
|
||||||
display: flex;
|
.filter-section {
|
||||||
flex-wrap: wrap;
|
margin-bottom: 1rem;
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.phase-button {
|
.filter-card-compact {
|
||||||
padding: 0.5rem 1rem;
|
background-color: var(--color-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem; /* Reduced from 1.5rem */
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-card-compact:hover {
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header-compact {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.75rem; /* Reduced from 1.25rem */
|
||||||
|
padding-bottom: 0.5rem; /* Reduced from 0.75rem */
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header-compact h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem; /* Reduced from 1rem */
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Components */
|
||||||
|
.search-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.75rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 2.5rem 0.75rem 2.5rem;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
background-color: var(--color-bg);
|
background-color: var(--color-bg);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgb(37 99 235 / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-clear {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.75rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-clear:hover {
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filter Grids & Groups */
|
||||||
|
.filter-grid-compact {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 0.75rem; /* Reduced from 1rem */
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select {
|
||||||
|
padding: 0.625rem 0.75rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 0 0 2px rgb(37 99 235 / 10%);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Phase Controls */
|
||||||
|
.phase-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-chip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
min-height: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-chip:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-chip.active {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-filters-compact {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-toggles-compact {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-wrapper:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
color: var(--color-text);
|
||||||
transition: var(--transition-fast);
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.phase-button:hover {
|
/* Tag System */
|
||||||
border-color: var(--color-primary);
|
.tag-section {
|
||||||
background-color: var(--color-bg-secondary);
|
display: flex;
|
||||||
transform: translateY(-1px);
|
flex-direction: column;
|
||||||
box-shadow: var(--shadow-sm);
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phase-button.active {
|
.selected-tags {
|
||||||
background-color: var(--color-primary);
|
display: none;
|
||||||
border-color: var(--color-primary);
|
padding: 1rem;
|
||||||
color: white;
|
background-color: var(--color-bg-secondary);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.phase-button.active:hover {
|
.selected-tags-header {
|
||||||
background-color: var(--color-primary-hover);
|
font-size: 0.875rem;
|
||||||
border-color: var(--color-primary-hover);
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-tags-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-tag {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-tag:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tag Cloud */
|
/* Tag Cloud */
|
||||||
.tag-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-cloud {
|
.tag-cloud {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -1346,26 +1559,105 @@ input[type="checkbox"] {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-tag-toggle {
|
/* Reset Buttons */
|
||||||
padding: 0.25rem 0.75rem;
|
.filter-reset, .filter-reset-all {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-reset:hover {
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border-color: var(--color-warning);
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-reset-all {
|
||||||
|
background-color: var(--color-warning);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-reset-all:hover {
|
||||||
|
background-color: var(--color-warning);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag Controls */
|
||||||
|
.tag-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-toggle {
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-toggle:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-controls-compact {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#results-counter {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View Toggle Buttons */
|
||||||
|
.view-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.375rem;
|
||||||
background-color: var(--color-bg-secondary);
|
background-color: var(--color-bg);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text);
|
||||||
font-size: 0.75rem;
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: var(--transition-fast);
|
transition: var(--transition-fast);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-tag-toggle:hover {
|
.view-toggle:hover {
|
||||||
background-color: var(--color-bg-tertiary);
|
background-color: var(--color-bg-secondary);
|
||||||
color: var(--color-text);
|
border-color: var(--color-primary);
|
||||||
transform: translateY(-1px);
|
text-decoration: none;
|
||||||
}
|
|
||||||
|
|
||||||
/* View Toggle */
|
|
||||||
.view-toggle {
|
|
||||||
transition: var(--transition-fast);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-toggle.active {
|
.view-toggle.active {
|
||||||
@ -1374,6 +1666,29 @@ input[type="checkbox"] {
|
|||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.view-toggle.active:hover {
|
||||||
|
background-color: var(--color-primary-hover);
|
||||||
|
border-color: var(--color-primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ai-view-toggle {
|
||||||
|
background-color: var(--color-accent) !important;
|
||||||
|
color: white !important;
|
||||||
|
border-color: var(--color-accent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ai-view-toggle:hover {
|
||||||
|
background-color: var(--color-accent-hover) !important;
|
||||||
|
border-color: var(--color-accent-hover) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ai-view-toggle.active {
|
||||||
|
background-color: var(--color-accent) !important;
|
||||||
|
border-color: var(--color-accent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
16. AI INTERFACE (CONSOLIDATED)
|
16. AI INTERFACE (CONSOLIDATED)
|
||||||
================================================================= */
|
================================================================= */
|
||||||
@ -2755,24 +3070,22 @@ footer {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phase-buttons {
|
.phase-grid {
|
||||||
gap: 0.375rem;
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phase-button {
|
.phase-chip {
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.625rem 0.75rem;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
|
min-height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-cloud {
|
.tag-cloud {
|
||||||
max-height: 100px;
|
max-height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-header {
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.matrix-table {
|
.matrix-table {
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
}
|
}
|
||||||
@ -2899,19 +3212,58 @@ footer {
|
|||||||
.grid-4 {
|
.grid-4 {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filters-container {
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-grid-compact {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-card-compact {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header-compact {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding-bottom: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header-compact h3 {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-filters-compact {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-toggles-compact {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-wrapper {
|
||||||
|
padding: 0.625rem 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-controls-compact {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 640px) {
|
@media (width <= 640px) {
|
||||||
.phase-buttons {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phase-button {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-cloud {
|
.tag-cloud {
|
||||||
max-height: 80px;
|
max-height: 80px;
|
||||||
}
|
}
|
||||||
@ -2970,15 +3322,6 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 480px) {
|
@media (width <= 480px) {
|
||||||
.phase-buttons {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phase-button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phase-tools {
|
.phase-tools {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@ -3030,6 +3373,28 @@ footer {
|
|||||||
.approach-selector {
|
.approach-selector {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-controls {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-chip {
|
||||||
|
padding: 0.75rem;
|
||||||
|
min-height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-grid-compact {
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-card-compact {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user