diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5525e873e..f1b2e0ef9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build shared packages + run: pnpm run build:packages + - name: Type check run: pnpm --filter @${{ matrix.project }}/backend type-check continue-on-error: true @@ -117,6 +120,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build shared packages + run: pnpm run build:packages + - name: Type check run: pnpm --filter @${{ matrix.project }}/mobile type-check continue-on-error: true @@ -176,6 +182,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build shared packages + run: pnpm run build:packages + - name: Type check run: pnpm --filter @${{ matrix.project }}/web check continue-on-error: true @@ -228,6 +237,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build shared packages + run: pnpm run build:packages + - name: Install Playwright browsers run: pnpm --filter @${{ matrix.project }}/web exec playwright install --with-deps chromium @@ -273,6 +285,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build shared packages + run: pnpm run build:packages + - name: Type check shared packages run: pnpm --filter '@manacore/*' type-check continue-on-error: true @@ -318,6 +333,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Build shared packages + run: pnpm run build:packages + - name: Check formatting run: pnpm run format:check diff --git a/.prettierignore b/.prettierignore index 3e227d090..3b5590518 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,6 +11,9 @@ build/ node_modules/ .turbo/ +# Archived apps (excluded from workspace) +apps-archived/ + # Lock files pnpm-lock.yaml package-lock.json @@ -31,8 +34,57 @@ TESTING_STRATEGY_AUTH_CREDITS.md .eslintcache .prettiercache -# Files with pseudo-code or example syntax -apps/maerchenzauber/apps/mobile/ANALYTICS.md -apps/maerchenzauber/apps/landing/src/components/sections/CharacterGallery.astro -apps/maerchenzauber/apps/landing/src/components/sections/StoryShowcase.astro -apps/maerchenzauber/apps/landing/src/pages/privacy.astro +# Astro files with JSX-style comments (valid Astro but Prettier can't parse) +apps/manacore/apps/landing/src/components/content/HeroSection.astro +apps/manacore/apps/landing/src/components/sections/ManaPrinciple.astro +apps/manacore/apps/landing/src/pages/apps/index.astro +apps/manacore/apps/landing/src/pages/privacy/index.astro +games/mana-games/apps/web/src/layouts/Layout.astro +games/mana-games/apps/web/src/pages/games/[slug].astro +games/mana-games/apps/web/src/pages/games/*.astro +packages/shared-landing-ui/src/sections/HeroSection.astro +packages/shared-landing-ui/src/sections/StepsSection.astro +packages/shared-landing-ui/src/sections/TestimonialSection.astro + +# Documentation files with unfenced code examples +**/docs/**/*.md +**/Docs/**/*.md +**/*CLAUDE.md +**/*README.md +**/*GUIDE*.md +**/*SETUP*.md +**/*ARCHITECTURE*.md +**/*PLAN*.md +**/*REPORT*.md +**/*MIGRATION*.md +**/*SYSTEM*.md +**/*IMPLEMENTATION*.md +**/*TESTING*.md +**/*SPACING*.md +**/*CREDIT*.md +**/*AUTH*.md +**/*JWT*.md +**/*I18N*.md +**/*ROUTE*.md +**/*ANDROID*.md +**/*IOS*.md +apps/chat/FEATURE_REQUIREMENTS.md +apps/manadeck/CI_CD_SETUP_GUIDE.md +apps/manadeck/MANA_CORE_INTEGRATION_CHECKLIST.md +apps/picture/DEPLOYMENT_COMPLETE.md +apps/zitare/apps/web/THEMING.md +BACKEND_DESIGN_PATTERN_AUDIT.md +COMPATIBILITY_MATRIX_AND_REMEDIATION.md +cicd/ + +# Astro files with JSX comments +apps/picture/apps/landing/src/components/Features.astro +apps/picture/apps/landing/src/components/promptTemplates/CategoryGrid.astro + +# Partial/temporary files +**/.!* + +# Quick start guides with code examples +**/*QUICK*.md +**/*QUICKSTART*.md + diff --git a/AUTH_DOCUMENTATION_INDEX.md b/AUTH_DOCUMENTATION_INDEX.md index 98f29900b..3d0edf2ed 100644 --- a/AUTH_DOCUMENTATION_INDEX.md +++ b/AUTH_DOCUMENTATION_INDEX.md @@ -12,7 +12,9 @@ Choose the document that best fits your needs: ### I need quick answers + → **AUTH_QUICK_REFERENCE.md** (6.4 KB) + - Essential endpoints table - Common curl commands - Guard patterns @@ -21,7 +23,9 @@ Choose the document that best fits your needs: - 5-minute read ### I'm implementing auth in a new backend + → **AUTH_VALIDATION_CHECKLIST.md** (11 KB) + - Pre-integration checklist - Implementation steps - Testing procedures @@ -30,7 +34,9 @@ Choose the document that best fits your needs: - Use for approval ### I need comprehensive details + → **AUTH_ARCHITECTURE_REPORT.md** (24 KB) + - Complete 15-section guide - API routes documented - JWT format explained @@ -41,7 +47,9 @@ Choose the document that best fits your needs: - Use as reference ### I need executive summary + → **AUTH_ANALYSIS_SUMMARY.md** (11 KB) + - Key findings - Architecture decisions - Validation results @@ -53,14 +61,14 @@ Choose the document that best fits your needs: ## Document Comparison -| Aspect | Quick Ref | Checklist | Report | Summary | -|--------|-----------|-----------|--------|---------| -| **Audience** | Developers | Implementers | Architects | Managers | -| **Length** | Short | Medium | Comprehensive | Medium | -| **Details** | Minimal | Practical | Complete | Strategic | -| **Use Case** | Daily lookup | Integration | Reference | Overview | -| **Sign-off** | N/A | Yes | N/A | N/A | -| **Code Examples** | Many | Some | Complete | Few | +| Aspect | Quick Ref | Checklist | Report | Summary | +| ----------------- | ------------ | ------------ | ------------- | --------- | +| **Audience** | Developers | Implementers | Architects | Managers | +| **Length** | Short | Medium | Comprehensive | Medium | +| **Details** | Minimal | Practical | Complete | Strategic | +| **Use Case** | Daily lookup | Integration | Reference | Overview | +| **Sign-off** | N/A | Yes | N/A | N/A | +| **Code Examples** | Many | Some | Complete | Few | --- @@ -69,6 +77,7 @@ Choose the document that best fits your needs: ### Core Concepts **Covered in:** + - **Service Architecture** → Report (Section 1) - **JWT Algorithm** → Report (Section 2), Summary (Finding 2) - **Token Claims** → Report (Section 2), Quick Ref (Token Structure) @@ -77,6 +86,7 @@ Choose the document that best fits your needs: ### Implementation **Covered in:** + - **Backend Setup** → Checklist (Implementation), Report (Section 9) - **Guard Usage** → Quick Ref (Guard Patterns), Report (Section 4) - **Decorator Patterns** → Report (Section 4), Checklist (Guard Setup) @@ -85,6 +95,7 @@ Choose the document that best fits your needs: ### Testing & Validation **Covered in:** + - **Manual Testing** → Checklist (Testing section), Quick Ref (Requests) - **Dev Bypass** → Quick Ref (Development Bypass), Checklist (Testing) - **Integration Testing** → Checklist (Integration Testing) @@ -93,6 +104,7 @@ Choose the document that best fits your needs: ### Security & Operations **Covered in:** + - **Security** → Report (Section 13), Summary (Risk Assessment) - **Environment Config** → Report (Section 6), Checklist (Env Variables) - **Troubleshooting** → Report (Section 10), Quick Ref (Troubleshooting) @@ -103,26 +115,31 @@ Choose the document that best fits your needs: ## Implementation Workflow ### Step 1: Review Architecture (30 min) + 1. Start with **AUTH_QUICK_REFERENCE.md** - understand basics 2. Read **AUTH_ANALYSIS_SUMMARY.md** - understand decisions 3. Skim **AUTH_ARCHITECTURE_REPORT.md** sections 1-4 ### Step 2: Plan Integration (15 min) + 1. Read **AUTH_VALIDATION_CHECKLIST.md** Pre-Integration section 2. Determine integration path (A or B) 3. Set up environment variables ### Step 3: Implement (2-3 hours) + 1. Reference **AUTH_ARCHITECTURE_REPORT.md** Section 9 2. Follow **AUTH_VALIDATION_CHECKLIST.md** Implementation section 3. Use code examples from Quick Reference ### Step 4: Test (1-2 hours) + 1. Follow **AUTH_VALIDATION_CHECKLIST.md** Testing section 2. Use curl commands from Quick Reference 3. Verify development bypass works ### Step 5: Validate (30 min) + 1. Complete **AUTH_VALIDATION_CHECKLIST.md** all sections 2. Get code review approval 3. Sign off checklist @@ -132,6 +149,7 @@ Choose the document that best fits your needs: ## File Locations in Monorepo ### Documentation (At Monorepo Root) + ``` / ├── AUTH_DOCUMENTATION_INDEX.md (this file) @@ -142,6 +160,7 @@ Choose the document that best fits your needs: ``` ### Source Code (Analyzed) + ``` services/mana-core-auth/ ├── src/auth/ @@ -163,6 +182,7 @@ packages/ ## Key Findings Summary ### Central Service + - **Name:** mana-core-auth - **Port:** 3001 - **Framework:** NestJS + Better Auth @@ -170,11 +190,13 @@ packages/ - **Database:** PostgreSQL with Drizzle ### Integration Patterns + - **Path A:** `@manacore/shared-nestjs-auth` (lightweight) - **Path B:** `@mana-core/nestjs-integration` (with credits) - **Pattern:** Centralized validation via `/api/v1/auth/validate` ### Canonical Design + - **JWT Claims:** Minimal (sub, email, role, sid only) - **Token Expiry:** 15 minutes (access), 7 days (refresh) - **Rotation:** Refresh token rotation + soft delete @@ -182,6 +204,7 @@ packages/ - **Injection:** Use `@CurrentUser()` decorator ### Environment Setup + ```env # Required MANA_CORE_AUTH_URL=http://localhost:3001 @@ -251,6 +274,7 @@ See **AUTH_ARCHITECTURE_REPORT.md** Section 10 for troubleshooting guide. ## Testing Quick Commands ### Get Token + ```bash curl -X POST http://localhost:3001/api/v1/auth/login \ -H "Content-Type: application/json" \ @@ -258,12 +282,14 @@ curl -X POST http://localhost:3001/api/v1/auth/login \ ``` ### Test Protected Endpoint + ```bash curl http://localhost:3007/api/favorites \ -H "Authorization: Bearer $TOKEN" ``` ### Validate Token + ```bash curl -X POST http://localhost:3001/api/v1/auth/validate \ -H "Content-Type: application/json" \ @@ -271,6 +297,7 @@ curl -X POST http://localhost:3001/api/v1/auth/validate \ ``` ### Decode Token + ```bash echo $TOKEN | cut -d'.' -f2 | base64 -d | jq '.' ``` @@ -299,17 +326,20 @@ More commands in **AUTH_QUICK_REFERENCE.md**. ## Support & Resources ### Documents in This Analysis + - **Getting started?** → AUTH_QUICK_REFERENCE.md - **Implementing?** → AUTH_VALIDATION_CHECKLIST.md - **Deep dive?** → AUTH_ARCHITECTURE_REPORT.md - **Executive brief?** → AUTH_ANALYSIS_SUMMARY.md ### External Resources + - **Better Auth Docs:** https://www.better-auth.com/docs - **JWT.io:** https://jwt.io (decoder) - **EdDSA:** https://en.wikipedia.org/wiki/EdDSA ### Project Resources + - **Source code:** services/mana-core-auth/ - **Project guide:** services/mana-core-auth/CLAUDE.md - **Example backend:** apps/zitare/apps/backend/ @@ -323,12 +353,14 @@ More commands in **AUTH_QUICK_REFERENCE.md**. **Version:** 1.0 ### When to Update + - Architecture changes - New integration patterns discovered - Breaking changes to API - Security updates ### Update Process + 1. Update AUTH_ARCHITECTURE_REPORT.md (source of truth) 2. Update AUTH_VALIDATION_CHECKLIST.md if implementation changes 3. Update AUTH_QUICK_REFERENCE.md if commands change @@ -344,6 +376,7 @@ More commands in **AUTH_QUICK_REFERENCE.md**. **Status:** APPROVED FOR PRODUCTION USE **Next Steps:** + 1. Share documents with development team 2. Reference in PR review process 3. Use checklist for new backend integrations @@ -356,6 +389,7 @@ More commands in **AUTH_QUICK_REFERENCE.md**. ## Table of Contents (All Documents) ### AUTH_QUICK_REFERENCE.md + 1. Core Service 2. Essential Endpoints 3. Backend Integration @@ -371,6 +405,7 @@ More commands in **AUTH_QUICK_REFERENCE.md**. 13. Related Packages ### AUTH_VALIDATION_CHECKLIST.md + 1. Pre-Integration Checklist 2. Implementation Checklist 3. API Route Validation @@ -384,6 +419,7 @@ More commands in **AUTH_QUICK_REFERENCE.md**. 11. Sign-Off ### AUTH_ARCHITECTURE_REPORT.md + 1. Executive Summary 2. API Route Structure & Versioning 3. JWT Token Format & Structure @@ -401,6 +437,7 @@ More commands in **AUTH_QUICK_REFERENCE.md**. 15. References & Further Reading ### AUTH_ANALYSIS_SUMMARY.md + 1. Objective 2. Key Findings 3. Architecture Decisions (Validated) diff --git a/CLAUDE.md b/CLAUDE.md index 10946440c..6ad47d2e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -157,12 +157,12 @@ All projects use **mana-core-auth** as the central authentication service: #### Key Components -| Component | Purpose | -|-----------|---------| -| `services/mana-core-auth` | Central auth service (Better Auth + EdDSA JWT) | -| `@manacore/shared-nestjs-auth` | Shared NestJS guards/decorators for JWT validation | -| `@mana-core/nestjs-integration` | Extended NestJS module with auth + credits | -| `@manacore/shared-auth` | Client-side auth for web/mobile apps | +| Component | Purpose | +| ------------------------------- | -------------------------------------------------- | +| `services/mana-core-auth` | Central auth service (Better Auth + EdDSA JWT) | +| `@manacore/shared-nestjs-auth` | Shared NestJS guards/decorators for JWT validation | +| `@mana-core/nestjs-integration` | Extended NestJS module with auth + credits | +| `@manacore/shared-auth` | Client-side auth for web/mobile apps | #### NestJS Backend Integration @@ -175,10 +175,10 @@ import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nes @Controller('api') @UseGuards(JwtAuthGuard) export class MyController { - @Get('profile') - getProfile(@CurrentUser() user: CurrentUserData) { - return { userId: user.userId, email: user.email }; - } + @Get('profile') + getProfile(@CurrentUser() user: CurrentUserData) { + return { userId: user.userId, email: user.email }; + } } ``` @@ -189,17 +189,17 @@ export class MyController { import { ManaCoreModule } from '@mana-core/nestjs-integration'; @Module({ - imports: [ - ManaCoreModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (config: ConfigService) => ({ - appId: config.get('APP_ID'), - serviceKey: config.get('MANA_CORE_SERVICE_KEY'), - debug: config.get('NODE_ENV') === 'development', - }), - inject: [ConfigService], - }), - ], + imports: [ + ManaCoreModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (config: ConfigService) => ({ + appId: config.get('APP_ID'), + serviceKey: config.get('MANA_CORE_SERVICE_KEY'), + debug: config.get('NODE_ENV') === 'development', + }), + inject: [ConfigService], + }), + ], }) export class AppModule {} @@ -211,13 +211,13 @@ import { CreditClientService } from '@mana-core/nestjs-integration'; @Controller('api') @UseGuards(AuthGuard) export class ApiController { - constructor(private creditClient: CreditClientService) {} + constructor(private creditClient: CreditClientService) {} - @Post('generate') - async generate(@CurrentUser() user: any) { - await this.creditClient.consumeCredits(user.sub, 'generation', 10, 'AI generation'); - // ... do work - } + @Post('generate') + async generate(@CurrentUser() user: any) { + await this.creditClient.consumeCredits(user.sub, 'generation', 10, 'AI generation'); + // ... do work + } } ``` @@ -241,13 +241,13 @@ APP_ID=your-app-id ```json { - "sub": "user-id", - "email": "user@example.com", - "role": "user", - "sid": "session-id", - "exp": 1764606251, - "iss": "manacore", - "aud": "manacore" + "sub": "user-id", + "email": "user@example.com", + "role": "user", + "sid": "session-id", + "exp": 1764606251, + "iss": "manacore", + "aud": "manacore" } ``` @@ -272,12 +272,12 @@ curl http://localhost:3007/api/favorites \ #### Integrated Backends -| Backend | Package | Port | -|---------|---------|------| -| Chat | `@mana-core/nestjs-integration` | 3002 | -| Picture | `@manacore/shared-nestjs-auth` | 3006 | -| Zitare | `@manacore/shared-nestjs-auth` | 3007 | -| Presi | Custom (same pattern) | 3008 | +| Backend | Package | Port | +| -------- | ------------------------------- | ---- | +| Chat | `@mana-core/nestjs-integration` | 3002 | +| Picture | `@manacore/shared-nestjs-auth` | 3006 | +| Zitare | `@manacore/shared-nestjs-auth` | 3007 | +| Presi | Custom (same pattern) | 3008 | | ManaDeck | `@mana-core/nestjs-integration` | 3009 | ### Svelte 5 Runes Mode (Web Apps) @@ -299,17 +299,17 @@ $: doubled = count * 2; ## Shared Packages (`packages/`) -| Package | Purpose | -| -------------------------------- | ------------------------------------------------------- | -| `@manacore/shared-nestjs-auth` | NestJS JWT validation guards via mana-core-auth | -| `@mana-core/nestjs-integration` | NestJS module with auth guards + credit client | -| `@manacore/shared-auth` | Client-side auth service for web/mobile apps | -| `@manacore/shared-supabase` | Unified Supabase client | -| `@manacore/shared-types` | Common TypeScript types | -| `@manacore/shared-utils` | Utility functions | -| `@manacore/shared-ui` | React Native UI components | -| `@manacore/shared-theme` | Theme configuration | -| `@manacore/shared-i18n` | Internationalization | +| Package | Purpose | +| ------------------------------- | ----------------------------------------------- | +| `@manacore/shared-nestjs-auth` | NestJS JWT validation guards via mana-core-auth | +| `@mana-core/nestjs-integration` | NestJS module with auth guards + credit client | +| `@manacore/shared-auth` | Client-side auth service for web/mobile apps | +| `@manacore/shared-supabase` | Unified Supabase client | +| `@manacore/shared-types` | Common TypeScript types | +| `@manacore/shared-utils` | Utility functions | +| `@manacore/shared-ui` | React Native UI components | +| `@manacore/shared-theme` | Theme configuration | +| `@manacore/shared-i18n` | Internationalization | Import shared packages: diff --git a/COMMANDS.md b/COMMANDS.md index 395271bb6..3ce65006d 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -1,4 +1,5 @@ # Manacore Monorepo - Befehle + # Alles starten (PostgreSQL, Redis, Auth, Chat) pnpm docker:up:all @@ -10,8 +11,6 @@ pnpm dev:manacore:app pnpm dev:zitare:app pnpm dev:presi:app - - Übersicht aller wichtigen Befehle zum Starten, Stoppen und Verwalten der Apps. ## Inhaltsverzeichnis @@ -116,12 +115,12 @@ pnpm db:studio # Drizzle Studio öffnen ## Chat -| App | Port | Befehl | -|-----|------|--------| -| Web | 5174 | `pnpm dev:chat:web` | +| App | Port | Befehl | +| ------- | ---- | ----------------------- | +| Web | 5174 | `pnpm dev:chat:web` | | Backend | 3002 | `pnpm dev:chat:backend` | -| Mobile | 8081 | `pnpm dev:chat:mobile` | -| Landing | - | `pnpm dev:chat:landing` | +| Mobile | 8081 | `pnpm dev:chat:mobile` | +| Landing | - | `pnpm dev:chat:landing` | ```bash # Web + Backend zusammen starten @@ -135,12 +134,12 @@ pnpm chat:dev ## Picture -| App | Port | Befehl | -|-----|------|--------| -| Web | 5173 | `pnpm dev:picture:web` | -| Backend | - | `pnpm dev:picture:backend` | -| Mobile | 8081 | `pnpm dev:picture:mobile` | -| Landing | - | `pnpm dev:picture:landing` | +| App | Port | Befehl | +| ------- | ---- | -------------------------- | +| Web | 5173 | `pnpm dev:picture:web` | +| Backend | - | `pnpm dev:picture:backend` | +| Mobile | 8081 | `pnpm dev:picture:mobile` | +| Landing | - | `pnpm dev:picture:landing` | ```bash # Web + Backend zusammen starten @@ -154,12 +153,12 @@ pnpm picture:dev ## Manadeck -| App | Port | Befehl | -|-----|------|--------| -| Web | - | `pnpm dev:manadeck:web` | -| Backend | - | `pnpm dev:manadeck:backend` | -| Mobile | 8081 | `pnpm dev:manadeck:mobile` | -| Landing | - | `pnpm dev:manadeck:landing` | +| App | Port | Befehl | +| ------- | ---- | --------------------------- | +| Web | - | `pnpm dev:manadeck:web` | +| Backend | - | `pnpm dev:manadeck:backend` | +| Mobile | 8081 | `pnpm dev:manadeck:mobile` | +| Landing | - | `pnpm dev:manadeck:landing` | ```bash # Web + Backend zusammen starten @@ -173,12 +172,12 @@ pnpm manadeck:dev ## Zitare -| App | Port | Befehl | -|-----|------|--------| -| Web | - | `pnpm dev:zitare:web` | -| Backend | - | `pnpm dev:zitare:backend` | -| Mobile | 8081 | `pnpm dev:zitare:mobile` | -| Landing | - | `pnpm dev:zitare:landing` | +| App | Port | Befehl | +| ------- | ---- | ------------------------- | +| Web | - | `pnpm dev:zitare:web` | +| Backend | - | `pnpm dev:zitare:backend` | +| Mobile | 8081 | `pnpm dev:zitare:mobile` | +| Landing | - | `pnpm dev:zitare:landing` | ```bash # Web + Backend zusammen starten @@ -192,11 +191,11 @@ pnpm zitare:dev ## Presi -| App | Port | Befehl | -|-----|------|--------| -| Web | - | `pnpm dev:presi:web` | -| Backend | - | `pnpm dev:presi:backend` | -| Mobile | 8081 | `pnpm dev:presi:mobile` | +| App | Port | Befehl | +| ------- | ---- | ------------------------ | +| Web | - | `pnpm dev:presi:web` | +| Backend | - | `pnpm dev:presi:backend` | +| Mobile | 8081 | `pnpm dev:presi:mobile` | ```bash # Web + Backend zusammen starten @@ -215,11 +214,11 @@ pnpm presi:db:seed # Seed-Daten ## Manacore -| App | Port | Befehl | -|-----|------|--------| -| Web | - | `pnpm dev:manacore:web` | -| Mobile | 8081 | `pnpm dev:manacore:mobile` | -| Landing | - | `pnpm dev:manacore:landing` | +| App | Port | Befehl | +| ------- | ---- | --------------------------- | +| Web | - | `pnpm dev:manacore:web` | +| Mobile | 8081 | `pnpm dev:manacore:mobile` | +| Landing | - | `pnpm dev:manacore:landing` | ```bash # Alles diff --git a/apps/chat/CLAUDE.md b/apps/chat/CLAUDE.md index 1e1569ef5..8dc0f8a9b 100644 --- a/apps/chat/CLAUDE.md +++ b/apps/chat/CLAUDE.md @@ -114,11 +114,11 @@ EXPO_PUBLIC_BACKEND_URL=http://localhost:3001 ## AI Models Available -| Model ID | Name | Description | Default | -| ------------------------------------ | ----------------- | ------------------------------ | ------- | -| 550e8400-e29b-41d4-a716-446655440101 | Gemini 2.5 Flash | Fast, efficient responses | Yes | -| 550e8400-e29b-41d4-a716-446655440102 | Gemini 2.0 Flash-Lite | Ultra-lightweight model | No | -| 550e8400-e29b-41d4-a716-446655440103 | Gemini 2.5 Pro | Most capable model | No | +| Model ID | Name | Description | Default | +| ------------------------------------ | --------------------- | ------------------------- | ------- | +| 550e8400-e29b-41d4-a716-446655440101 | Gemini 2.5 Flash | Fast, efficient responses | Yes | +| 550e8400-e29b-41d4-a716-446655440102 | Gemini 2.0 Flash-Lite | Ultra-lightweight model | No | +| 550e8400-e29b-41d4-a716-446655440103 | Gemini 2.5 Pro | Most capable model | No | ## Important Notes diff --git a/apps/chat/apps/mobile/app/settings.tsx b/apps/chat/apps/mobile/app/settings.tsx index 974c3fc2c..985077615 100644 --- a/apps/chat/apps/mobile/app/settings.tsx +++ b/apps/chat/apps/mobile/app/settings.tsx @@ -134,8 +134,7 @@ export default function SettingsScreen() { style={[ styles.themeOption, { - borderColor: - selectedTheme === theme.id ? colors.primary : colors.border, + borderColor: selectedTheme === theme.id ? colors.primary : colors.border, backgroundColor: selectedTheme === theme.id ? colors.primary + '10' : 'transparent', }, @@ -272,9 +271,7 @@ export default function SettingsScreen() { style={styles.settingIcon} /> - - Passwort ändern - + Passwort ändern Aktualisiere dein Passwort regelmäßig @@ -285,12 +282,7 @@ export default function SettingsScreen() { - + Chat-Verlauf löschen diff --git a/apps/chat/apps/web/src/lib/components/Toast.svelte b/apps/chat/apps/web/src/lib/components/Toast.svelte index af43a277e..f56e66e97 100644 --- a/apps/chat/apps/web/src/lib/components/Toast.svelte +++ b/apps/chat/apps/web/src/lib/components/Toast.svelte @@ -28,7 +28,9 @@ {#each toasts as toast (toast.id)} {@const Icon = icons[toast.type]} - - + + - - - + + + - - - + + + - -
-
-
-
-

