improve layout knowledgebase
This commit is contained in:
parent
58329bb8ce
commit
30be8cca93
2
.astro/content.d.ts
vendored
2
.astro/content.d.ts
vendored
@ -202,6 +202,6 @@ declare module 'astro:content' {
|
|||||||
LiveContentConfig['collections'][C]['loader']
|
LiveContentConfig['collections'][C]['loader']
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type ContentConfig = never;
|
export type ContentConfig = typeof import("../src/content/config.js");
|
||||||
export type LiveContentConfig = never;
|
export type LiveContentConfig = never;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
336
context.md
Normal file
336
context.md
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
# ForensicPathways Architecture System Prompt
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
ForensicPathways is a curated directory of Digital Forensics and Incident Response (DFIR) tools, methods, and concepts built with Astro and TypeScript. It serves as an educational and professional resource for forensic investigators, following the NIST SP 800-86 framework (Kent, Chevalier, Grance & Dang).
|
||||||
|
|
||||||
|
## Core Technology Stack
|
||||||
|
- **Framework**: Astro (static site generator with islands architecture)
|
||||||
|
- **Language**: TypeScript with strict typing
|
||||||
|
- **Styling**: Vanilla CSS with custom properties (CSS variables)
|
||||||
|
- **Data Storage**: YAML files for tool catalog
|
||||||
|
- **Content**: Astro Content Collections for knowledge base articles
|
||||||
|
- **Authentication**: OIDC (OpenID Connect) with configurable requirements
|
||||||
|
- **AI Integration**: Mistral API for workflow recommendations
|
||||||
|
- **File Storage**: Nextcloud with local fallback
|
||||||
|
- **Version Control**: Git integration for community contributions
|
||||||
|
|
||||||
|
## Data Model Architecture
|
||||||
|
|
||||||
|
### Core Entity: Tool
|
||||||
|
```yaml
|
||||||
|
name: string # Tool identifier
|
||||||
|
icon: string? # Emoji icon
|
||||||
|
type: 'software'|'method'|'concept' # Tool classification
|
||||||
|
description: string # Detailed description
|
||||||
|
domains: string[] # Forensic domains (incident-response, malware-analysis, etc.)
|
||||||
|
phases: string[] # NIST framework phases (data-collection, examination, analysis, reporting)
|
||||||
|
platforms: string[] # Operating systems (for software only)
|
||||||
|
skillLevel: string # novice|beginner|intermediate|advanced|expert
|
||||||
|
url: string # Primary documentation/homepage
|
||||||
|
projectUrl: string? # Hosted instance URL (CC24 server)
|
||||||
|
license: string? # Software license
|
||||||
|
knowledgebase: boolean? # Has detailed documentation
|
||||||
|
tags: string[] # Searchable keywords
|
||||||
|
related_concepts: string[]? # Links to concept-type tools
|
||||||
|
```
|
||||||
|
|
||||||
|
### Taxonomies
|
||||||
|
- **Domains**: Forensic specializations (7 main domains)
|
||||||
|
- **Phases**: NIST investigation phases (4 phases)
|
||||||
|
- **Domain-Agnostic-Software**: Cross-cutting tools and platforms
|
||||||
|
- **Skill Levels**: Standardized competency requirements
|
||||||
|
|
||||||
|
## Component Architecture
|
||||||
|
|
||||||
|
### Layout System
|
||||||
|
- **BaseLayout.astro**: Global layout with theme system, authentication setup, shared utilities
|
||||||
|
- **Navigation.astro**: Main navigation with active state management
|
||||||
|
- **Footer.astro**: Site footer with links and licensing info
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
- **ToolCard.astro**: Individual tool display with metadata, actions, and type-specific styling
|
||||||
|
- **ToolMatrix.astro**: Matrix view showing tools by domain/phase intersection
|
||||||
|
- **ToolFilters.astro**: Search, filtering, and view controls
|
||||||
|
- **AIQueryInterface.astro**: AI-powered workflow recommendation system
|
||||||
|
|
||||||
|
### Utility Components
|
||||||
|
- **ShareButton.astro**: URL sharing with multiple view options
|
||||||
|
- **ContributionButton.astro**: Authenticated contribution links
|
||||||
|
- **ThemeToggle.astro**: Light/dark/auto theme switching
|
||||||
|
|
||||||
|
## Feature Systems
|
||||||
|
|
||||||
|
### 1. Authentication System (`src/utils/auth.ts`)
|
||||||
|
- **OIDC Integration**: Uses environment-configured provider
|
||||||
|
- **Contextual Requirements**: Different auth requirements for contributions vs AI features
|
||||||
|
- **Session Management**: JWT-based with configurable expiration
|
||||||
|
- **Client-Side Utilities**: Window-level auth checking functions
|
||||||
|
|
||||||
|
### 2. AI Recommendation System
|
||||||
|
- **Dual Modes**:
|
||||||
|
- Workflow mode: Multi-phase recommendations for scenarios
|
||||||
|
- Tool mode: Specific tool recommendations for problems
|
||||||
|
- **Rate Limiting**: Queue-based system with status updates
|
||||||
|
- **Data Integration**: Uses compressed tool database for AI context
|
||||||
|
- **Response Validation**: Ensures AI only recommends existing tools
|
||||||
|
|
||||||
|
### 3. Contribution System
|
||||||
|
- **Git Integration**: Automated issue creation via Gitea/GitHub/GitLab APIs
|
||||||
|
- **Tool Contributions**: Form-based tool submissions
|
||||||
|
- **Knowledge Base**: Rich text with file upload support
|
||||||
|
- **Validation**: Client and server-side validation with Zod schemas
|
||||||
|
|
||||||
|
### 4. File Upload System
|
||||||
|
- **Primary**: Nextcloud integration with public link generation
|
||||||
|
- **Fallback**: Local file storage with public URL generation
|
||||||
|
- **Validation**: File type and size restrictions
|
||||||
|
- **Rate Limiting**: Per-user upload quotas
|
||||||
|
|
||||||
|
## Styling Architecture
|
||||||
|
|
||||||
|
### CSS Custom Properties System
|
||||||
|
```css
|
||||||
|
/* Core color system */
|
||||||
|
--color-primary: /* Adaptive based on theme */
|
||||||
|
--color-accent: /* Secondary brand color */
|
||||||
|
--color-bg: /* Main background */
|
||||||
|
--color-text: /* Primary text */
|
||||||
|
|
||||||
|
/* Component-specific colors */
|
||||||
|
--color-hosted: /* CC24 server hosted tools */
|
||||||
|
--color-oss: /* Open source tools */
|
||||||
|
--color-method: /* Methodology entries */
|
||||||
|
--color-concept: /* Knowledge concepts */
|
||||||
|
|
||||||
|
/* Theme system */
|
||||||
|
[data-theme="light"] { /* Light theme values */ }
|
||||||
|
[data-theme="dark"] { /* Dark theme values */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Styling Patterns
|
||||||
|
- **Card System**: Consistent `.card` base with type-specific variants
|
||||||
|
- **Badge System**: Status and metadata indicators
|
||||||
|
- **Button System**: Semantic button classes with size variants
|
||||||
|
- **Grid System**: CSS Grid with responsive breakpoints
|
||||||
|
|
||||||
|
## Data Flow Architecture
|
||||||
|
|
||||||
|
### 1. Data Loading (`src/utils/dataService.ts`)
|
||||||
|
```typescript
|
||||||
|
// Daily randomization with seeded shuffle
|
||||||
|
getToolsData() → shuffled tools array
|
||||||
|
getCompressedToolsDataForAI() → AI-optimized dataset
|
||||||
|
clearCache() → cache invalidation
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Client-Side State Management
|
||||||
|
- **Global Tools Data**: Attached to `window.toolsData`
|
||||||
|
- **Filter State**: Component-local state with event emission
|
||||||
|
- **View State**: Grid/Matrix/AI view switching
|
||||||
|
- **Modal State**: Tool detail overlays with multi-modal support
|
||||||
|
|
||||||
|
### 3. Search and Filtering
|
||||||
|
- **Text Search**: Name, description, tags
|
||||||
|
- **Faceted Filtering**: Domain, phase, skill level, license type
|
||||||
|
- **Tag Cloud**: Frequency-weighted tag selection
|
||||||
|
- **Real-time Updates**: Immediate filtering with event system
|
||||||
|
|
||||||
|
## API Architecture
|
||||||
|
|
||||||
|
### Endpoint Structure
|
||||||
|
```
|
||||||
|
/api/auth/ # Authentication endpoints
|
||||||
|
login.ts # OIDC initiation
|
||||||
|
process.ts # OIDC callback processing
|
||||||
|
status.ts # Auth status checking
|
||||||
|
|
||||||
|
/api/contribute/ # Contribution endpoints
|
||||||
|
tool.ts # Tool submissions
|
||||||
|
knowledgebase.ts # KB article submissions
|
||||||
|
|
||||||
|
/api/ai/ # AI features
|
||||||
|
query.ts # Workflow recommendations
|
||||||
|
queue-status.ts # Queue monitoring
|
||||||
|
|
||||||
|
/api/upload/ # File handling
|
||||||
|
media.ts # File upload processing
|
||||||
|
|
||||||
|
/api/health.ts # System health check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Patterns
|
||||||
|
All APIs use consolidated response utilities (`src/utils/api.ts`):
|
||||||
|
- **Success**: `apiResponse.success()`, `apiResponse.created()`
|
||||||
|
- **Errors**: `apiError.badRequest()`, `apiError.unauthorized()`, etc.
|
||||||
|
- **Server Errors**: `apiServerError.internal()`, etc.
|
||||||
|
|
||||||
|
## File Organization Patterns
|
||||||
|
|
||||||
|
### Page Structure
|
||||||
|
- **Static Pages**: About, impressum, status
|
||||||
|
- **Dynamic Pages**: Tool details, knowledge base articles
|
||||||
|
- **Authenticated Pages**: Contribution forms
|
||||||
|
- **API Routes**: RESTful endpoints with consistent naming
|
||||||
|
|
||||||
|
### Utility Organization
|
||||||
|
- **Tool Operations**: `toolHelpers.ts` - Core tool manipulation
|
||||||
|
- **Data Management**: `dataService.ts` - YAML loading and caching
|
||||||
|
- **Authentication**: `auth.ts` - OIDC flow and session management
|
||||||
|
- **External APIs**: `gitContributions.ts`, `nextcloud.ts`
|
||||||
|
- **Rate Limiting**: `rateLimitedQueue.ts` - AI request queuing
|
||||||
|
|
||||||
|
## Key Architectural Decisions
|
||||||
|
|
||||||
|
### 1. Static-First with Dynamic Islands
|
||||||
|
- Astro's islands architecture for interactivity
|
||||||
|
- Static generation for performance
|
||||||
|
- Selective hydration for complex components
|
||||||
|
|
||||||
|
### 2. YAML-Based Data Management
|
||||||
|
- Human-readable tool catalog
|
||||||
|
- Git-friendly versioning
|
||||||
|
- Type-safe loading with Zod validation
|
||||||
|
|
||||||
|
### 3. Contextual Authentication
|
||||||
|
- Optional authentication based on feature
|
||||||
|
- Graceful degradation for unauthenticated users
|
||||||
|
- Environment-configurable requirements
|
||||||
|
|
||||||
|
### 4. Multi-Modal UI Patterns
|
||||||
|
- Grid view for browsing
|
||||||
|
- Matrix view for relationship visualization
|
||||||
|
- AI interface for guided recommendations
|
||||||
|
|
||||||
|
### 5. Progressive Enhancement
|
||||||
|
- Core functionality works without JavaScript
|
||||||
|
- Enhanced features require client-side hydration
|
||||||
|
- Responsive design with mobile-first approach
|
||||||
|
|
||||||
|
## Development Patterns
|
||||||
|
|
||||||
|
### Component Design
|
||||||
|
- Props interfaces with TypeScript
|
||||||
|
- Consistent styling via CSS classes
|
||||||
|
- Event-driven communication between components
|
||||||
|
- Server-side rendering with client-side enhancement
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Comprehensive try-catch in API routes
|
||||||
|
- User-friendly error messages
|
||||||
|
- Graceful fallbacks for external services
|
||||||
|
- Logging for debugging and monitoring
|
||||||
|
|
||||||
|
### Performance Optimizations
|
||||||
|
- Daily data randomization with caching
|
||||||
|
- Compressed datasets for AI context
|
||||||
|
- Rate limiting for external API calls
|
||||||
|
- Efficient DOM updates with targeted selectors
|
||||||
|
|
||||||
|
This architecture emphasizes maintainability, user experience, and extensibility while managing the complexity of a multi-feature forensics tool directory.
|
||||||
|
|
||||||
|
|
||||||
|
## Absolutely Essential (Core Architecture - 8 files)
|
||||||
|
|
||||||
|
**File**: `src/data/tools.yaml.example`
|
||||||
|
**Why**: Defines the core data model - what a "tool" is, the schema, domains, phases, etc. Without this, an AI can't understand what the application manages.
|
||||||
|
|
||||||
|
**File**: `src/pages/index.astro`
|
||||||
|
**Why**: Main application entry point showing the core functionality (filters, matrix, AI interface, tool grid). Contains the primary application logic flow.
|
||||||
|
|
||||||
|
**File**: `src/layouts/BaseLayout.astro`
|
||||||
|
**Why**: Global layout with theme system, authentication setup, and shared utility functions. Shows the overall app structure.
|
||||||
|
|
||||||
|
**File**: `src/utils/dataService.ts`
|
||||||
|
**Why**: Core data loading and processing logic. Essential for understanding how data flows through the application.
|
||||||
|
|
||||||
|
**File**: `src/utils/toolHelpers.ts`
|
||||||
|
**Why**: Core utility functions used throughout the app (slug creation, tool identification, hosting checks).
|
||||||
|
|
||||||
|
**File**: `src/styles/global.css`
|
||||||
|
**Why**: Complete styling system - defines visual architecture, component styling, theme system, responsive design.
|
||||||
|
|
||||||
|
**File**: `src/env.d.ts`
|
||||||
|
**Why**: Global TypeScript definitions and window interface extensions. Shows the global API surface.
|
||||||
|
|
||||||
|
**File**: `src/components/ToolCard.astro`
|
||||||
|
**Why**: Shows how the core entity (tools) are rendered and structured. Represents the component architecture pattern.
|
||||||
|
|
||||||
|
## Secondary Priority (Major Features - 4 files)
|
||||||
|
|
||||||
|
**File**: `src/components/ToolMatrix.astro` - Matrix view functionality
|
||||||
|
**File**: `src/components/AIQueryInterface.astro` - AI recommendation system
|
||||||
|
**File**: `src/utils/auth.ts` - Authentication system
|
||||||
|
**File**: `src/content/config.ts` - Content collection schema
|
||||||
|
|
||||||
|
## Strategy for Context Management
|
||||||
|
|
||||||
|
1. **Always provide**: The 8 essential files above
|
||||||
|
2. **Add selectively**: Include 1-3 secondary files based on the specific development task
|
||||||
|
3. **Reference others**: Mention other relevant files by name/purpose without including full content
|
||||||
|
|
||||||
|
user01@altiera /v/h/u/P/cc24-hub (main)> tree src
|
||||||
|
src
|
||||||
|
├── components
|
||||||
|
│ ├── AIQueryInterface.astro
|
||||||
|
│ ├── ContributionButton.astro
|
||||||
|
│ ├── Footer.astro
|
||||||
|
│ ├── Navigation.astro
|
||||||
|
│ ├── ShareButton.astro
|
||||||
|
│ ├── ThemeToggle.astro
|
||||||
|
│ ├── ToolCard.astro
|
||||||
|
│ ├── ToolFilters.astro
|
||||||
|
│ └── ToolMatrix.astro
|
||||||
|
├── content
|
||||||
|
│ ├── config.ts
|
||||||
|
│ └── knowledgebase
|
||||||
|
│ ├── android-logical-imaging.md
|
||||||
|
│ ├── kali-linux.md
|
||||||
|
│ ├── misp.md
|
||||||
|
│ ├── nextcloud.md
|
||||||
|
│ ├── regular-expressions-regex.md
|
||||||
|
│ └── velociraptor.md
|
||||||
|
├── data
|
||||||
|
│ ├── tools.yaml
|
||||||
|
│ └── tools.yaml.example
|
||||||
|
├── env.d.ts
|
||||||
|
├── layouts
|
||||||
|
│ └── BaseLayout.astro
|
||||||
|
├── pages
|
||||||
|
│ ├── about.astro
|
||||||
|
│ ├── api
|
||||||
|
│ │ ├── ai
|
||||||
|
│ │ │ ├── query.ts
|
||||||
|
│ │ │ └── queue-status.ts
|
||||||
|
│ │ ├── auth
|
||||||
|
│ │ │ ├── login.ts
|
||||||
|
│ │ │ ├── process.ts
|
||||||
|
│ │ │ └── status.ts
|
||||||
|
│ │ ├── contribute
|
||||||
|
│ │ │ ├── knowledgebase.ts
|
||||||
|
│ │ │ └── tool.ts
|
||||||
|
│ │ ├── health.ts
|
||||||
|
│ │ └── upload
|
||||||
|
│ │ └── media.ts
|
||||||
|
│ ├── auth
|
||||||
|
│ │ └── callback.astro
|
||||||
|
│ ├── contribute
|
||||||
|
│ │ ├── index.astro
|
||||||
|
│ │ ├── knowledgebase.astro
|
||||||
|
│ │ └── tool.astro
|
||||||
|
│ ├── impressum.astro
|
||||||
|
│ ├── index.astro
|
||||||
|
│ ├── knowledgebase
|
||||||
|
│ │ └── [slug].astro
|
||||||
|
│ ├── knowledgebase.astro
|
||||||
|
│ └── status.astro
|
||||||
|
├── styles
|
||||||
|
│ └── global.css
|
||||||
|
└── utils
|
||||||
|
├── api.ts
|
||||||
|
├── auth.ts
|
||||||
|
├── dataService.ts
|
||||||
|
├── gitContributions.ts
|
||||||
|
├── nextcloud.ts
|
||||||
|
├── rateLimitedQueue.ts
|
||||||
|
└── toolHelpers.ts
|
||||||
|
17 directories, 47 files
|
@ -43,16 +43,16 @@ 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 style="padding: 2rem 0;">
|
||||||
<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="text-center mb-8 p-8 bg-secondary rounded-lg border">
|
||||||
<h1 style="margin-bottom: 1rem; font-size: 2.5rem; color: var(--color-primary);">Knowledgebase</h1>
|
<h1 class="mb-4 text-2xl text-primary">Knowledgebase</h1>
|
||||||
<p style="font-size: 1.25rem; color: var(--color-text-secondary); margin-bottom: 1.125rem;">
|
<p class="text-lg text-secondary mb-4">
|
||||||
Erweiterte Dokumentation und Erkenntnisse
|
Erweiterte Dokumentation und Erkenntnisse
|
||||||
</p>
|
</p>
|
||||||
<p style="font-size: 1rem; color: var(--color-text-secondary); margin-bottom: 1.5rem;">
|
<p class="text-base text-secondary mb-6">
|
||||||
Praktische Erfahrungen, Konfigurationshinweise und Lektionen aus der Praxis
|
Praktische Erfahrungen, Konfigurationshinweise und Lektionen aus der Praxis
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
|
<div class="flex gap-4 justify-center flex-wrap">
|
||||||
<ContributionButton type="write" variant="primary" text="Artikel schreiben" style="padding: 0.75rem 1.5rem;" />
|
<ContributionButton type="write" variant="primary" text="Artikel schreiben" style="padding: 0.75rem 1.5rem;" />
|
||||||
<a href="#kb-entries" class="btn btn-secondary" style="padding: 0.75rem 1.5rem;">
|
<a href="#kb-entries" class="btn btn-secondary" style="padding: 0.75rem 1.5rem;">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||||
@ -64,24 +64,24 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 2rem;">
|
<div class="mb-6">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="kb-search"
|
id="kb-search"
|
||||||
placeholder="Knowledgebase durchsuchen..."
|
placeholder="Knowledgebase durchsuchen..."
|
||||||
style="max-width: 500px; margin: 0 auto; display: block;"
|
class="w-full max-w-lg mx-auto block"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: center; margin-bottom: 2rem;">
|
<div class="text-center mb-6">
|
||||||
<p class="text-muted" style="font-size: 0.875rem;">
|
<p class="text-secondary text-sm">
|
||||||
<span id="visible-count">{knowledgebaseEntries.length}</span> von {knowledgebaseEntries.length} Einträgen
|
<span id="visible-count">{knowledgebaseEntries.length}</span> von {knowledgebaseEntries.length} Einträgen
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="max-width: 1000px; margin: 0 auto;">
|
<div class="max-w-6xl mx-auto">
|
||||||
{knowledgebaseEntries.length === 0 ? (
|
{knowledgebaseEntries.length === 0 ? (
|
||||||
<div class="card" style="text-align: center; padding: 3rem;">
|
<div class="card text-center p-8">
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="1.5" style="margin: 0 auto 1rem;">
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="1.5" style="margin: 0 auto 1rem;">
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||||
<polyline points="14 2 14 8 20 8"/>
|
<polyline points="14 2 14 8 20 8"/>
|
||||||
@ -89,13 +89,13 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
<line x1="16" y1="17" x2="8" y2="17"/>
|
<line x1="16" y1="17" x2="8" y2="17"/>
|
||||||
<polyline points="10 9 9 9 8 9"/>
|
<polyline points="10 9 9 9 8 9"/>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 style="color: var(--color-text-secondary); margin-bottom: 0.5rem;">Noch keine Knowledgebase-Einträge</h3>
|
<h3 class="text-secondary mb-2">Noch keine Knowledgebase-Einträge</h3>
|
||||||
<p class="text-muted">
|
<p class="text-secondary">
|
||||||
Knowledgebase-Einträge werden automatisch angezeigt, sobald Artikel veröffentlicht werden.
|
Knowledgebase-Einträge werden automatisch angezeigt, sobald Artikel veröffentlicht werden.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div id="kb-entries">
|
<div id="kb-entries" class="grid gap-4">
|
||||||
{knowledgebaseEntries.map((entry: any, index: number) => {
|
{knowledgebaseEntries.map((entry: any, index: number) => {
|
||||||
const hasAssociatedTool = !!entry.associatedTool;
|
const hasAssociatedTool = !!entry.associatedTool;
|
||||||
const hasValidProjectUrl = hasAssociatedTool &&
|
const hasValidProjectUrl = hasAssociatedTool &&
|
||||||
@ -110,90 +110,97 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
class="kb-entry card"
|
class="kb-entry card cursor-pointer"
|
||||||
id={`kb-${entry.slug}`}
|
id={`kb-${entry.slug}`}
|
||||||
data-tool-name={entry.title.toLowerCase()}
|
data-tool-name={entry.title.toLowerCase()}
|
||||||
data-article-type={isStandalone ? 'standalone' : 'tool-associated'}
|
data-article-type={isStandalone ? 'standalone' : 'tool-associated'}
|
||||||
|
onclick={`window.location.href='/knowledgebase/${entry.slug}'`}
|
||||||
>
|
>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<!-- Card Header -->
|
||||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
<div class="flex-between mb-3">
|
||||||
<h3 style="margin: 0; color: var(--color-primary);">
|
<div class="flex items-start gap-3 flex-1 min-w-0">
|
||||||
<span style="margin-right: 0.5rem;">{entry.icon}</span>
|
<span class="text-2xl flex-shrink-0">{entry.icon}</span>
|
||||||
{entry.title}
|
<div class="min-w-0 flex-1">
|
||||||
</h3>
|
<h3 class="text-lg font-semibold text-primary mb-1 leading-tight">
|
||||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
{entry.title}
|
||||||
{isStandalone && <span class="badge" style="background-color: var(--color-accent); color: white;">Artikel</span>}
|
</h3>
|
||||||
{isConcept && <span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>}
|
<div class="flex gap-2 flex-wrap mb-2">
|
||||||
{isMethod && <span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>}
|
{isStandalone && <span class="badge" style="background-color: var(--color-accent); color: white;">Artikel</span>}
|
||||||
{hasAssociatedTool && !isMethod && !isConcept && <span class="badge" style="background-color: var(--color-primary); color: white;">Software</span>}
|
{isConcept && <span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>}
|
||||||
|
{isMethod && <span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>}
|
||||||
{hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
|
{hasAssociatedTool && !isMethod && !isConcept && <span class="badge badge-primary">Software</span>}
|
||||||
{hasAssociatedTool && entry.associatedTool.license !== 'Proprietary' && !isMethod && !isConcept && <span class="badge badge-success">Open Source</span>}
|
{hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
|
||||||
|
{hasAssociatedTool && entry.associatedTool.license !== 'Proprietary' && !isMethod && !isConcept && <span class="badge badge-success">Open Source</span>}
|
||||||
{entry.difficulty && (
|
<span class="badge badge-error">📖</span>
|
||||||
<span class="badge" style="background-color: var(--color-text-secondary); color: white; font-size: 0.75rem;">
|
</div>
|
||||||
{entry.difficulty}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<span class="badge badge-error">📖</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; gap: 0.5rem; align-items: center; flex-shrink: 0;">
|
<div class="flex gap-2 flex-shrink-0" onclick="event.stopPropagation();">
|
||||||
<a href={`/knowledgebase/${entry.slug}`} class="btn btn-primary" style="font-size: 0.8125rem;">
|
<a href={`/knowledgebase/${entry.slug}`} class="btn btn-primary btn-sm">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
||||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||||
<polyline points="15 3 21 3 21 9"/>
|
<polyline points="15 3 21 3 21 9"/>
|
||||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||||
</svg>
|
</svg>
|
||||||
Artikel öffnen
|
Öffnen
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ContributionButton type="edit" toolName={entry.tool_name || entry.title} variant="secondary" text="Edit" style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;" />
|
<ContributionButton type="edit" toolName={entry.tool_name || entry.title} variant="secondary" text="Edit" style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="margin: 1rem 0; color: var(--color-text-secondary); line-height: 1.5;">
|
<!-- Description -->
|
||||||
|
<p class="text-secondary mb-4 leading-relaxed">
|
||||||
{entry.description}
|
{entry.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; margin-top: 1rem;">
|
<!-- Metadata Footer -->
|
||||||
|
<div class="border-t pt-3 mt-auto">
|
||||||
|
<div class="flex items-center justify-between text-xs text-secondary flex-wrap gap-2">
|
||||||
|
<div class="flex items-center gap-4 flex-wrap">
|
||||||
|
{entry.difficulty && (
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
<path d="M12 6v6l4 2"/>
|
||||||
|
</svg>
|
||||||
|
{entry.difficulty}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasAssociatedTool && entry.platforms && entry.platforms.length > 0 && !isMethod && !isConcept && (
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
<svg width="12" height="12" 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"/>
|
||||||
|
</svg>
|
||||||
|
{entry.platforms.slice(0, 2).join(', ')}{entry.platforms.length > 2 ? '...' : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{entry.categories && entry.categories.length > 0 && (
|
||||||
|
<span class="flex items-center gap-1">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10"/>
|
||||||
|
</svg>
|
||||||
|
{entry.categories.slice(0, 2).join(', ')}{entry.categories.length > 2 ? '...' : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span>{entry.author}</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{entry.last_updated.toLocaleDateString('de-DE')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{entry.tags && entry.tags.length > 0 && (
|
{entry.tags && entry.tags.length > 0 && (
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
|
<div class="flex flex-wrap gap-1 mt-2">
|
||||||
{entry.tags.map((tag: string) => (
|
{entry.tags.slice(0, 8).map((tag: string) => (
|
||||||
<span class="tag" style="font-size: 0.75rem;">{tag}</span>
|
<span class="tag text-xs">{tag}</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{entry.categories && entry.categories.length > 0 && (
|
|
||||||
<div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
|
|
||||||
<strong>Kategorien:</strong> {entry.categories.join(', ')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasAssociatedTool && entry.phases && entry.phases.length > 0 && (
|
|
||||||
<div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
|
|
||||||
<strong>Phasen:</strong> {entry.phases.join(', ')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasAssociatedTool && entry.platforms && entry.platforms.length > 0 && !isMethod && !isConcept && (
|
|
||||||
<div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
|
|
||||||
<strong>Plattformen:</strong> {entry.platforms.join(', ')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isStandalone && entry.related_tools && entry.related_tools.length > 0 && (
|
|
||||||
<div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
|
|
||||||
<strong>Verwandte Tools:</strong> {entry.related_tools.join(', ')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div style="font-size: 0.8125rem; color: var(--color-text-secondary); margin-left: auto;">
|
|
||||||
<strong>Autor:</strong> {entry.author} • <strong>Aktualisiert:</strong> {entry.last_updated.toLocaleDateString('de-DE')}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
@ -202,13 +209,13 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="no-kb-results" class="card" style="text-align: center; padding: 3rem; display: none;">
|
<div id="no-kb-results" class="card text-center p-8" style="display: none;">
|
||||||
<h3 style="color: var(--color-text-secondary); margin-bottom: 0.5rem;">Keine Ergebnisse gefunden</h3>
|
<h3 class="text-secondary mb-2">Keine Ergebnisse gefunden</h3>
|
||||||
<p class="text-muted">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" style="position: fixed; bottom: 2rem; right: 2rem; z-index: 100; display: none;">
|
<div id="fab-container" class="fixed bottom-8 right-8 z-50" style="display: none;">
|
||||||
<ContributionButton
|
<ContributionButton
|
||||||
type="write"
|
type="write"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
@ -415,7 +415,10 @@ select:focus {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tool Cards */
|
.grid-auto-fit-lg {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.tool-card {
|
.tool-card {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -909,6 +912,16 @@ select:focus {
|
|||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-xs {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Loading state improvements */
|
/* Loading state improvements */
|
||||||
.btn.loading {
|
.btn.loading {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
@ -978,15 +991,6 @@ select:focus {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
.collaboration-header:hover {
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
margin: -0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
Collaboration Section Collapse */
|
|
||||||
|
|
||||||
.collaboration-expand-icon {
|
.collaboration-expand-icon {
|
||||||
transition: var(--transition-medium);
|
transition: var(--transition-medium);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
@ -1068,7 +1072,6 @@ Collaboration Section Collapse */
|
|||||||
transition: var(--transition-fast);
|
transition: var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode adjustments for toggle */
|
|
||||||
[data-theme="dark"] .toggle-slider {
|
[data-theme="dark"] .toggle-slider {
|
||||||
box-shadow: 0 2px 4px 0 rgb(255 255 255 / 10%);
|
box-shadow: 0 2px 4px 0 rgb(255 255 255 / 10%);
|
||||||
}
|
}
|
||||||
@ -1077,7 +1080,6 @@ Collaboration Section Collapse */
|
|||||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 50%);
|
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Focus states for accessibility */
|
|
||||||
.toggle-switch:focus-visible {
|
.toggle-switch:focus-visible {
|
||||||
outline: 2px solid var(--color-primary);
|
outline: 2px solid var(--color-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
@ -1241,7 +1243,6 @@ Collaboration Section Collapse */
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tool Results Styles */
|
|
||||||
.tool-results-container {
|
.tool-results-container {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@ -1305,7 +1306,6 @@ Collaboration Section Collapse */
|
|||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enhanced highlight flash for different contexts */
|
|
||||||
.tool-card.highlight-flash,
|
.tool-card.highlight-flash,
|
||||||
.tool-chip.highlight-flash,
|
.tool-chip.highlight-flash,
|
||||||
.tool-recommendation.highlight-flash {
|
.tool-recommendation.highlight-flash {
|
||||||
@ -1359,25 +1359,22 @@ Collaboration Section Collapse */
|
|||||||
border-left: 3px solid var(--color-text-secondary);
|
border-left: 3px solid var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Knowledgebase */
|
|
||||||
.kb-entry {
|
.kb-entry {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
position: relative;
|
|
||||||
transition: var(--transition-medium);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
.kb-entry {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
border-left: 4px solid var(--color-accent);
|
|
||||||
transition: var(--transition-fast);
|
transition: var(--transition-fast);
|
||||||
}*/
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.kb-entry:hover {
|
.kb-entry:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.kb-entry:target { animation: highlight-flash 2s ease-out; }
|
.kb-entry:target {
|
||||||
|
animation: highlight-flash 2s ease-out;
|
||||||
|
}
|
||||||
.kb-entry-header {
|
.kb-entry-header {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
transition: var(--transition-fast);
|
transition: var(--transition-fast);
|
||||||
@ -1413,10 +1410,13 @@ footer {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Utility Classes - Only keep what's actually used */
|
|
||||||
.text-muted { color: var(--color-text-secondary); }
|
.text-muted { color: var(--color-text-secondary); }
|
||||||
|
|
||||||
/* Animations */
|
.leading-tight { line-height: 1.25; }
|
||||||
|
.leading-relaxed { line-height: 1.625; }
|
||||||
|
|
||||||
|
.z-50 { z-index: 50; }
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; }
|
from { opacity: 0; }
|
||||||
to { opacity: 1; }
|
to { opacity: 1; }
|
||||||
@ -1546,7 +1546,6 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Consolidated Responsive Design */
|
|
||||||
@media (width <= 1200px) {
|
@media (width <= 1200px) {
|
||||||
.modals-side-by-side #tool-details-primary.active,
|
.modals-side-by-side #tool-details-primary.active,
|
||||||
.modals-side-by-side #tool-details-secondary.active {
|
.modals-side-by-side #tool-details-secondary.active {
|
||||||
@ -1555,6 +1554,13 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width >= 1200px) {
|
||||||
|
#kb-entries {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
@media (width <= 768px) {
|
||||||
.nav-wrapper {
|
.nav-wrapper {
|
||||||
@ -1632,6 +1638,13 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width >= 768px) {
|
||||||
|
#kb-entries {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (width <= 640px) {
|
@media (width <= 640px) {
|
||||||
.phase-buttons { justify-content: center; }
|
.phase-buttons { justify-content: center; }
|
||||||
.phase-button {
|
.phase-button {
|
||||||
@ -1691,6 +1704,29 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.checkbox-container {
|
.checkbox-container {
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
}
|
}
|
||||||
|
.kb-entry {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-entry .flex-between {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-entry .flex-shrink-0 {
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-entry .flex-shrink-0 .flex {
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 480px) {
|
@media (width <= 480px) {
|
||||||
@ -1728,6 +1764,22 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.kb-entry .badge {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-entry .text-lg {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-entry .flex.gap-4 {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kb-entry .flex.gap-2 {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1850,7 +1902,6 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === LAYOUT UTILITIES === */
|
|
||||||
.flex { display: flex; }
|
.flex { display: flex; }
|
||||||
.flex-col { flex-direction: column; }
|
.flex-col { flex-direction: column; }
|
||||||
.flex-row { flex-direction: row; }
|
.flex-row { flex-direction: row; }
|
||||||
@ -1859,7 +1910,6 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.flex-shrink-0 { flex-shrink: 0; }
|
.flex-shrink-0 { flex-shrink: 0; }
|
||||||
.flex-shrink-1 { flex-shrink: 1; }
|
.flex-shrink-1 { flex-shrink: 1; }
|
||||||
|
|
||||||
/* Alignment */
|
|
||||||
.items-center { align-items: center; }
|
.items-center { align-items: center; }
|
||||||
.items-start { align-items: flex-start; }
|
.items-start { align-items: flex-start; }
|
||||||
.items-end { align-items: flex-end; }
|
.items-end { align-items: flex-end; }
|
||||||
@ -1870,14 +1920,18 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.text-left { text-align: left; }
|
.text-left { text-align: left; }
|
||||||
.text-right { text-align: right; }
|
.text-right { text-align: right; }
|
||||||
|
|
||||||
/* Grid */
|
|
||||||
.grid { display: grid; }
|
.grid { display: grid; }
|
||||||
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
|
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
|
||||||
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
|
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
|
||||||
.grid-auto-fit-sm { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
|
.grid-auto-fit-sm { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
|
||||||
.grid-auto-fit-md { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
|
.grid-auto-fit-md { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
|
||||||
|
|
||||||
/* === SPACING UTILITIES === */
|
.bottom-8 { bottom: 2rem; }
|
||||||
|
.right-8 { right: 2rem; }
|
||||||
|
|
||||||
|
.max-w-lg { max-width: 32rem; }
|
||||||
|
.max-w-6xl { max-width: 72rem; }
|
||||||
|
|
||||||
.gap-0 { gap: 0; }
|
.gap-0 { gap: 0; }
|
||||||
.gap-1 { gap: 0.25rem; }
|
.gap-1 { gap: 0.25rem; }
|
||||||
.gap-2 { gap: 0.5rem; }
|
.gap-2 { gap: 0.5rem; }
|
||||||
@ -1887,7 +1941,6 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.gap-6 { gap: 1.5rem; }
|
.gap-6 { gap: 1.5rem; }
|
||||||
.gap-8 { gap: 2rem; }
|
.gap-8 { gap: 2rem; }
|
||||||
|
|
||||||
/* Margin */
|
|
||||||
.m-0 { margin: 0; }
|
.m-0 { margin: 0; }
|
||||||
.mb-0 { margin-bottom: 0; }
|
.mb-0 { margin-bottom: 0; }
|
||||||
.mb-1 { margin-bottom: 0.25rem; }
|
.mb-1 { margin-bottom: 0.25rem; }
|
||||||
@ -1897,16 +1950,21 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.mb-6 { margin-bottom: 1.5rem; }
|
.mb-6 { margin-bottom: 1.5rem; }
|
||||||
.mb-8 { margin-bottom: 2rem; }
|
.mb-8 { margin-bottom: 2rem; }
|
||||||
.mt-auto { margin-top: auto; }
|
.mt-auto { margin-top: auto; }
|
||||||
|
.mt-8 { margin-top: 2rem; }
|
||||||
.mr-2 { margin-right: 0.5rem; }
|
.mr-2 { margin-right: 0.5rem; }
|
||||||
.mr-3 { margin-right: 0.75rem; }
|
.mr-3 { margin-right: 0.75rem; }
|
||||||
|
|
||||||
/* Padding */
|
|
||||||
.p-0 { padding: 0; }
|
.p-0 { padding: 0; }
|
||||||
.p-4 { padding: 1rem; }
|
.p-4 { padding: 1rem; }
|
||||||
.p-6 { padding: 1.5rem; }
|
.p-6 { padding: 1.5rem; }
|
||||||
.p-8 { padding: 2rem; }
|
.p-8 { padding: 2rem; }
|
||||||
|
|
||||||
/* === TYPOGRAPHY UTILITIES === */
|
.pt-3 { padding-top: 0.75rem; }
|
||||||
|
.pb-3 { padding-bottom: 0.75rem; }
|
||||||
|
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
|
||||||
|
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
||||||
|
|
||||||
|
|
||||||
.text-xs { font-size: 0.75rem; }
|
.text-xs { font-size: 0.75rem; }
|
||||||
.text-sm { font-size: 0.875rem; }
|
.text-sm { font-size: 0.875rem; }
|
||||||
.text-base { font-size: 1rem; }
|
.text-base { font-size: 1rem; }
|
||||||
@ -1918,7 +1976,6 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.font-semibold { font-weight: 600; }
|
.font-semibold { font-weight: 600; }
|
||||||
.font-bold { font-weight: 700; }
|
.font-bold { font-weight: 700; }
|
||||||
|
|
||||||
/* === VISUAL UTILITIES === */
|
|
||||||
.rounded { border-radius: 0.375rem; }
|
.rounded { border-radius: 0.375rem; }
|
||||||
.rounded-md { border-radius: 0.5rem; }
|
.rounded-md { border-radius: 0.5rem; }
|
||||||
.rounded-lg { border-radius: 0.75rem; }
|
.rounded-lg { border-radius: 0.75rem; }
|
||||||
@ -1939,7 +1996,6 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.text-secondary { color: var(--color-text-secondary); }
|
.text-secondary { color: var(--color-text-secondary); }
|
||||||
.text-white { color: white; }
|
.text-white { color: white; }
|
||||||
|
|
||||||
/* === POSITION & SIZING === */
|
|
||||||
.relative { position: relative; }
|
.relative { position: relative; }
|
||||||
.absolute { position: absolute; }
|
.absolute { position: absolute; }
|
||||||
.fixed { position: fixed; }
|
.fixed { position: fixed; }
|
||||||
@ -1949,7 +2005,6 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
.overflow-hidden { overflow: hidden; }
|
.overflow-hidden { overflow: hidden; }
|
||||||
.cursor-pointer { cursor: pointer; }
|
.cursor-pointer { cursor: pointer; }
|
||||||
|
|
||||||
/* === COMMON COMBINATIONS === */
|
|
||||||
.flex-center {
|
.flex-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -2073,3 +2128,27 @@ This will literally assault the user's retinas. They'll need sunglasses to look
|
|||||||
animation: shimmer 2s ease-in-out infinite alternate;
|
animation: shimmer 2s ease-in-out infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#kb-entries {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-button {
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-button:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .fab-button {
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .fab-button:hover {
|
||||||
|
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user