diff --git a/src/components/ToolCard.astro b/src/components/ToolCard.astro index 81d5351..5da266b 100644 --- a/src/components/ToolCard.astro +++ b/src/components/ToolCard.astro @@ -21,8 +21,10 @@ export interface Props { const { tool } = Astro.props; -// Check if this is a method vs software + +// Check types const isMethod = tool.type === 'method'; +const isConcept = tool.type === 'concept'; // Check if tool has a valid project URL (means we're hosting it) const hasValidProjectUrl = tool.projectUrl !== undefined && @@ -34,7 +36,8 @@ const hasValidProjectUrl = tool.projectUrl !== undefined && const hasKnowledgebase = tool.knowledgebase === true; // Determine card styling based on type and hosting status -const cardClass = isMethod ? 'card card-method tool-card' : +const cardClass = isConcept ? 'card card-concept tool-card' : + isMethod ? 'card card-method tool-card' : hasValidProjectUrl ? 'card card-hosted tool-card' : (tool.license !== 'Proprietary' ? 'card card-oss tool-card' : 'card tool-card'); --- @@ -99,9 +102,14 @@ const cardClass = isMethod ? 'card card-method tool-card' : ))} - +
- {isMethod ? ( + {isConcept ? ( + + + Mehr erfahren + + ) : isMethod ? ( Zur Methode diff --git a/src/components/ToolFilters.astro b/src/components/ToolFilters.astro index be04c21..25e653a 100644 --- a/src/components/ToolFilters.astro +++ b/src/components/ToolFilters.astro @@ -342,35 +342,6 @@ const sortedTags = Object.entries(tagFrequency) return true; }); -// Sort filtered results: methods first, then self-hosted, then OSS, proprietary last - filtered.sort((a, b) => { - const aMethod = isMethod(a); - const bMethod = isMethod(b); - const aHosted = isToolHosted(a); - const bHosted = isToolHosted(b); - const aProprietary = !aMethod && a.license === 'Proprietary'; - const bProprietary = !bMethod && b.license === 'Proprietary'; - - // Methods first - //if (aMethod && !bMethod) return -1; - //if (!aMethod && bMethod) return 1; - - // If both are methods or both are tools - if (aMethod === bMethod) { - // Self-hosted tools first (regardless of license) - if (aHosted && !bHosted) return -1; - if (!aHosted && bHosted) return 1; - - // If both have same hosting status, proprietary tools go last - if (aHosted === bHosted) { - if (!aProprietary && bProprietary) return -1; - if (aProprietary && !bProprietary) return 1; - } - } - - return 0; - }); - // Update matrix highlighting updateMatrixHighlighting(); // Emit custom event with filtered results @@ -417,17 +388,7 @@ const sortedTags = Object.entries(tagFrequency) window.dispatchEvent(new CustomEvent('viewChanged', { detail: view })); if (view === 'hosted') { - const hosted = window.toolsData.filter(tool => isToolHosted(tool)); - hosted.sort((a, b) => { - const aProprietary = a.license === 'Proprietary'; - const bProprietary = b.license === 'Proprietary'; - - if (!aProprietary && bProprietary) return -1; - if (aProprietary && !bProprietary) return 1; - - return 0; - }); - + const hosted = window.toolsData.filter(tool => isToolHosted(tool)); window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: hosted })); } else { filterTools(); diff --git a/src/components/ToolMatrix.astro b/src/components/ToolMatrix.astro index e2a7416..9edd5ec 100644 --- a/src/components/ToolMatrix.astro +++ b/src/components/ToolMatrix.astro @@ -24,6 +24,7 @@ domains.forEach((domain: any) => { matrix[domain.id] = {}; phases.forEach((phase: any) => { matrix[domain.id][phase.id] = tools.filter((tool: any) => + tool.type !== 'concept' && // Exclude concepts from matrix tool.domains && tool.domains.includes(domain.id) && tool.phases && tool.phases.includes(phase.id) ); @@ -407,6 +408,11 @@ domains.forEach((domain: any) => { // Re-populate with filtered tools based on domains × phases filtered.forEach(tool => { + // Skip concepts - they don't belong in matrix + if (tool.type === 'concept') { + return; + } + const isMethod = tool.type === 'method'; const hasValidProjectUrl = tool.projectUrl !== undefined && tool.projectUrl !== null && diff --git a/src/data/tools.yaml b/src/data/tools.yaml index 7a68d42..8838e17 100644 --- a/src/data/tools.yaml +++ b/src/data/tools.yaml @@ -1904,6 +1904,95 @@ tools: - jamf - enterprise - commandline + - name: Regular Expressions (Regex) + icon: 🔤 + type: concept + description: >- + Pattern matching language for searching, extracting, and manipulating text. + Essential for log analysis, malware signature creation, and data extraction from + unstructured sources. Forms the backbone of many forensic tools and custom scripts. + domains: + - incident-response + - malware-analysis + - network-forensics + - fraud-investigation + phases: + - examination + - analysis + platforms: [] + domain-agnostic-software: null + skillLevel: intermediate + accessType: null + url: https://regexr.com/ + projectUrl: null + license: null + knowledgebase: false + tags: + - pattern-matching + - text-processing + - log-analysis + - string-manipulation + - search-algorithms + + - name: SQL Query Fundamentals + icon: 🗃️ + type: concept + description: >- + Structured Query Language for database interrogation and analysis. Critical for + examining application databases, SQLite artifacts from mobile devices, and + browser history databases. Enables complex correlation and filtering of large datasets. + domains: + - incident-response + - mobile-forensics + - fraud-investigation + - cloud-forensics + phases: + - examination + - analysis + platforms: [] + domain-agnostic-software: null + skillLevel: intermediate + accessType: null + url: https://www.w3schools.com/sql/ + projectUrl: null + license: null + knowledgebase: false + tags: + - database-analysis + - query-language + - data-correlation + - mobile-artifacts + - browser-forensics + + - name: Hash Functions & Digital Signatures + icon: 🔐 + type: concept + description: >- + Cryptographic principles for data integrity verification and authentication. + Fundamental for evidence preservation, malware identification, and establishing + chain of custody. Understanding of MD5, SHA, and digital signature validation. + domains: + - incident-response + - law-enforcement + - malware-analysis + - cloud-forensics + phases: + - data-collection + - examination + platforms: [] + domain-agnostic-software: null + skillLevel: advanced + accessType: null + url: https://en.wikipedia.org/wiki/Cryptographic_hash_function + projectUrl: null + license: null + knowledgebase: false + tags: + - cryptography + - data-integrity + - evidence-preservation + - malware-identification + - chain-of-custody domains: - id: incident-response name: Incident Response & Breach-Untersuchung diff --git a/src/pages/index.astro b/src/pages/index.astro index 176fc3a..f16970f 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -112,6 +112,29 @@ const tools = data.tools; // Initial tools HTML const initialToolsHTML = toolsContainer.innerHTML; + // Simple sorting function - no external imports needed + function sortTools(tools, sortBy = 'default') { + const sorted = [...tools]; // Don't mutate original array + + switch (sortBy) { + case 'alphabetical': + return sorted.sort((a, b) => a.name.localeCompare(b.name)); + case 'difficulty': + const difficultyOrder = { 'novice': 0, 'beginner': 1, 'intermediate': 2, 'advanced': 3, 'expert': 4 }; + return sorted.sort((a, b) => + (difficultyOrder[a.skillLevel] || 999) - (difficultyOrder[b.skillLevel] || 999) + ); + case 'type': + const typeOrder = { 'concept': 0, 'method': 1, 'software': 2 }; + return sorted.sort((a, b) => + (typeOrder[a.type] || 999) - (typeOrder[b.type] || 999) + ); + case 'default': + default: + return sorted; // No sorting - embrace the entropy + } + } + // Authentication check function async function checkAuthentication() { try { @@ -177,7 +200,7 @@ const tools = data.tools; // Hide filter controls in AI mode - AGGRESSIVE APPROACH const domainPhaseContainer = document.querySelector('.domain-phase-container') as HTMLElement; - const searchInput = document.getElementById('search-tools') as HTMLElement; + const searchInput = document.getElementById('search-input') as HTMLElement; const tagCloud = document.querySelector('.tag-cloud') as HTMLElement; // Hide all checkbox wrappers const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper'); @@ -227,7 +250,7 @@ const tools = data.tools; // Show filter controls in matrix mode const domainPhaseContainerMatrix = document.querySelector('.domain-phase-container') as HTMLElement; - const searchInputMatrix = document.getElementById('search-tools') as HTMLElement; + const searchInputMatrix = document.getElementById('search-input') as HTMLElement; const tagCloudMatrix = document.querySelector('.tag-cloud') as HTMLElement; const checkboxWrappersMatrix = document.querySelectorAll('.checkbox-wrapper'); const tagHeaderMatrix = document.querySelector('.tag-header') as HTMLElement; @@ -262,7 +285,7 @@ const tools = data.tools; // Show filter controls in grid mode const domainPhaseContainerGrid = document.querySelector('.domain-phase-container') as HTMLElement; - const searchInputGrid = document.getElementById('search-tools') as HTMLElement; + const searchInputGrid = document.getElementById('search-input') as HTMLElement; const tagCloudGrid = document.querySelector('.tag-cloud') as HTMLElement; const checkboxWrappersGrid = document.querySelectorAll('.checkbox-wrapper'); const tagHeaderGrid = document.querySelector('.tag-header') as HTMLElement; @@ -318,8 +341,12 @@ const tools = data.tools; } else { noResults.style.display = 'none'; - // Render filtered tools - filtered.forEach((tool: any) => { + // Apply sorting here - single place for all sorting logic + const currentSortOption = 'default'; // Will be dynamic later + const sortedTools = sortTools(filtered, currentSortOption); + + // Render sorted tools + sortedTools.forEach((tool: any) => { const toolCard = createToolCard(tool); toolsContainer.appendChild(toolCard); }); @@ -338,6 +365,7 @@ const tools = data.tools; function createToolCard(tool) { const isMethod = tool.type === 'method'; + const isConcept = tool.type === 'concept'; const hasValidProjectUrl = tool.projectUrl !== undefined && tool.projectUrl !== null && tool.projectUrl !== "" && @@ -346,7 +374,8 @@ function createToolCard(tool) { const hasKnowledgebase = tool.knowledgebase === true; const cardDiv = document.createElement('div'); - const cardClass = isMethod ? 'card card-method tool-card' : + const cardClass = isConcept ? 'card card-concept tool-card' : + isMethod ? 'card card-method tool-card' : hasValidProjectUrl ? 'card card-hosted tool-card' : (tool.license !== 'Proprietary' ? 'card card-oss tool-card' : 'card tool-card'); cardDiv.className = cardClass; @@ -358,7 +387,7 @@ function createToolCard(tool) {

${tool.icon ? `${tool.icon}` : ''}${tool.name}

- ${!isMethod && hasValidProjectUrl ? 'CC24-Server' : ''} + ${!isMethod && !isConcept && hasValidProjectUrl ? 'CC24-Server' : ''} ${hasKnowledgebase ? '📖' : ''}
@@ -395,7 +424,7 @@ function createToolCard(tool) { - ${isMethod ? 'Methode' : tool.license === 'Proprietary' ? 'Prop.' : tool.license?.split(' ')[0] || 'N/A'} + ${isConcept ? 'Konzept' : isMethod ? 'Methode' : tool.license === 'Proprietary' ? 'Prop.' : tool.license?.split(' ')[0] || 'N/A'}
@@ -405,7 +434,11 @@ function createToolCard(tool) {
- ${isMethod ? ` + ${isConcept ? ` + + Mehr erfahren + + ` : isMethod ? ` Zur Methode diff --git a/src/styles/global.css b/src/styles/global.css index ede704f..99034e0 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -11,23 +11,28 @@ :root { /* Light Theme Colors */ --color-bg: #fff; - --color-bg-secondary: #f5f5f5; - --color-bg-tertiary: #e0e0e0; - --color-text: #1a1a1a; - --color-text-secondary: #666; - --color-border: #d0d0d0; + --color-bg-secondary: #f8fafc; + --color-bg-tertiary: #e2e8f0; + --color-text: #1e293b; + --color-text-secondary: #64748b; + --color-border: #cbd5e1; --color-primary: #2563eb; --color-primary-hover: #1d4ed8; - --color-accent: #10b981; - --color-accent-hover: #059669; - --color-warning: #f59e0b; - --color-error: #ef4444; - --color-hosted: #8b5cf6; + --color-accent: #059669; + --color-accent-hover: #047857; + --color-warning: #d97706; + --color-error: #dc2626; + + /* Enhanced card type colors */ + --color-hosted: #7c3aed; /* More vibrant purple */ --color-hosted-bg: #f3f0ff; - --color-oss: #10b981; + --color-oss: #059669; /* Deeper green for better contrast */ --color-oss-bg: #ecfdf5; - --color-method: #3a90ed; - --color-method-bg: #f3f4f6; + --color-method: #0891b2; /* Distinct teal-cyan for methods */ + --color-method-bg: #f0f9ff; /* Fixed: proper light background */ + --color-concept: #ea580c; /* Warmer orange-red for concepts */ + --color-concept-bg: #fff7ed; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 5%); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 10%); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 10%); @@ -39,24 +44,29 @@ [data-theme="dark"] { /* Dark Theme Colors */ - --color-bg: #0f0f0f; - --color-bg-secondary: #1a1a1a; - --color-bg-tertiary: #262626; - --color-text: #e5e5e5; - --color-text-secondary: #a3a3a3; - --color-border: #404040; + --color-bg: #0f172a; + --color-bg-secondary: #1e293b; + --color-bg-tertiary: #334155; + --color-text: #f1f5f9; + --color-text-secondary: #94a3b8; + --color-border: #475569; --color-primary: #3b82f6; --color-primary-hover: #60a5fa; - --color-accent: #34d399; - --color-accent-hover: #6ee7b7; - --color-warning: #fbbf24; + --color-accent: #10b981; + --color-accent-hover: #34d399; + --color-warning: #f59e0b; --color-error: #f87171; - --color-hosted: #a78bfa; + + /* Enhanced dark card type colors */ + --color-hosted: #a855f7; /* Brighter purple for dark mode */ --color-hosted-bg: #2e1065; - --color-oss: #34d399; + --color-oss: #10b981; /* Vibrant green */ --color-oss-bg: #064e3b; - --color-method: #8bbdfa; - --color-method-bg: #2e4e81; + --color-method: #0891b2; /* Distinct teal-cyan */ + --color-method-bg: #164e63; /* Proper dark background */ + --color-concept: #f97316; /* Bright orange for dark mode */ + --color-concept-bg: #7c2d12; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 30%); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 40%); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 50%); @@ -313,6 +323,11 @@ input[type="checkbox"] { border-color: var(--color-method); } +.card-concept { + background-color: var(--color-concept-bg); + border-color: var(--color-concept); +} + .grid-auto-fit { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 350px)); diff --git a/src/utils/dataService.ts b/src/utils/dataService.ts index 707f276..f5dabb1 100644 --- a/src/utils/dataService.ts +++ b/src/utils/dataService.ts @@ -6,7 +6,7 @@ import { z } from 'zod'; const ToolSchema = z.object({ name: z.string(), icon: z.string().optional().nullable(), - type: z.string(), + type: z.enum(['software', 'method', 'concept']), // Make this more explicit description: z.string(), domains: z.array(z.string()).optional().nullable().default([]), phases: z.array(z.string()).optional().nullable().default([]), @@ -127,15 +127,18 @@ export async function getToolsData(): Promise { return cachedRandomizedData; } -// Get compressed data for AI (removes projectUrl and statusUrl) +// Get compressed data for AI (removes projectUrl and statusUrl, excludes concepts) export async function getCompressedToolsDataForAI(): Promise { if (!cachedCompressedData) { const data = await getToolsData(); - const compressedTools = data.tools.map(tool => { - const { projectUrl, statusUrl, ...compressedTool } = tool; - return compressedTool; - }); + // Filter out concepts and compress remaining tools + const compressedTools = data.tools + .filter(tool => tool.type !== 'concept') // Exclude concepts from AI + .map(tool => { + const { projectUrl, statusUrl, ...compressedTool } = tool; + return compressedTool; + }); cachedCompressedData = { tools: compressedTools,