Generiertes Spiel

-
- - - - -
-
-
-
-

🎮 Hier erscheint dein generiertes Spiel

-
- -
-
-
-
- - + +
+
+
+
+

Generiertes Spiel

+
+ + + + +
+
+
+
+

🎮 Hier erscheint dein generiertes Spiel

+
+ +
+
+
+
+ + \ No newline at end of file + .create-page { + height: calc(100vh - 60px); + overflow: hidden; + padding: 1rem; + box-sizing: border-box; + } + + .split-container { + display: grid; + grid-template-columns: 1fr 1fr; + height: 100%; + gap: 1px; + background: var(--color-border); + border-radius: 12px; + overflow: hidden; + } + + /* Left Panel */ + .left-panel { + background: var(--color-bg); + display: flex; + flex-direction: column; + overflow-y: auto; + padding: 2rem; + align-items: center; + } + + .left-panel > * { + width: 100%; + max-width: 600px; + } + + /* Right Panel */ + .right-panel { + background: var(--color-bg); + display: flex; + flex-direction: column; + overflow: hidden; + } + + /* Game Preview Section */ + .game-preview-section { + height: 100%; + display: flex; + flex-direction: column; + } + + .preview-panel { + background: var(--color-surface); + border: 1px solid var(--color-border); + overflow: hidden; + height: 100%; + display: flex; + flex-direction: column; + } + + .preview-header { + background: var(--color-bg); + padding: 0.75rem 1rem; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--color-border); + flex-shrink: 0; + } + + .preview-header h3 { + margin: 0; + font-size: 1rem; + color: var(--color-text); + } + + .preview-actions { + display: flex; + gap: 0.5rem; + } + + .preview-frame { + flex: 1; + background: var(--color-bg); + position: relative; + overflow: hidden; + } + + .game-iframe { + width: 100%; + height: 100%; + border: none; + display: block; + } + + /* Editor Panel */ + .editor-panel { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 12px; + overflow: hidden; + display: flex; + flex-direction: column; + height: calc(100% - 2rem); + margin-top: 1rem; + width: 100%; + } + + .editor-panel.hidden { + display: none; + } + + .editor-header { + background: var(--color-bg); + padding: 0.75rem 1rem; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--color-border); + } + + .editor-header h3 { + margin: 0; + font-size: 1rem; + color: var(--color-text); + } + + .editor-actions { + display: flex; + gap: 0.5rem; + } + + .code-editor { + flex: 1; + overflow: hidden; + } + + .code-editor .CodeMirror { + height: 100%; + font-family: 'Fira Code', 'Monaco', 'Consolas', monospace; + font-size: 14px; + } + + /* Generator Section */ + .generator-section { + flex: 1; + width: 100%; + } + + .generator-section.hidden { + display: none; + } + + .page-header { + text-align: center; + margin-bottom: 2rem; + transition: + opacity 0.3s ease, + max-height 0.3s ease; + } + + .page-header.hidden { + opacity: 0; + max-height: 0; + margin: 0; + overflow: hidden; + } + + .page-header h1 { + font-size: 2rem; + font-weight: 700; + color: var(--color-text); + margin-bottom: 0.5rem; + } + + .subtitle { + color: var(--color-text-secondary); + font-size: 1.1rem; + } + + /* Form Card */ + .form-card { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 12px; + padding: 1.5rem; + margin-bottom: 1rem; + } + + .form-content { + display: flex; + flex-direction: column; + gap: 1.5rem; + } + + .form-group { + display: flex; + flex-direction: column; + gap: 0.75rem; + } + + .form-label { + color: var(--color-text); + font-weight: 500; + font-size: 1.1rem; + } + + .form-textarea { + background: var(--color-bg); + color: var(--color-text); + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 1rem; + font-size: 1rem; + font-family: inherit; + transition: all 0.2s ease; + resize: vertical; + min-height: 120px; + } + + .form-textarea:focus { + outline: none; + border-color: var(--color-accent); + box-shadow: 0 0 0 3px rgba(0, 255, 136, 0.1); + } + + .form-textarea::placeholder { + color: var(--color-text-muted); + font-size: 0.95rem; + } + + /* Hints */ + .form-hints { + background: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 1rem; + margin-top: 0.5rem; + } + + .hint-title { + color: var(--color-text); + font-weight: 500; + margin: 0 0 0.5rem 0; + font-size: 0.9rem; + } + + .hint-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + .hint-list li { + color: var(--color-text-secondary); + font-size: 0.85rem; + padding-left: 1.5rem; + position: relative; + } + + .hint-list li::before { + content: '•'; + position: absolute; + left: 0.5rem; + color: var(--color-accent); + } + + /* Quick Actions */ + .quick-actions { + margin-top: 1rem; + } + + .quick-actions-title { + color: var(--color-text-secondary); + font-size: 0.9rem; + margin: 0 0 0.75rem 0; + } + + .quick-actions-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 0.5rem; + } + + .quick-action-btn { + background: var(--color-bg); + color: var(--color-text); + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 0.75rem 1rem; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s ease; + text-align: center; + font-family: inherit; + } + + .quick-action-btn:hover { + border-color: var(--color-accent); + background: rgba(0, 255, 136, 0.05); + transform: translateY(-1px); + } + + /* Model Selection */ + .model-selection { + margin-top: 1rem; + } + + .model-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 0.75rem; + margin-top: 0.75rem; + } + + .model-option { + position: relative; + cursor: pointer; + } + + .model-option input[type='radio'] { + position: absolute; + opacity: 0; + pointer-events: none; + } + + .model-card { + display: flex; + flex-direction: column; + padding: 0.75rem; + background: var(--color-bg); + border: 2px solid var(--color-border); + border-radius: 8px; + transition: all 0.2s ease; + text-align: center; + } + + .model-option:hover .model-card { + border-color: var(--color-accent); + background: rgba(0, 255, 136, 0.05); + } + + .model-option input[type='radio']:checked + .model-card { + border-color: var(--color-accent); + background: rgba(0, 255, 136, 0.1); + box-shadow: 0 0 0 3px rgba(0, 255, 136, 0.2); + } + + .model-provider { + font-size: 0.65rem; + color: var(--color-accent); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.15rem; + } + + .model-name { + font-weight: 600; + color: var(--color-text); + font-size: 0.9rem; + margin-bottom: 0.25rem; + } + + .model-desc { + font-size: 0.75rem; + color: var(--color-text-secondary); + } + + /* Examples */ + .examples { + margin-top: 1rem; + } + + .examples-title { + color: var(--color-text-secondary); + font-size: 0.9rem; + margin: 0 0 0.75rem 0; + } + + .example-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.75rem; + } + + .example-btn { + background: var(--color-bg); + color: var(--color-text); + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 0.75rem 1rem; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s ease; + text-align: left; + font-family: inherit; + } + + .example-btn:hover { + border-color: var(--color-accent); + background: rgba(0, 255, 136, 0.05); + } + + /* Editor Buttons */ + .editor-btn { + background: var(--color-surface); + color: var(--color-text); + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 0.5rem 1rem; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 0.5rem; + font-family: inherit; + } + + .editor-btn:hover { + background: var(--color-bg); + border-color: var(--color-accent); + } + + .editor-btn.primary { + background: var(--color-accent); + color: var(--color-bg); + border-color: var(--color-accent); + } + + .editor-btn.primary:hover { + background: var(--color-accent-hover); + } + + .editor-btn .icon { + font-size: 1rem; + } + + /* Generate Button */ + .generate-btn { + background: var(--color-accent); + color: var(--color-bg); + border: none; + border-radius: 8px; + padding: 1rem; + font-size: 1.1rem; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + } + + .generate-btn:hover:not(:disabled) { + background: var(--color-accent-hover); + transform: translateY(-1px); + } + + .generate-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .generate-btn.iteration-mode { + background: #3b82f6; + } + + .generate-btn.iteration-mode:hover:not(:disabled) { + background: #2563eb; + } + + .generate-btn .icon { + font-size: 1.2rem; + } + + /* Loading State */ + .loading-container { + background: rgba(0, 0, 0, 0.8); + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + } + + .loading-content { + background: var(--color-surface); + padding: 2rem; + border-radius: 12px; + border: 1px solid var(--color-border); + display: flex; + align-items: center; + gap: 1rem; + } + + .spinner { + width: 2rem; + height: 2rem; + color: var(--color-accent); + animation: spin 1s linear infinite; + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + .loading-text { + color: var(--color-text); + font-size: 1.1rem; + } + + /* Error State */ + .error-container { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.5); + border-radius: 8px; + padding: 1rem; + margin: 2rem 0; + } + + .error-message { + color: #ef4444; + margin: 0; + } + + /* Notifications */ + .notification { + position: fixed; + bottom: 2rem; + right: 2rem; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 1rem 1.5rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + transform: translateY(100px); + opacity: 0; + transition: all 0.3s ease; + z-index: 1000; + } + + .notification.show { + transform: translateY(0); + opacity: 1; + } + + .notification.success { + border-color: var(--color-accent); + background: rgba(0, 255, 136, 0.1); + } + + .notification.error { + border-color: #ef4444; + background: rgba(239, 68, 68, 0.1); + } + + /* Utilities */ + .hidden { + display: none !important; + } + + /* Placeholder Message */ + .placeholder-message { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + color: var(--color-text-muted); + font-size: 1.2rem; + } + + .placeholder-message p { + margin: 0; + } + + /* Responsive */ + @media (max-width: 1200px) { + .split-container { + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; + } + } + + @media (max-width: 768px) { + .create-page { + padding: 0.5rem; + } + + .page-header h1 { + font-size: 2rem; + } + + .form-card { + padding: 1.5rem; + } + + .example-grid { + grid-template-columns: 1fr; + } + + .quick-actions-grid { + grid-template-columns: 1fr; + } + + .preview-frame { + height: 400px; + } + } + + /* Debug Borders for Create Page */ + body.debug-borders .split-container { + outline-color: rgba(255, 0, 255, 0.8) !important; + outline-width: 3px !important; + } + + body.debug-borders .left-panel { + outline-color: rgba(0, 255, 255, 0.6) !important; + } + + body.debug-borders .right-panel { + outline-color: rgba(255, 255, 0, 0.6) !important; + } + + body.debug-borders .generator-section { + outline-color: rgba(0, 255, 0, 0.5) !important; + } + + body.debug-borders .editor-panel { + outline-color: rgba(255, 128, 0, 0.6) !important; + } + + body.debug-borders .preview-panel { + outline-color: rgba(128, 0, 255, 0.6) !important; + } + diff --git a/games/mana-games/apps/web/src/pages/datenschutz.astro b/games/mana-games/apps/web/src/pages/datenschutz.astro index f0927cf66..e1727041e 100644 --- a/games/mana-games/apps/web/src/pages/datenschutz.astro +++ b/games/mana-games/apps/web/src/pages/datenschutz.astro @@ -4,448 +4,442 @@ import Button from '../components/Button.astro'; --- -
-
-

Datenschutzerklärung

-

Stand: Januar 2024

-
+
+
+

Datenschutzerklärung

+

Stand: Januar 2024

+
-
-

1. Datenschutz auf einen Blick

- -
-

Allgemeine Hinweise

-

- Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren - personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene - Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. -

-
+
+

1. Datenschutz auf einen Blick

-
-
🛡️
-
-

Ihre Daten sind bei uns sicher

-

- Wir erheben nur minimal notwendige Daten. Keine Tracker, keine Werbung, - keine versteckten Datensammlungen. Ihre Privatsphäre ist uns wichtig. -

-
-
-
+
+

Allgemeine Hinweise

+

+ Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren + personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten + sind alle Daten, mit denen Sie persönlich identifiziert werden können. +

+
-
-

2. Datenerfassung auf dieser Website

- -

Wer ist verantwortlich für die Datenerfassung?

-

- Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. - Die Kontaktdaten können Sie dem Abschnitt „Hinweis zur verantwortlichen Stelle" - in dieser Datenschutzerklärung entnehmen. -

+
+
🛡️
+
+

Ihre Daten sind bei uns sicher

+

+ Wir erheben nur minimal notwendige Daten. Keine Tracker, keine Werbung, keine + versteckten Datensammlungen. Ihre Privatsphäre ist uns wichtig. +

+
+
+
-

Wie erfassen wir Ihre Daten?

-

- Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. - Hierbei kann es sich z.B. um Daten handeln, die Sie in ein Kontaktformular eingeben. -

-

- Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website - durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z.B. Internetbrowser, - Betriebssystem oder Uhrzeit des Seitenaufrufs). -

