simplify knowledgebase articles
This commit is contained in:
		
							parent
							
								
									a9c15eb9c6
								
							
						
					
					
						commit
						4cc28bceb7
					
				
							
								
								
									
										6
									
								
								.astro/content.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.astro/content.d.ts
									
									
									
									
										vendored
									
									
								
							@ -164,11 +164,9 @@ declare module 'astro:content' {
 | 
			
		||||
	type DataEntryMap = {
 | 
			
		||||
		"knowledgebase": Record<string, {
 | 
			
		||||
  id: string;
 | 
			
		||||
  render(): Render[".md"];
 | 
			
		||||
  slug: string;
 | 
			
		||||
  body: string;
 | 
			
		||||
  body?: string;
 | 
			
		||||
  collection: "knowledgebase";
 | 
			
		||||
  data: InferEntrySchema<"knowledgebase">;
 | 
			
		||||
  data: any;
 | 
			
		||||
  rendered?: RenderedContent;
 | 
			
		||||
  filePath?: string;
 | 
			
		||||
}>;
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
	"_variables": {
 | 
			
		||||
		"lastUpdateCheck": 1752478949435
 | 
			
		||||
		"lastUpdateCheck": 1753528124767
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -81,3 +81,6 @@ src/_data/config.local.yaml
 | 
			
		||||
tmp/
 | 
			
		||||
temp/
 | 
			
		||||
.astro/data-store.json
 | 
			
		||||
.astro/settings.json
 | 
			
		||||
.astro/data-store.json
 | 
			
		||||
.astro/content.d.ts
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								README.md
									
									
									
									
									
								
							@ -37,7 +37,7 @@ Ein kuratiertes Verzeichnis für Digital Forensics und Incident Response (DFIR)
 | 
			
		||||
 | 
			
		||||
## 🛠 Technische Grundlage
 | 
			
		||||
 | 
			
		||||
- **Framework:** Astro 5.x mit TypeScript
 | 
			
		||||
- **Framework:** Astro 4.x mit TypeScript
 | 
			
		||||
- **Styling:** CSS Custom Properties mit Dark/Light Mode
 | 
			
		||||
- **API:** Node.js Backend mit Astro API Routes
 | 
			
		||||
- **Datenbank:** YAML-basierte Konfiguration (tools.yaml)
 | 
			
		||||
@ -117,8 +117,8 @@ sudo systemctl enable nginx
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Klonen des Repositorys
 | 
			
		||||
sudo git clone https://git.cc24.dev/mstoeck3/cc24-hub /var/www/cc24-hub
 | 
			
		||||
cd /var/www/cc24-hub
 | 
			
		||||
sudo git clone https://git.cc24.dev/mstoeck3/cc24-hub /opt/cc24-hub
 | 
			
		||||
cd /opt/cc24-hub
 | 
			
		||||
 | 
			
		||||
# Abhängigkeiten installieren
 | 
			
		||||
sudo npm install
 | 
			
		||||
@ -127,12 +127,12 @@ sudo npm install
 | 
			
		||||
sudo npm run build
 | 
			
		||||
 | 
			
		||||
# Berechtigungen setzen
 | 
			
		||||
sudo chown -R www-data:www-data /var/www/cc24-hub
 | 
			
		||||
sudo chown -R www-data:www-data /opt/cc24-hub
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3. Umgebungsvariablen konfigurieren
 | 
			
		||||
 | 
			
		||||
Erstelle `/var/www/cc24-hub/.env`:
 | 
			
		||||
Erstelle `/opt/cc24-hub/.env`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# === GRUNDKONFIGURATION ===
 | 
			
		||||
@ -166,13 +166,13 @@ GIT_API_TOKEN=your_git_api_token
 | 
			
		||||
GIT_REPO_URL=https://ihr-git-server.de/user/cc24-hub
 | 
			
		||||
 | 
			
		||||
# === UPLOAD-KONFIGURATION ===
 | 
			
		||||
LOCAL_UPLOAD_PATH=/var/www/cc24-hub/public/uploads
 | 
			
		||||
LOCAL_UPLOAD_PATH=/opt/cc24-hub/public/uploads
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Berechtigungen sichern
 | 
			
		||||
sudo chmod 600 /var/www/cc24-hub/.env
 | 
			
		||||
sudo chown www-data:www-data /var/www/cc24-hub/.env
 | 
			
		||||
sudo chmod 600 /opt/cc24-hub/.env
 | 
			
		||||
sudo chown www-data:www-data /opt/cc24-hub/.env
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4. Nginx konfigurieren
 | 
			
		||||
@ -205,7 +205,7 @@ server {
 | 
			
		||||
    # Static Files
 | 
			
		||||
    location / {
 | 
			
		||||
        try_files $uri $uri/ @nodejs;
 | 
			
		||||
        root /var/www/cc24-hub/dist;
 | 
			
		||||
        root /opt/cc24-hub/dist;
 | 
			
		||||
        index index.html;
 | 
			
		||||
        
 | 
			
		||||
        # Cache static assets
 | 
			
		||||
@ -256,7 +256,7 @@ Wants=nginx.service
 | 
			
		||||
Type=exec
 | 
			
		||||
User=www-data
 | 
			
		||||
Group=www-data
 | 
			
		||||
WorkingDirectory=/var/www/cc24-hub
 | 
			
		||||
WorkingDirectory=/opt/cc24-hub
 | 
			
		||||
Environment=NODE_ENV=production
 | 
			
		||||
ExecStart=/usr/bin/node ./dist/server/entry.mjs
 | 
			
		||||
Restart=always
 | 
			
		||||
@ -269,7 +269,7 @@ NoNewPrivileges=yes
 | 
			
		||||
PrivateTmp=yes
 | 
			
		||||
ProtectSystem=strict
 | 
			
		||||
ProtectHome=yes
 | 
			
		||||
ReadWritePaths=/var/www/cc24-hub
 | 
			
		||||
ReadWritePaths=/opt/cc24-hub
 | 
			
		||||
CapabilityBoundingSet=
 | 
			
		||||
 | 
			
		||||
# Resource Limits
 | 
			
		||||
@ -431,7 +431,7 @@ domain-agnostic-software:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Repository aktualisieren
 | 
			
		||||
cd /var/www/cc24-hub
 | 
			
		||||
cd /opt/cc24-hub
 | 
			
		||||
sudo git pull
 | 
			
		||||
 | 
			
		||||
# Dependencies aktualisieren
 | 
			
		||||
@ -449,8 +449,8 @@ sudo systemctl restart cc24-hub
 | 
			
		||||
Wichtige Dateien für Backup:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
/var/www/cc24-hub/src/data/tools.yaml
 | 
			
		||||
/var/www/cc24-hub/.env
 | 
			
		||||
/opt/cc24-hub/src/data/tools.yaml
 | 
			
		||||
/opt/cc24-hub/.env
 | 
			
		||||
/etc/nginx/sites-available/cc24-hub
 | 
			
		||||
/etc/systemd/system/cc24-hub.service
 | 
			
		||||
```
 | 
			
		||||
@ -472,4 +472,5 @@ Bei Problemen oder Fragen:
 | 
			
		||||
- **Dokumentation:** Siehe `/knowledgebase` auf der Website
 | 
			
		||||
 | 
			
		||||
## 📄 Lizenz
 | 
			
		||||
Dieses Projekt steht unter der BSD-3-Clause Lizenz.
 | 
			
		||||
 | 
			
		||||
Dieses Projekt steht unter der **BSD-3-Clause** Lizenz.
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -4,26 +4,18 @@ const knowledgebaseCollection = defineCollection({
 | 
			
		||||
  type: 'content',
 | 
			
		||||
  schema: z.object({
 | 
			
		||||
    title: z.string(),
 | 
			
		||||
    tool_name: z.string(),
 | 
			
		||||
    description: z.string(),
 | 
			
		||||
    last_updated: z.date(),
 | 
			
		||||
    author: z.string().default('CC24-Team'),
 | 
			
		||||
    difficulty: z.enum(['novice', 'beginner', 'intermediate', 'advanced', 'expert']),
 | 
			
		||||
    
 | 
			
		||||
    tool_name: z.string().optional(),           
 | 
			
		||||
    related_tools: z.array(z.string()).default([]), 
 | 
			
		||||
    
 | 
			
		||||
    author: z.string().default('Anon'),
 | 
			
		||||
    difficulty: z.enum(['novice', 'beginner', 'intermediate', 'advanced', 'expert']).optional(),
 | 
			
		||||
    categories: z.array(z.string()).default([]),
 | 
			
		||||
    tags: z.array(z.string()).default([]),
 | 
			
		||||
    sections: z.object({
 | 
			
		||||
      overview: z.boolean().default(true),
 | 
			
		||||
      installation: z.boolean().default(false),
 | 
			
		||||
      configuration: z.boolean().default(false),
 | 
			
		||||
      usage_examples: z.boolean().default(true),
 | 
			
		||||
      best_practices: z.boolean().default(true),
 | 
			
		||||
      troubleshooting: z.boolean().default(false),
 | 
			
		||||
      advanced_topics: z.boolean().default(false),
 | 
			
		||||
    }).default({}),
 | 
			
		||||
    review_status: z.enum(['draft', 'review', 'published']).default('published'),
 | 
			
		||||
    
 | 
			
		||||
    published: z.boolean().default(true),
 | 
			
		||||
    
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const collections = {
 | 
			
		||||
  knowledgebase: knowledgebaseCollection,
 | 
			
		||||
};
 | 
			
		||||
});
 | 
			
		||||
@ -3,7 +3,7 @@ title: "Kali Linux - Die Hacker-Distribution für Forensik & Penetration Testing
 | 
			
		||||
tool_name: "Kali Linux"
 | 
			
		||||
description: "Leitfaden zur Installation, Nutzung und Best Practices für Kali Linux – die All-in-One-Plattform für Security-Profis."
 | 
			
		||||
last_updated: 2025-07-20
 | 
			
		||||
author: "CC24-Team"
 | 
			
		||||
author: "Claude 4 Sonnet"
 | 
			
		||||
difficulty: "intermediate"
 | 
			
		||||
categories: ["incident-response", "forensics", "penetration-testing"]
 | 
			
		||||
tags: ["live-boot", "tool-collection", "penetration-testing", "forensics-suite", "virtualization", "arm-support"]
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ title: "MISP - Plattform für Threat Intelligence Sharing"
 | 
			
		||||
tool_name: "MISP"
 | 
			
		||||
description: "Das Rückgrat des modernen Threat-Intelligence-Sharings mit über 40.000 aktiven Instanzen weltweit."
 | 
			
		||||
last_updated: 2025-07-20
 | 
			
		||||
author: "CC24-Team"
 | 
			
		||||
author: "Claude 4 Sonnet"
 | 
			
		||||
difficulty: "intermediate"
 | 
			
		||||
categories: ["incident-response", "static-investigations", "malware-analysis", "network-forensics", "cloud-forensics"]
 | 
			
		||||
tags: ["web-based", "threat-intelligence", "api", "correlation", "ioc-sharing", "automation"]
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ title: "Nextcloud - Sichere Kollaborationsplattform"
 | 
			
		||||
tool_name: "Nextcloud"
 | 
			
		||||
description: "Detaillierte Anleitung und Best Practices für Nextcloud in forensischen Einsatzszenarien"
 | 
			
		||||
last_updated: 2025-07-20
 | 
			
		||||
author: "CC24-Team"
 | 
			
		||||
author: "Claude 4 Sonnet"
 | 
			
		||||
difficulty: "novice"
 | 
			
		||||
categories: ["collaboration-general"]
 | 
			
		||||
tags: ["web-based", "collaboration", "file-sharing", "api", "encryption", "document-management"]
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ title: "Regular Expressions (Regex) – Musterbasierte Textanalyse"
 | 
			
		||||
tool_name: "Regular Expressions (Regex)"
 | 
			
		||||
description: "Pattern matching language für Suche, Extraktion und Manipulation von Text in forensischen Analysen."
 | 
			
		||||
last_updated: 2025-07-20
 | 
			
		||||
author: "CC24-Team"
 | 
			
		||||
author: "Claude 4 Sonnet"
 | 
			
		||||
difficulty: "intermediate"
 | 
			
		||||
categories: ["incident-response", "malware-analysis", "network-forensics", "fraud-investigation"]
 | 
			
		||||
tags: ["pattern-matching", "text-processing", "log-analysis", "string-manipulation", "search-algorithms"]
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ title: "Velociraptor – Skalierbare Endpoint-Forensik mit VQL"
 | 
			
		||||
tool_name: "Velociraptor"
 | 
			
		||||
description: "Detaillierte Anleitung und Best Practices für Velociraptor – Remote-Forensik der nächsten Generation"
 | 
			
		||||
last_updated: 2025-07-20
 | 
			
		||||
author: "CC24-Team"
 | 
			
		||||
author: "Claude 4 Sonnet"
 | 
			
		||||
difficulty: "advanced"
 | 
			
		||||
categories: ["incident-response", "malware-analysis", "network-forensics"]
 | 
			
		||||
tags: ["web-based", "endpoint-monitoring", "artifact-extraction", "scripting", "live-forensics", "hunting"]
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,51 @@
 | 
			
		||||
---
 | 
			
		||||
import BaseLayout from '../layouts/BaseLayout.astro';
 | 
			
		||||
import { getCollection } from 'astro:content';
 | 
			
		||||
import { getToolsData } from '../utils/dataService.js';
 | 
			
		||||
import ContributionButton from '../components/ContributionButton.astro';
 | 
			
		||||
 | 
			
		||||
// Load tools data
 | 
			
		||||
// Load tools data and knowledgebase articles
 | 
			
		||||
const data = await getToolsData();
 | 
			
		||||
const allKnowledgebaseEntries = await getCollection('knowledgebase', (entry) => {
 | 
			
		||||
  // Only include published articles
 | 
			
		||||
  return entry.data.published !== false;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Filter tools that have knowledgebase flag set to true
 | 
			
		||||
const knowledgebaseTools = data.tools.filter((tool: any) => tool.knowledgebase === true);
 | 
			
		||||
// Create unified knowledgebase entries with optional tool association
 | 
			
		||||
const knowledgebaseEntries = allKnowledgebaseEntries.map((entry) => {
 | 
			
		||||
  const associatedTool = entry.data.tool_name 
 | 
			
		||||
    ? data.tools.find((tool: any) => tool.name === entry.data.tool_name)
 | 
			
		||||
    : null;
 | 
			
		||||
  
 | 
			
		||||
  return {
 | 
			
		||||
    // Article metadata
 | 
			
		||||
    slug: entry.slug,
 | 
			
		||||
    title: entry.data.title,
 | 
			
		||||
    description: entry.data.description,
 | 
			
		||||
    author: entry.data.author,
 | 
			
		||||
    last_updated: entry.data.last_updated,
 | 
			
		||||
    difficulty: entry.data.difficulty,
 | 
			
		||||
    categories: entry.data.categories || [],
 | 
			
		||||
    tags: entry.data.tags || [],
 | 
			
		||||
    
 | 
			
		||||
    // Tool association (optional)
 | 
			
		||||
    tool_name: entry.data.tool_name,
 | 
			
		||||
    related_tools: entry.data.related_tools || [],
 | 
			
		||||
    associatedTool,
 | 
			
		||||
    
 | 
			
		||||
    // Derived properties for consistency with existing UI
 | 
			
		||||
    name: entry.data.title, // For search compatibility
 | 
			
		||||
    type: associatedTool?.type || 'article',
 | 
			
		||||
    icon: associatedTool?.icon || '📖',
 | 
			
		||||
    platforms: associatedTool?.platforms || [],
 | 
			
		||||
    skillLevel: entry.data.difficulty || associatedTool?.skillLevel || 'intermediate',
 | 
			
		||||
    phases: associatedTool?.phases || [],
 | 
			
		||||
    license: associatedTool?.license
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Sort alphabetically by name
 | 
			
		||||
knowledgebaseTools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
// Sort alphabetically by title
 | 
			
		||||
knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<BaseLayout title="Knowledgebase" description="Extended documentation and insights for DFIR tools">
 | 
			
		||||
@ -48,16 +83,16 @@ knowledgebaseTools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Tools Count -->
 | 
			
		||||
    <!-- Articles Count -->
 | 
			
		||||
    <div style="text-align: center; margin-bottom: 2rem;">
 | 
			
		||||
      <p class="text-muted" style="font-size: 0.875rem;">
 | 
			
		||||
        <span id="visible-count">{knowledgebaseTools.length}</span> von {knowledgebaseTools.length} Einträgen
 | 
			
		||||
        <span id="visible-count">{knowledgebaseEntries.length}</span> von {knowledgebaseEntries.length} Einträgen
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Knowledgebase Entries -->
 | 
			
		||||
    <div style="max-width: 1000px; margin: 0 auto;">
 | 
			
		||||
      {knowledgebaseTools.length === 0 ? (
 | 
			
		||||
      {knowledgebaseEntries.length === 0 ? (
 | 
			
		||||
        <div class="card" style="text-align: center; padding: 3rem;">
 | 
			
		||||
          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="1.5" style="margin: 0 auto 1rem;">
 | 
			
		||||
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
 | 
			
		||||
@ -68,54 +103,61 @@ knowledgebaseTools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
          </svg>
 | 
			
		||||
          <h3 style="color: var(--color-text-secondary); margin-bottom: 0.5rem;">Noch keine Knowledgebase-Einträge</h3>
 | 
			
		||||
          <p class="text-muted">
 | 
			
		||||
            Knowledgebase-Einträge werden automatisch angezeigt, sobald Datenbankeinträge das Attribut "knowledgebase: true" haben.
 | 
			
		||||
            Knowledgebase-Einträge werden automatisch angezeigt, sobald Artikel veröffentlicht werden.
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <div id="kb-entries">
 | 
			
		||||
          {knowledgebaseTools.map((tool: any, index: number) => {
 | 
			
		||||
            const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
                                      tool.projectUrl !== null && 
 | 
			
		||||
                                      tool.projectUrl !== "" && 
 | 
			
		||||
                                      tool.projectUrl.trim() !== "";
 | 
			
		||||
          {knowledgebaseEntries.map((entry: any, index: number) => {
 | 
			
		||||
            const hasAssociatedTool = !!entry.associatedTool;
 | 
			
		||||
            const hasValidProjectUrl = hasAssociatedTool && 
 | 
			
		||||
                                      entry.associatedTool.projectUrl !== undefined && 
 | 
			
		||||
                                      entry.associatedTool.projectUrl !== null && 
 | 
			
		||||
                                      entry.associatedTool.projectUrl !== "" && 
 | 
			
		||||
                                      entry.associatedTool.projectUrl.trim() !== "";
 | 
			
		||||
            
 | 
			
		||||
            const toolSlug = tool.name.toLowerCase()
 | 
			
		||||
              .replace(/[^a-z0-9\s-]/g, '') // Remove special characters
 | 
			
		||||
              .replace(/\s+/g, '-')         // Replace spaces with hyphens
 | 
			
		||||
              .replace(/-+/g, '-')          // Remove duplicate hyphens
 | 
			
		||||
              .replace(/^-|-$/g, '');       // Remove leading/trailing hyphens
 | 
			
		||||
            const isMethod = hasAssociatedTool && entry.associatedTool.type === 'method';
 | 
			
		||||
            const isConcept = hasAssociatedTool && entry.associatedTool.type === 'concept';
 | 
			
		||||
            const isStandalone = !hasAssociatedTool;
 | 
			
		||||
            
 | 
			
		||||
            return (
 | 
			
		||||
              <article 
 | 
			
		||||
                class="kb-entry card" 
 | 
			
		||||
                id={`kb-${toolSlug}`} 
 | 
			
		||||
                data-tool-name={tool.name.toLowerCase()}
 | 
			
		||||
                id={`kb-${entry.slug}`} 
 | 
			
		||||
                data-tool-name={entry.title.toLowerCase()}
 | 
			
		||||
                data-article-type={isStandalone ? 'standalone' : 'tool-associated'}
 | 
			
		||||
              >
 | 
			
		||||
                <div style="display: flex; justify-content: space-between; align-items: center;">
 | 
			
		||||
                  <div style="display: flex; align-items: center; gap: 1rem;">
 | 
			
		||||
                    <h3 style="margin: 0; color: var(--color-primary);">
 | 
			
		||||
                      {tool.icon && <span style="margin-right: 0.5rem;">{tool.icon}</span>}
 | 
			
		||||
                      {tool.name}
 | 
			
		||||
                      <span style="margin-right: 0.5rem;">{entry.icon}</span>
 | 
			
		||||
                      {entry.title}
 | 
			
		||||
                    </h3>
 | 
			
		||||
                    <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
 | 
			
		||||
                      <!-- Type indicator badges -->
 | 
			
		||||
                      {tool.type === 'concept' && <span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>}
 | 
			
		||||
                      {tool.type === 'method' && <span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>}
 | 
			
		||||
                      {tool.type === 'software' && <span class="badge" style="background-color: var(--color-primary); color: white;">Software</span>}
 | 
			
		||||
                      {isStandalone && <span class="badge" style="background-color: var(--color-accent); color: white;">Artikel</span>}
 | 
			
		||||
                      {isConcept && <span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>}
 | 
			
		||||
                      {isMethod && <span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>}
 | 
			
		||||
                      {hasAssociatedTool && !isMethod && !isConcept && <span class="badge" style="background-color: var(--color-primary); color: white;">Software</span>}
 | 
			
		||||
                      
 | 
			
		||||
                      {hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
 | 
			
		||||
                      {tool.license !== 'Proprietary' && tool.type !== 'concept' && tool.type !== 'method' && <span class="badge badge-success">Open Source</span>}
 | 
			
		||||
                      {hasAssociatedTool && entry.associatedTool.license !== 'Proprietary' && !isMethod && !isConcept && <span class="badge badge-success">Open Source</span>}
 | 
			
		||||
                      
 | 
			
		||||
                      <!-- Difficulty indicator -->
 | 
			
		||||
                      <span class="badge" style="background-color: var(--color-text-secondary); color: white; font-size: 0.75rem;">
 | 
			
		||||
                        {tool.skillLevel || 'intermediate'}
 | 
			
		||||
                      </span>
 | 
			
		||||
                      {entry.difficulty && (
 | 
			
		||||
                        <span class="badge" style="background-color: var(--color-text-secondary); color: white; font-size: 0.75rem;">
 | 
			
		||||
                          {entry.difficulty}
 | 
			
		||||
                        </span>
 | 
			
		||||
                      )}
 | 
			
		||||
                      
 | 
			
		||||
                      <!-- Knowledge Base indicator -->
 | 
			
		||||
                      <span class="badge badge-error">📖</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  
 | 
			
		||||
                  <!-- Action buttons -->
 | 
			
		||||
                  <div style="display: flex; gap: 0.5rem; align-items: center; flex-shrink: 0;">
 | 
			
		||||
                    <a href={`/knowledgebase/${toolSlug}`} class="btn btn-primary" style="font-size: 0.8125rem;">
 | 
			
		||||
                    <a href={`/knowledgebase/${entry.slug}`} class="btn btn-primary" style="font-size: 0.8125rem;">
 | 
			
		||||
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                        <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
                        <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
@ -124,38 +166,58 @@ knowledgebaseTools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
                      Artikel öffnen
 | 
			
		||||
                    </a>
 | 
			
		||||
                    
 | 
			
		||||
                    <!-- NEW: Edit button for existing knowledgebase articles -->
 | 
			
		||||
                    <ContributionButton type="edit" toolName={tool.name} variant="secondary" text="Edit" style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;" />
 | 
			
		||||
                    <!-- Edit button for knowledgebase articles -->
 | 
			
		||||
                    <ContributionButton type="edit" toolName={entry.tool_name || entry.title} variant="secondary" text="Edit" style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;" />
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <!-- Rest of existing article content remains unchanged -->
 | 
			
		||||
                <!-- Description -->
 | 
			
		||||
                <p style="margin: 1rem 0; color: var(--color-text-secondary); line-height: 1.5;">
 | 
			
		||||
                  {tool.description}
 | 
			
		||||
                  {entry.description}
 | 
			
		||||
                </p>
 | 
			
		||||
 | 
			
		||||
                <!-- Tags and Metadata -->
 | 
			
		||||
                <!-- Metadata and Tags -->
 | 
			
		||||
                <div style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; margin-top: 1rem;">
 | 
			
		||||
                  {tool.tags && tool.tags.length > 0 && (
 | 
			
		||||
                  <!-- Tags -->
 | 
			
		||||
                  {entry.tags && entry.tags.length > 0 && (
 | 
			
		||||
                    <div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
 | 
			
		||||
                      {tool.tags.map((tag: string) => (
 | 
			
		||||
                      {entry.tags.map((tag: string) => (
 | 
			
		||||
                        <span class="tag" style="font-size: 0.75rem;">{tag}</span>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
                  
 | 
			
		||||
                  {tool.phases && tool.phases.length > 0 && (
 | 
			
		||||
                  <!-- Categories -->
 | 
			
		||||
                  {entry.categories && entry.categories.length > 0 && (
 | 
			
		||||
                    <div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
 | 
			
		||||
                      <strong>Phasen:</strong> {tool.phases.join(', ')}
 | 
			
		||||
                      <strong>Kategorien:</strong> {entry.categories.join(', ')}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
                  
 | 
			
		||||
                  {tool.platforms && tool.platforms.length > 0 && tool.type !== 'concept' && tool.type !== 'method' && (
 | 
			
		||||
                  <!-- Tool-specific metadata (only if associated with tool) -->
 | 
			
		||||
                  {hasAssociatedTool && entry.phases && entry.phases.length > 0 && (
 | 
			
		||||
                    <div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
 | 
			
		||||
                      <strong>Plattformen:</strong> {tool.platforms.join(', ')}
 | 
			
		||||
                      <strong>Phasen:</strong> {entry.phases.join(', ')}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
                  
 | 
			
		||||
                  {hasAssociatedTool && entry.platforms && entry.platforms.length > 0 && !isMethod && !isConcept && (
 | 
			
		||||
                    <div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
 | 
			
		||||
                      <strong>Plattformen:</strong> {entry.platforms.join(', ')}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
 | 
			
		||||
                  <!-- Related tools for standalone articles -->
 | 
			
		||||
                  {isStandalone && entry.related_tools && entry.related_tools.length > 0 && (
 | 
			
		||||
                    <div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
 | 
			
		||||
                      <strong>Verwandte Tools:</strong> {entry.related_tools.join(', ')}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
 | 
			
		||||
                  <!-- Author and date -->
 | 
			
		||||
                  <div style="font-size: 0.8125rem; color: var(--color-text-secondary); margin-left: auto;">
 | 
			
		||||
                    <strong>Autor:</strong> {entry.author} • <strong>Aktualisiert:</strong> {entry.last_updated.toLocaleDateString('de-DE')}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </article>
 | 
			
		||||
            );
 | 
			
		||||
@ -170,18 +232,19 @@ knowledgebaseTools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
      <p class="text-muted">Versuchen Sie es mit anderen Suchbegriffen.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </section>
 | 
			
		||||
  
 | 
			
		||||
  <!-- Floating Action Button -->
 | 
			
		||||
  <div id="fab-container" style="position: fixed; bottom: 2rem; right: 2rem; z-index: 100; display: none;">
 | 
			
		||||
  <ContributionButton 
 | 
			
		||||
    type="write" 
 | 
			
		||||
    variant="primary" 
 | 
			
		||||
    text="✍️" 
 | 
			
		||||
    style="border-radius: 50%; width: 56px; height: 56px; display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-lg); font-size: 1.5rem; padding: 0;"
 | 
			
		||||
    className="fab-button"
 | 
			
		||||
  />
 | 
			
		||||
</div>
 | 
			
		||||
    <ContributionButton 
 | 
			
		||||
      type="write" 
 | 
			
		||||
      variant="primary" 
 | 
			
		||||
      text="✍️" 
 | 
			
		||||
      style="border-radius: 50%; width: 56px; height: 56px; display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-lg); font-size: 1.5rem; padding: 0;"
 | 
			
		||||
      className="fab-button"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</BaseLayout>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  // Enhanced knowledgebase functionality with search
 | 
			
		||||
  document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
@ -228,5 +291,22 @@ knowledgebaseTools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
        filterEntries(target.value);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Show floating action button on scroll (optional enhancement)
 | 
			
		||||
    let lastScrollY = window.scrollY;
 | 
			
		||||
    const fabContainer = document.getElementById('fab-container');
 | 
			
		||||
    
 | 
			
		||||
    window.addEventListener('scroll', () => {
 | 
			
		||||
      if (fabContainer) {
 | 
			
		||||
        if (window.scrollY > 200 && window.scrollY < lastScrollY) {
 | 
			
		||||
          // Scrolling up and past threshold
 | 
			
		||||
          fabContainer.style.display = 'block';
 | 
			
		||||
        } else {
 | 
			
		||||
          // Scrolling down or at top
 | 
			
		||||
          fabContainer.style.display = 'none';
 | 
			
		||||
        }
 | 
			
		||||
        lastScrollY = window.scrollY;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
@ -26,20 +26,35 @@ const { Content } = await entry.render();
 | 
			
		||||
 | 
			
		||||
// Load tools data to get the tool details
 | 
			
		||||
const data = await getToolsData();
 | 
			
		||||
const tool = data.tools.find((t: any) => t.name === entry.data.tool_name);
 | 
			
		||||
 | 
			
		||||
if (!tool) {
 | 
			
		||||
  console.warn(`Tool not found for knowledgebase entry: ${entry.data.tool_name}`);
 | 
			
		||||
  return Astro.redirect('/knowledgebase');
 | 
			
		||||
// UPGRADED: Handle optional tool association
 | 
			
		||||
const primaryTool = entry.data.tool_name 
 | 
			
		||||
  ? data.tools.find((t: any) => t.name === entry.data.tool_name)
 | 
			
		||||
  : null;
 | 
			
		||||
 | 
			
		||||
// UPGRADED: Handle multiple related tools
 | 
			
		||||
const relatedTools = entry.data.related_tools 
 | 
			
		||||
  ? entry.data.related_tools.map((toolName: string) => 
 | 
			
		||||
      data.tools.find((t: any) => t.name === toolName)
 | 
			
		||||
    ).filter(Boolean)
 | 
			
		||||
  : [];
 | 
			
		||||
 | 
			
		||||
// UPGRADED: Use primary tool or first related tool for styling, fallback to generic
 | 
			
		||||
const displayTool = primaryTool || relatedTools[0];
 | 
			
		||||
 | 
			
		||||
// UPGRADED: Don't redirect - show article even without tool association
 | 
			
		||||
if (!displayTool && !entry.data.tool_name && relatedTools.length === 0) {
 | 
			
		||||
  console.log(`Standalone knowledgebase article: ${entry.slug}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Determine tool type for styling
 | 
			
		||||
const isMethod = tool.type === 'method';
 | 
			
		||||
const isConcept = tool.type === 'concept';
 | 
			
		||||
const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
                          tool.projectUrl !== null && 
 | 
			
		||||
                          tool.projectUrl !== "" && 
 | 
			
		||||
                          tool.projectUrl.trim() !== "";
 | 
			
		||||
// Determine styling based on tool type or fallback to generic
 | 
			
		||||
const isMethod = displayTool?.type === 'method';
 | 
			
		||||
const isConcept = displayTool?.type === 'concept';
 | 
			
		||||
const isStandalone = !displayTool;
 | 
			
		||||
const hasValidProjectUrl = displayTool && displayTool.projectUrl !== undefined && 
 | 
			
		||||
                          displayTool.projectUrl !== null && 
 | 
			
		||||
                          displayTool.projectUrl !== "" && 
 | 
			
		||||
                          displayTool.projectUrl.trim() !== "";
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<BaseLayout title={entry.data.title} description={entry.data.description}>
 | 
			
		||||
@ -49,7 +64,7 @@ const hasValidProjectUrl = tool.projectUrl !== undefined &&
 | 
			
		||||
      <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
 | 
			
		||||
        <div style="flex: 1;">
 | 
			
		||||
          <h1 style="margin: 0 0 0.5rem 0; color: var(--color-primary);">
 | 
			
		||||
            {tool.icon && <span style="margin-right: 0.75rem; font-size: 1.5rem;">{tool.icon}</span>}
 | 
			
		||||
            {displayTool?.icon && <span style="margin-right: 0.75rem; font-size: 1.5rem;">{displayTool.icon}</span>}
 | 
			
		||||
            {entry.data.title}
 | 
			
		||||
          </h1>
 | 
			
		||||
          <p style="margin: 0; color: var(--color-text-secondary); font-size: 1.125rem;">
 | 
			
		||||
@ -58,32 +73,59 @@ const hasValidProjectUrl = tool.projectUrl !== undefined &&
 | 
			
		||||
        </div>
 | 
			
		||||
        <div style="display: flex; flex-direction: column; gap: 0.5rem; align-items: end;">
 | 
			
		||||
          <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
 | 
			
		||||
            {isConcept && <span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>}
 | 
			
		||||
            {isMethod && <span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>}
 | 
			
		||||
            {!isMethod && !isConcept && <span class="badge" style="background-color: var(--color-primary); color: white;">Software</span>}
 | 
			
		||||
            {!isMethod && !isConcept && hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
 | 
			
		||||
            {!isMethod && !isConcept && tool.license !== 'Proprietary' && <span class="badge badge-success">Open Source</span>}
 | 
			
		||||
            <!-- UPGRADED: Conditional badges based on tool type or standalone -->
 | 
			
		||||
            {isStandalone ? (
 | 
			
		||||
              <span class="badge" style="background-color: var(--color-accent); color: white;">Artikel</span>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <>
 | 
			
		||||
                {isConcept && <span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>}
 | 
			
		||||
                {isMethod && <span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>}
 | 
			
		||||
                {!isMethod && !isConcept && !isStandalone && <span class="badge" style="background-color: var(--color-primary); color: white;">Software</span>}
 | 
			
		||||
                {!isMethod && !isConcept && hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
 | 
			
		||||
                {!isMethod && !isConcept && !isStandalone && displayTool?.license !== 'Proprietary' && <span class="badge badge-success">Open Source</span>}
 | 
			
		||||
              </>
 | 
			
		||||
            )}
 | 
			
		||||
            <span class="badge badge-error">📖</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      
 | 
			
		||||
      <!-- Metadata -->
 | 
			
		||||
      <!-- UPGRADED: Flexible metadata section -->
 | 
			
		||||
      <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border);">
 | 
			
		||||
        <div>
 | 
			
		||||
          <strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Schwierigkeit</strong>
 | 
			
		||||
          <p style="margin: 0; font-size: 0.9375rem;">{entry.data.difficulty}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- Difficulty (always shown if present) -->
 | 
			
		||||
        {entry.data.difficulty && (
 | 
			
		||||
          <div>
 | 
			
		||||
            <strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Schwierigkeit</strong>
 | 
			
		||||
            <p style="margin: 0; font-size: 0.9375rem;">{entry.data.difficulty}</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
        
 | 
			
		||||
        <!-- Last Updated (always shown) -->
 | 
			
		||||
        <div>
 | 
			
		||||
          <strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Letztes Update</strong>
 | 
			
		||||
          <p style="margin: 0; font-size: 0.9375rem;">{entry.data.last_updated.toLocaleDateString('de-DE')}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- Author (always shown) -->
 | 
			
		||||
        <div>
 | 
			
		||||
          <strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Autor</strong>
 | 
			
		||||
          <p style="margin: 0; font-size: 0.9375rem;">{entry.data.author}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- UPGRADED: Show article type -->
 | 
			
		||||
        <div>
 | 
			
		||||
          <strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Typ</strong>
 | 
			
		||||
          <p style="margin: 0; font-size: 0.9375rem;">
 | 
			
		||||
            {isStandalone ? 'Allgemeiner Artikel' : 
 | 
			
		||||
             isConcept ? 'Konzept-Artikel' : 
 | 
			
		||||
             isMethod ? 'Methoden-Artikel' : 
 | 
			
		||||
             'Software-Artikel'}
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- UPGRADED: Categories (if present) -->
 | 
			
		||||
        {entry.data.categories && entry.data.categories.length > 0 && (
 | 
			
		||||
          <div>
 | 
			
		||||
          <div style="grid-column: 1 / -1;">
 | 
			
		||||
            <strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Kategorien</strong>
 | 
			
		||||
            <div style="display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.25rem;">
 | 
			
		||||
              {entry.data.categories.map((cat: string) => (
 | 
			
		||||
@ -105,57 +147,107 @@ const hasValidProjectUrl = tool.projectUrl !== undefined &&
 | 
			
		||||
      </a>
 | 
			
		||||
    </nav>
 | 
			
		||||
 | 
			
		||||
  <!-- Content -->
 | 
			
		||||
  <div class="card" style="padding: 2rem;">
 | 
			
		||||
    <div class="kb-content markdown-content" style="line-height: 1.7;">
 | 
			
		||||
      <Content />
 | 
			
		||||
    <!-- Content -->
 | 
			
		||||
    <div class="card" style="padding: 2rem;">
 | 
			
		||||
      <div class="kb-content markdown-content" style="line-height: 1.7;">
 | 
			
		||||
        <Content />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Tool Actions -->
 | 
			
		||||
    <!-- UPGRADED: Flexible Tool Actions Section -->
 | 
			
		||||
    <div class="card" style="margin-top: 2rem; background-color: var(--color-bg-secondary);">
 | 
			
		||||
      <h3 style="margin: 0 0 1rem 0; color: var(--color-text);">Tool-Aktionen</h3>
 | 
			
		||||
      <h3 style="margin: 0 0 1rem 0; color: var(--color-text);">
 | 
			
		||||
        {isStandalone ? 'Verwandte Aktionen' : 'Tool-Aktionen'}
 | 
			
		||||
      </h3>
 | 
			
		||||
      
 | 
			
		||||
      <div style="display: flex; gap: 1rem; flex-wrap: wrap;">
 | 
			
		||||
        {isConcept ? (
 | 
			
		||||
          <a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="background-color: var(--color-concept); border-color: var(--color-concept);">
 | 
			
		||||
        {isStandalone ? (
 | 
			
		||||
          <!-- UPGRADED: Standalone article actions -->
 | 
			
		||||
          <a href="/knowledgebase" class="btn btn-primary">
 | 
			
		||||
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
              <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
              <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
              <line x1="10" y1="14" x2="21" y2="3"/>
 | 
			
		||||
              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
 | 
			
		||||
              <polyline points="14 2 14 8 20 8"/>
 | 
			
		||||
              <line x1="16" y1="13" x2="8" y2="13"/>
 | 
			
		||||
              <line x1="16" y1="17" x2="8" y2="17"/>
 | 
			
		||||
              <polyline points="10 9 9 9 8 9"/>
 | 
			
		||||
            </svg>
 | 
			
		||||
            Mehr erfahren
 | 
			
		||||
          </a>
 | 
			
		||||
        ) : isMethod ? (
 | 
			
		||||
          <a href={tool.projectUrl || tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="background-color: var(--color-method); border-color: var(--color-method);">
 | 
			
		||||
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
              <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
              <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
              <line x1="10" y1="14" x2="21" y2="3"/>
 | 
			
		||||
            </svg>
 | 
			
		||||
            Zur Methode
 | 
			
		||||
            Weitere Artikel
 | 
			
		||||
          </a>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <!-- UPGRADED: Tool-specific actions (existing logic) -->
 | 
			
		||||
          <>
 | 
			
		||||
            <a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
 | 
			
		||||
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
                <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
                <line x1="10" y1="14" x2="21" y2="3"/>
 | 
			
		||||
              </svg>
 | 
			
		||||
              Software-Homepage
 | 
			
		||||
            </a>
 | 
			
		||||
            {hasValidProjectUrl && (
 | 
			
		||||
              <a href={tool.projectUrl} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
 | 
			
		||||
            {isConcept ? (
 | 
			
		||||
              <a href={displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="background-color: var(--color-concept); border-color: var(--color-concept);">
 | 
			
		||||
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                  <circle cx="12" cy="12" r="10"/>
 | 
			
		||||
                  <path d="M12 16l4-4-4-4"/>
 | 
			
		||||
                  <path d="M8 12h8"/>
 | 
			
		||||
                  <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
                  <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
                  <line x1="10" y1="14" x2="21" y2="3"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
                Zugreifen
 | 
			
		||||
                Mehr erfahren
 | 
			
		||||
              </a>
 | 
			
		||||
            ) : isMethod ? (
 | 
			
		||||
              <a href={displayTool.projectUrl || displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="background-color: var(--color-method); border-color: var(--color-method);">
 | 
			
		||||
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                  <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
                  <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
                  <line x1="10" y1="14" x2="21" y2="3"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
                Zur Methode
 | 
			
		||||
              </a>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <>
 | 
			
		||||
                <a href={displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
 | 
			
		||||
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                    <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
 | 
			
		||||
                    <polyline points="15 3 21 3 21 9"/>
 | 
			
		||||
                    <line x1="10" y1="14" x2="21" y2="3"/>
 | 
			
		||||
                  </svg>
 | 
			
		||||
                  Software-Homepage
 | 
			
		||||
                </a>
 | 
			
		||||
                {hasValidProjectUrl && (
 | 
			
		||||
                  <a href={displayTool.projectUrl} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
 | 
			
		||||
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                      <circle cx="12" cy="12" r="10"/>
 | 
			
		||||
                      <path d="M12 16l4-4-4-4"/>
 | 
			
		||||
                      <path d="M8 12h8"/>
 | 
			
		||||
                    </svg>
 | 
			
		||||
                    Zugreifen
 | 
			
		||||
                  </a>
 | 
			
		||||
                )}
 | 
			
		||||
              </>
 | 
			
		||||
            )}
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
        
 | 
			
		||||
        <!-- UPGRADED: Show related tools if present -->
 | 
			
		||||
        {relatedTools.length > 0 && relatedTools.length > (primaryTool ? 1 : 0) && (
 | 
			
		||||
          <div style="margin-left: auto;">
 | 
			
		||||
            <details style="position: relative;">
 | 
			
		||||
              <summary class="btn btn-secondary" style="cursor: pointer; list-style: none;">
 | 
			
		||||
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
                  <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
 | 
			
		||||
                  <circle cx="8.5" cy="7" r="4"/>
 | 
			
		||||
                  <line x1="20" y1="8" x2="20" y2="14"/>
 | 
			
		||||
                  <line x1="23" y1="11" x2="17" y2="11"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
                Verwandte Tools ({relatedTools.length})
 | 
			
		||||
              </summary>
 | 
			
		||||
              <div style="position: absolute; top: 100%; left: 0; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 0.5rem; padding: 1rem; min-width: 200px; z-index: 100; box-shadow: var(--shadow-lg);">
 | 
			
		||||
                {relatedTools.map((tool: any) => (
 | 
			
		||||
                  <a href={tool.projectUrl || tool.url} target="_blank" rel="noopener noreferrer" 
 | 
			
		||||
                     style="display: block; padding: 0.5rem; border-radius: 0.25rem; text-decoration: none; color: var(--color-text); margin-bottom: 0.25rem;"
 | 
			
		||||
                     onmouseover="this.style.backgroundColor='var(--color-bg-secondary)'"
 | 
			
		||||
                     onmouseout="this.style.backgroundColor='transparent'">
 | 
			
		||||
                    {tool.icon && <span style="margin-right: 0.5rem;">{tool.icon}</span>}
 | 
			
		||||
                    {tool.name}
 | 
			
		||||
                  </a>
 | 
			
		||||
                ))}
 | 
			
		||||
              </div>
 | 
			
		||||
            </details>
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
        
 | 
			
		||||
        <!-- Always show return to main page -->
 | 
			
		||||
        <a href="/" class="btn btn-secondary">
 | 
			
		||||
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
            <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
 | 
			
		||||
@ -166,4 +258,4 @@ const hasValidProjectUrl = tool.projectUrl !== undefined &&
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </article>
 | 
			
		||||
</BaseLayout>
 | 
			
		||||
</BaseLayout>
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user