adjust dual urls
This commit is contained in:
		
							parent
							
								
									b8183ec961
								
							
						
					
					
						commit
						b842df040c
					
				@ -9,6 +9,7 @@ export interface Props {
 | 
				
			|||||||
    skillLevel: string;
 | 
					    skillLevel: string;
 | 
				
			||||||
    accessType: string;
 | 
					    accessType: string;
 | 
				
			||||||
    url: string;
 | 
					    url: string;
 | 
				
			||||||
 | 
					    projectUrl?: string;
 | 
				
			||||||
    license: string;
 | 
					    license: string;
 | 
				
			||||||
    tags: string[];
 | 
					    tags: string[];
 | 
				
			||||||
    isHosted: boolean;
 | 
					    isHosted: boolean;
 | 
				
			||||||
@ -20,6 +21,12 @@ const { tool } = Astro.props;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Determine card styling
 | 
					// Determine card styling
 | 
				
			||||||
const cardClass = tool.isHosted ? 'card card-hosted' : (tool.license !== 'Proprietary' ? 'card card-oss' : 'card');
 | 
					const cardClass = tool.isHosted ? 'card card-hosted' : (tool.license !== 'Proprietary' ? 'card card-oss' : 'card');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Check if tool has a valid project URL for hosted services
 | 
				
			||||||
 | 
					const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
				
			||||||
 | 
					                          tool.projectUrl !== null && 
 | 
				
			||||||
 | 
					                          tool.projectUrl !== "" && 
 | 
				
			||||||
 | 
					                          tool.projectUrl.trim() !== "";
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class={cardClass}>
 | 
					<div class={cardClass}>
 | 
				
			||||||
@ -74,7 +81,26 @@ const cardClass = tool.isHosted ? 'card card-hosted' : (tool.license !== 'Propri
 | 
				
			|||||||
    ))}
 | 
					    ))}
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  <a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
					  <!-- Button section - different layouts for hosted vs non-hosted -->
 | 
				
			||||||
    {tool.isHosted ? 'Access Service' : 'Visit Website'}
 | 
					  {tool.isHosted && hasValidProjectUrl ? (
 | 
				
			||||||
  </a>
 | 
					    <!-- Two buttons for self-hosted tools with both URLs -->
 | 
				
			||||||
 | 
					    <div style="display: flex; gap: 0.5rem;">
 | 
				
			||||||
 | 
					      <a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
 | 
				
			||||||
 | 
					        Project Page
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					      <a href={tool.projectUrl} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="flex: 1;">
 | 
				
			||||||
 | 
					        Access Service
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  ) : tool.isHosted ? (
 | 
				
			||||||
 | 
					    <!-- Single button for self-hosted tools with only project URL -->
 | 
				
			||||||
 | 
					    <a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
				
			||||||
 | 
					      Project Page
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					  ) : (
 | 
				
			||||||
 | 
					    <!-- Single button for non-hosted tools -->
 | 
				
			||||||
 | 
					    <a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
				
			||||||
 | 
					      Visit Website
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					  )}
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
@ -12,12 +12,21 @@ const domains = data.domains;
 | 
				
			|||||||
const phases = data.phases;
 | 
					const phases = data.phases;
 | 
				
			||||||
const tools = data.tools;
 | 
					const tools = data.tools;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Create matrix structure
 | 
					// Separate collaboration tools from domain-specific tools
 | 
				
			||||||
 | 
					const collaborationTools = tools.filter((tool: any) => 
 | 
				
			||||||
 | 
					  tool.phases.includes('collaboration')
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const domainTools = tools.filter((tool: any) => 
 | 
				
			||||||
 | 
					  !tool.phases.includes('collaboration')
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create matrix structure for domain-specific tools only
 | 
				
			||||||
const matrix: Record<string, Record<string, any[]>> = {};
 | 
					const matrix: Record<string, Record<string, any[]>> = {};
 | 
				
			||||||
domains.forEach((domain: any) => {
 | 
					domains.forEach((domain: any) => {
 | 
				
			||||||
  matrix[domain.id] = {};
 | 
					  matrix[domain.id] = {};
 | 
				
			||||||
  phases.forEach((phase: any) => {
 | 
					  phases.filter((phase: any) => phase.id !== 'collaboration').forEach((phase: any) => {
 | 
				
			||||||
    matrix[domain.id][phase.id] = tools.filter((tool: any) => 
 | 
					    matrix[domain.id][phase.id] = domainTools.filter((tool: any) => 
 | 
				
			||||||
      tool.domains.includes(domain.id) && tool.phases.includes(phase.id)
 | 
					      tool.domains.includes(domain.id) && tool.phases.includes(phase.id)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@ -25,36 +34,67 @@ domains.forEach((domain: any) => {
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div id="matrix-container" class="matrix-wrapper" style="display: none;">
 | 
					<div id="matrix-container" class="matrix-wrapper" style="display: none;">
 | 
				
			||||||
  <table class="matrix-table">
 | 
					  <!-- Collaboration Tools Section (compact horizontal layout for matrix view) -->
 | 
				
			||||||
    <thead>
 | 
					  <div id="collaboration-tools-section" style="margin-bottom: 1.5rem;">
 | 
				
			||||||
      <tr>
 | 
					    <h3 style="margin-bottom: 0.75rem; color: var(--color-text); font-size: 1.125rem;">General Tools for Collaboration</h3>
 | 
				
			||||||
        <th style="width: 200px;">Domain / Phase</th>
 | 
					    <div class="collaboration-tools-compact" id="collaboration-tools-container">
 | 
				
			||||||
        {phases.map((phase: any) => (
 | 
					      {collaborationTools.map((tool: any) => (
 | 
				
			||||||
          <th>{phase.name}</th>
 | 
					        <div class={`collaboration-tool-compact ${tool.isHosted ? 'hosted' : tool.license !== 'Proprietary' ? 'oss' : ''}`}
 | 
				
			||||||
        ))}
 | 
					             onclick={`window.showToolDetails('${tool.name}')`}>
 | 
				
			||||||
      </tr>
 | 
					          <div class="tool-compact-header">
 | 
				
			||||||
    </thead>
 | 
					            <h4 style="margin: 0; font-size: 0.875rem; font-weight: 600;">{tool.name}</h4>
 | 
				
			||||||
    <tbody>
 | 
					            <div style="display: flex; gap: 0.25rem;">
 | 
				
			||||||
      {domains.map((domain: any) => (
 | 
					              {tool.isHosted && <span class="badge-mini badge-primary">Self-Hosted</span>}
 | 
				
			||||||
 | 
					              {tool.license !== 'Proprietary' && <span class="badge-mini badge-success">OSS</span>}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <p style="font-size: 0.75rem; color: var(--color-text-secondary); margin: 0.25rem 0; line-height: 1.3;">
 | 
				
			||||||
 | 
					            {tool.description}
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					          <div style="display: flex; gap: 0.75rem; font-size: 0.6875rem; color: var(--color-text-secondary);">
 | 
				
			||||||
 | 
					            <span>{tool.platforms.join(', ')}</span>
 | 
				
			||||||
 | 
					            <span>•</span>
 | 
				
			||||||
 | 
					            <span>{tool.skillLevel}</span>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      ))}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <!-- DFIR Tools Matrix -->
 | 
				
			||||||
 | 
					  <div id="dfir-matrix-section">
 | 
				
			||||||
 | 
					    <h2 style="margin-bottom: 1rem; color: var(--color-text);">DFIR Tools Matrix</h2>
 | 
				
			||||||
 | 
					    <table class="matrix-table">
 | 
				
			||||||
 | 
					      <thead>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
          <th>{domain.name}</th>
 | 
					          <th style="width: 200px;">Domain / Phase</th>
 | 
				
			||||||
          {phases.map((phase: any) => (
 | 
					          {phases.filter((phase: any) => phase.id !== 'collaboration').map((phase: any) => (
 | 
				
			||||||
            <td class="matrix-cell" data-domain={domain.id} data-phase={phase.id}>
 | 
					            <th>{phase.name}</th>
 | 
				
			||||||
              {matrix[domain.id][phase.id].map((tool: any) => (
 | 
					 | 
				
			||||||
                <span 
 | 
					 | 
				
			||||||
                  class={`tool-chip ${tool.isHosted ? 'tool-chip-hosted' : tool.license !== 'Proprietary' ? 'tool-chip-oss' : ''}`}
 | 
					 | 
				
			||||||
                  data-tool-name={tool.name}
 | 
					 | 
				
			||||||
                  onclick={`window.showToolDetails('${tool.name}')`}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  {tool.name}
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
              ))}
 | 
					 | 
				
			||||||
            </td>
 | 
					 | 
				
			||||||
          ))}
 | 
					          ))}
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
      ))}
 | 
					      </thead>
 | 
				
			||||||
    </tbody>
 | 
					      <tbody>
 | 
				
			||||||
  </table>
 | 
					        {domains.map((domain: any) => (
 | 
				
			||||||
 | 
					          <tr>
 | 
				
			||||||
 | 
					            <th>{domain.name}</th>
 | 
				
			||||||
 | 
					            {phases.filter((phase: any) => phase.id !== 'collaboration').map((phase: any) => (
 | 
				
			||||||
 | 
					              <td class="matrix-cell" data-domain={domain.id} data-phase={phase.id}>
 | 
				
			||||||
 | 
					                {matrix[domain.id][phase.id].map((tool: any) => (
 | 
				
			||||||
 | 
					                  <span 
 | 
				
			||||||
 | 
					                    class={`tool-chip ${tool.isHosted ? 'tool-chip-hosted' : tool.license !== 'Proprietary' ? 'tool-chip-oss' : ''}`}
 | 
				
			||||||
 | 
					                    data-tool-name={tool.name}
 | 
				
			||||||
 | 
					                    onclick={`window.showToolDetails('${tool.name}')`}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    {tool.name}
 | 
				
			||||||
 | 
					                  </span>
 | 
				
			||||||
 | 
					                ))}
 | 
				
			||||||
 | 
					              </td>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					        ))}
 | 
				
			||||||
 | 
					      </tbody>
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- Tool Details Modal -->
 | 
					<!-- Tool Details Modal -->
 | 
				
			||||||
@ -78,12 +118,11 @@ domains.forEach((domain: any) => {
 | 
				
			|||||||
  
 | 
					  
 | 
				
			||||||
  <div id="tool-tags" style="margin-bottom: 1rem;"></div>
 | 
					  <div id="tool-tags" style="margin-bottom: 1rem;"></div>
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  <a id="tool-link" href="#" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
					  <!-- Updated button container for dual buttons -->
 | 
				
			||||||
    Visit Website
 | 
					  <div id="tool-links" style="display: flex; gap: 0.5rem;"></div>
 | 
				
			||||||
  </a>
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script define:vars={{ toolsData: tools }}>
 | 
					<script define:vars={{ toolsData: tools, collaborationTools, domainTools }}>
 | 
				
			||||||
  // Tool details functions
 | 
					  // Tool details functions
 | 
				
			||||||
  window.showToolDetails = function(toolName) {
 | 
					  window.showToolDetails = function(toolName) {
 | 
				
			||||||
    const tool = toolsData.find(t => t.name === toolName);
 | 
					    const tool = toolsData.find(t => t.name === toolName);
 | 
				
			||||||
@ -105,14 +144,16 @@ domains.forEach((domain: any) => {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    // Metadata
 | 
					    // Metadata
 | 
				
			||||||
    const metadataContainer = document.getElementById('tool-metadata');
 | 
					    const metadataContainer = document.getElementById('tool-metadata');
 | 
				
			||||||
 | 
					    const domainsText = tool.domains.length > 0 ? tool.domains.join(', ') : 'Domain-agnostic';
 | 
				
			||||||
 | 
					    const phasesText = tool.phases.join(', ');
 | 
				
			||||||
    metadataContainer.innerHTML = `
 | 
					    metadataContainer.innerHTML = `
 | 
				
			||||||
      <div style="display: grid; gap: 0.5rem;">
 | 
					      <div style="display: grid; gap: 0.5rem;">
 | 
				
			||||||
        <div><strong>Platforms:</strong> ${tool.platforms.join(', ')}</div>
 | 
					        <div><strong>Platforms:</strong> ${tool.platforms.join(', ')}</div>
 | 
				
			||||||
        <div><strong>Skill Level:</strong> ${tool.skillLevel}</div>
 | 
					        <div><strong>Skill Level:</strong> ${tool.skillLevel}</div>
 | 
				
			||||||
        <div><strong>License:</strong> ${tool.license}</div>
 | 
					        <div><strong>License:</strong> ${tool.license}</div>
 | 
				
			||||||
        <div><strong>Access Type:</strong> ${tool.accessType}</div>
 | 
					        <div><strong>Access Type:</strong> ${tool.accessType}</div>
 | 
				
			||||||
        <div><strong>Domains:</strong> ${tool.domains.join(', ')}</div>
 | 
					        <div><strong>Domains:</strong> ${domainsText}</div>
 | 
				
			||||||
        <div><strong>Phases:</strong> ${tool.phases.join(', ')}</div>
 | 
					        <div><strong>Phases:</strong> ${phasesText}</div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -124,10 +165,38 @@ domains.forEach((domain: any) => {
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Link
 | 
					    // Links - Updated to handle dual buttons for self-hosted tools
 | 
				
			||||||
    const linkElement = document.getElementById('tool-link');
 | 
					    const linksContainer = document.getElementById('tool-links');
 | 
				
			||||||
    linkElement.href = tool.url;
 | 
					    const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
				
			||||||
    linkElement.textContent = tool.isHosted ? 'Access Service' : 'Visit Website';
 | 
					                              tool.projectUrl !== null && 
 | 
				
			||||||
 | 
					                              tool.projectUrl !== "" && 
 | 
				
			||||||
 | 
					                              tool.projectUrl.trim() !== "";
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (tool.isHosted && hasValidProjectUrl) {
 | 
				
			||||||
 | 
					      // Two buttons for self-hosted tools with both URLs
 | 
				
			||||||
 | 
					      linksContainer.innerHTML = `
 | 
				
			||||||
 | 
					        <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
 | 
				
			||||||
 | 
					          Project Page
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <a href="${tool.projectUrl}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="flex: 1;">
 | 
				
			||||||
 | 
					          Access Service
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					    } else if (tool.isHosted) {
 | 
				
			||||||
 | 
					      // Single button for self-hosted tools with only project URL
 | 
				
			||||||
 | 
					      linksContainer.innerHTML = `
 | 
				
			||||||
 | 
					        <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
				
			||||||
 | 
					          Project Page
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Single button for non-hosted tools
 | 
				
			||||||
 | 
					      linksContainer.innerHTML = `
 | 
				
			||||||
 | 
					        <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
				
			||||||
 | 
					          Visit Website
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Show modal
 | 
					    // Show modal
 | 
				
			||||||
    document.getElementById('modal-overlay').classList.add('active');
 | 
					    document.getElementById('modal-overlay').classList.add('active');
 | 
				
			||||||
@ -145,26 +214,99 @@ domains.forEach((domain: any) => {
 | 
				
			|||||||
    const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
 | 
					    const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (currentView === 'matrix') {
 | 
					    if (currentView === 'matrix') {
 | 
				
			||||||
      // Update matrix cells
 | 
					      const selectedPhase = document.getElementById('phase-select')?.value;
 | 
				
			||||||
      document.querySelectorAll('.matrix-cell').forEach(cell => {
 | 
					 | 
				
			||||||
        cell.innerHTML = '';
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      // Re-populate with filtered tools
 | 
					      // Handle collaboration tools section
 | 
				
			||||||
      filtered.forEach(tool => {
 | 
					      const collaborationSection = document.getElementById('collaboration-tools-section');
 | 
				
			||||||
        tool.domains.forEach(domain => {
 | 
					      const dfirMatrixSection = document.getElementById('dfir-matrix-section');
 | 
				
			||||||
          tool.phases.forEach(phase => {
 | 
					      const collaborationContainer = document.getElementById('collaboration-tools-container');
 | 
				
			||||||
            const cell = document.querySelector(`[data-domain="${domain}"][data-phase="${phase}"]`);
 | 
					      
 | 
				
			||||||
            if (cell) {
 | 
					      if (selectedPhase === 'collaboration') {
 | 
				
			||||||
              const chip = document.createElement('span');
 | 
					        // Show only collaboration tools, hide matrix
 | 
				
			||||||
              chip.className = `tool-chip ${tool.isHosted ? 'tool-chip-hosted' : tool.license !== 'Proprietary' ? 'tool-chip-oss' : ''}`;
 | 
					        collaborationSection.style.display = 'block';
 | 
				
			||||||
              chip.textContent = tool.name;
 | 
					        dfirMatrixSection.style.display = 'none';
 | 
				
			||||||
              chip.onclick = () => window.showToolDetails(tool.name);
 | 
					        
 | 
				
			||||||
              cell.appendChild(chip);
 | 
					        // Filter collaboration tools
 | 
				
			||||||
            }
 | 
					        const filteredCollaboration = filtered.filter(tool => tool.phases.includes('collaboration'));
 | 
				
			||||||
 | 
					        collaborationContainer.innerHTML = '';
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        filteredCollaboration.forEach(tool => {
 | 
				
			||||||
 | 
					          const toolCard = createCollaborationToolCardCompact(tool);
 | 
				
			||||||
 | 
					          collaborationContainer.appendChild(toolCard);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // Show matrix, handle collaboration tools visibility
 | 
				
			||||||
 | 
					        dfirMatrixSection.style.display = 'block';
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (selectedPhase === '' || selectedPhase === null) {
 | 
				
			||||||
 | 
					          // Show collaboration tools when no specific phase is selected
 | 
				
			||||||
 | 
					          collaborationSection.style.display = 'block';
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          // Show all collaboration tools that pass general filters
 | 
				
			||||||
 | 
					          const filteredCollaboration = filtered.filter(tool => tool.phases.includes('collaboration'));
 | 
				
			||||||
 | 
					          collaborationContainer.innerHTML = '';
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          filteredCollaboration.forEach(tool => {
 | 
				
			||||||
 | 
					            const toolCard = createCollaborationToolCardCompact(tool);
 | 
				
			||||||
 | 
					            collaborationContainer.appendChild(toolCard);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          // Hide collaboration tools when specific DFIR phase is selected
 | 
				
			||||||
 | 
					          collaborationSection.style.display = 'none';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Clear and update matrix cells with DFIR tools only
 | 
				
			||||||
 | 
					        document.querySelectorAll('.matrix-cell').forEach(cell => {
 | 
				
			||||||
 | 
					          cell.innerHTML = '';
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Re-populate with filtered DFIR tools
 | 
				
			||||||
 | 
					        const filteredDfirTools = filtered.filter(tool => !tool.phases.includes('collaboration'));
 | 
				
			||||||
 | 
					        filteredDfirTools.forEach(tool => {
 | 
				
			||||||
 | 
					          tool.domains.forEach(domain => {
 | 
				
			||||||
 | 
					            tool.phases.forEach(phase => {
 | 
				
			||||||
 | 
					              if (phase !== 'collaboration') {
 | 
				
			||||||
 | 
					                const cell = document.querySelector(`[data-domain="${domain}"][data-phase="${phase}"]`);
 | 
				
			||||||
 | 
					                if (cell) {
 | 
				
			||||||
 | 
					                  const chip = document.createElement('span');
 | 
				
			||||||
 | 
					                  chip.className = `tool-chip ${tool.isHosted ? 'tool-chip-hosted' : tool.license !== 'Proprietary' ? 'tool-chip-oss' : ''}`;
 | 
				
			||||||
 | 
					                  chip.textContent = tool.name;
 | 
				
			||||||
 | 
					                  chip.onclick = () => window.showToolDetails(tool.name);
 | 
				
			||||||
 | 
					                  cell.appendChild(chip);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Helper function to create compact collaboration tool cards for matrix view
 | 
				
			||||||
 | 
					  function createCollaborationToolCardCompact(tool) {
 | 
				
			||||||
 | 
					    const cardDiv = document.createElement('div');
 | 
				
			||||||
 | 
					    const cardClass = `collaboration-tool-compact ${tool.isHosted ? 'hosted' : tool.license !== 'Proprietary' ? 'oss' : ''}`;
 | 
				
			||||||
 | 
					    cardDiv.className = cardClass;
 | 
				
			||||||
 | 
					    cardDiv.onclick = () => window.showToolDetails(tool.name);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    cardDiv.innerHTML = `
 | 
				
			||||||
 | 
					      <div class="tool-compact-header">
 | 
				
			||||||
 | 
					        <h4 style="margin: 0; font-size: 0.875rem; font-weight: 600;">${tool.name}</h4>
 | 
				
			||||||
 | 
					        <div style="display: flex; gap: 0.25rem;">
 | 
				
			||||||
 | 
					          ${tool.isHosted ? '<span class="badge-mini badge-primary">Self-Hosted</span>' : ''}
 | 
				
			||||||
 | 
					          ${tool.license !== 'Proprietary' ? '<span class="badge-mini badge-success">OSS</span>' : ''}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <p style="font-size: 0.75rem; color: var(--color-text-secondary); margin: 0.25rem 0; line-height: 1.3;">
 | 
				
			||||||
 | 
					        ${tool.description}
 | 
				
			||||||
 | 
					      </p>
 | 
				
			||||||
 | 
					      <div style="display: flex; gap: 0.75rem; font-size: 0.6875rem; color: var(--color-text-secondary);">
 | 
				
			||||||
 | 
					        <span>${tool.platforms.join(', ')}</span>
 | 
				
			||||||
 | 
					        <span>•</span>
 | 
				
			||||||
 | 
					        <span>${tool.skillLevel}</span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return cardDiv;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@ -15,6 +15,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://www.autopsy.com/"
 | 
					    url: "https://www.autopsy.com/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Apache 2.0"
 | 
					    license: "Apache 2.0"
 | 
				
			||||||
    tags: ["disk-forensics", "file-recovery", "timeline-analysis"]
 | 
					    tags: ["disk-forensics", "file-recovery", "timeline-analysis"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -30,6 +31,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "advanced"
 | 
					    skillLevel: "advanced"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://www.volatilityfoundation.org/"
 | 
					    url: "https://www.volatilityfoundation.org/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "VSL"
 | 
					    license: "VSL"
 | 
				
			||||||
    tags: ["memory-forensics", "malware-analysis", "incident-response"]
 | 
					    tags: ["memory-forensics", "malware-analysis", "incident-response"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -48,7 +50,8 @@ tools:
 | 
				
			|||||||
    platforms: ["Web"]
 | 
					    platforms: ["Web"]
 | 
				
			||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "self-hosted"
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
    url: "https://thehive.example.lab"
 | 
					    url: "https://strangebee.com/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "AGPL-3.0"
 | 
					    license: "AGPL-3.0"
 | 
				
			||||||
    tags: ["incident-response", "case-management", "collaboration"]
 | 
					    tags: ["incident-response", "case-management", "collaboration"]
 | 
				
			||||||
    isHosted: true
 | 
					    isHosted: true
 | 
				
			||||||
@ -66,7 +69,8 @@ tools:
 | 
				
			|||||||
    platforms: ["Web"]
 | 
					    platforms: ["Web"]
 | 
				
			||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "self-hosted"
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
    url: "https://misp.example.lab"
 | 
					    url: "https://misp-project.org/"
 | 
				
			||||||
 | 
					    projectUrl: "https://misp.cc24.dev"
 | 
				
			||||||
    license: "AGPL-3.0"
 | 
					    license: "AGPL-3.0"
 | 
				
			||||||
    tags: ["threat-intelligence", "ioc-sharing", "collaboration"]
 | 
					    tags: ["threat-intelligence", "ioc-sharing", "collaboration"]
 | 
				
			||||||
    isHosted: true
 | 
					    isHosted: true
 | 
				
			||||||
@ -83,7 +87,8 @@ tools:
 | 
				
			|||||||
    platforms: ["Web"]
 | 
					    platforms: ["Web"]
 | 
				
			||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "self-hosted"
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
    url: "https://timesketch.example.lab"
 | 
					    url: "https://timesketch.org/"
 | 
				
			||||||
 | 
					    projectUrl: "https://timesketch.cc24.dev"
 | 
				
			||||||
    license: "Apache 2.0"
 | 
					    license: "Apache 2.0"
 | 
				
			||||||
    tags: ["timeline-analysis", "collaboration", "visualization"]
 | 
					    tags: ["timeline-analysis", "collaboration", "visualization"]
 | 
				
			||||||
    isHosted: true
 | 
					    isHosted: true
 | 
				
			||||||
@ -101,6 +106,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://www.wireshark.org/"
 | 
					    url: "https://www.wireshark.org/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "GPL-2.0"
 | 
					    license: "GPL-2.0"
 | 
				
			||||||
    tags: ["network-analysis", "pcap", "protocol-analysis"]
 | 
					    tags: ["network-analysis", "pcap", "protocol-analysis"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -119,6 +125,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "advanced"
 | 
					    skillLevel: "advanced"
 | 
				
			||||||
    accessType: "commercial"
 | 
					    accessType: "commercial"
 | 
				
			||||||
    url: "https://www.opentext.com/products/encase-forensic"
 | 
					    url: "https://www.opentext.com/products/encase-forensic"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Proprietary"
 | 
					    license: "Proprietary"
 | 
				
			||||||
    tags: ["commercial", "enterprise", "court-approved"]
 | 
					    tags: ["commercial", "enterprise", "court-approved"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -135,6 +142,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "advanced"
 | 
					    skillLevel: "advanced"
 | 
				
			||||||
    accessType: "self-hosted"
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
    url: "https://cuckoosandbox.org/"
 | 
					    url: "https://cuckoosandbox.org/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "GPL-3.0"
 | 
					    license: "GPL-3.0"
 | 
				
			||||||
    tags: ["malware-analysis", "sandbox", "dynamic-analysis"]
 | 
					    tags: ["malware-analysis", "sandbox", "dynamic-analysis"]
 | 
				
			||||||
    isHosted: true
 | 
					    isHosted: true
 | 
				
			||||||
@ -151,6 +159,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://exterro.com/ftk-imager"
 | 
					    url: "https://exterro.com/ftk-imager"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Proprietary"
 | 
					    license: "Proprietary"
 | 
				
			||||||
    tags: ["disk-imaging", "preview", "data-acquisition"]
 | 
					    tags: ["disk-imaging", "preview", "data-acquisition"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -167,6 +176,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "advanced"
 | 
					    skillLevel: "advanced"
 | 
				
			||||||
    accessType: "self-hosted"
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
    url: "https://github.com/google/grr"
 | 
					    url: "https://github.com/google/grr"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Apache 2.0"
 | 
					    license: "Apache 2.0"
 | 
				
			||||||
    tags: ["live-forensics", "remote-response", "dfir"]
 | 
					    tags: ["live-forensics", "remote-response", "dfir"]
 | 
				
			||||||
    isHosted: true
 | 
					    isHosted: true
 | 
				
			||||||
@ -183,6 +193,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://plaso.readthedocs.io/"
 | 
					    url: "https://plaso.readthedocs.io/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Apache 2.0"
 | 
					    license: "Apache 2.0"
 | 
				
			||||||
    tags: ["timeline-analysis", "log-parsing", "dfir"]
 | 
					    tags: ["timeline-analysis", "log-parsing", "dfir"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -198,6 +209,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://www.netresec.com/?page=NetworkMiner"
 | 
					    url: "https://www.netresec.com/?page=NetworkMiner"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Freeware/Commercial"
 | 
					    license: "Freeware/Commercial"
 | 
				
			||||||
    tags: ["pcap-analysis", "passive-sniffing", "credential-recovery"]
 | 
					    tags: ["pcap-analysis", "passive-sniffing", "credential-recovery"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -214,6 +226,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://www.mandiant.com/resources/download/redline"
 | 
					    url: "https://www.mandiant.com/resources/download/redline"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Proprietary"
 | 
					    license: "Proprietary"
 | 
				
			||||||
    tags: ["memory-analysis", "ioc-scan", "host-analysis"]
 | 
					    tags: ["memory-analysis", "ioc-scan", "host-analysis"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -230,6 +243,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "intermediate"
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
    accessType: "download"
 | 
					    accessType: "download"
 | 
				
			||||||
    url: "https://www.kroll.com/en/services/cyber-risk/incident-response-litigation-support/kroll-artifact-parser-extractor-kape"
 | 
					    url: "https://www.kroll.com/en/services/cyber-risk/incident-response-litigation-support/kroll-artifact-parser-extractor-kape"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Freeware"
 | 
					    license: "Freeware"
 | 
				
			||||||
    tags: ["triage", "artifact-collection", "parsing"]
 | 
					    tags: ["triage", "artifact-collection", "parsing"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
@ -246,6 +260,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "advanced"
 | 
					    skillLevel: "advanced"
 | 
				
			||||||
    accessType: "self-hosted"
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
    url: "https://www.velociraptor.app/"
 | 
					    url: "https://www.velociraptor.app/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Apache 2.0"
 | 
					    license: "Apache 2.0"
 | 
				
			||||||
    tags: ["dfir", "hunting", "endpoint-monitoring"]
 | 
					    tags: ["dfir", "hunting", "endpoint-monitoring"]
 | 
				
			||||||
    isHosted: true
 | 
					    isHosted: true
 | 
				
			||||||
@ -262,6 +277,7 @@ tools:
 | 
				
			|||||||
    skillLevel: "advanced"
 | 
					    skillLevel: "advanced"
 | 
				
			||||||
    accessType: "self-hosted"
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
    url: "https://arkime.com/"
 | 
					    url: "https://arkime.com/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Apache 2.0"
 | 
					    license: "Apache 2.0"
 | 
				
			||||||
    tags: ["packet-capture", "full-packet-analysis", "network-forensics"]
 | 
					    tags: ["packet-capture", "full-packet-analysis", "network-forensics"]
 | 
				
			||||||
    isHosted: true
 | 
					    isHosted: true
 | 
				
			||||||
@ -279,10 +295,43 @@ tools:
 | 
				
			|||||||
    skillLevel: "advanced"
 | 
					    skillLevel: "advanced"
 | 
				
			||||||
    accessType: "commercial"
 | 
					    accessType: "commercial"
 | 
				
			||||||
    url: "https://www.x-ways.net/forensics/"
 | 
					    url: "https://www.x-ways.net/forensics/"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
    license: "Proprietary"
 | 
					    license: "Proprietary"
 | 
				
			||||||
    tags: ["disk-forensics", "file-recovery", "commercial"]
 | 
					    tags: ["disk-forensics", "file-recovery", "commercial"]
 | 
				
			||||||
    isHosted: false
 | 
					    isHosted: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Collaboration Tools - Domain-agnostic
 | 
				
			||||||
 | 
					  - name: "Nextcloud"
 | 
				
			||||||
 | 
					    description: "Self-hosted file sharing and collaboration platform for secure data exchange"
 | 
				
			||||||
 | 
					    domains: []  # Domain-agnostic
 | 
				
			||||||
 | 
					    phases:
 | 
				
			||||||
 | 
					      - "collaboration"
 | 
				
			||||||
 | 
					    platforms: ["Web"]
 | 
				
			||||||
 | 
					    skillLevel: "beginner"
 | 
				
			||||||
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
 | 
					    url: "https://nextcloud.com/de/"
 | 
				
			||||||
 | 
					    projectUrl: "https://cloud.cc24.dev"
 | 
				
			||||||
 | 
					    license: "AGPL-3.0"
 | 
				
			||||||
 | 
					    tags: ["file-sharing", "collaboration", "document-management", "secure-storage"]
 | 
				
			||||||
 | 
					    isHosted: true
 | 
				
			||||||
 | 
					    statusUrl: "https://uptime.example.lab/api/badge/10/status"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: "Gitea"
 | 
				
			||||||
 | 
					    description: "Lightweight self-hosted Git service for code collaboration and version control"
 | 
				
			||||||
 | 
					    domains: []  # Domain-agnostic
 | 
				
			||||||
 | 
					    phases:
 | 
				
			||||||
 | 
					      - "collaboration"
 | 
				
			||||||
 | 
					    platforms: ["Web"]
 | 
				
			||||||
 | 
					    skillLevel: "intermediate"
 | 
				
			||||||
 | 
					    accessType: "self-hosted"
 | 
				
			||||||
 | 
					    url: "https://git.example.lab"
 | 
				
			||||||
 | 
					    projectUrl: ""
 | 
				
			||||||
 | 
					    license: "MIT"
 | 
				
			||||||
 | 
					    tags: ["version-control", "git", "code-collaboration", "documentation"]
 | 
				
			||||||
 | 
					    isHosted: true
 | 
				
			||||||
 | 
					    statusUrl: "https://uptime.example.lab/api/badge/11/status"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Domain definitions for reference
 | 
					# Domain definitions for reference
 | 
				
			||||||
domains:
 | 
					domains:
 | 
				
			||||||
  - id: "storage-file-system"
 | 
					  - id: "storage-file-system"
 | 
				
			||||||
@ -309,4 +358,6 @@ phases:
 | 
				
			|||||||
  - id: "analysis"
 | 
					  - id: "analysis"
 | 
				
			||||||
    name: "Analysis"
 | 
					    name: "Analysis"
 | 
				
			||||||
  - id: "reporting"
 | 
					  - id: "reporting"
 | 
				
			||||||
    name: "Reporting"
 | 
					    name: "Reporting"
 | 
				
			||||||
 | 
					  - id: "collaboration"
 | 
				
			||||||
 | 
					    name: "General Tools for Collaboration"
 | 
				
			||||||
@ -104,68 +104,107 @@ const tools = data.tools;
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Create tool card element
 | 
					// This replaces the createToolCard function in index.astro script section
 | 
				
			||||||
    function createToolCard(tool: any): HTMLElement {
 | 
					
 | 
				
			||||||
      const cardDiv = document.createElement('div');
 | 
					// This replaces the createToolCard function in index.astro script section
 | 
				
			||||||
      const cardClass = tool.isHosted ? 'card card-hosted' : (tool.license !== 'Proprietary' ? 'card card-oss' : 'card');
 | 
					
 | 
				
			||||||
      cardDiv.className = cardClass;
 | 
					// This replaces the createToolCard function in index.astro script section
 | 
				
			||||||
      
 | 
					
 | 
				
			||||||
      cardDiv.innerHTML = `
 | 
					// Create tool card element
 | 
				
			||||||
        <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
 | 
					function createToolCard(tool) {
 | 
				
			||||||
          <h3 style="margin: 0;">${tool.name}</h3>
 | 
					  const cardDiv = document.createElement('div');
 | 
				
			||||||
          <div style="display: flex; gap: 0.5rem;">
 | 
					  const cardClass = tool.isHosted ? 'card card-hosted' : (tool.license !== 'Proprietary' ? 'card card-oss' : 'card');
 | 
				
			||||||
            ${tool.isHosted ? '<span class="badge badge-primary">Self-Hosted</span>' : ''}
 | 
					  cardDiv.className = cardClass;
 | 
				
			||||||
            ${tool.license !== 'Proprietary' ? '<span class="badge badge-success">Open Source</span>' : ''}
 | 
					  
 | 
				
			||||||
          </div>
 | 
					  // Create button HTML based on hosting status
 | 
				
			||||||
        </div>
 | 
					  const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
				
			||||||
        
 | 
					                            tool.projectUrl !== null && 
 | 
				
			||||||
        <p class="text-muted" style="font-size: 0.875rem; margin-bottom: 1rem;">
 | 
					                            tool.projectUrl !== "" && 
 | 
				
			||||||
          ${tool.description}
 | 
					                            tool.projectUrl.trim() !== "";
 | 
				
			||||||
        </p>
 | 
					  
 | 
				
			||||||
        
 | 
					  let buttonHTML;
 | 
				
			||||||
        <div style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem;">
 | 
					  if (tool.isHosted && hasValidProjectUrl) {
 | 
				
			||||||
          <div style="display: flex; align-items: center; gap: 0.25rem;">
 | 
					    // Two buttons for self-hosted tools with both URLs
 | 
				
			||||||
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
					    buttonHTML = `
 | 
				
			||||||
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
 | 
					      <div style="display: flex; gap: 0.5rem;">
 | 
				
			||||||
              <line x1="9" y1="9" x2="15" y2="9"></line>
 | 
					        <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
 | 
				
			||||||
              <line x1="9" y1="15" x2="15" y2="15"></line>
 | 
					          Project Page
 | 
				
			||||||
            </svg>
 | 
					 | 
				
			||||||
            <span class="text-muted" style="font-size: 0.75rem;">
 | 
					 | 
				
			||||||
              ${tool.platforms.join(', ')}
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          <div style="display: flex; align-items: center; gap: 0.25rem;">
 | 
					 | 
				
			||||||
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
					 | 
				
			||||||
              <circle cx="12" cy="12" r="10"></circle>
 | 
					 | 
				
			||||||
              <path d="M12 6v6l4 2"></path>
 | 
					 | 
				
			||||||
            </svg>
 | 
					 | 
				
			||||||
            <span class="text-muted" style="font-size: 0.75rem;">
 | 
					 | 
				
			||||||
              ${tool.skillLevel}
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          <div style="display: flex; align-items: center; gap: 0.25rem;">
 | 
					 | 
				
			||||||
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
					 | 
				
			||||||
              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
 | 
					 | 
				
			||||||
              <polyline points="14 2 14 8 20 8"></polyline>
 | 
					 | 
				
			||||||
            </svg>
 | 
					 | 
				
			||||||
            <span class="text-muted" style="font-size: 0.75rem;">
 | 
					 | 
				
			||||||
              ${tool.license}
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        <div style="display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 1rem;">
 | 
					 | 
				
			||||||
          ${tool.tags.map((tag: string) => `<span class="tag">${tag}</span>`).join('')}
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
					 | 
				
			||||||
          ${tool.isHosted ? 'Access Service' : 'Visit Website'}
 | 
					 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
      `;
 | 
					        <a href="${tool.projectUrl}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="flex: 1;">
 | 
				
			||||||
 | 
					          Access Service
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					  } else if (tool.isHosted) {
 | 
				
			||||||
 | 
					    // Single button for self-hosted tools with only project URL
 | 
				
			||||||
 | 
					    buttonHTML = `
 | 
				
			||||||
 | 
					      <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
				
			||||||
 | 
					        Project Page
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // Single button for non-hosted tools
 | 
				
			||||||
 | 
					    buttonHTML = `
 | 
				
			||||||
 | 
					      <a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%;">
 | 
				
			||||||
 | 
					        Visit Website
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  cardDiv.innerHTML = `
 | 
				
			||||||
 | 
					    <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
 | 
				
			||||||
 | 
					      <h3 style="margin: 0;">${tool.name}</h3>
 | 
				
			||||||
 | 
					      <div style="display: flex; gap: 0.5rem;">
 | 
				
			||||||
 | 
					        ${tool.isHosted ? '<span class="badge badge-primary">Self-Hosted</span>' : ''}
 | 
				
			||||||
 | 
					        ${tool.license !== 'Proprietary' ? '<span class="badge badge-success">Open Source</span>' : ''}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <p class="text-muted" style="font-size: 0.875rem; margin-bottom: 1rem;">
 | 
				
			||||||
 | 
					      ${tool.description}
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <div style="display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem;">
 | 
				
			||||||
 | 
					      <div style="display: flex; align-items: center; gap: 0.25rem;">
 | 
				
			||||||
 | 
					        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
				
			||||||
 | 
					          <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
 | 
				
			||||||
 | 
					          <line x1="9" y1="9" x2="15" y2="9"></line>
 | 
				
			||||||
 | 
					          <line x1="9" y1="15" x2="15" y2="15"></line>
 | 
				
			||||||
 | 
					        </svg>
 | 
				
			||||||
 | 
					        <span class="text-muted" style="font-size: 0.75rem;">
 | 
				
			||||||
 | 
					          ${tool.platforms.join(', ')}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      return cardDiv;
 | 
					      <div style="display: flex; align-items: center; gap: 0.25rem;">
 | 
				
			||||||
    }
 | 
					        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
				
			||||||
 | 
					          <circle cx="12" cy="12" r="10"></circle>
 | 
				
			||||||
 | 
					          <path d="M12 6v6l4 2"></path>
 | 
				
			||||||
 | 
					        </svg>
 | 
				
			||||||
 | 
					        <span class="text-muted" style="font-size: 0.75rem;">
 | 
				
			||||||
 | 
					          ${tool.skillLevel}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      <div style="display: flex; align-items: center; gap: 0.25rem;">
 | 
				
			||||||
 | 
					        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
				
			||||||
 | 
					          <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
 | 
				
			||||||
 | 
					          <polyline points="14 2 14 8 20 8"></polyline>
 | 
				
			||||||
 | 
					        </svg>
 | 
				
			||||||
 | 
					        <span class="text-muted" style="font-size: 0.75rem;">
 | 
				
			||||||
 | 
					          ${tool.license}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <div style="display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 1rem;">
 | 
				
			||||||
 | 
					      ${tool.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    ${buttonHTML}
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return cardDiv;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@ -472,6 +472,15 @@ footer {
 | 
				
			|||||||
    flex-direction: column;
 | 
					    flex-direction: column;
 | 
				
			||||||
    text-align: center;
 | 
					    text-align: center;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  /* Add this inside the existing @media (max-width: 768px) block */
 | 
				
			||||||
 | 
					  .collaboration-tools-compact {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .collaboration-tool-compact {
 | 
				
			||||||
 | 
					    min-width: auto;
 | 
				
			||||||
 | 
					    max-width: none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Animations */
 | 
					/* Animations */
 | 
				
			||||||
@ -482,4 +491,64 @@ footer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.fade-in {
 | 
					.fade-in {
 | 
				
			||||||
  animation: fadeIn 0.3s ease-in;
 | 
					  animation: fadeIn 0.3s ease-in;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Compact Collaboration Tools for Matrix View */
 | 
				
			||||||
 | 
					.collaboration-tools-compact {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  gap: 1rem;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.collaboration-tool-compact {
 | 
				
			||||||
 | 
					  background-color: var(--color-bg);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: 0.375rem;
 | 
				
			||||||
 | 
					  padding: 0.75rem;
 | 
				
			||||||
 | 
					  min-width: 200px;
 | 
				
			||||||
 | 
					  max-width: 300px;
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  transition: all 0.2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.collaboration-tool-compact:hover {
 | 
				
			||||||
 | 
					  border-color: var(--color-primary);
 | 
				
			||||||
 | 
					  box-shadow: var(--shadow-sm);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.collaboration-tool-compact.hosted {
 | 
				
			||||||
 | 
					  background-color: var(--color-hosted-bg);
 | 
				
			||||||
 | 
					  border-color: var(--color-hosted);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.collaboration-tool-compact.oss {
 | 
				
			||||||
 | 
					  background-color: var(--color-oss-bg);
 | 
				
			||||||
 | 
					  border-color: var(--color-oss);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.tool-compact-header {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  align-items: start;
 | 
				
			||||||
 | 
					  margin-bottom: 0.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-mini {
 | 
				
			||||||
 | 
					  display: inline-flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  padding: 0.0625rem 0.375rem;
 | 
				
			||||||
 | 
					  border-radius: 9999px;
 | 
				
			||||||
 | 
					  font-size: 0.625rem;
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-mini.badge-primary {
 | 
				
			||||||
 | 
					  background-color: var(--color-primary);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.badge-mini.badge-success {
 | 
				
			||||||
 | 
					  background-color: var(--color-accent);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user