+
+

2. Datenerfassung auf dieser Website

-

Wofür nutzen wir Ihre Daten?

-

- Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu - gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden. -

-
+

Wer ist verantwortlich für die Datenerfassung?

+

+ Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Die + Kontaktdaten können Sie dem Abschnitt „Hinweis zur verantwortlichen Stelle" in dieser + Datenschutzerklärung entnehmen. +

-
-

3. Hosting und Content Delivery Networks (CDN)

- -
-
+

Wie erfassen wir Ihre Daten?

+

+ Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es + sich z.B. um Daten handeln, die Sie in ein Kontaktformular eingeben. +

+

+ Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website durch + unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z.B. Internetbrowser, + Betriebssystem oder Uhrzeit des Seitenaufrufs). +

-
-

4. Allgemeine Hinweise und Pflichtinformationen

- -

Datenschutz

-

- Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. - Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den - gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung. -

+

Wofür nutzen wir Ihre Daten?

+

+ Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu + gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden. +

+
-

Hinweis zur verantwortlichen Stelle

-

Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:

- -
-

- [Ihr Name/Firma]
- [Ihre Adresse]
- [PLZ und Ort] -

-

- E-Mail: [Ihre E-Mail-Adresse] -

-
+
+

3. Hosting und Content Delivery Networks (CDN)

-

Speicherdauer

-

- Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer genannt - wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck für die - Datenverarbeitung entfällt. -

-
+
+

Netlify

+

+ Wir hosten unsere Website bei Netlify. Anbieter ist die Netlify, Inc., 2325 3rd Street, + Suite 296, San Francisco, CA 94107, USA. +

+

+ Beim Besuch unserer Website erfasst Netlify verschiedene Logfiles inklusive Ihrer + IP-Adressen. Details entnehmen Sie der Datenschutzerklärung von Netlify: + + https://www.netlify.com/privacy/ + +

+

+ Die Verwendung von Netlify erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Wir haben + ein berechtigtes Interesse an einer möglichst zuverlässigen Darstellung unserer Website. +

+
+
-
-

5. Datenerfassung auf dieser Website

- -

Server-Log-Dateien

-

- Der Provider der Seiten erhebt und speichert automatisch Informationen in so - genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind: -

-
    -
  • Browsertyp und Browserversion
  • -
  • Verwendetes Betriebssystem
  • -
  • Referrer URL
  • -
  • Hostname des zugreifenden Rechners
  • -
  • Uhrzeit der Serveranfrage
  • -
  • IP-Adresse
  • -
-

- Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen. -

+
+

4. Allgemeine Hinweise und Pflichtinformationen

-

Progressive Web App (PWA)

-

- Diese Website kann als Progressive Web App (PWA) installiert werden. Bei der - Installation werden folgende Daten lokal auf Ihrem Gerät gespeichert: -

-
    -
  • App-Manifest und Icons
  • -
  • Service Worker für Offline-Funktionalität
  • -
  • Spielstände und Einstellungen (im localStorage)
  • -
-

- Diese Daten verbleiben ausschließlich auf Ihrem Gerät und werden nicht an uns - oder Dritte übertragen. -

-
+

Datenschutz

+

+ Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir + behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen + Datenschutzvorschriften sowie dieser Datenschutzerklärung. +

-
-

6. Analyse-Tools und Werbung

- -
-
-
-

Keine Analyse-Tools

-

- Wir verwenden keine Analyse-Tools wie Google Analytics oder ähnliche Dienste. - Ihre Nutzung unserer Website wird nicht getrackt oder analysiert. -

-
-
+

Hinweis zur verantwortlichen Stelle

+

Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:

-
-
🚫
-
-

Keine Werbung

-

- Unsere Website ist komplett werbefrei. Wir verwenden keine Werbenetzwerke - oder Display-Werbung jeglicher Art. -

-
-
-
+
+

+ [Ihr Name/Firma]
+ [Ihre Adresse]
+ [PLZ und Ort] +

+

E-Mail: [Ihre E-Mail-Adresse]

+
-
-

7. Plugins und Tools

- -

Keine externen Plugins

-

- Wir verwenden keine Social Media Plugins, keine eingebetteten Videos von - Drittanbietern und keine externen Schriftarten. Alle Ressourcen werden - direkt von unserem Server ausgeliefert. -

-
+

Speicherdauer

+

+ Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer genannt wurde, + verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck für die Datenverarbeitung + entfällt. +

+
-
-

8. Ihre Rechte

- -

Sie haben folgende Rechte:

-
-
-

Auskunftsrecht

-

Sie können Auskunft über Ihre gespeicherten personenbezogenen Daten verlangen.

-
-
-

Berichtigung

-

Sie können die Berichtigung unrichtiger Daten verlangen.

-
-
-

Löschung

-

Sie können die Löschung Ihrer personenbezogenen Daten verlangen.

-
-
-

Einschränkung

-

Sie können die Einschränkung der Verarbeitung verlangen.

-
-
-

Widerspruch

-

Sie können der Verarbeitung Ihrer Daten widersprechen.

-
-
-

Datenübertragbarkeit

-

Sie haben das Recht auf Datenübertragbarkeit.

-
-
-
+
+

5. Datenerfassung auf dieser Website

-
-

9. Änderungen

-

- Wir behalten uns vor, diese Datenschutzerklärung anzupassen, damit sie stets den - aktuellen rechtlichen Anforderungen entspricht oder um Änderungen unserer Leistungen - in der Datenschutzerklärung umzusetzen, z.B. bei der Einführung neuer Services. - Für Ihren erneuten Besuch gilt dann die neue Datenschutzerklärung. -

-
+

Server-Log-Dateien

+

+ Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten + Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind: +

+
    +
  • Browsertyp und Browserversion
  • +
  • Verwendetes Betriebssystem
  • +
  • Referrer URL
  • +
  • Hostname des zugreifenden Rechners
  • +
  • Uhrzeit der Serveranfrage
  • +
  • IP-Adresse
  • +
+

Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen.

- -
+

Progressive Web App (PWA)

+

+ Diese Website kann als Progressive Web App (PWA) installiert werden. Bei der Installation + werden folgende Daten lokal auf Ihrem Gerät gespeichert: +

+
    +
  • App-Manifest und Icons
  • +
  • Service Worker für Offline-Funktionalität
  • +
  • Spielstände und Einstellungen (im localStorage)
  • +
+

+ Diese Daten verbleiben ausschließlich auf Ihrem Gerät und werden nicht an uns oder Dritte + übertragen. +

+ + +
+

6. Analyse-Tools und Werbung

+ +
+
+
+

Keine Analyse-Tools

+

+ Wir verwenden keine Analyse-Tools wie Google Analytics oder ähnliche Dienste. Ihre + Nutzung unserer Website wird nicht getrackt oder analysiert. +

+
+
+ +
+
🚫
+
+

Keine Werbung

+

+ Unsere Website ist komplett werbefrei. Wir verwenden keine Werbenetzwerke oder + Display-Werbung jeglicher Art. +

+
+
+
+ +
+

7. Plugins und Tools

+ +

Keine externen Plugins

+

+ Wir verwenden keine Social Media Plugins, keine eingebetteten Videos von Drittanbietern und + keine externen Schriftarten. Alle Ressourcen werden direkt von unserem Server ausgeliefert. +

+
+ +
+

8. Ihre Rechte

+ +

Sie haben folgende Rechte:

+
+
+

Auskunftsrecht

+

Sie können Auskunft über Ihre gespeicherten personenbezogenen Daten verlangen.

+
+
+

Berichtigung

+

Sie können die Berichtigung unrichtiger Daten verlangen.

+
+
+

Löschung

+

Sie können die Löschung Ihrer personenbezogenen Daten verlangen.

+
+
+

Einschränkung

+

Sie können die Einschränkung der Verarbeitung verlangen.

+
+
+

Widerspruch

+

Sie können der Verarbeitung Ihrer Daten widersprechen.

+
+
+

Datenübertragbarkeit

+

Sie haben das Recht auf Datenübertragbarkeit.

+
+
+
+ +
+

9. Änderungen

+

+ Wir behalten uns vor, diese Datenschutzerklärung anzupassen, damit sie stets den aktuellen + rechtlichen Anforderungen entspricht oder um Änderungen unserer Leistungen in der + Datenschutzerklärung umzusetzen, z.B. bei der Einführung neuer Services. Für Ihren erneuten + Besuch gilt dann die neue Datenschutzerklärung. +

+
+ + +
\ No newline at end of file + .rights-grid { + grid-template-columns: 1fr; + } + } + diff --git a/games/mana-games/apps/web/src/pages/games/[slug]/playground.astro b/games/mana-games/apps/web/src/pages/games/[slug]/playground.astro index 95f755946..eb9adfddf 100644 --- a/games/mana-games/apps/web/src/pages/games/[slug]/playground.astro +++ b/games/mana-games/apps/web/src/pages/games/[slug]/playground.astro @@ -3,414 +3,425 @@ import Layout from '../../../layouts/Layout.astro'; import { games } from '../../../data/games'; export function getStaticPaths() { - return games.map((game) => ({ - params: { slug: game.slug }, - props: { game }, - })); + return games.map((game) => ({ + params: { slug: game.slug }, + props: { game }, + })); } const { game } = Astro.props; --- - -
-
-
-
-

Code Editor

-
- - -
-
-
-
- -
-
-

Vorschau

- -
-
- -
-
-
-
+ +
+
+
+
+

Code Editor

+
+ + +
+
+
+
-
-
-

Code wird geladen...

-
+
+
+

Vorschau

+ +
+
+ +
+
+
+
+ +
+
+

Code wird geladen...

+
\ No newline at end of file + .editor-header h3::after { + content: ' (Vorschau ausgeblendet)'; + font-size: 0.8rem; + opacity: 0.6; + } + } + diff --git a/games/mana-games/apps/web/src/pages/impressum.astro b/games/mana-games/apps/web/src/pages/impressum.astro index b5f7d9e41..e4bfe3a50 100644 --- a/games/mana-games/apps/web/src/pages/impressum.astro +++ b/games/mana-games/apps/web/src/pages/impressum.astro @@ -4,431 +4,429 @@ import Button from '../components/Button.astro'; --- -
-
-

Impressum

-

Angaben gemäß § 5 TMG

-
+
+
+

Impressum

+

Angaben gemäß § 5 TMG

+
-
-

Verantwortlich für den Inhalt

-
-
👤
-
-

[Ihr Name]

-

[Ihre Straße und Hausnummer]

-

[PLZ und Ort]

-

Deutschland

-
-
-
+
+

Verantwortlich für den Inhalt

+
+
👤
+
+

[Ihr Name]

+

[Ihre Straße und Hausnummer]

+

[PLZ und Ort]

+

Deutschland

+
+
+
-
-

Kontakt

-
-
- 📧 -
-

E-Mail

-

[ihre-email@beispiel.de]

-
-
-
- 📱 -
-

Telefon

-

[+49 123 456789]

-
-
-
-
+
+

Kontakt

+
+
+ 📧 +
+

E-Mail

+

[ihre-email@beispiel.de]

+
+
+
+ 📱 +
+

Telefon

+

[+49 123 456789]

+
+
+
+
-
-

Umsatzsteuer-ID

-

- Umsatzsteuer-Identifikationsnummer gemäß §27 a Umsatzsteuergesetz: -

-
- DE[IHRE-UST-ID] -
-

- Falls Sie keine Umsatzsteuer-ID haben, können Sie diesen Abschnitt entfernen. -

-
+
+

Umsatzsteuer-ID

+

Umsatzsteuer-Identifikationsnummer gemäß §27 a Umsatzsteuergesetz:

+
+ DE[IHRE-UST-ID] +
+

+ Falls Sie keine Umsatzsteuer-ID haben, können Sie diesen Abschnitt entfernen. +

+
-
-

Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV

-
-

[Ihr Name]

-

[Ihre Adresse]

-

[PLZ und Ort]

-
-
+
+

Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV

+
+

[Ihr Name]

+

[Ihre Adresse]

+

[PLZ und Ort]

+
+
-
-

EU-Streitschlichtung

-

- Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: -

- -

- Unsere E-Mail-Adresse finden Sie oben im Impressum. -

-
+
+

EU-Streitschlichtung

+

+ Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: +

+ +

Unsere E-Mail-Adresse finden Sie oben im Impressum.

+
-
-

Verbraucherstreitbeilegung / Universalschlichtungsstelle

-

- Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer - Verbraucherschlichtungsstelle teilzunehmen. -

-
+
+

Verbraucherstreitbeilegung / Universalschlichtungsstelle

+

+ Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer + Verbraucherschlichtungsstelle teilzunehmen. +

+
-
-

Haftungsausschluss (Disclaimer)

- -

Haftung für Inhalte

-

- Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen - Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind - wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte - fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine - rechtswidrige Tätigkeit hinweisen. -

-

- Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach - den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung - ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung - möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese - Inhalte umgehend entfernen. -

+
+

Haftungsausschluss (Disclaimer)

-

Haftung für Links

-

- Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir - keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine - Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige - Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden - zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige - Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. -

-

- Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete - Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von - Rechtsverletzungen werden wir derartige Links umgehend entfernen. -

+

Haftung für Inhalte

+

+ Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach + den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter + jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen + oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. +

+

+ Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den + allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst + ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden + von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen. +

-

Urheberrecht

-

- Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten - unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, - Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes - bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. - Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen - Gebrauch gestattet. -

-

- Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden - die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche - gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam - werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von - Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen. -

-
+

Haftung für Links

+

+ Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen + Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. + Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der + Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf + mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung + nicht erkennbar. +

+

+ Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete + Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von + Rechtsverletzungen werden wir derartige Links umgehend entfernen. +

-
-

Open Source Hinweis

-
-
💻
-
-

Diese Website ist Open Source

-

- Der Quellcode dieser Website ist öffentlich verfügbar. Sie finden das Repository auf: -

- - 📦 - GitHub Repository - -
-
-
+

Urheberrecht

+

+ Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem + deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der + Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung + des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den + privaten, nicht kommerziellen Gebrauch gestattet. +

+

+ Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die + Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche + gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, + bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden + wir derartige Inhalte umgehend entfernen. +

+
- -
+
+

Open Source Hinweis

+
+
💻
+
+

Diese Website ist Open Source

+

+ Der Quellcode dieser Website ist öffentlich verfügbar. Sie finden das Repository auf: +

+ + 📦 + GitHub Repository + +
+
+
+ + +
\ No newline at end of file + .opensource-box { + flex-direction: column; + text-align: center; + gap: 1rem; + } + } + diff --git a/games/mana-games/apps/web/src/pages/index.astro b/games/mana-games/apps/web/src/pages/index.astro index c1e380315..43f65844c 100644 --- a/games/mana-games/apps/web/src/pages/index.astro +++ b/games/mana-games/apps/web/src/pages/index.astro @@ -6,13 +6,16 @@ import HorizontalScroller from '../components/HorizontalScroller.astro'; import { games } from '../data/games'; // Filtere offizielle Spiele (ohne community flag) -const officialGames = games.filter(game => !game.community); +const officialGames = games.filter((game) => !game.community); // Kategorisiere Spiele nach Genres für verschiedene Scroller -const arcadeGames = officialGames.filter(game => game.tags.includes('Arcade')); -const puzzleGames = officialGames.filter(game => game.tags.includes('Puzzle')); -const actionGames = officialGames.filter(game => - game.tags.includes('Action') || game.tags.includes('Shooter') || game.tags.includes('Jump n Run') +const arcadeGames = officialGames.filter((game) => game.tags.includes('Arcade')); +const puzzleGames = officialGames.filter((game) => game.tags.includes('Puzzle')); +const actionGames = officialGames.filter( + (game) => + game.tags.includes('Action') || + game.tags.includes('Shooter') || + game.tags.includes('Jump n Run') ); // Sortiere nach Beliebtheit/Komplexität @@ -20,633 +23,615 @@ const featuredGames = [...officialGames].slice(0, 8); --- -
-
-

- - Spiele - ohne   Grenzen - -

-
-
-
-
-
-
-
-
-
+
+
+

+ + Spiele + ohne   Grenzen + +

+
+
+
+
+
+
+
+
+
-
-
-
- {officialGames.length} - Spiele -
-
-
- 100% - Kostenlos -
-
-
- 100% - Werbefrei -
-
- - 📊 - Meine Stats - -
-
+
+
+
+ {officialGames.length} + Spiele +
+
+
+ 100% + Kostenlos +
+
+
+ 100% + Werbefrei +
+
+ + 📊 + Meine Stats + +
+
- - + + - - + + - - {arcadeGames.length > 0 && ( - - )} + + { + arcadeGames.length > 0 && ( + + ) + } - {puzzleGames.length > 0 && ( - - )} + { + puzzleGames.length > 0 && ( + + ) + } - {actionGames.length > 0 && ( - - )} + { + actionGames.length > 0 && ( + + ) + } -
-
-

Alle Spiele durchsuchen

-
- - - - -
-
- -
- {officialGames.map((game, index) => ( -
- -
- ))} -
- - - - - - -
+
+
+

Alle Spiele durchsuchen

+
+ + + + +
+
+ +
+ { + officialGames.map((game, index) => ( +
+ +
+ )) + } +
+ + + + + + +
diff --git a/games/mana-games/apps/web/src/pages/jugendschutz.astro b/games/mana-games/apps/web/src/pages/jugendschutz.astro index 53acaa29d..6deb86dd9 100644 --- a/games/mana-games/apps/web/src/pages/jugendschutz.astro +++ b/games/mana-games/apps/web/src/pages/jugendschutz.astro @@ -4,598 +4,598 @@ import Button from '../components/Button.astro'; --- -
-
-
🛡️
-

Jugendschutz bei Mana Games

-

Sicher spielen für alle Altersgruppen

-
+
+
+
🛡️
+

Jugendschutz bei Mana Games

+

Sicher spielen für alle Altersgruppen

+
-
-
-

- Bei Mana Games liegt uns die Sicherheit und das Wohlbefinden junger Spieler besonders - am Herzen. Unsere Plattform ist so gestaltet, dass Kinder und Jugendliche sicher und - altersgerecht spielen können. -

-
-
+
+
+

+ Bei Mana Games liegt uns die Sicherheit und das Wohlbefinden junger Spieler besonders am + Herzen. Unsere Plattform ist so gestaltet, dass Kinder und Jugendliche sicher und + altersgerecht spielen können. +

+
+
-
-

Unsere Jugendschutz-Prinzipien

- -
-
-
🎮
-

Altersgerechte Inhalte

-

- Alle unsere Spiele sind familienfreundlich und enthalten keine Gewalt, - explizite Inhalte oder verstörende Elemente. -

-
- -
-
🚫
-

Keine Werbung

-

- Unsere Plattform ist komplett werbefrei. Kinder werden nicht mit - kommerziellen Inhalten oder In-App-Käufen konfrontiert. -

-
- -
-
🔒
-

Datenschutz

-

- Wir sammeln keine persönlichen Daten von Kindern. Alle Spielstände - werden nur lokal im Browser gespeichert. -

-
- -
-
💬
-

Kein Chat

-

- Es gibt keine Chat-Funktionen oder soziale Features, die eine - Kontaktaufnahme zwischen Nutzern ermöglichen. -

-
-
-
+
+

Unsere Jugendschutz-Prinzipien

-
-

Altersempfehlungen

- -
-
-
0-6 Jahre
-

Vorschulalter

-

Einfache Spiele mit großen Buttons und klaren visuellen Elementen:

-
    -
  • Memory-Spiele
  • -
  • Einfache Puzzle
  • -
  • Farb- und Formspiele
  • -
-
- -
-
6-12 Jahre
-

Grundschulalter

-

Spiele die Geschicklichkeit und logisches Denken fördern:

-
    -
  • Jump'n'Run Spiele
  • -
  • Einfache Strategiespiele
  • -
  • Lernspiele
  • -
-
- -
-
12+ Jahre
-

Jugendliche

-

Komplexere Spiele mit anspruchsvollen Herausforderungen:

-
    -
  • Tower Defense
  • -
  • Komplexe Puzzle
  • -
  • Strategiespiele
  • -
-
-
-
+
+
+
🎮
+

Altersgerechte Inhalte

+

+ Alle unsere Spiele sind familienfreundlich und enthalten keine Gewalt, explizite Inhalte + oder verstörende Elemente. +

+
-
-

Hinweise für Eltern

- -
-
-

🕐 Spielzeiten begrenzen

-

- Auch wenn unsere Spiele pädagogisch wertvoll sind, empfehlen wir - altersgerechte Bildschirmzeiten einzuhalten. -

-
-
- 3-6 Jahre: max. 30 Minuten täglich -
-
- 6-9 Jahre: max. 1 Stunde täglich -
-
- 10+ Jahre: max. 2 Stunden täglich -
-
-
- -
-

👨‍👩‍👧 Gemeinsam spielen

-

- Nutzen Sie die Gelegenheit, mit Ihren Kindern gemeinsam zu spielen. - Das fördert nicht nur die Bindung, sondern ermöglicht auch Gespräche - über das Spielerlebnis. -

-
- -
-

🎯 Altersgerechte Auswahl

-

- Achten Sie auf die Komplexitätsstufen unserer Spiele: -

-
    -
  • Minimal - Für die Kleinsten
  • -
  • Einfach - Ab Grundschulalter
  • -
  • Mittel - Für erfahrene Spieler
  • -
  • Komplex - Herausfordernd
  • -
-
-
-
+
+
🚫
+

Keine Werbung

+

+ Unsere Plattform ist komplett werbefrei. Kinder werden nicht mit kommerziellen Inhalten + oder In-App-Käufen konfrontiert. +

+
-
-

KI-Generator und Jugendschutz

- -
-
-

Sichere KI-Nutzung

-

- Unser KI-Spielegenerator verfügt über eingebaute Sicherheitsmechanismen: -

-
    -
  • Filterung ungeeigneter Begriffe und Themen
  • -
  • Automatische Prüfung generierter Inhalte
  • -
  • Keine Generierung von gewalttätigen oder ungeeigneten Spielen
  • -
-
- -
-

⚠️ Empfehlung

-

- Wir empfehlen, dass Kinder unter 12 Jahren den KI-Generator nur - unter Aufsicht von Erwachsenen nutzen. -

-
-
-
+
+
🔒
+

Datenschutz

+

+ Wir sammeln keine persönlichen Daten von Kindern. Alle Spielstände werden nur lokal im + Browser gespeichert. +

+
-
-

Technische Schutzmaßnahmen

- -
-
-
🌐
-
-

Keine externen Links

-

Unsere Spiele enthalten keine Links zu externen Websites.

-
-
- -
-
📵
-
-

Offline spielbar

-

Nach dem ersten Laden können alle Spiele offline gespielt werden.

-
-
- -
-
🔐
-
-

Lokale Datenspeicherung

-

Alle Daten bleiben auf dem Gerät des Nutzers.

-
-
-
-
+
+
💬
+

Kein Chat

+

+ Es gibt keine Chat-Funktionen oder soziale Features, die eine Kontaktaufnahme zwischen + Nutzern ermöglichen. +

+
+
+
-
-

Kontakt und Meldungen

- -
-

- Haben Sie Bedenken bezüglich eines Spiels oder möchten Sie uns auf - problematische Inhalte hinweisen? Wir nehmen jeden Hinweis ernst. -

- -
-

Jugendschutzbeauftragter

-

- E-Mail: jugendschutz@[ihre-domain].de
- Wir antworten innerhalb von 24 Stunden auf alle Anfragen. -

-
-
-
+
+

Altersempfehlungen

-
-

Weitere Ressourcen

- - -
+
+
+
0-6 Jahre
+

Vorschulalter

+

Einfache Spiele mit großen Buttons und klaren visuellen Elementen:

+
    +
  • Memory-Spiele
  • +
  • Einfache Puzzle
  • +
  • Farb- und Formspiele
  • +
+
- -
+
+
6-12 Jahre
+

Grundschulalter

+

Spiele die Geschicklichkeit und logisches Denken fördern:

+
    +
  • Jump'n'Run Spiele
  • +
  • Einfache Strategiespiele
  • +
  • Lernspiele
  • +
+
+ +
+
12+ Jahre
+

Jugendliche

+

Komplexere Spiele mit anspruchsvollen Herausforderungen:

+
    +
  • Tower Defense
  • +
  • Komplexe Puzzle
  • +
  • Strategiespiele
  • +
+
+
+ + +
+

Hinweise für Eltern

+ +
+
+

🕐 Spielzeiten begrenzen

+

+ Auch wenn unsere Spiele pädagogisch wertvoll sind, empfehlen wir altersgerechte + Bildschirmzeiten einzuhalten. +

+
+
+ 3-6 Jahre: max. 30 Minuten täglich +
+
+ 6-9 Jahre: max. 1 Stunde täglich +
+
+ 10+ Jahre: max. 2 Stunden täglich +
+
+
+ +
+

👨‍👩‍👧 Gemeinsam spielen

+

+ Nutzen Sie die Gelegenheit, mit Ihren Kindern gemeinsam zu spielen. Das fördert nicht + nur die Bindung, sondern ermöglicht auch Gespräche über das Spielerlebnis. +

+
+ +
+

🎯 Altersgerechte Auswahl

+

Achten Sie auf die Komplexitätsstufen unserer Spiele:

+
    +
  • Minimal - Für die Kleinsten
  • +
  • Einfach - Ab Grundschulalter
  • +
  • Mittel - Für erfahrene Spieler
  • +
  • Komplex - Herausfordernd
  • +
+
+
+
+ +
+

KI-Generator und Jugendschutz

+ +
+
+

Sichere KI-Nutzung

+

Unser KI-Spielegenerator verfügt über eingebaute Sicherheitsmechanismen:

+
    +
  • Filterung ungeeigneter Begriffe und Themen
  • +
  • Automatische Prüfung generierter Inhalte
  • +
  • Keine Generierung von gewalttätigen oder ungeeigneten Spielen
  • +
+
+ +
+

⚠️ Empfehlung

+

+ Wir empfehlen, dass Kinder unter 12 Jahren den KI-Generator nur unter Aufsicht von + Erwachsenen nutzen. +

+
+
+
+ +
+

Technische Schutzmaßnahmen

+ +
+
+
🌐
+
+

Keine externen Links

+

Unsere Spiele enthalten keine Links zu externen Websites.

+
+
+ +
+
📵
+
+

Offline spielbar

+

Nach dem ersten Laden können alle Spiele offline gespielt werden.

+
+
+ +
+
🔐
+
+

Lokale Datenspeicherung

+

Alle Daten bleiben auf dem Gerät des Nutzers.

+
+
+
+
+ +
+

Kontakt und Meldungen

+ +
+

+ Haben Sie Bedenken bezüglich eines Spiels oder möchten Sie uns auf problematische Inhalte + hinweisen? Wir nehmen jeden Hinweis ernst. +

+ +
+

Jugendschutzbeauftragter

+

+ E-Mail: jugendschutz@[ihre-domain].de
+ Wir antworten innerhalb von 24 Stunden auf alle Anfragen. +

+
+
+
+ +
+

Weitere Ressourcen

+ + +
+ + +
\ No newline at end of file + .age-recommendations { + grid-template-columns: 1fr; + } + } + diff --git a/games/mana-games/apps/web/src/pages/mitmachen.astro b/games/mana-games/apps/web/src/pages/mitmachen.astro index 4c6a5bec8..37005f562 100644 --- a/games/mana-games/apps/web/src/pages/mitmachen.astro +++ b/games/mana-games/apps/web/src/pages/mitmachen.astro @@ -4,633 +4,641 @@ import Button from '../components/Button.astro'; --- -
-
-
-
-
-
-
-

- Werde Teil der - Community -

-

- Gemeinsam erschaffen wir die Zukunft des Web-Gaming -

-
-
+
+
+
+
+
+
+
+

+ Werde Teil der + Community +

+

Gemeinsam erschaffen wir die Zukunft des Web-Gaming

+
+
-
- -
-
-

- Mana Games ist mehr als nur eine Spielesammlung – es ist eine wachsende Community - von Entwicklern, Kreativen und Gaming-Enthusiasten. Deine Ideen und Beiträge - können Teil dieser Vision werden. -

-
-
+
+ +
+
+

+ Mana Games ist mehr als nur eine Spielesammlung – es ist eine wachsende Community von + Entwicklern, Kreativen und Gaming-Enthusiasten. Deine Ideen und Beiträge können Teil + dieser Vision werden. +

+
+
- -
-
- 01 -

Wie du beitragen kannst

-
- -
-
-
-
💡
-
-
-

Spielideen einreichen

-

- Du hast eine geniale Spielidee? Teile sie mit uns! Wir sind immer auf der Suche - nach innovativen Konzepten, die Spaß machen und gleichzeitig technisch interessant sind. -

-
    -
  • Neue Gameplay-Mechaniken
  • -
  • Kreative Themes und Settings
  • -
  • Innovative Steuerungskonzepte
  • -
-
-
+ +
+
+ 01 +

Wie du beitragen kannst

+
-
-
-

Code & Entwicklung

-

- Als Open-Source-Projekt freuen wir uns über Code-Beiträge jeder Art. - Ob Bug-Fixes, Performance-Optimierungen oder neue Features – jeder Beitrag zählt. -

-
    -
  • JavaScript/HTML5 Canvas Expertise
  • -
  • Performance-Optimierungen
  • -
  • Bug-Fixes und Verbesserungen
  • -
-
-
-
🚀
-
-
+
+
+
+
💡
+
+
+

Spielideen einreichen

+

+ Du hast eine geniale Spielidee? Teile sie mit uns! Wir sind immer auf der Suche nach + innovativen Konzepten, die Spaß machen und gleichzeitig technisch interessant sind. +

+
    +
  • Neue Gameplay-Mechaniken
  • +
  • Kreative Themes und Settings
  • +
  • Innovative Steuerungskonzepte
  • +
+
+
-
-
-
🎨
-
-
-

Design & Grafik

-

- Hilf uns dabei, Mana Games noch schöner zu machen! Von Spiel-Assets über - UI-Verbesserungen bis hin zu komplett neuen visuellen Konzepten. -

-
    -
  • Pixel Art & Sprites
  • -
  • UI/UX Verbesserungen
  • -
  • Visuelle Effekte & Animationen
  • -
-
-
-
-
+
+
+

Code & Entwicklung

+

+ Als Open-Source-Projekt freuen wir uns über Code-Beiträge jeder Art. Ob Bug-Fixes, + Performance-Optimierungen oder neue Features – jeder Beitrag zählt. +

+
    +
  • JavaScript/HTML5 Canvas Expertise
  • +
  • Performance-Optimierungen
  • +
  • Bug-Fixes und Verbesserungen
  • +
+
+
+
🚀
+
+
- -
-
- 02 -

Was dich erwartet

-
-
-
-
🏆
-

Anerkennung

-

Dein Name in den Credits und der Contributors-Liste

-
-
-
📚
-

Lernerfahrung

-

Arbeite mit modernen Web-Technologien und lerne von der Community

-
-
-
🌍
-

Reichweite

-

Deine Arbeit wird von Spielern weltweit gesehen und gespielt

-
-
-
🤝
-

Netzwerk

-

Verbinde dich mit gleichgesinnten Entwicklern und Kreativen

-
-
-
+
+
+
🎨
+
+
+

Design & Grafik

+

+ Hilf uns dabei, Mana Games noch schöner zu machen! Von Spiel-Assets über + UI-Verbesserungen bis hin zu komplett neuen visuellen Konzepten. +

+
    +
  • Pixel Art & Sprites
  • +
  • UI/UX Verbesserungen
  • +
  • Visuelle Effekte & Animationen
  • +
+
+
+
+
- -
-
- 03 -

Unsere Richtlinien

-
-
-
- -
- Qualität über Quantität -

Wir legen Wert auf durchdachte, gut implementierte Beiträge

-
-
-
- 🎯 -
- Performance im Fokus -

Spiele müssen flüssig auf allen Geräten laufen

-
-
-
- 🌟 -
- Kreativität fördern -

Neue Ideen und innovative Ansätze sind immer willkommen

-
-
-
- 👥 -
- Respektvolle Community -

Ein freundlicher und konstruktiver Umgang miteinander

-
-
-
-
+ +
+
+ 02 +

Was dich erwartet

+
+
+
+
🏆
+

Anerkennung

+

Dein Name in den Credits und der Contributors-Liste

+
+
+
📚
+

Lernerfahrung

+

Arbeite mit modernen Web-Technologien und lerne von der Community

+
+
+
🌍
+

Reichweite

+

Deine Arbeit wird von Spielern weltweit gesehen und gespielt

+
+
+
🤝
+

Netzwerk

+

Verbinde dich mit gleichgesinnten Entwicklern und Kreativen

+
+
+
- -
-
- 04 -

Unser Tech-Stack

-
-
-

- Arbeite mit modernen Web-Technologien und erweitere deine Fähigkeiten: -

-
-
- HTML5 Canvas - Grafik-Engine -
-
- JavaScript ES6+ - Programmierung -
-
- Astro - Framework -
-
- PWA - App-Technologie -
-
-
-
+ +
+
+ 03 +

Unsere Richtlinien

+
+
+
+ +
+ Qualität über Quantität +

Wir legen Wert auf durchdachte, gut implementierte Beiträge

+
+
+
+ 🎯 +
+ Performance im Fokus +

Spiele müssen flüssig auf allen Geräten laufen

+
+
+
+ 🌟 +
+ Kreativität fördern +

Neue Ideen und innovative Ansätze sind immer willkommen

+
+
+
+ 👥 +
+ Respektvolle Community +

Ein freundlicher und konstruktiver Umgang miteinander

+
+
+
+
- -
-
-

Bereit durchzustarten?

-

- Egal ob du Entwickler, Designer oder einfach voller Ideen bist – - wir freuen uns auf deinen Beitrag zur Mana Games Community! -

-
- - -
-
-
-
+ +
+
+ 04 +

Unser Tech-Stack

+
+
+

+ Arbeite mit modernen Web-Technologien und erweitere deine Fähigkeiten: +

+
+
+ HTML5 Canvas + Grafik-Engine +
+
+ JavaScript ES6+ + Programmierung +
+
+ Astro + Framework +
+
+ PWA + App-Technologie +
+
+
+
+ + +
+
+

Bereit durchzustarten?

+

+ Egal ob du Entwickler, Designer oder einfach voller Ideen bist – wir freuen uns auf deinen + Beitrag zur Mana Games Community! +

+
+ + +
+
+
+
\ No newline at end of file + .cta-section { + padding: 2rem; + } + } + diff --git a/games/mana-games/apps/web/src/pages/play-generated.astro b/games/mana-games/apps/web/src/pages/play-generated.astro index 0a2ffe8a9..bc05d5e29 100644 --- a/games/mana-games/apps/web/src/pages/play-generated.astro +++ b/games/mana-games/apps/web/src/pages/play-generated.astro @@ -3,399 +3,395 @@ import Layout from '../layouts/Layout.astro'; --- -
-
- -

Lade Spiel...

-
- - - -
-
- -
-
-
-

Lade dein Spiel...

-
- - - - -
- -
-

-

-
-
+
+
+ +

Lade Spiel...

+
+ + + +
+
+ +
+
+
+

Lade dein Spiel...

+
+ + + + +
+ +
+

+

+
+
\ No newline at end of file + #gameTitle { + font-size: 1.25rem; + } + } + diff --git a/games/mana-games/apps/web/src/pages/stats.astro b/games/mana-games/apps/web/src/pages/stats.astro index c9df136ce..5a1879fc9 100644 --- a/games/mana-games/apps/web/src/pages/stats.astro +++ b/games/mana-games/apps/web/src/pages/stats.astro @@ -4,90 +4,89 @@ import { games } from '../data/games'; --- -
-

Meine Spielstatistiken

- -
- -
Lade Statistiken...
-
-
+
+

Meine Spielstatistiken

+ +
+ +
Lade Statistiken...
+
+
\ No newline at end of file + .stats-page { + max-width: 1200px; + margin: 0 auto; + padding: 2rem 1rem; + min-height: calc(100vh - 200px); + } + + .loading { + text-align: center; + color: var(--color-text-secondary); + padding: 3rem; + font-size: 1.1rem; + opacity: 0.7; + } + + h1 { + font-size: clamp(2rem, 5vw, 3.5rem); + font-weight: 900; + margin-bottom: 0.5rem; + text-align: center; + position: relative; + padding-bottom: 2rem; + background: linear-gradient( + 90deg, + var(--color-accent), + var(--color-accent-secondary), + var(--color-accent) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + background-size: 200% auto; + animation: shine 3s linear infinite; + } + + @keyframes shine { + to { + background-position: 200% center; + } + } + + @media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + } + + h1::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 100px; + height: 4px; + background: linear-gradient(90deg, transparent, var(--color-accent), transparent); + border-radius: 2px; + } + + h2 { + font-size: 1.5rem; + font-weight: 800; + margin-bottom: 2rem; + color: var(--color-text); + text-transform: uppercase; + letter-spacing: 0.1em; + position: relative; + padding-left: 1.5rem; + } + + h2::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 4px; + height: 70%; + background: var(--color-accent); + border-radius: 2px; + } + + /* Global Stats Section */ + .global-stats { + display: flex; + gap: 1rem; + margin-bottom: 3rem; + padding: 1.5rem 0; + overflow-x: auto; + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.1) transparent; + } + + .global-stats::-webkit-scrollbar { + height: 6px; + } + + .global-stats::-webkit-scrollbar-track { + background: transparent; + } + + .global-stats::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 3px; + } + + .stat-card { + flex: 1; + min-width: 140px; + position: relative; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01)); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 1rem; + padding: 1.25rem 1rem; + display: flex; + align-items: center; + gap: 1rem; + transition: + transform 0.2s ease, + border-color 0.2s ease; + overflow: hidden; + } + + .stat-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, transparent, currentColor, transparent); + opacity: 0; + transition: opacity 0.2s ease; + } + + .stat-card:hover { + transform: translateY(-4px); + border-color: rgba(255, 255, 255, 0.12); + } + + .stat-card:hover::before { + opacity: 1; + } + + .stat-icon { + font-size: 2rem; + flex-shrink: 0; + } + + .stat-content { + display: flex; + flex-direction: column; + gap: 0.25rem; + min-width: 0; + } + + .stat-number { + font-size: 1.5rem; + font-weight: 900; + line-height: 1; + letter-spacing: -0.02em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .stat-label { + font-size: 0.75rem; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + opacity: 0.7; + white-space: nowrap; + } + + .total-games { + --card-color: #60a5fa; + color: var(--card-color); + } + + .total-time { + --card-color: #4ade80; + color: var(--card-color); + } + + .favorite { + --card-color: #f87171; + color: var(--card-color); + } + + .games-tried { + --card-color: #fbbf24; + color: var(--card-color); + } + + /* Games List Section */ + .games-stats { + margin-bottom: 3rem; + } + + .games-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 2rem; + } + + .game-stat-card { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 1.5rem; + padding: 0; + overflow: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + } + + .game-stat-card::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, transparent 40%, rgba(0, 255, 136, 0.05)); + opacity: 0; + transition: opacity 0.3s ease; + } + + .game-stat-card:hover { + transform: translateY(-8px); + border-color: rgba(0, 255, 136, 0.3); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.4), + 0 0 60px rgba(0, 255, 136, 0.1); + } + + .game-stat-card:hover::after { + opacity: 1; + } + + .game-header { + display: flex; + gap: 1.5rem; + padding: 1.5rem; + background: rgba(0, 0, 0, 0.3); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + .game-thumb { + width: 100px; + height: 75px; + object-fit: cover; + border-radius: 0.75rem; + border: 2px solid rgba(255, 255, 255, 0.1); + } + + .game-info { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + } + + .game-info h3 { + margin: 0 0 0.5rem 0; + font-size: 1.4rem; + font-weight: 800; + color: var(--color-text); + letter-spacing: -0.02em; + } + + .last-played { + font-size: 0.85rem; + color: var(--color-text-secondary); + opacity: 0.7; + } + + .game-stats-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1px; + background: rgba(255, 255, 255, 0.05); + padding: 1px; + margin: 1.5rem; + border-radius: 0.75rem; + overflow: hidden; + } + + .mini-stat { + background: rgba(0, 0, 0, 0.4); + padding: 1.25rem 0.75rem; + text-align: center; + position: relative; + overflow: hidden; + } + + .mini-stat::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 40px; + height: 2px; + background: currentColor; + opacity: 0.3; + } + + .mini-stat:nth-child(1) { + color: #60a5fa; + } + .mini-stat:nth-child(2) { + color: #4ade80; + } + .mini-stat:nth-child(3) { + color: #fbbf24; + } + + .mini-label { + display: block; + font-size: 0.7rem; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.1em; + margin-bottom: 0.5rem; + opacity: 0.6; + } + + .mini-value { + display: block; + font-size: 1.5rem; + font-weight: 900; + letter-spacing: -0.02em; + } + + /* Achievements */ + .achievements-section { + padding: 1.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.05); + } + + .achievements-section h4 { + font-size: 0.9rem; + margin-bottom: 1rem; + color: var(--color-text); + text-transform: uppercase; + letter-spacing: 0.1em; + opacity: 0.8; + } + + .achievement-badges { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + } + + .achievement-badge { + padding: 0.5rem 1rem; + background: linear-gradient(135deg, rgba(255, 215, 0, 0.15), rgba(255, 215, 0, 0.05)); + border: 1px solid rgba(255, 215, 0, 0.3); + border-radius: 2rem; + font-size: 0.85rem; + color: #fbbf24; + display: flex; + align-items: center; + gap: 0.5rem; + transition: all 0.2s ease; + } + + .achievement-badge:hover { + background: linear-gradient(135deg, rgba(255, 215, 0, 0.25), rgba(255, 215, 0, 0.1)); + border-color: #fbbf24; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(255, 215, 0, 0.2); + } + + /* Play Again Button */ + .play-again-btn { + display: block; + margin: 1.5rem; + padding: 1rem; + background: linear-gradient(135deg, var(--color-accent), var(--color-accent-secondary)); + color: #000; + text-decoration: none; + border-radius: 0.75rem; + font-weight: 700; + text-align: center; + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: 0.9rem; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + } + + .play-again-btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); + transition: left 0.5s ease; + } + + .play-again-btn:hover { + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(0, 255, 136, 0.3); + } + + .play-again-btn:hover::before { + left: 100%; + } + + /* Empty State */ + .empty-state { + text-align: center; + padding: 5rem 2rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + } + + .empty-state::before { + content: '🎮'; + font-size: 5rem; + opacity: 0.2; + } + + .empty-state p { + font-size: 1.25rem; + color: var(--color-text-secondary); + max-width: 500px; + line-height: 1.6; + } + + .cta-button { + display: inline-block; + padding: 1rem 3rem; + background: linear-gradient(135deg, var(--color-accent), var(--color-accent-secondary)); + color: #000; + text-decoration: none; + border-radius: 3rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.1em; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + } + + .cta-button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: rgba(255, 255, 255, 0.2); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: + width 0.6s ease, + height 0.6s ease; + } + + .cta-button:hover { + transform: translateY(-3px); + box-shadow: 0 15px 40px rgba(0, 255, 136, 0.4); + } + + .cta-button:hover::before { + width: 300px; + height: 300px; + } + + /* Animations - Optimized for Performance */ + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .stat-card { + animation: fadeIn 0.3s ease backwards; + will-change: transform; + } + + .stat-card:nth-child(1) { + animation-delay: 0.05s; + } + .stat-card:nth-child(2) { + animation-delay: 0.1s; + } + .stat-card:nth-child(3) { + animation-delay: 0.15s; + } + .stat-card:nth-child(4) { + animation-delay: 0.2s; + } + + .game-stat-card { + animation: fadeIn 0.3s ease backwards; + contain: layout style paint; + } + + /* Table Controls */ + .table-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + gap: 1rem; + flex-wrap: wrap; + } + + .view-toggle { + display: flex; + background: rgba(255, 255, 255, 0.05); + border-radius: 0.5rem; + padding: 0.25rem; + gap: 0.25rem; + } + + .view-btn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: transparent; + border: none; + color: var(--color-text-secondary); + cursor: pointer; + border-radius: 0.375rem; + transition: all 0.2s ease; + font-size: 0.875rem; + font-weight: 600; + } + + .view-btn svg { + width: 16px; + height: 16px; + opacity: 0.7; + } + + .view-btn.active { + background: var(--color-accent); + color: #000; + } + + .view-btn:hover:not(.active) { + background: rgba(255, 255, 255, 0.1); + color: var(--color-text); + } + + .sort-control { + display: flex; + align-items: center; + gap: 0.75rem; + } + + .sort-control label { + font-size: 0.875rem; + color: var(--color-text-secondary); + font-weight: 500; + } + + .sort-select { + padding: 0.5rem 2rem 0.5rem 1rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0.5rem; + color: var(--color-text); + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s ease; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M6 8.5L1.5 4h9L6 8.5z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + } + + .sort-select:hover { + background-color: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.2); + } + + .sort-select:focus { + outline: 2px solid var(--color-accent); + outline-offset: 2px; + } + + /* Table Styles */ + .table-container { + overflow-x: auto; + background: rgba(255, 255, 255, 0.02); + border-radius: 1rem; + border: 1px solid rgba(255, 255, 255, 0.05); + } + + .stats-table { + width: 100%; + border-collapse: collapse; + } + + .stats-table thead { + background: rgba(0, 0, 0, 0.3); + border-bottom: 2px solid rgba(255, 255, 255, 0.1); + } + + .stats-table th { + padding: 1rem 1.5rem; + text-align: left; + font-weight: 700; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-text-secondary); + white-space: nowrap; + } + + .stats-table th.sortable { + cursor: pointer; + user-select: none; + transition: color 0.2s ease; + } + + .stats-table th.sortable:hover { + color: var(--color-text); + } + + .sort-icon { + display: inline-block; + margin-left: 0.5rem; + color: var(--color-accent); + font-size: 0.75rem; + } + + .th-numeric { + text-align: center; + } + + .th-date { + text-align: center; + } + + .th-achievements { + text-align: center; + } + + .th-action { + text-align: center; + } + + .stats-table tbody tr { + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + transition: background 0.2s ease; + } + + .stats-table tbody tr:hover { + background: rgba(255, 255, 255, 0.03); + } + + .stats-table td { + padding: 1rem 1.5rem; + font-size: 0.9rem; + } + + .td-game { + min-width: 200px; + } + + .game-cell { + display: flex; + align-items: center; + gap: 1rem; + } + + .game-thumb-small { + width: 48px; + height: 36px; + object-fit: cover; + border-radius: 0.5rem; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .game-thumb-placeholder { + width: 48px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.05); + border-radius: 0.5rem; + font-size: 1.5rem; + opacity: 0.5; + } + + .game-name { + font-weight: 600; + color: var(--color-text); + } + + .td-numeric { + text-align: center; + } + + .stat-value { + font-weight: 700; + color: var(--color-text); + } + + .highlight-score { + color: var(--color-accent); + font-size: 1rem; + } + + .td-date { + text-align: center; + } + + .date-value { + color: var(--color-text-secondary); + font-size: 0.85rem; + } + + .td-achievements { + text-align: center; + } + + .achievements-compact { + position: relative; + display: inline-block; + } + + .achievement-count { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.75rem; + background: linear-gradient(135deg, rgba(255, 215, 0, 0.15), rgba(255, 215, 0, 0.05)); + border: 1px solid rgba(255, 215, 0, 0.3); + border-radius: 1rem; + font-size: 0.85rem; + color: #fbbf24; + cursor: help; + transition: all 0.2s ease; + } + + .achievement-count:hover { + background: linear-gradient(135deg, rgba(255, 215, 0, 0.25), rgba(255, 215, 0, 0.1)); + transform: translateY(-1px); + } + + .achievements-tooltip { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + margin-top: 0.5rem; + background: rgba(0, 0, 0, 0.95); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0.75rem; + padding: 1rem; + min-width: 250px; + display: none; + z-index: 10; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + } + + .achievements-compact:hover .achievements-tooltip { + display: block; + } + + .tooltip-achievement { + display: flex; + align-items: start; + gap: 0.75rem; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + .tooltip-achievement:last-child { + border-bottom: none; + } + + .tooltip-icon { + font-size: 1.25rem; + flex-shrink: 0; + } + + .tooltip-name { + font-weight: 600; + color: #fbbf24; + margin-bottom: 0.25rem; + } + + .tooltip-desc { + font-size: 0.85rem; + color: var(--color-text-secondary); + line-height: 1.4; + } + + .no-achievements { + color: var(--color-text-secondary); + opacity: 0.5; + } + + .td-action { + text-align: center; + } + + .play-btn-small { + display: inline-block; + padding: 0.5rem 1rem; + background: linear-gradient(135deg, var(--color-accent), var(--color-accent-secondary)); + color: #000; + text-decoration: none; + border-radius: 0.5rem; + font-weight: 600; + font-size: 0.85rem; + transition: all 0.2s ease; + white-space: nowrap; + } + + .play-btn-small:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 255, 136, 0.3); + } + + /* Responsive */ + @media (max-width: 768px) { + .stats-page { + padding: 1.5rem 0.75rem; + } + + h1 { + font-size: 2rem; + } + + .global-stats { + gap: 0.75rem; + padding: 1rem 0; + } + + .stat-card { + min-width: 120px; + padding: 1rem 0.75rem; + gap: 0.75rem; + } + + .stat-icon { + font-size: 1.75rem; + } + + .stat-number { + font-size: 1.25rem; + } + + .stat-label { + font-size: 0.7rem; + } + + .games-list { + grid-template-columns: 1fr; + gap: 1.5rem; + } + + .game-stats-grid { + margin: 1rem; + } + + .game-header { + flex-direction: column; + text-align: center; + } + + .game-thumb { + width: 120px; + height: 90px; + margin: 0 auto; + } + } + + @media (max-width: 480px) { + .global-stats { + gap: 0.5rem; + } + + .stat-card { + min-width: 100px; + padding: 0.75rem 0.5rem; + flex-direction: column; + text-align: center; + } + + .stat-icon { + font-size: 1.5rem; + } + + .stat-number { + font-size: 1.1rem; + } + + .mini-stat { + padding: 1rem 0.5rem; + } + + .mini-value { + font-size: 1.25rem; + } + + /* Table Responsive */ + .table-controls { + flex-direction: column; + align-items: stretch; + } + + .view-toggle { + width: 100%; + justify-content: center; + } + + .sort-control { + width: 100%; + justify-content: space-between; + } + + .sort-select { + flex: 1; + max-width: 200px; + } + + .table-container { + border-radius: 0.75rem; + margin: 0 -0.75rem; + border-left: none; + border-right: none; + } + + .stats-table { + font-size: 0.85rem; + } + + .stats-table th, + .stats-table td { + padding: 0.75rem 0.5rem; + } + + .th-achievements, + .td-achievements { + display: none; + } + + .game-thumb-small { + width: 40px; + height: 30px; + } + + .game-thumb-placeholder { + width: 40px; + height: 30px; + font-size: 1.25rem; + } + + .game-name { + font-size: 0.875rem; + } + + .stat-value { + font-size: 0.875rem; + } + + .highlight-score { + font-size: 0.9rem; + } + + .date-value { + font-size: 0.8rem; + } + + .play-btn-small { + padding: 0.375rem 0.75rem; + font-size: 0.8rem; + } + } + + @media (max-width: 480px) { + .stats-table th:nth-child(4), + .stats-table td:nth-child(4) { + display: none; + } + + .game-cell { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .td-game { + min-width: unset; + } + } + diff --git a/games/mana-games/apps/web/src/pages/submit.astro b/games/mana-games/apps/web/src/pages/submit.astro index 35bcc2c5f..ac928afa2 100644 --- a/games/mana-games/apps/web/src/pages/submit.astro +++ b/games/mana-games/apps/web/src/pages/submit.astro @@ -2,743 +2,753 @@ import Layout from '../layouts/Layout.astro'; --- - -
- +
+ -
-
-

📋 Richtlinien für Einreichungen

-
    -
  • Das Spiel muss in einer einzelnen HTML-Datei funktionieren
  • -
  • Keine externen Abhängigkeiten (CDNs sind erlaubt)
  • -
  • Maximale Dateigröße: 1MB für HTML, 500KB für Screenshot
  • -
  • Das Spiel muss familienfreundlich sein
  • -
  • Kein urheberrechtlich geschütztes Material
  • -
  • Das Spiel sollte die postMessage API für Score-Integration nutzen
  • -
-
+
+
+

📋 Richtlinien für Einreichungen

+
    +
  • Das Spiel muss in einer einzelnen HTML-Datei funktionieren
  • +
  • Keine externen Abhängigkeiten (CDNs sind erlaubt)
  • +
  • Maximale Dateigröße: 1MB für HTML, 500KB für Screenshot
  • +
  • Das Spiel muss familienfreundlich sein
  • +
  • Kein urheberrechtlich geschütztes Material
  • +
  • Das Spiel sollte die postMessage API für Score-Integration nutzen
  • +
+
-
-
-

Spiel-Informationen

- -
- - -
+ +
+

Spiel-Informationen

-
- - -
+
+ + +
-
- - -
+
+ + +
-
-
- - -
+
+ + +
-
- - -
-
+
+
+ + +
-
- - -
-
+
+ + +
+
-
-

Dateien hochladen

- -
- -
- -
- Keine Datei ausgewählt - -
-
- -
+
+ + +
+
-
- -
- -
- Keine Datei ausgewählt - -
-
- -
-
+
+

Dateien hochladen

-
-

Deine Informationen

- -
- - -
+
+ +
+ +
+ Keine Datei ausgewählt + +
+
+ +
-
- - -
+
+ +
+ +
+ Keine Datei ausgewählt + +
+
+ +
+
-
- - -
-
+
+

Deine Informationen

-
-
- -
-
+
+ + +
-
- - -
- +
+ + +
- -
-
+
+ + +
+ + +
+
+ +
+
+ +
+ + +
+ + + + +
\ No newline at end of file + if (!response.ok) { + throw new Error('Einreichung fehlgeschlagen'); + } + + const result = await response.json(); + + // Show success message + alert( + `Vielen Dank! Dein Spiel "${submission.title}" wurde erfolgreich eingereicht. Du erhältst Updates über den Pull Request: ${result.prUrl}` + ); + + // Reset form + form.reset(); + codePreview.style.display = 'none'; + imagePreview.style.display = 'none'; + validationResults.style.display = 'none'; + isValidated = false; + validationPassed = false; + } catch (error) { + console.error('Submission error:', error); + alert('Fehler beim Einreichen. Bitte versuche es später erneut.'); + } finally { + submitBtn.disabled = false; + submitBtn.innerHTML = '📤 Einreichen'; + } + }); + diff --git a/games/mana-games/apps/web/src/scripts/game-communication.ts b/games/mana-games/apps/web/src/scripts/game-communication.ts index 6a0e893e6..7140205fa 100644 --- a/games/mana-games/apps/web/src/scripts/game-communication.ts +++ b/games/mana-games/apps/web/src/scripts/game-communication.ts @@ -1,71 +1,71 @@ import { statsService } from '../services/statsService'; export interface GameMessage { - type: 'GAME_EVENT' | 'GAME_LOADED' | 'GAME_ENDED'; - gameId: string; - event?: string; - data?: any; + type: 'GAME_EVENT' | 'GAME_LOADED' | 'GAME_ENDED'; + gameId: string; + event?: string; + data?: any; } export function initGameCommunication(gameSlug: string) { - let gameStartTime: number | null = null; - - window.addEventListener('message', (event) => { - if (event.origin !== window.location.origin) return; - - const message = event.data as GameMessage; - if (!message.type || message.gameId !== gameSlug) return; - - switch (message.type) { - case 'GAME_LOADED': - gameStartTime = Date.now(); - statsService.incrementGamesPlayed(gameSlug); - break; - - case 'GAME_EVENT': - handleGameEvent(gameSlug, message.event!, message.data); - break; - - case 'GAME_ENDED': - if (gameStartTime) { - const playTime = Math.floor((Date.now() - gameStartTime) / 1000); - statsService.addPlayTime(gameSlug, playTime); - gameStartTime = null; - } - break; - } - }); - - window.addEventListener('beforeunload', () => { - if (gameStartTime) { - const playTime = Math.floor((Date.now() - gameStartTime) / 1000); - statsService.addPlayTime(gameSlug, playTime); - } - }); + let gameStartTime: number | null = null; + + window.addEventListener('message', (event) => { + if (event.origin !== window.location.origin) return; + + const message = event.data as GameMessage; + if (!message.type || message.gameId !== gameSlug) return; + + switch (message.type) { + case 'GAME_LOADED': + gameStartTime = Date.now(); + statsService.incrementGamesPlayed(gameSlug); + break; + + case 'GAME_EVENT': + handleGameEvent(gameSlug, message.event!, message.data); + break; + + case 'GAME_ENDED': + if (gameStartTime) { + const playTime = Math.floor((Date.now() - gameStartTime) / 1000); + statsService.addPlayTime(gameSlug, playTime); + gameStartTime = null; + } + break; + } + }); + + window.addEventListener('beforeunload', () => { + if (gameStartTime) { + const playTime = Math.floor((Date.now() - gameStartTime) / 1000); + statsService.addPlayTime(gameSlug, playTime); + } + }); } function handleGameEvent(gameId: string, event: string, data: any) { - switch (event) { - case 'SCORE_UPDATE': - if (data.score) { - statsService.updateStats(gameId, { - lastScore: data.score - }); - } - break; - - case 'ACHIEVEMENT_UNLOCKED': - if (data.achievement) { - statsService.unlockAchievement(gameId, data.achievement); - } - break; - - case 'GAME_OVER': - if (data.score) { - statsService.updateStats(gameId, { - lastScore: data.score - }); - } - break; - } -} \ No newline at end of file + switch (event) { + case 'SCORE_UPDATE': + if (data.score) { + statsService.updateStats(gameId, { + lastScore: data.score, + }); + } + break; + + case 'ACHIEVEMENT_UNLOCKED': + if (data.achievement) { + statsService.unlockAchievement(gameId, data.achievement); + } + break; + + case 'GAME_OVER': + if (data.score) { + statsService.updateStats(gameId, { + lastScore: data.score, + }); + } + break; + } +} diff --git a/games/mana-games/apps/web/src/scripts/stats-integration-template.js b/games/mana-games/apps/web/src/scripts/stats-integration-template.js index 5e2d55e07..a71d33cf9 100644 --- a/games/mana-games/apps/web/src/scripts/stats-integration-template.js +++ b/games/mana-games/apps/web/src/scripts/stats-integration-template.js @@ -6,57 +6,72 @@ const GAME_ID = 'dein-spiel-slug'; // 2. Beim Spielstart senden window.addEventListener('load', () => { - window.parent.postMessage({ - type: 'GAME_LOADED', - gameId: GAME_ID - }, '*'); + window.parent.postMessage( + { + type: 'GAME_LOADED', + gameId: GAME_ID, + }, + '*' + ); }); // 3. Bei Score-Updates senden function updateScore(newScore) { - score = newScore; - // UI Update... - - window.parent.postMessage({ - type: 'GAME_EVENT', - gameId: GAME_ID, - event: 'SCORE_UPDATE', - data: { score: score } - }, '*'); + score = newScore; + // UI Update... + + window.parent.postMessage( + { + type: 'GAME_EVENT', + gameId: GAME_ID, + event: 'SCORE_UPDATE', + data: { score: score }, + }, + '*' + ); } // 4. Bei Game Over senden function gameOver() { - // Game Over Logik... - - window.parent.postMessage({ - type: 'GAME_EVENT', - gameId: GAME_ID, - event: 'GAME_OVER', - data: { score: finalScore } - }, '*'); - - // Achievement Beispiele - if (score >= 100) { - window.parent.postMessage({ - type: 'GAME_EVENT', - gameId: GAME_ID, - event: 'ACHIEVEMENT_UNLOCKED', - data: { - achievement: { - id: 'first-100', - name: 'Erste 100', - description: '100 Punkte erreicht!' - } - } - }, '*'); - } + // Game Over Logik... + + window.parent.postMessage( + { + type: 'GAME_EVENT', + gameId: GAME_ID, + event: 'GAME_OVER', + data: { score: finalScore }, + }, + '*' + ); + + // Achievement Beispiele + if (score >= 100) { + window.parent.postMessage( + { + type: 'GAME_EVENT', + gameId: GAME_ID, + event: 'ACHIEVEMENT_UNLOCKED', + data: { + achievement: { + id: 'first-100', + name: 'Erste 100', + description: '100 Punkte erreicht!', + }, + }, + }, + '*' + ); + } } // 5. Optional: Bei Spielende/Verlassen window.addEventListener('beforeunload', () => { - window.parent.postMessage({ - type: 'GAME_ENDED', - gameId: GAME_ID - }, '*'); -}); \ No newline at end of file + window.parent.postMessage( + { + type: 'GAME_ENDED', + gameId: GAME_ID, + }, + '*' + ); +}); diff --git a/games/mana-games/apps/web/src/services/statsService.ts b/games/mana-games/apps/web/src/services/statsService.ts index a0a677e20..f51431a6a 100644 --- a/games/mana-games/apps/web/src/services/statsService.ts +++ b/games/mana-games/apps/web/src/services/statsService.ts @@ -1,192 +1,192 @@ export interface GameStats { - gameId: string; - highScore: number; - lastScore: number; - gamesPlayed: number; - totalPlayTime: number; - lastPlayed: string; - achievements?: Achievement[]; + gameId: string; + highScore: number; + lastScore: number; + gamesPlayed: number; + totalPlayTime: number; + lastPlayed: string; + achievements?: Achievement[]; } export interface Achievement { - id: string; - name: string; - description: string; - unlockedAt?: string; + id: string; + name: string; + description: string; + unlockedAt?: string; } export interface GlobalStats { - totalGamesPlayed: number; - totalPlayTime: number; - favoriteGame?: string; - lastPlayedGame?: string; - gamesWithStats: number; + totalGamesPlayed: number; + totalPlayTime: number; + favoriteGame?: string; + lastPlayedGame?: string; + gamesWithStats: number; } class StatsService { - private readonly STATS_KEY = 'mana-games-stats'; + private readonly STATS_KEY = 'mana-games-stats'; - private getStoredStats(): Record { - if (typeof window === 'undefined') return {}; - - try { - const stored = localStorage.getItem(this.STATS_KEY); - return stored ? JSON.parse(stored) : {}; - } catch (error) { - console.error('Error reading stats:', error); - return {}; - } - } + private getStoredStats(): Record { + if (typeof window === 'undefined') return {}; - private saveStats(stats: Record): void { - if (typeof window === 'undefined') return; - - try { - localStorage.setItem(this.STATS_KEY, JSON.stringify(stats)); - } catch (error) { - console.error('Error saving stats:', error); - } - } + try { + const stored = localStorage.getItem(this.STATS_KEY); + return stored ? JSON.parse(stored) : {}; + } catch (error) { + console.error('Error reading stats:', error); + return {}; + } + } - getStats(gameId: string): GameStats | null { - const allStats = this.getStoredStats(); - return allStats[gameId] || null; - } + private saveStats(stats: Record): void { + if (typeof window === 'undefined') return; - updateStats(gameId: string, update: Partial): void { - const allStats = this.getStoredStats(); - const currentStats = allStats[gameId] || { - gameId, - highScore: 0, - lastScore: 0, - gamesPlayed: 0, - totalPlayTime: 0, - lastPlayed: new Date().toISOString(), - achievements: [] - }; + try { + localStorage.setItem(this.STATS_KEY, JSON.stringify(stats)); + } catch (error) { + console.error('Error saving stats:', error); + } + } - allStats[gameId] = { - ...currentStats, - ...update, - gameId, - lastPlayed: new Date().toISOString() - }; + getStats(gameId: string): GameStats | null { + const allStats = this.getStoredStats(); + return allStats[gameId] || null; + } - if (update.lastScore && update.lastScore > currentStats.highScore) { - allStats[gameId].highScore = update.lastScore; - } + updateStats(gameId: string, update: Partial): void { + const allStats = this.getStoredStats(); + const currentStats = allStats[gameId] || { + gameId, + highScore: 0, + lastScore: 0, + gamesPlayed: 0, + totalPlayTime: 0, + lastPlayed: new Date().toISOString(), + achievements: [], + }; - this.saveStats(allStats); - } + allStats[gameId] = { + ...currentStats, + ...update, + gameId, + lastPlayed: new Date().toISOString(), + }; - getAllStats(): Record { - return this.getStoredStats(); - } + if (update.lastScore && update.lastScore > currentStats.highScore) { + allStats[gameId].highScore = update.lastScore; + } - getGlobalStats(): GlobalStats { - const allStats = this.getStoredStats(); - const statsArray = Object.values(allStats); + this.saveStats(allStats); + } - if (statsArray.length === 0) { - return { - totalGamesPlayed: 0, - totalPlayTime: 0, - gamesWithStats: 0 - }; - } + getAllStats(): Record { + return this.getStoredStats(); + } - const totalGamesPlayed = statsArray.reduce((sum, stat) => sum + stat.gamesPlayed, 0); - const totalPlayTime = statsArray.reduce((sum, stat) => sum + stat.totalPlayTime, 0); + getGlobalStats(): GlobalStats { + const allStats = this.getStoredStats(); + const statsArray = Object.values(allStats); - const favoriteGame = statsArray.reduce((fav, stat) => - (!fav || stat.gamesPlayed > fav.gamesPlayed) ? stat : fav - ).gameId; + if (statsArray.length === 0) { + return { + totalGamesPlayed: 0, + totalPlayTime: 0, + gamesWithStats: 0, + }; + } - const lastPlayedGame = statsArray.reduce((last, stat) => - (!last || new Date(stat.lastPlayed) > new Date(last.lastPlayed)) ? stat : last - ).gameId; + const totalGamesPlayed = statsArray.reduce((sum, stat) => sum + stat.gamesPlayed, 0); + const totalPlayTime = statsArray.reduce((sum, stat) => sum + stat.totalPlayTime, 0); - return { - totalGamesPlayed, - totalPlayTime, - favoriteGame, - lastPlayedGame, - gamesWithStats: statsArray.length - }; - } + const favoriteGame = statsArray.reduce((fav, stat) => + !fav || stat.gamesPlayed > fav.gamesPlayed ? stat : fav + ).gameId; - incrementGamesPlayed(gameId: string): void { - const stats = this.getStats(gameId) || { - gameId, - highScore: 0, - lastScore: 0, - gamesPlayed: 0, - totalPlayTime: 0, - lastPlayed: new Date().toISOString() - }; + const lastPlayedGame = statsArray.reduce((last, stat) => + !last || new Date(stat.lastPlayed) > new Date(last.lastPlayed) ? stat : last + ).gameId; - this.updateStats(gameId, { - gamesPlayed: stats.gamesPlayed + 1 - }); - } + return { + totalGamesPlayed, + totalPlayTime, + favoriteGame, + lastPlayedGame, + gamesWithStats: statsArray.length, + }; + } - addPlayTime(gameId: string, seconds: number): void { - const stats = this.getStats(gameId) || { - gameId, - highScore: 0, - lastScore: 0, - gamesPlayed: 0, - totalPlayTime: 0, - lastPlayed: new Date().toISOString() - }; + incrementGamesPlayed(gameId: string): void { + const stats = this.getStats(gameId) || { + gameId, + highScore: 0, + lastScore: 0, + gamesPlayed: 0, + totalPlayTime: 0, + lastPlayed: new Date().toISOString(), + }; - this.updateStats(gameId, { - totalPlayTime: stats.totalPlayTime + seconds - }); - } + this.updateStats(gameId, { + gamesPlayed: stats.gamesPlayed + 1, + }); + } - unlockAchievement(gameId: string, achievement: Achievement): void { - const stats = this.getStats(gameId); - if (!stats) return; + addPlayTime(gameId: string, seconds: number): void { + const stats = this.getStats(gameId) || { + gameId, + highScore: 0, + lastScore: 0, + gamesPlayed: 0, + totalPlayTime: 0, + lastPlayed: new Date().toISOString(), + }; - const achievements = stats.achievements || []; - const exists = achievements.find(a => a.id === achievement.id); - - if (!exists) { - achievements.push({ - ...achievement, - unlockedAt: new Date().toISOString() - }); + this.updateStats(gameId, { + totalPlayTime: stats.totalPlayTime + seconds, + }); + } - this.updateStats(gameId, { achievements }); - } - } + unlockAchievement(gameId: string, achievement: Achievement): void { + const stats = this.getStats(gameId); + if (!stats) return; - formatPlayTime(seconds: number): string { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - if (hours > 0) { - return `${hours}h ${minutes}m`; - } - return `${minutes}m`; - } + const achievements = stats.achievements || []; + const exists = achievements.find((a) => a.id === achievement.id); - getRelativeTime(dateString: string): string { - const date = new Date(dateString); - const now = new Date(); - const diffMs = now.getTime() - date.getTime(); - const diffMins = Math.floor(diffMs / 60000); - const diffHours = Math.floor(diffMs / 3600000); - const diffDays = Math.floor(diffMs / 86400000); + if (!exists) { + achievements.push({ + ...achievement, + unlockedAt: new Date().toISOString(), + }); - if (diffMins < 1) return 'Gerade eben'; - if (diffMins < 60) return `Vor ${diffMins} Minuten`; - if (diffHours < 24) return `Vor ${diffHours} Stunden`; - if (diffDays < 7) return `Vor ${diffDays} Tagen`; - - return date.toLocaleDateString('de-DE'); - } + this.updateStats(gameId, { achievements }); + } + } + + formatPlayTime(seconds: number): string { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + return `${minutes}m`; + } + + getRelativeTime(dateString: string): string { + const date = new Date(dateString); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + const diffDays = Math.floor(diffMs / 86400000); + + if (diffMins < 1) return 'Gerade eben'; + if (diffMins < 60) return `Vor ${diffMins} Minuten`; + if (diffHours < 24) return `Vor ${diffHours} Stunden`; + if (diffDays < 7) return `Vor ${diffDays} Tagen`; + + return date.toLocaleDateString('de-DE'); + } } -export const statsService = new StatsService(); \ No newline at end of file +export const statsService = new StatsService(); diff --git a/games/mana-games/apps/web/tsconfig.json b/games/mana-games/apps/web/tsconfig.json index 8bf91d3bb..a9210e68f 100644 --- a/games/mana-games/apps/web/tsconfig.json +++ b/games/mana-games/apps/web/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "astro/tsconfigs/strict", - "include": [".astro/types.d.ts", "**/*"], - "exclude": ["dist"] + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] } diff --git a/games/mana-games/package.json b/games/mana-games/package.json index 07c5ec9d3..6f7d57760 100644 --- a/games/mana-games/package.json +++ b/games/mana-games/package.json @@ -1,10 +1,10 @@ { - "name": "mana-games", - "version": "1.0.0", - "private": true, - "description": "AI-powered browser games platform", - "scripts": { - "dev": "turbo run dev", - "build": "turbo run build" - } + "name": "mana-games", + "version": "1.0.0", + "private": true, + "description": "AI-powered browser games platform", + "scripts": { + "dev": "turbo run dev", + "build": "turbo run build" + } } diff --git a/packages/mana-core-nestjs-integration/src/guards/optional-auth.guard.ts b/packages/mana-core-nestjs-integration/src/guards/optional-auth.guard.ts index 11506a522..ee5ba7442 100644 --- a/packages/mana-core-nestjs-integration/src/guards/optional-auth.guard.ts +++ b/packages/mana-core-nestjs-integration/src/guards/optional-auth.guard.ts @@ -1,10 +1,4 @@ -import { - Injectable, - CanActivate, - ExecutionContext, - Inject, - Optional, -} from '@nestjs/common'; +import { Injectable, CanActivate, ExecutionContext, Inject, Optional } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { MANA_CORE_OPTIONS } from '../mana-core.module'; import { ManaCoreModuleOptions } from '../interfaces/mana-core-options.interface'; diff --git a/packages/mana-core-nestjs-integration/src/services/credit-client.service.ts b/packages/mana-core-nestjs-integration/src/services/credit-client.service.ts index 1e62022aa..2ea4f9729 100644 --- a/packages/mana-core-nestjs-integration/src/services/credit-client.service.ts +++ b/packages/mana-core-nestjs-integration/src/services/credit-client.service.ts @@ -47,10 +47,7 @@ export class CreditClientService { private getAppId(): string { return ( - this.options?.appId || - this.configService?.get('APP_ID') || - process.env.APP_ID || - '' + this.options?.appId || this.configService?.get('APP_ID') || process.env.APP_ID || '' ); } diff --git a/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte b/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte index 886a10ee1..9db522ddc 100644 --- a/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte +++ b/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte @@ -99,7 +99,9 @@ ); // Effective dark mode based on user preference or system - let isDark = $derived(userThemePreference !== null ? userThemePreference === 'dark' : systemIsDark); + let isDark = $derived( + userThemePreference !== null ? userThemePreference === 'dark' : systemIsDark + ); $effect(() => { if (typeof window !== 'undefined') { @@ -159,7 +161,13 @@ {#if headerControls} -
+
{@render headerControls()}
{/if} diff --git a/packages/shared-auth-ui/src/pages/LoginPage.svelte b/packages/shared-auth-ui/src/pages/LoginPage.svelte index c398acee3..dc6e8fcfb 100644 --- a/packages/shared-auth-ui/src/pages/LoginPage.svelte +++ b/packages/shared-auth-ui/src/pages/LoginPage.svelte @@ -120,7 +120,9 @@ ); // Effective dark mode based on user preference or system - let isDark = $derived(userThemePreference !== null ? userThemePreference === 'dark' : systemIsDark); + let isDark = $derived( + userThemePreference !== null ? userThemePreference === 'dark' : systemIsDark + ); $effect(() => { if (typeof window !== 'undefined') { @@ -293,10 +295,7 @@
-
+

{t.title}

{t.subtitle}

@@ -309,7 +308,13 @@
{/if} -
{ e.preventDefault(); handleLogin(); }} aria-busy={loading}> + { + e.preventDefault(); + handleLogin(); + }} + aria-busy={loading} + >
@@ -366,7 +371,12 @@ {t.rememberMe} -
@@ -380,7 +390,13 @@ style:border-color={showSuccess ? '#22c55e' : primaryColor} > {#if loading} - + @@ -624,7 +640,9 @@ border: 1px solid; border-radius: 0.75rem; font-size: 1rem; - transition: border-color 0.2s, box-shadow 0.2s; + transition: + border-color 0.2s, + box-shadow 0.2s; /* Dark mode default */ background-color: rgba(0, 0, 0, 0.2); border-color: rgba(255, 255, 255, 0.2); @@ -698,7 +716,7 @@ color: rgba(0, 0, 0, 0.7); } - .remember-label input[type="checkbox"] { + .remember-label input[type='checkbox'] { width: 1.125rem; height: 1.125rem; cursor: pointer; @@ -711,8 +729,8 @@ place-content: center; } - .remember-label input[type="checkbox"]::before { - content: ""; + .remember-label input[type='checkbox']::before { + content: ''; width: 0.65rem; height: 0.65rem; transform: scale(0); @@ -721,15 +739,15 @@ clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } - .remember-label input[type="checkbox"]:checked { + .remember-label input[type='checkbox']:checked { border-color: var(--primary-color, #fff); } - .remember-label input[type="checkbox"]:checked::before { + .remember-label input[type='checkbox']:checked::before { transform: scale(1); } - .light .remember-label input[type="checkbox"] { + .light .remember-label input[type='checkbox'] { border-color: rgba(0, 0, 0, 0.3); } @@ -857,8 +875,12 @@ } @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } .logo-section { @@ -875,9 +897,23 @@ /* Interactive Animations */ @keyframes shake { - 0%, 100% { transform: translateX(0); } - 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } - 20%, 40%, 60%, 80% { transform: translateX(4px); } + 0%, + 100% { + transform: translateX(0); + } + 10%, + 30%, + 50%, + 70%, + 90% { + transform: translateX(-4px); + } + 20%, + 40%, + 60%, + 80% { + transform: translateX(4px); + } } .shake { @@ -885,7 +921,9 @@ } @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } .spinner { @@ -895,8 +933,13 @@ } @keyframes success-pulse { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.05); } + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } } .success-pulse { diff --git a/packages/shared-auth-ui/src/pages/RegisterPage.svelte b/packages/shared-auth-ui/src/pages/RegisterPage.svelte index b917de3b3..4b80f787e 100644 --- a/packages/shared-auth-ui/src/pages/RegisterPage.svelte +++ b/packages/shared-auth-ui/src/pages/RegisterPage.svelte @@ -117,7 +117,9 @@ ); // Effective dark mode based on user preference or system - let isDark = $derived(userThemePreference !== null ? userThemePreference === 'dark' : systemIsDark); + let isDark = $derived( + userThemePreference !== null ? userThemePreference === 'dark' : systemIsDark + ); $effect(() => { if (typeof window !== 'undefined') { @@ -241,7 +243,13 @@ {#if headerControls} -
+
{@render headerControls()}
{/if} diff --git a/packages/shared-auth-ui/src/types.ts b/packages/shared-auth-ui/src/types.ts index a02e5f5ff..e522a53b0 100644 --- a/packages/shared-auth-ui/src/types.ts +++ b/packages/shared-auth-ui/src/types.ts @@ -60,4 +60,3 @@ export interface AuthResult { error?: string; needsVerification?: boolean; } - diff --git a/packages/shared-branding/src/app-icons.ts b/packages/shared-branding/src/app-icons.ts index 487915d39..91b9ad413 100644 --- a/packages/shared-branding/src/app-icons.ts +++ b/packages/shared-branding/src/app-icons.ts @@ -5,9 +5,7 @@ // Helper to convert SVG string to data URL const svgToDataUrl = (svg: string): string => { - const encoded = encodeURIComponent(svg) - .replace(/'/g, '%27') - .replace(/"/g, '%22'); + const encoded = encodeURIComponent(svg).replace(/'/g, '%27').replace(/"/g, '%22'); return `data:image/svg+xml,${encoded}`; }; diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts index 083bd8479..e10410e08 100644 --- a/packages/shared-branding/src/mana-apps.ts +++ b/packages/shared-branding/src/mana-apps.ts @@ -286,7 +286,8 @@ export function getPillAppItems( isDev?: boolean, customUrls?: Partial> ): PillAppItemConfig[] { - const isDevMode = isDev ?? (typeof window !== 'undefined' && window.location.hostname === 'localhost'); + const isDevMode = + isDev ?? (typeof window !== 'undefined' && window.location.hostname === 'localhost'); // Only show active (non-archived) apps return getActiveManaApps().map((app) => ({ diff --git a/packages/shared-branding/src/types.ts b/packages/shared-branding/src/types.ts index 13191ede8..41066bb1d 100644 --- a/packages/shared-branding/src/types.ts +++ b/packages/shared-branding/src/types.ts @@ -1,7 +1,17 @@ /** * App identifiers for branding */ -export type AppId = 'memoro' | 'manacore' | 'manadeck' | 'maerchenzauber' | 'uload' | 'chat' | 'presi' | 'nutriphi' | 'zitare' | 'picture'; +export type AppId = + | 'memoro' + | 'manacore' + | 'manadeck' + | 'maerchenzauber' + | 'uload' + | 'chat' + | 'presi' + | 'nutriphi' + | 'zitare' + | 'picture'; /** * App branding configuration diff --git a/packages/shared-feedback-service/package.json b/packages/shared-feedback-service/package.json index d6cdc97f1..757f070c6 100644 --- a/packages/shared-feedback-service/package.json +++ b/packages/shared-feedback-service/package.json @@ -11,7 +11,9 @@ }, "main": "./src/index.ts", "types": "./src/index.ts", - "files": ["src"], + "files": [ + "src" + ], "scripts": { "type-check": "tsc --noEmit" }, diff --git a/packages/shared-feedback-service/src/createFeedbackService.ts b/packages/shared-feedback-service/src/createFeedbackService.ts index 9213cc2eb..e0f0563c3 100644 --- a/packages/shared-feedback-service/src/createFeedbackService.ts +++ b/packages/shared-feedback-service/src/createFeedbackService.ts @@ -39,10 +39,7 @@ export function createFeedbackService(config: FeedbackServiceConfig) { /** * Helper to make authenticated requests */ - async function fetchWithAuth( - endpoint: string, - options: RequestInit = {} - ): Promise { + async function fetchWithAuth(endpoint: string, options: RequestInit = {}): Promise { const token = await getAuthToken(); if (!token) { diff --git a/packages/shared-feedback-ui/src/FeedbackPage.svelte b/packages/shared-feedback-ui/src/FeedbackPage.svelte index b9251e12c..8dc68b39b 100644 --- a/packages/shared-feedback-ui/src/FeedbackPage.svelte +++ b/packages/shared-feedback-ui/src/FeedbackPage.svelte @@ -104,8 +104,17 @@