Merge pull request 'style-fixes' (#26) from mstoeck3/forensic-pathways:style-fixes into main

Reviewed-on: mstoeck3/cc24-hub#26
This commit is contained in:
Mario Stöckl 2025-07-27 20:00:08 +00:00
commit e94c3a0f47
16 changed files with 5082 additions and 1897 deletions

View File

@ -88,6 +88,8 @@ npm install
cp .env.example .env cp .env.example .env
# .env bearbeiten (siehe Konfiguration unten) # .env bearbeiten (siehe Konfiguration unten)
npm run astro build
# Development Server starten # Development Server starten
npm run dev npm run dev
``` ```
@ -125,6 +127,7 @@ sudo npm install
# Production-Build erstellen # Production-Build erstellen
sudo npm run build sudo npm run build
npm run astro build
# Berechtigungen setzen # Berechtigungen setzen
sudo chown -R www-data:www-data /opt/forensic-pathways sudo chown -R www-data:www-data /opt/forensic-pathways

View File

@ -8,9 +8,9 @@ const phases = data.phases;
const domainAgnosticSoftware = data['domain-agnostic-software'] || []; const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
--- ---
<section id="ai-interface" class="ai-interface" style="display: none;"> <section id="ai-interface" class="ai-interface hidden">
<div class="ai-query-section"> <div class="ai-query-section">
<div style="text-align: center; margin-bottom: 2rem;"> <div class="content-center-lg">
<h2 style="margin-bottom: 1rem; color: var(--color-primary);"> <h2 style="margin-bottom: 1rem; color: var(--color-primary);">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.75rem; vertical-align: middle;"> <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.75rem; vertical-align: middle;">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/> <path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
@ -27,7 +27,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
<div class="ai-input-container" style="max-width: 1000px; margin: 0 auto;"> <div class="ai-input-container" style="max-width: 1000px; margin: 0 auto;">
<div class="ai-mode-toggle" style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.75rem; border: 1px solid var(--color-border);"> <div class="ai-mode-toggle" style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.75rem; border: 1px solid var(--color-border);">
<span id="workflow-label" class="toggle-label active" style="font-weight: 500; color: var(--color-primary); cursor: pointer; transition: var(--transition-fast);"> <span id="workflow-label" class="toggle-label active" style="font-weight: 500; color: var(--color-primary); cursor: pointer; transition: var(--transition-fast);">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="mr-2 align-middle">
<polyline points="9,11 12,14 22,4"/> <polyline points="9,11 12,14 22,4"/>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/> <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
</svg> </svg>
@ -39,7 +39,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div> </div>
<span id="tool-label" class="toggle-label" style="font-weight: 500; color: var(--color-text-secondary); cursor: pointer; transition: var(--transition-fast);"> <span id="tool-label" class="toggle-label" style="font-weight: 500; color: var(--color-text-secondary); cursor: pointer; transition: var(--transition-fast);">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="mr-2 align-middle">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/> <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg> </svg>
Spezifische Software oder Methode Spezifische Software oder Methode
@ -78,19 +78,19 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div> </div>
</div> </div>
</div> </div>
<div id="smart-prompting-container" class="smart-prompting-container" style="display: none;"> <div id="smart-prompting-container" class="smart-prompting-container hidden">
<div class="prompting-card"> <div class="prompting-card">
<div id="prompting-status" class="prompting-status"> <div id="prompting-status" class="prompting-status">
<div class="status-icon">💡</div> <div class="status-icon">💡</div>
<span class="status-text">Analysiere Eingabe...</span> <span class="status-text">Analysiere Eingabe...</span>
<div id="prompting-spinner" class="prompting-spinner" style="display: none;"> <div id="prompting-spinner" class="prompting-spinner hidden">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2">
<path d="M21 12a9 9 0 11-6.219-8.56"/> <path d="M21 12a9 9 0 11-6.219-8.56"/>
</svg> </svg>
</div> </div>
</div> </div>
<div id="suggested-questions" class="suggested-questions" style="display: none;"> <div id="suggested-questions" class="suggested-questions hidden">
<div class="suggestions-header"> <div class="suggestions-header">
<span class="suggestions-label">Zur besseren Analyse:</span> <span class="suggestions-label">Zur besseren Analyse:</span>
</div> </div>
@ -139,9 +139,9 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
<path d="M12 17h.01"/> <path d="M12 17h.01"/>
</svg> </svg>
</div> </div>
<p id="loading-text" style="color: var(--color-text-secondary);">Analysiere Szenario und generiere Empfehlungen...</p> <p id="loading-text" class="text-secondary">Analysiere Szenario und generiere Empfehlungen...</p>
<div id="queue-status" class="queue-status-card" style="display: none;"> <div id="queue-status" class="queue-status-card hidden">
<div class="queue-header"> <div class="queue-header">
<div class="queue-position-display"> <div class="queue-position-display">
<div id="queue-position-badge" class="position-badge">1</div> <div id="queue-position-badge" class="position-badge">1</div>
@ -180,7 +180,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</div> </div>
</div> </div>
<br> <br>
<div id="ai-results" class="ai-results" style="display: none;"> <div id="ai-results" class="ai-results hidden">
</div> </div>
</section> </section>
@ -812,7 +812,7 @@ document.addEventListener('DOMContentLoaded', () => {
<div class="tool-results-container"> <div class="tool-results-container">
<div style="text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, var(--color-primary) 0%, #525252 100%); color: white; border-radius: 0.75rem;"> <div style="text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, var(--color-primary) 0%, #525252 100%); color: white; border-radius: 0.75rem;">
<h3 style="margin: 0 0 0.75rem 0; font-size: 1.5rem;"> <h3 style="margin: 0 0 0.75rem 0; font-size: 1.5rem;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="mr-2 align-middle">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/> <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg> </svg>
Passende Empfehlungen Passende Empfehlungen
@ -887,7 +887,7 @@ document.addEventListener('DOMContentLoaded', () => {
</div> </div>
<div class="tool-detailed-explanation" style="margin-bottom: 1.5rem;"> <div class="tool-detailed-explanation" style="margin-bottom: 1.5rem;">
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-accent); font-size: 1rem;"> <h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-accent); font-size: 1rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="mr-2 align-middle">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/> <path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/> <path d="M9 11V7a3 3 0 0 1 6 0v4"/>
</svg> </svg>
@ -896,7 +896,7 @@ document.addEventListener('DOMContentLoaded', () => {
${formatWorkflowSuggestion(toolRec.detailed_explanation)} ${formatWorkflowSuggestion(toolRec.detailed_explanation)}
${toolRec.implementation_approach ? ` ${toolRec.implementation_approach ? `
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-primary); font-size: 1rem;"> <h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-primary); font-size: 1rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="mr-2 align-middle">
<polyline points="9,11 12,14 22,4"/> <polyline points="9,11 12,14 22,4"/>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/> <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
</svg> </svg>

View File

@ -65,7 +65,7 @@ const sortedTags = Object.entries(tagFrequency)
<label for="include-proprietary">Proprietäre Software mit einschließen</label> <label for="include-proprietary">Proprietäre Software mit einschließen</label>
</div> </div>
<div style="margin-bottom: 1rem;"> <div class="mb-4">
<div class="tag-header"> <div class="tag-header">
<label style="font-weight: 500;"> <label style="font-weight: 500;">
Nach Tags filtern Nach Tags filtern
@ -95,8 +95,8 @@ const sortedTags = Object.entries(tagFrequency)
</div> </div>
<div style="display: flex; gap: 1rem; margin-bottom: 1.5rem; align-items: center;"> <div style="display: flex; gap: 1rem; margin-bottom: 1.5rem; align-items: center;">
<button class="btn btn-secondary view-toggle active" style="height:50px" data-view="grid">Kachelansicht</button> <button class="btn btn-secondary view-toggle active h-12" data-view="grid">Kachelansicht</button>
<button class="btn btn-secondary view-toggle" style="height:50px" data-view="matrix">Matrix-Ansicht</button> <button class="btn btn-secondary view-toggle h-12" data-view="matrix">Matrix-Ansicht</button>
<button <button
id="ai-view-toggle" id="ai-view-toggle"

View File

@ -29,7 +29,7 @@ domains.forEach((domain: any) => {
}); });
--- ---
<div id="matrix-container" class="matrix-wrapper" style="display: none;"> <div id="matrix-container" class="matrix-wrapper hidden">
{domainAgnosticTools.map((sectionData: any, index: number) => ( {domainAgnosticTools.map((sectionData: any, index: number) => (
<div id={`domain-agnostic-section-${sectionData.section.id}`} class="card collaboration-section-collapsed mb-6 border-l-4" style="border-left-color: var(--color-accent);"> <div id={`domain-agnostic-section-${sectionData.section.id}`} class="card collaboration-section-collapsed mb-6 border-l-4" style="border-left-color: var(--color-accent);">
<div class="collaboration-header cursor-pointer flex items-center gap-3" onclick={`toggleDomainAgnosticSection('${sectionData.section.id}')`} style="margin-bottom: 0.1rem;"> <div class="collaboration-header cursor-pointer flex items-center gap-3" onclick={`toggleDomainAgnosticSection('${sectionData.section.id}')`} style="margin-bottom: 0.1rem;">
@ -39,7 +39,7 @@ domains.forEach((domain: any) => {
<line x1="20" y1="8" x2="20" y2="14"/> <line x1="20" y1="8" x2="20" y2="14"/>
<line x1="23" y1="11" x2="17" y2="11"/> <line x1="23" y1="11" x2="17" y2="11"/>
</svg> </svg>
<h3 class="m-0 text-lg" style="color: var(--color-accent);"> <h3 class="m-0 text-lg text-accent">
{sectionData.section.name} {sectionData.section.name}
<span id={`count-${sectionData.section.id}`} class="badge text-xs" style="background-color: var(--color-text-secondary); color: var(--color-bg); margin-left: 0.5rem;"> <span id={`count-${sectionData.section.id}`} class="badge text-xs" style="background-color: var(--color-text-secondary); color: var(--color-bg); margin-left: 0.5rem;">
{sectionData.tools.length} {sectionData.tools.length}
@ -51,7 +51,7 @@ domains.forEach((domain: any) => {
</svg> </svg>
</div> </div>
</div> </div>
<div class="collaboration-content" style="display: none;"> <div class="collaboration-content hidden">
<div class="collaboration-tools-compact" id={`domain-agnostic-tools-${sectionData.section.id}`}> <div class="collaboration-tools-compact" id={`domain-agnostic-tools-${sectionData.section.id}`}>
{sectionData.tools.map((tool: any) => { {sectionData.tools.map((tool: any) => {
const hasValidProjectUrl = tool.projectUrl !== undefined && const hasValidProjectUrl = tool.projectUrl !== undefined &&
@ -74,7 +74,7 @@ domains.forEach((domain: any) => {
<p class="text-muted"> <p class="text-muted">
{tool.description} {tool.description}
</p> </p>
<div class="flex gap-3 text-xs" style="color: var(--color-text-secondary);"> <div class="flex gap-3 text-xs text-secondary">
<span>{tool.platforms.join(', ')}</span> <span>{tool.platforms.join(', ')}</span>
<span>•</span> <span>•</span>
<span>{tool.skillLevel}</span> <span>{tool.skillLevel}</span>
@ -140,9 +140,9 @@ domains.forEach((domain: any) => {
<div class="flex justify-between items-start mb-4"> <div class="flex justify-between items-start mb-4">
<h2 id="tool-name-primary" class="m-0">Tool Name</h2> <h2 id="tool-name-primary" class="m-0">Tool Name</h2>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div id="share-button-primary" style="display: none;"> <div id="share-button-primary" class="hidden">
</div> </div>
<div id="contribute-button-primary" style="display: none;"> <div id="contribute-button-primary" class="hidden">
</div> </div>
<button class="btn-icon" onclick="window.hideToolDetails('primary')"> <button class="btn-icon" onclick="window.hideToolDetails('primary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@ -168,9 +168,9 @@ domains.forEach((domain: any) => {
<div class="flex justify-between items-start mb-4"> <div class="flex justify-between items-start mb-4">
<h2 id="tool-name-secondary" class="m-0">Tool Name</h2> <h2 id="tool-name-secondary" class="m-0">Tool Name</h2>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div id="share-button-secondary" style="display: none;"> <div id="share-button-secondary" class="hidden">
</div> </div>
<div id="contribute-button-secondary" style="display: none;"> <div id="contribute-button-secondary" class="hidden">
</div> </div>
<button class="btn-icon" onclick="window.hideToolDetails('secondary')"> <button class="btn-icon" onclick="window.hideToolDetails('secondary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@ -549,7 +549,7 @@ domains.forEach((domain: any) => {
</button> </button>
` : ''} ` : ''}
</div> </div>
<div ${collapseOnMobile ? 'style="display: none;"' : ''} class="flex flex-wrap gap-1"> <div ${collapseOnMobile ? 'class="hidden"' : ''} class="flex flex-wrap gap-1">
${conceptLinks} ${conceptLinks}
</div> </div>
</div> </div>

View File

@ -20,219 +20,224 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
<title>{title} - ForensicPathways</title> <title>{title} - ForensicPathways</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <link rel="icon" type="image/x-icon" href="/favicon.ico">
<script> <script>
document.addEventListener('DOMContentLoaded', () => { // Move utility functions OUTSIDE DOMContentLoaded to avoid race conditions
const THEME_KEY = 'dfir-theme'; function createToolSlug(toolName) {
if (!toolName || typeof toolName !== 'string') {
console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName);
return '';
}
return toolName.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
}
function getSystemTheme() { function findToolByIdentifier(tools, identifier) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; if (!identifier || !Array.isArray(tools)) return undefined;
}
return tools.find(tool =>
tool.name === identifier ||
createToolSlug(tool.name) === identifier.toLowerCase()
);
}
function getStoredTheme() { function isToolHosted(tool) {
return localStorage.getItem(THEME_KEY) || 'auto'; return tool.projectUrl !== undefined &&
} tool.projectUrl !== null &&
tool.projectUrl !== "" &&
tool.projectUrl.trim() !== "";
}
function applyTheme(theme) { // Consolidated scrolling utility - also moved outside DOMContentLoaded
const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme; function scrollToElement(element, options = {}) {
document.documentElement.setAttribute('data-theme', effectiveTheme); if (!element) return;
}
// Calculate target position manually to avoid double-scroll
function updateThemeToggle(theme) { setTimeout(() => {
document.querySelectorAll('[data-theme-toggle]').forEach(button => { const headerHeight = document.querySelector('nav')?.offsetHeight || 80;
button.setAttribute('data-current-theme', theme); const elementRect = element.getBoundingClientRect();
}); const absoluteElementTop = elementRect.top + window.pageYOffset;
} const targetPosition = absoluteElementTop - headerHeight - 20; // Adjust this 20 as needed
function initTheme() { window.scrollTo({
const storedTheme = getStoredTheme(); top: targetPosition,
applyTheme(storedTheme); behavior: 'smooth'
updateThemeToggle(storedTheme);
}
function toggleTheme() {
const current = getStoredTheme();
const themes = ['light', 'dark', 'auto'];
const currentIndex = themes.indexOf(current);
const nextIndex = (currentIndex + 1) % themes.length;
const nextTheme = themes[nextIndex];
localStorage.setItem(THEME_KEY, nextTheme);
applyTheme(nextTheme);
updateThemeToggle(nextTheme);
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (getStoredTheme() === 'auto') {
applyTheme('auto');
}
}); });
}, 100);
}
(window as any).themeUtils = { // Convenience functions for common scroll targets
initTheme, function scrollToElementById(elementId, options = {}) {
toggleTheme, const element = document.getElementById(elementId);
getStoredTheme scrollToElement(element, options);
}; }
// Consolidated scrolling utility function scrollToElementBySelector(selector, options = {}) {
(window as any).scrollToElement = function(element, options = {}) { const element = document.querySelector(selector);
if (!element) return; scrollToElement(element, options);
}
// Calculate target position manually to avoid double-scroll
setTimeout(() => {
const headerHeight = document.querySelector('nav')?.offsetHeight || 80;
const elementRect = element.getBoundingClientRect();
const absoluteElementTop = elementRect.top + window.pageYOffset;
const targetPosition = absoluteElementTop - headerHeight - 20; // Adjust this 20 as needed
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
}, 100);
};
// Convenience functions for common scroll targets // Attach to window immediately - BEFORE DOMContentLoaded
(window as any).scrollToElementById = function(elementId, options = {}) { (window as any).createToolSlug = createToolSlug;
const element = document.getElementById(elementId); (window as any).findToolByIdentifier = findToolByIdentifier;
(window as any).scrollToElement(element, options); (window as any).isToolHosted = isToolHosted;
}; (window as any).scrollToElement = scrollToElement;
(window as any).scrollToElementById = scrollToElementById;
(window as any).scrollToElementBySelector = scrollToElementBySelector;
(window as any).scrollToElementBySelector = function(selector, options = {}) { document.addEventListener('DOMContentLoaded', () => {
const element = document.querySelector(selector); const THEME_KEY = 'dfir-theme';
(window as any).scrollToElement(element, options);
};
function createToolSlug(toolName) { function getSystemTheme() {
if (!toolName || typeof toolName !== 'string') { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName); }
return '';
}
return toolName.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
}
function findToolByIdentifier(tools, identifier) { function getStoredTheme() {
if (!identifier || !Array.isArray(tools)) return undefined; return localStorage.getItem(THEME_KEY) || 'auto';
}
return tools.find(tool =>
tool.name === identifier ||
createToolSlug(tool.name) === identifier.toLowerCase()
);
}
function isToolHosted(tool) { function applyTheme(theme) {
return tool.projectUrl !== undefined && const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme;
tool.projectUrl !== null && document.documentElement.setAttribute('data-theme', effectiveTheme);
tool.projectUrl !== "" && }
tool.projectUrl.trim() !== "";
}
(window as any).createToolSlug = createToolSlug; function updateThemeToggle(theme) {
(window as any).findToolByIdentifier = findToolByIdentifier; document.querySelectorAll('[data-theme-toggle]').forEach(button => {
(window as any).isToolHosted = isToolHosted; button.setAttribute('data-current-theme', theme);
});
}
async function checkClientAuth(context = 'general') { function initTheme() {
try { const storedTheme = getStoredTheme();
const response = await fetch('/api/auth/status'); applyTheme(storedTheme);
const data = await response.json(); updateThemeToggle(storedTheme);
}
switch (context) {
case 'contributions':
return {
authenticated: data.contributionAuthenticated,
authRequired: data.contributionAuthRequired,
expires: data.expires
};
case 'ai':
return {
authenticated: data.aiAuthenticated,
authRequired: data.aiAuthRequired,
expires: data.expires
};
default:
return {
authenticated: data.authenticated,
authRequired: data.contributionAuthRequired || data.aiAuthRequired,
expires: data.expires
};
}
} catch (error) {
console.error('Auth check failed:', error);
return {
authenticated: false,
authRequired: true
};
}
}
async function requireClientAuth(callback, returnUrl, context = 'general') { function toggleTheme() {
const authStatus = await checkClientAuth(context); const current = getStoredTheme();
const themes = ['light', 'dark', 'auto'];
if (authStatus.authRequired && !authStatus.authenticated) { const currentIndex = themes.indexOf(current);
const targetUrl = returnUrl || window.location.href; const nextIndex = (currentIndex + 1) % themes.length;
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`; const nextTheme = themes[nextIndex];
return false;
} else {
if (typeof callback === 'function') {
callback();
}
return true;
}
}
async function showIfAuthenticated(selector, context = 'general') {
const authStatus = await checkClientAuth(context);
const element = document.querySelector(selector);
if (element) {
element.style.display = (!authStatus.authRequired || authStatus.authenticated)
? 'inline-flex'
: 'none';
}
}
function setupAuthButtons(selector = '[data-contribute-button]') {
document.addEventListener('click', async (e) => {
if (!e.target) return;
const button = (e.target as Element).closest(selector);
if (!button) return;
e.preventDefault();
console.log('[AUTH] Contribute button clicked:', button.getAttribute('data-contribute-button'));
await requireClientAuth(() => {
console.log('[AUTH] Navigation approved, redirecting to:', (button as HTMLAnchorElement).href);
window.location.href = (button as HTMLAnchorElement).href;
}, (button as HTMLAnchorElement).href, 'contributions');
});
}
(window as any).checkClientAuth = checkClientAuth;
(window as any).requireClientAuth = requireClientAuth;
(window as any).showIfAuthenticated = showIfAuthenticated;
(window as any).setupAuthButtons = setupAuthButtons;
initTheme();
setupAuthButtons('[data-contribute-button]');
const initAIButton = async () => { localStorage.setItem(THEME_KEY, nextTheme);
await showIfAuthenticated('#ai-view-toggle', 'ai'); applyTheme(nextTheme);
}; updateThemeToggle(nextTheme);
initAIButton(); }
console.log('[CONSOLIDATED] All utilities loaded and initialized'); window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (getStoredTheme() === 'auto') {
applyTheme('auto');
}
}); });
</script>
(window as any).themeUtils = {
initTheme,
toggleTheme,
getStoredTheme
};
async function checkClientAuth(context = 'general') {
try {
const response = await fetch('/api/auth/status');
const data = await response.json();
switch (context) {
case 'contributions':
return {
authenticated: data.contributionAuthenticated,
authRequired: data.contributionAuthRequired,
expires: data.expires
};
case 'ai':
return {
authenticated: data.aiAuthenticated,
authRequired: data.aiAuthRequired,
expires: data.expires
};
default:
return {
authenticated: data.authenticated,
authRequired: data.contributionAuthRequired || data.aiAuthRequired,
expires: data.expires
};
}
} catch (error) {
console.error('Auth check failed:', error);
return {
authenticated: false,
authRequired: true
};
}
}
async function requireClientAuth(callback, returnUrl, context = 'general') {
const authStatus = await checkClientAuth(context);
if (authStatus.authRequired && !authStatus.authenticated) {
const targetUrl = returnUrl || window.location.href;
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
return false;
} else {
if (typeof callback === 'function') {
callback();
}
return true;
}
}
async function showIfAuthenticated(selector, context = 'general') {
const authStatus = await checkClientAuth(context);
const element = document.querySelector(selector);
if (element) {
element.style.display = (!authStatus.authRequired || authStatus.authenticated)
? 'inline-flex'
: 'none';
}
}
function setupAuthButtons(selector = '[data-contribute-button]') {
document.addEventListener('click', async (e) => {
if (!e.target) return;
const button = (e.target as Element).closest(selector);
if (!button) return;
e.preventDefault();
console.log('[AUTH] Contribute button clicked:', button.getAttribute('data-contribute-button'));
await requireClientAuth(() => {
console.log('[AUTH] Navigation approved, redirecting to:', (button as HTMLAnchorElement).href);
window.location.href = (button as HTMLAnchorElement).href;
}, (button as HTMLAnchorElement).href, 'contributions');
});
}
(window as any).checkClientAuth = checkClientAuth;
(window as any).requireClientAuth = requireClientAuth;
(window as any).showIfAuthenticated = showIfAuthenticated;
(window as any).setupAuthButtons = setupAuthButtons;
initTheme();
setupAuthButtons('[data-contribute-button]');
const initAIButton = async () => {
await showIfAuthenticated('#ai-view-toggle', 'ai');
};
initAIButton();
console.log('[CONSOLIDATED] All utilities loaded and initialized');
});
</script>
</head> </head>
<body> <body>
<Navigation /> <Navigation />
<main class="container" style="flex: 1; padding: 2rem 1rem;"> <main class="container flex-1 py-8 px-4">
<slot /> <slot />
</main> </main>
<Footer /> <Footer />

View File

@ -3,36 +3,36 @@ import BaseLayout from '../layouts/BaseLayout.astro';
--- ---
<BaseLayout title="Über das Projekt" description="ForensicPathways - Ein Projekt für die Seminargruppe CC24-w1"> <BaseLayout title="Über das Projekt" description="ForensicPathways - Ein Projekt für die Seminargruppe CC24-w1">
<section style="padding: 2rem 0; max-width: 900px; margin: 0 auto;"> <section class="section container-narrow">
<div style="text-align: center; margin-bottom: 3rem; padding: 2rem; background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%); border-radius: 1rem; border: 1px solid var(--color-border);"> <div class="header-center header-gradient mb-8">
<h1 style="margin-bottom: 1rem; font-size: 2.5rem; color: var(--color-primary);">ForensicPathways</h1> <h1 class="text-primary mb-4 text-2xl">ForensicPathways</h1>
<p style="font-size: 1.25rem; color: var(--color-text-secondary); margin-bottom: 0.5rem;"> <p class="text-xl text-secondary mb-2">
Forensik im Dienst der Transparenz Forensik im Dienst der Transparenz
</p> </p>
<p style="font-size: 1rem; color: var(--color-text-secondary);"> <p class="text-base text-secondary">
Ein Studienprojekt Ein Studienprojekt
</p> </p>
</div> </div>
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-primary);"> <div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-primary);">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;"> <div class="flex items-center gap-3 mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/> <path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/> <path d="M9 11V7a3 3 0 0 1 6 0v4"/>
</svg> </svg>
<h2 style="margin: 0; color: var(--color-primary);">Das Ziel</h2> <h2 class="m-0 text-primary">Das Ziel</h2>
</div> </div>
<p style="margin-bottom: 1rem; line-height: 1.7;"> <p class="mb-4 leading-relaxed">
Diese Seite soll meinen Kommilitonen der Seminargruppe CC24-w1 und anderen interessierten Forensikbegeisterten eine <strong>einfache und übersichtliche Orientierung</strong> Diese Seite soll meinen Kommilitonen der Seminargruppe CC24-w1 und anderen interessierten Forensikbegeisterten eine <strong>einfache und übersichtliche Orientierung</strong>
bieten: Welches Werkzeug, welche Methode eignet sich für welchen Zweck in der digitalen Forensik? bieten: Welches Werkzeug, welche Methode eignet sich für welchen Zweck in der digitalen Forensik?
</p> </p>
<p style="margin-bottom: 1rem; line-height: 1.7;"> <p class="mb-4 leading-relaxed">
Ich stelle euch dafür <strong>Infrastruktur</strong> zur Verfügung, mit der ihr spezialisierte Ich stelle euch dafür <strong>Infrastruktur</strong> zur Verfügung, mit der ihr spezialisierte
Forensik-Software direkt einsetzen und erforschen könnt sei es für <em>Bildungszwecke</em> oder im Forensik-Software direkt einsetzen und erforschen könnt sei es für <em>Bildungszwecke</em> oder im
<em>begrenzten Produktiveinsatz</em>. <em>begrenzten Produktiveinsatz</em>.
</p> </p>
<div style="background-color: var(--color-bg-secondary); padding: 1rem; border-radius: 0.5rem; margin-top: 1rem;"> <div class="bg-secondary p-4 rounded-lg mt-4">
<p style="margin: 0; font-size: 0.875rem; color: var(--color-text-secondary);"> <p class="m-0 text-sm text-secondary">
<strong>💡 Zugang:</strong> Die meisten Dienste sind bequem über das SSO der CC24-Cloud zugänglich, <strong>💡 Zugang:</strong> Die meisten Dienste sind bequem über das SSO der CC24-Cloud zugänglich,
mit nur wenigen Ausnahmen. Der Zugang ist entsprechend vorbehaltlich Absprache auf diesen Personenkreis limitiert. mit nur wenigen Ausnahmen. Der Zugang ist entsprechend vorbehaltlich Absprache auf diesen Personenkreis limitiert.
</p> </p>
@ -40,42 +40,42 @@ import BaseLayout from '../layouts/BaseLayout.astro';
</div> </div>
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-accent);"> <div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-accent);">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;"> <div class="flex items-center gap-3 mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/> <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9,22 9,12 15,12 15,22"/> <polyline points="9,22 9,12 15,12 15,22"/>
</svg> </svg>
<h2 style="margin: 0; color: var(--color-accent);">Wissenschaftliche Grundlage</h2> <h2 style="margin: 0; color: var(--color-accent);">Wissenschaftliche Grundlage</h2>
</div> </div>
<p style="margin-bottom: 1rem; line-height: 1.7;"> <p class="mb-4 leading-relaxed">
Bei der vorgestellten Software handelt es sich um eine <strong>kuratierte Auswahl</strong> von Anwendungen, Bei der vorgestellten Software handelt es sich um eine <strong>kuratierte Auswahl</strong> von Anwendungen,
vor allem aus dem Bereich der Open-Source-Software. vor allem aus dem Bereich der Open-Source-Software.
</p> </p>
<p style="margin-bottom: 1rem; line-height: 1.7;"> <p class="mb-4 leading-relaxed">
Das zugrunde liegende Modell folgt dem bewährten <strong>NIST-Framework</strong> nach Kent, Chevalier, Grance und Dang Das zugrunde liegende Modell folgt dem bewährten <strong>NIST-Framework</strong> nach Kent, Chevalier, Grance und Dang
(NIST SP 800-86), das ich wegen seiner <em>Einfachheit</em> und <em>praktischen Adaptierbarkeit</em> schätze: (NIST SP 800-86), das ich wegen seiner <em>Einfachheit</em> und <em>praktischen Adaptierbarkeit</em> schätze:
</p> </p>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; margin: 1.5rem 0; text-align: center;"> <div class="grid-4 gap-2 my-6 text-center">
<div style="background-color: var(--color-bg-secondary); padding: 1rem; border-radius: 0.5rem; border: 2px solid var(--color-accent);"> <div class="bg-secondary p-4 rounded-lg border-2 border-accent">
<strong style="color: var(--color-accent);">Datensammlung</strong> <strong class="text-accent">Datensammlung</strong>
</div> </div>
<div style="background-color: var(--color-bg-secondary); padding: 1rem; border-radius: 0.5rem; border: 2px solid var(--color-accent);"> <div class="bg-secondary p-4 rounded-lg border-2 border-accent">
<strong style="color: var(--color-accent);">Auswertung</strong> <strong class="text-accent">Auswertung</strong>
</div> </div>
<div style="background-color: var(--color-bg-secondary); padding: 1rem; border-radius: 0.5rem; border: 2px solid var(--color-accent);"> <div class="bg-secondary p-4 rounded-lg border-2 border-accent">
<strong style="color: var(--color-accent);">Analyse</strong> <strong class="text-accent">Analyse</strong>
</div> </div>
<div style="background-color: var(--color-bg-secondary); padding: 1rem; border-radius: 0.5rem; border: 2px solid var(--color-accent);"> <div class="bg-secondary p-4 rounded-lg border-2 border-accent">
<strong style="color: var(--color-accent);">Bericht</strong> <strong class="text-accent">Bericht</strong>
</div> </div>
</div> </div>
<p style="margin: 0; font-size: 0.875rem; color: var(--color-text-secondary);"> <p class="m-0 text-sm text-secondary">
Diese Prozessphasen werden in der <strong>Matrix-Ansicht</strong> auf der Startseite mit forensischen Wissensdomänen verknüpft. Diese Prozessphasen werden in der <strong>Matrix-Ansicht</strong> auf der Startseite mit forensischen Wissensdomänen verknüpft.
</p> </p>
</div> </div>
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-warning);"> <div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-warning);">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;"> <div class="flex items-center gap-3 mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-warning)" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-warning)" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/> <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="M9 12l2 2 4-4"/> <path d="M9 12l2 2 4-4"/>
@ -83,9 +83,9 @@ import BaseLayout from '../layouts/BaseLayout.astro';
<h2 style="margin: 0; color: var(--color-warning);">Weshalb der Fokus auf Open Source?</h2> <h2 style="margin: 0; color: var(--color-warning);">Weshalb der Fokus auf Open Source?</h2>
</div> </div>
<div style="background: linear-gradient(135deg, var(--color-warning) 0%, var(--color-accent) 100%); color: white; padding: 1.5rem; border-radius: 0.75rem; margin-bottom: 1.5rem;"> <div class="card-warning rounded-xl p-6 mb-6">
<h3 style="margin: 0 0 1rem 0; font-size: 1.125rem;">💡 Zentrale Hypothese</h3> <h3 class="m-0 mb-4 text-lg">💡 Zentrale Hypothese</h3>
<p style="margin: 0; line-height: 1.6; font-style: italic;"> <p class="m-0 leading-normal italic">
"Die IT-Forensik verlangt nach der Anwendung von wissenschaftlichen Methoden und Objektivität. "Die IT-Forensik verlangt nach der Anwendung von wissenschaftlichen Methoden und Objektivität.
Die wissenschaftliche Methode verlangt einen transparenten Untersuchungsprozess, der in der Hauptverhandlung Die wissenschaftliche Methode verlangt einen transparenten Untersuchungsprozess, der in der Hauptverhandlung
bzw. dem Auftraggeber gegenüber stets nachvollziehbar und erklärbar sein muss. bzw. dem Auftraggeber gegenüber stets nachvollziehbar und erklärbar sein muss.
@ -127,13 +127,13 @@ import BaseLayout from '../layouts/BaseLayout.astro';
</div> </div>
<div id= "support" class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-primary);"> <div id= "support" class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-primary);">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;"> <div class="flex items-center gap-3 mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/> <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
</svg> </svg>
<h2 style="margin: 0; color: var(--color-primary);">Unterstützung und Kontakt</h2> <h2 class="m-0 text-primary">Unterstützung und Kontakt</h2>
</div> </div>
<p style="margin-bottom: 1rem; line-height: 1.7;"> <p class="mb-4 leading-relaxed">
Falls eine Anwendung nicht wie vorgesehen funktioniert, ihr Unterstützung braucht, der Speicherplatz ausgeht Falls eine Anwendung nicht wie vorgesehen funktioniert, ihr Unterstützung braucht, der Speicherplatz ausgeht
oder sonstige Probleme auftreten: <strong>Schreibt mir einfach auf Signal</strong> oder an <a href="mailto:mstoeck3@hs-mittweida.de">mstoeck3@hs-mittweida.de</a>. oder sonstige Probleme auftreten: <strong>Schreibt mir einfach auf Signal</strong> oder an <a href="mailto:mstoeck3@hs-mittweida.de">mstoeck3@hs-mittweida.de</a>.
</p> </p>
@ -172,7 +172,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
</div> </div>
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-accent);"> <div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-accent);">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;"> <div class="flex items-center gap-3 mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/> <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"/> <circle cx="8.5" cy="7" r="4"/>

View File

@ -51,7 +51,7 @@
<div class="spinner"></div> <div class="spinner"></div>
<h2>Processing authentication...</h2> <h2>Processing authentication...</h2>
<p>Please wait while we complete your login.</p> <p>Please wait while we complete your login.</p>
<div id="error-message" style="display: none;" class="error"></div> <div id="error-message" class="hidden error"></div>
</div> </div>
<script> <script>

View File

@ -14,8 +14,8 @@ const { authenticated, userEmail, userId } = authResult;
--- ---
<BaseLayout title="Contribute" description="Inhalte zum ForensicPathways beitragen"> <BaseLayout title="Contribute" description="Inhalte zum ForensicPathways beitragen">
<section style="padding: 2rem 0;"> <section class="section-padding">
<div style="text-align: center; margin-bottom: 3rem; padding: 2rem; background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%); color: white; border-radius: 1rem; border: 1px solid var(--color-border);"> <div class="header-center header-primary mb-8">
<h1 style="margin-bottom: 1rem; font-size: 2.5rem;"> <h1 style="margin-bottom: 1rem; font-size: 2.5rem;">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.75rem; vertical-align: middle;"> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.75rem; vertical-align: middle;">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/> <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
@ -49,7 +49,7 @@ const { authenticated, userEmail, userId } = authResult;
<div class="card" <div class="card"
style="padding: 2rem; border-left: 4px solid var(--color-primary); transition: var(--transition-fast); style="padding: 2rem; border-left: 4px solid var(--color-primary); transition: var(--transition-fast);
display:flex; flex-direction:column;"> display:flex; flex-direction:column;">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem;"> <div class="flex items-center gap-4 mb-6">
<div style="width: 48px; height: 48px; background-color: var(--color-primary); border-radius: 0.5rem; display: flex; align-items: center; justify-content: center;"> <div style="width: 48px; height: 48px; background-color: var(--color-primary); border-radius: 0.5rem; display: flex; align-items: center; justify-content: center;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/> <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
@ -108,7 +108,7 @@ const { authenticated, userEmail, userId } = authResult;
style="padding: 2rem; border-left: 4px solid var(--color-accent); cursor: pointer; transition: var(--transition-fast); style="padding: 2rem; border-left: 4px solid var(--color-accent); cursor: pointer; transition: var(--transition-fast);
display:flex; flex-direction:column;" display:flex; flex-direction:column;"
onclick="window.location.href='/contribute/knowledgebase'"> onclick="window.location.href='/contribute/knowledgebase'">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem;"> <div class="flex items-center gap-4 mb-6">
<div style="width: 48px; height: 48px; background-color: var(--color-accent); border-radius: 0.5rem; display: flex; align-items: center; justify-content: center;"> <div style="width: 48px; height: 48px; background-color: var(--color-accent); border-radius: 0.5rem; display: flex; align-items: center; justify-content: center;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" 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 d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
@ -142,7 +142,7 @@ const { authenticated, userEmail, userId } = authResult;
<div class="card" <div class="card"
style="padding: 2rem; border-left: 4px solid var(--color-accent); cursor: pointer; transition: var(--transition-fast); style="padding: 2rem; border-left: 4px solid var(--color-accent); cursor: pointer; transition: var(--transition-fast);
display:flex; flex-direction:column;"> display:flex; flex-direction:column;">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem;"> <div class="flex items-center gap-4 mb-6">
<div style="width: 48px; height: 48px; background-color: var(--color-warning); border-radius: 0.5rem; display: flex; align-items: center; justify-content: center;"> <div style="width: 48px; height: 48px; background-color: var(--color-warning); border-radius: 0.5rem; display: flex; align-items: center; justify-content: center;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
<circle cx="12" cy="12" r="10"/> <circle cx="12" cy="12" r="10"/>

View File

@ -20,7 +20,7 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
<BaseLayout title="Contribute Knowledge Base Article"> <BaseLayout title="Contribute Knowledge Base Article">
<div class="container" style="max-width: 900px; margin: 0 auto; padding: 2rem 1rem;"> <div class="container" style="max-width: 900px; margin: 0 auto; padding: 2rem 1rem;">
<div style="text-align: center; margin-bottom: 2rem; padding: 2rem; background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%); color: white; border-radius: 1rem;"> <div class="header-center header-primary rounded-xl">
<h1 style="margin-bottom: 1rem; font-size: 2rem;">Knowledgebase-Artikel</h1> <h1 style="margin-bottom: 1rem; font-size: 2rem;">Knowledgebase-Artikel</h1>
<p style="margin: 0.5rem 0; opacity: 0.9;">Danke für deinen Beitrag!</p> <p style="margin: 0.5rem 0; opacity: 0.9;">Danke für deinen Beitrag!</p>
{userEmail && <p style="margin: 0.5rem 0; opacity: 0.8;"><strong>Eingeloggt als:</strong> {userEmail}</p>} {userEmail && <p style="margin: 0.5rem 0; opacity: 0.8;"><strong>Eingeloggt als:</strong> {userEmail}</p>}
@ -114,7 +114,7 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
<div class="form-group"> <div class="form-group">
<label class="form-label">Dokumente, Bilder, Videos (Optional)</label> <label class="form-label">Dokumente, Bilder, Videos (Optional)</label>
<div class="upload-area" id="upload-area"> <div class="upload-area" id="upload-area">
<input type="file" id="file-input" multiple accept=".pdf,.doc,.docx,.txt,.md,.zip,.png,.jpg,.jpeg,.gif,.mp4,.webm" style="display: none;"> <input type="file" id="file-input" multiple accept=".pdf,.doc,.docx,.txt,.md,.zip,.png,.jpg,.jpeg,.gif,.mp4,.webm" class="hidden">
<div class="upload-placeholder"> <div class="upload-placeholder">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"> <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
@ -125,7 +125,7 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
<small>Die Dateien landen in der CC24-Cloud. Keine Malware.</small> <small>Die Dateien landen in der CC24-Cloud. Keine Malware.</small>
</div> </div>
</div> </div>
<div id="file-list" class="file-list" style="display: none;"> <div id="file-list" class="file-list hidden">
<h5>Ausgewählte Dateien</h5> <h5>Ausgewählte Dateien</h5>
<div id="files-container"></div> <div id="files-container"></div>
</div> </div>
@ -177,7 +177,7 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
<a href="/" class="btn btn-secondary">Abbruch</a> <a href="/" class="btn btn-secondary">Abbruch</a>
<button type="submit" id="submit-btn" class="btn btn-accent"> <button type="submit" id="submit-btn" class="btn btn-accent">
<span id="submit-text">Abschicken</span> <span id="submit-text">Abschicken</span>
<span id="submit-spinner" style="display: none;">⏳</span> <span id="submit-spinner" class="hidden">⏳</span>
</button> </button>
</div> </div>
</form> </form>
@ -193,7 +193,7 @@ const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.n
Your knowledgebase article has been submitted as an issue for review by maintainers. Your knowledgebase article has been submitted as an issue for review by maintainers.
</p> </p>
<div style="display:flex; gap:1rem; justify-content:center;"> <div style="display:flex; gap:1rem; justify-content:center;">
<a id="issue-link" href="#" target="_blank" class="btn btn-primary" style="display:none;">View Issue</a> <a id="issue-link" href="#" target="_blank" class="btn btn-primary hidden">View Issue</a>
<a href="/" class="btn btn-secondary">Back to Home</a> <a href="/" class="btn btn-secondary">Back to Home</a>
</div> </div>
</div> </div>

View File

@ -27,7 +27,7 @@ const isEdit = !!editTool;
<BaseLayout title={isEdit ? `Edit ${editTool?.name}` : 'Contribute Tool'}> <BaseLayout title={isEdit ? `Edit ${editTool?.name}` : 'Contribute Tool'}>
<div class="container" style="max-width: 900px; margin: 0 auto; padding: 2rem 1rem;"> <div class="container" style="max-width: 900px; margin: 0 auto; padding: 2rem 1rem;">
<div style="text-align: center; margin-bottom: 2rem; padding: 2rem; background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%); color: white; border-radius: 1rem;"> <div class="header-center header-primary rounded-xl">
<h1 style="margin-bottom: 1rem; font-size: 2rem;">{isEdit ? `Edit: ${editTool?.name}` : 'Tool / Methode / Konzept beitragen'}</h1> <h1 style="margin-bottom: 1rem; font-size: 2rem;">{isEdit ? `Edit: ${editTool?.name}` : 'Tool / Methode / Konzept beitragen'}</h1>
<p style="margin: 0.5rem 0; opacity: 0.9;"> <p style="margin: 0.5rem 0; opacity: 0.9;">
{isEdit {isEdit
@ -105,7 +105,7 @@ const isEdit = !!editTool;
<div style="border: 1px solid var(--color-border); border-radius: 0.5rem; padding: 1.5rem; margin-bottom: 2rem;"> <div style="border: 1px solid var(--color-border); border-radius: 0.5rem; padding: 1.5rem; margin-bottom: 2rem;">
<h3 style="margin: 0 0 1.5rem 0; color: var(--color-primary); border-bottom: 1px solid var(--color-border); padding-bottom: 0.5rem;">Kategorien</h3> <h3 style="margin: 0 0 1.5rem 0; color: var(--color-primary); border-bottom: 1px solid var(--color-border); padding-bottom: 0.5rem;">Kategorien</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;"> <div class="grid-2 gap-8">
<div> <div>
<label style="display: block; margin-bottom: 0.75rem; font-weight: 600;">Forensische Domänen</label> <label style="display: block; margin-bottom: 0.75rem; font-weight: 600;">Forensische Domänen</label>
<div style="display: grid; gap: 0.5rem;"> <div style="display: grid; gap: 0.5rem;">
@ -166,7 +166,7 @@ const isEdit = !!editTool;
</div> </div>
</div> </div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;"> <div class="grid-2 gap-8">
<div> <div>
<label for="accessType" style="display: block; margin-bottom: 0.75rem; font-weight: 600;">Zugriff über:</label> <label for="accessType" style="display: block; margin-bottom: 0.75rem; font-weight: 600;">Zugriff über:</label>
<select id="accessType" name="accessType"> <select id="accessType" name="accessType">
@ -257,7 +257,7 @@ const isEdit = !!editTool;
<h3 style="margin-bottom: 1rem;">Contribution Submitted!</h3> <h3 style="margin-bottom: 1rem;">Contribution Submitted!</h3>
<p id="success-message" style="margin-bottom: 1.5rem;">Your contribution has been submitted successfully.</p> <p id="success-message" style="margin-bottom: 1.5rem;">Your contribution has been submitted successfully.</p>
<div style="display: flex; gap: 1rem; justify-content: center;"> <div style="display: flex; gap: 1rem; justify-content: center;">
<a id="pr-link" href="#" target="_blank" class="btn btn-primary" style="display: none;">View Pull Request</a> <a id="pr-link" href="#" target="_blank" class="btn btn-primary hidden">View Pull Request</a>
<a href="/" class="btn btn-secondary">Back to Home</a> <a href="/" class="btn btn-secondary">Back to Home</a>
</div> </div>
</div> </div>

View File

@ -3,8 +3,8 @@ import BaseLayout from '../layouts/BaseLayout.astro';
--- ---
<BaseLayout title="Impressum" description="ForensicPathways - Impressum"> <BaseLayout title="Impressum" description="ForensicPathways - Impressum">
<section style="padding: 2rem 0; max-width: 900px; margin: 0 auto;"> <section class="section container-narrow">
<div style="text-align: center; margin-bottom: 3rem; padding: 2rem; background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%); border-radius: 1rem; border: 1px solid var(--color-border);"> <div class="header-center header-gradient mb-8">
<p>Bei dieser Webseite handelt es sich um ein privates Werk. Keine Bildungseinrichtung, kein Unternehmen sind finanziell oder moderierend Teil dieses Projekts.</p> <p>Bei dieser Webseite handelt es sich um ein privates Werk. Keine Bildungseinrichtung, kein Unternehmen sind finanziell oder moderierend Teil dieses Projekts.</p>
<h1>Impressum</h1> <h1>Impressum</h1>
<p>Angaben gemäß § 5 DDG</p> <p>Angaben gemäß § 5 DDG</p>
@ -16,7 +16,7 @@ Mario Stöckl<br>
</p> </p>
<p><strong>Kontakt:</strong> <br> <p><strong>Kontakt:</strong> <br>
Telefon: 01522-7086296<br> Telefon: 01522-7086296<br>
E-Mail: <a href='mailto:mario.stoeckl@posteo.de'>mario.stoeckl@posteo.de</a> E-Mail: <a href='mailto:mstoeck3@hs-mittweida.de'>mstoeck3@hs-mittweida.de</a>
</p> </p>
<p><strong>PGP-Schlüssel:</strong><br> <p><strong>PGP-Schlüssel:</strong><br>
Zur sicheren Kommunikation per E-Mail können Sie folgenden PGP-Schlüssel verwenden:<br><br> Zur sicheren Kommunikation per E-Mail können Sie folgenden PGP-Schlüssel verwenden:<br><br>

View File

@ -60,7 +60,7 @@ const phases = data.phases;
<p class="approach-info"> <p class="approach-info">
<span class="info-icon"></span> <span class="info-icon"></span>
Die lila gekennzeichneten Einträge sind über das Single-Sign-On der CC24-Cloud direkt zugänglich. Die lila gekennzeichneten Einträge sind über das Single-Sign-On der CC24-Cloud direkt zugänglich.
Teilnehmer der Seminargruppe CC24-w1 können die gehostete Infrastruktur nutzen. Teilnehmer der Seminargruppe CC24-w1 (oder andere Berechtigte) können die gehostete Infrastruktur nutzen.
<a href="/about#support">Kontakt bei Problemen</a> <a href="/about#support">Kontakt bei Problemen</a>
</p> </p>
@ -143,8 +143,8 @@ const phases = data.phases;
<TargetedScenarios /> <TargetedScenarios />
<section id="filters-section" style="padding: 2rem 0;"> <section id="filters-section" class="section">
<div style="text-align: center; margin-bottom: 2rem;"> <div class="content-center-lg">
<h3 style="color: var(--color-text); margin-bottom: 0.5rem;">Alle verfügbaren Werkzeuge durchsuchen</h3> <h3 style="color: var(--color-text); margin-bottom: 0.5rem;">Alle verfügbaren Werkzeuge durchsuchen</h3>
<p style="color: var(--color-text-secondary); margin: 0;"> <p style="color: var(--color-text-secondary); margin: 0;">
Nutzen Sie die erweiterten Filter und Kategorien für eine detaillierte Suche Nutzen Sie die erweiterten Filter und Kategorien für eine detaillierte Suche

View File

@ -42,7 +42,7 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
--- ---
<BaseLayout title="Knowledgebase" description="Extended documentation and insights for DFIR tools"> <BaseLayout title="Knowledgebase" description="Extended documentation and insights for DFIR tools">
<section style="padding: 2rem 0;"> <section class="section-padding">
<div class="text-center mb-8 p-8 bg-secondary rounded-lg border"> <div class="text-center mb-8 p-8 bg-secondary rounded-lg border">
<h1 class="mb-4 text-2xl text-primary">Knowledgebase</h1> <h1 class="mb-4 text-2xl text-primary">Knowledgebase</h1>
<p class="text-lg text-secondary mb-4"> <p class="text-lg text-secondary mb-4">
@ -209,13 +209,13 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
)} )}
</div> </div>
<div id="no-kb-results" class="card text-center p-8" style="display: none;"> <div id="no-kb-results" class="card text-center p-8 hidden">
<h3 class="text-secondary mb-2">Keine Ergebnisse gefunden</h3> <h3 class="text-secondary mb-2">Keine Ergebnisse gefunden</h3>
<p class="text-secondary">Versuchen Sie es mit anderen Suchbegriffen.</p> <p class="text-secondary">Versuchen Sie es mit anderen Suchbegriffen.</p>
</div> </div>
</section> </section>
<div id="fab-container" class="fixed bottom-8 right-8 z-50" style="display: none;"> <div id="fab-container" class="fixed bottom-8 right-8 z-50 hidden">
<ContributionButton <ContributionButton
type="write" type="write"
variant="primary" variant="primary"

View File

@ -14,8 +14,8 @@ const hostedServices = data.tools.filter((tool: any) => {
--- ---
<BaseLayout title="Service Status"> <BaseLayout title="Service Status">
<section style="padding: 2rem 0;"> <section class="section-padding">
<h1 style="text-align: center; margin-bottom: 1rem;">Service Status</h1> <h1 class="content-center">Service Status</h1>
<p class="text-muted" style="text-align: center; max-width: 600px; margin: 0 auto 3rem;"> <p class="text-muted" style="text-align: center; max-width: 600px; margin: 0 auto 3rem;">
Live-Monitoring zum Onlinestatus der Dienste. Live-Monitoring zum Onlinestatus der Dienste.
</p> </p>

2920
src/styles/global copy.css Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff