{
  "file_analyses": [
    {
      "file": ".cursor/rules/api-endpoints.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Error scenarios handled gracefully"
        ],
        "code_examples": [
          "```php if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) { http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Bitte gültige E-Mail-Adresse eingeben.' ]); exit; } ```",
          "```php try { // API logic here echo json_encode([ 'success' => true, 'message' => 'Erfolgreich gespeichert.', 'data' => $result ]); } catch (Exception $e) { error_log('API Error: ' . $e->getMessage()); error_log('Context: ' . json_encode($_POST)); http_response_code(500); echo json_encode([ 'success' => false, 'message' => 'Ein Fehler ist aufgetreten. Bitte versuche es erneut.' ]); } ```",
          "```php header('Content-Type: application/json'); echo json_encode([ 'success' => true|false, 'message' => 'User-friendly message (du tone)', 'data' => [...] // Optional ]); ```",
          "```php $logEntry = [ 'timestamp' => date('Y-m-d H:i:s'), 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', 'data' => $_POST ]; file_put_contents( __DIR__ . '/../logs/submissions.log', json_encode($logEntry) . PHP_EOL, FILE_APPEND ); ```",
          "```php error_log('API Error: ' . $e->getMessage()); error_log('Context: ' . json_encode($context)); ```",
          "```php \"context\" => array_filter([ // CRITICAL: hutk (hubspotutk) is essential for linking form submission to browser session // HubSpot uses this to associate page views, sessions, and other activity with the contact \"hutk\" => !empty($hubspotutk) ? $hubspotutk : null, \"ipAddress\" => $_SERVER['REMOTE_ADDR'] ?? null, // CRITICAL: pageUri and pageName ensure HubSpot tracks which page the form was submitted on // This appears in HubSpot as \"CONVERSION PAGE\" and helps with attribution \"pageUri\" => getActualPageUrl($page_url, $referrer), \"pageName\" => getPageNameFromUrl($page_url, $defaultPageName) ]) ```",
          "```php // CRITICAL FIX: Extract hubspotutk - prioritize cookie (more reliable for session tracking), then fallback to input parameter // This matches best practices for proper activity tracking $hubspotutk = isset($_COOKIE['hubspotutk']) ? $_COOKIE['hubspotutk'] : (!empty($input['hubspotutk']) ? trim($input['hubspotutk']) : ''); if (!empty($hubspotutk)) { $utmSource = isset($_COOKIE['hubspotutk']) ? 'cookie' : 'input'; error_log(\"filename.php - hubspotutk source: $utmSource\"); } ```",
          "```php // CRITICAL FIX: Retry logic with exponential backoff (3 attempts) $maxRetries = 3; $retryDelay = 1; // Start with 1 second $result = false; $httpCode = 0; $curlError = ''; $response = null; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { $ch = curl_init(); // ... curl setup ... $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); // Log attempt details error_log(\"filename.php - Forms API v3 Attempt $attempt/$maxRetries: HTTP $httpCode\"); // Check for HTTP 200 OR 204 (success) if ($result !== false && ($httpCode === 200 || $httpCode === 204)) { if ($attempt > 1) { error_log(\"filename.php - Forms API v3 succeeded on attempt $attempt\"); } break; } // If this is not the last attempt, wait before retrying if ($attempt < $maxRetries) { // Exponential backoff: 1s, 2s, 4s sleep($retryDelay); $retryDelay *= 2; // Log retry attempt error_log(\"filename.php - Forms API v3 failed (HTTP $httpCode), retrying in {$retryDelay}s...\"); if ($result !== false) { error_log(\"filename.php - Forms API v3 Response: \" . substr($result, 0, 500)); } if (!empty($curlError)) { error_log(\"filename.php - cURL Error: \" . $curlError); } } } $response = $result; ```",
          "```javascript // Get UTM data from the UTM tracker let utmData = {}; if ( window.utmTracker && typeof window.utmTracker.getUTMDataForAPI === \"function\" ) { utmData = window.utmTracker.getUTMDataForAPI(); // Now includes hubspotutk } else if (window.utmTracking) { // Fallback to direct window object utmData = { // ... UTM fields ... hubspotutk: window.utmTracker && typeof window.utmTracker.getHubspotutk === \"function\" ? window.utmTracker.getHubspotutk() || \"\" : \"\", }; } // In fetch request body body: JSON.stringify({ // ... form fields ... ...utmData, // Includes hubspotutk hubspotutk: utmData.hubspotutk || \"\", // Explicit inclusion as fallback }); ```",
          "```php // Track page view BEFORE contact creation (if hubspotutk exists) if (!empty($hubspotutk)) { trackPageViewBeforeCreation($hubspotutk, $pageUrl, $utmParams); } // Create contact via CRM API $contactId = createHubSpotContact($data); // Track page view AFTER contact creation (using contact ID) if (!empty($contactId)) { trackPageViewAfterCreation($contactId, $email, $pageUrl, $utmParams, $hubspotutk); } ```",
          "```php require_once __DIR__ . '/../helpers/hubspot-context.php'; \"context\" => array_filter([ \"hutk\" => !empty($hubspotutk) ? $hubspotutk : null, \"ipAddress\" => $_SERVER['REMOTE_ADDR'] ?? null, \"pageUri\" => getActualPageUrl($page_url, $referrer), \"pageName\" => getPageNameFromUrl($page_url, \"Default Page Name\") ]) ```",
          "``` GET /v2/api/lead-capture.php?debug_hubspot=test ```",
          "```php // Simple rate limiting example $ip = $_SERVER['REMOTE_ADDR']; $rateLimitFile = __DIR__ . '/../cache/rate_limit_' . md5($ip); if (file_exists($rateLimitFile)) { $lastRequest = (int)file_get_contents($rateLimitFile); if (time() - $lastRequest < 5) { // 5 seconds between requests http_response_code(429); echo json_encode([ 'success' => false, 'message' => 'Zu viele Anfragen. Bitte warte kurz.' ]); exit; } } file_put_contents($rateLimitFile, time()); ```",
          "```php header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment; filename=\"export-' . date('Y-m-d') . '.xlsx\"'); ```",
          "```php if (file_exists($tempFile)) { unlink($tempFile); } ```",
          "```html <!-- CRITICAL: Include hubspotutk for Forms API v3 context to link form submission to browser session --> <input type=\"hidden\" name=\"hubspotutk\" id=\"hubspotutk\" value=\"\" /> ```",
          "```javascript // Extract and populate hubspotutk for Forms API v3 context // Priority: window.utmTracker.getHubspotutk() → cookie fallback let hubspotutk = \"\"; if ( window.utmTracker && typeof window.utmTracker.getHubspotutk === \"function\" ) { hubspotutk = window.utmTracker.getHubspotutk() || \"\"; } else { // Fallback: direct cookie extraction const match = document.cookie.match( /(?:^|.*;\\s*)hubspotutk\\s*=\\s*([^;]*).*$/ ); hubspotutk = match ? decodeURIComponent(match[1]) : \"\"; } const hubspotutkField = document.getElementById(\"hubspotutk\"); if (hubspotutkField) { hubspotutkField.value = hubspotutk; } ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "du tone)',\n    'data' => [...] // Optional\n]);\n```"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 19
    },
    {
      "file": ".cursor/rules/base-components.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Rollback plan prepared (if needed)"
        ],
        "code_examples": [
          "```php <?php if ($pageType === 'comparison') { // Comparison-specific behavior } else { // Default behavior } ?> ```",
          "```php // Added 2025-01-15: Support for comparison page carousel ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 3
    },
    {
      "file": ".cursor/rules/comparison-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Images use WebP format",
          "- [ ] FAQ answers competitor-specific questions",
          "- [ ] Dates use ISO 8601 format",
          "- [ ] All PHP includes present and correct",
          "- [ ] Alpine.js components initialize correctly"
        ],
        "code_examples": [
          "```php <?php $generator = $pageGenerator; include __DIR__ . '/../components/comparison/hero.php'; ?> ```",
          "```html <img src=\"/v2/img/alternativen/{competitor}-vergleich-logo-160w.webp\" srcset=\" /v2/img/alternativen/{competitor}-vergleich-logo-160w.webp 160w, /v2/img/alternativen/{competitor}-vergleich-logo-320w.webp 320w \" sizes=\"(max-width: 640px) 160px, 160px\" alt=\"{Competitor}\" class=\"max-h-8 max-w-full object-contain\" width=\"160\" height=\"32\" fetchpriority=\"high\" /> ```",
          "```html <link rel=\"preload\" href=\"/v2/img/alternativen/{competitor}-vergleich-logo-160w.webp\" as=\"image\" fetchpriority=\"high\" /> ```",
          "```html <h1> {Competitor} Alternativen: <span class=\"text-ordio-blue\">Vergleich</span> & <span class=\"text-ordio-blue\">Bewertung</span> 2025 </h1> ```",
          "```php <div class=\"comparison-grid\" x-data=\"{ ordioDetailsHeight: 0 }\" @height-changed=\"ordioDetailsHeight = $event.detail.height\"> <!-- Ordio Column --> <div class=\"bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden ordio-card comparison-card\"> <div class=\"bg-ordio-blue px-8 py-6\"> <!-- Ordio header with logo and rating --> </div> <div class=\"p-8\"> <?php include '../components/ordio_comparison_content.php'; ?> <!-- Star Rating Distribution --> <!-- Ratings Section --> <!-- Pricing Section --> <!-- CTA Buttons --> </div> </div> <!-- Competitor Column --> <div class=\"bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden personio-card comparison-card\"> <!-- Competitor content --> </div> </div> ```",
          "```php <?php // Hero Section (component-based) $generator = $pageGenerator; include __DIR__ . '/../components/comparison/hero.php'; // Company Logos include __DIR__ . '/../components/comparison/company_logos.php'; // Comparison Grid include __DIR__ . '/../components/comparison/grid.php'; // Additional sections include __DIR__ . '/../components/comparison/legal_notice.php'; include __DIR__ . '/../components/comparison/all_in_one.php'; include __DIR__ . '/../components/comparison/ordio_features.php'; include __DIR__ . '/../components/comparison/testimonials.php'; include __DIR__ . '/../components/comparison/compare_carousel.php'; // FAQ (generated by ComparisonPageGenerator) echo $pageGenerator->generateFAQ(); ?> ```",
          "```php <?php // Ordio Comparison Content (shared comparison text) include '../components/ordio_comparison_content.php'; // Comparison Carousel (other alternatives) include '../base/compare_carousel.php'; ?> ```",
          "```php <link rel=\"stylesheet\" href=\"/v2/css/comparison-pages.css?v=<?php echo filemtime($_SERVER['DOCUMENT_ROOT'] . '/v2/css/comparison-pages.css'); ?>\" media=\"print\" onload=\"this.media='all'\"> <noscript><link rel=\"stylesheet\" href=\"/v2/css/comparison-pages.css\"></noscript> ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"Table\", \"@id\": \"https://www.ordio.com/alternativen/{competitor}-vergleich#comparison-table\", \"about\": { \"@type\": \"SoftwareApplication\", \"name\": \"{Competitor}\", \"applicationCategory\": \"BusinessApplication\" } } ```",
          "```json { \"@type\": \"Product\", \"name\": \"Ordio\", \"brand\": { \"@type\": \"Brand\", \"name\": \"Ordio\" }, \"aggregateRating\": { \"@type\": \"AggregateRating\", \"ratingValue\": \"4.9\", \"reviewCount\": \"54\" }, \"offers\": { \"@type\": \"Offer\", \"price\": \"89\", \"priceCurrency\": \"EUR\" } } ```",
          "```html <title>{Competitor} Alternativen: Vergleich & Bewertung 2025 - Ordio</title> ```",
          "```html <meta name=\"description\" content=\"{Competitor} Alternativen im Vergleich 2025: Ordio vs {Competitor}. [Focus/Use Case] für [Target Audience]. Finde die beste [Category]-Alternative.\" /> ```",
          "```html <meta name=\"keywords\" content=\"{Competitor} Alternativen, {Competitor} Vergleich, {Competitor} [Feature], {Competitor} Kosten, Ordio vs {Competitor}, [Category] Software\" /> ```",
          "```html <link rel=\"canonical\" href=\"https://www.ordio.com/alternativen/{competitor}-vergleich\" /> ```",
          "```bash node scripts/generate_responsive_logos.js ```",
          "```bash cp v2/pages/compare_template_details.php v2/pages/compare_{new_competitor}.php # OR cp v2/pages/compare_template_nodetails.php v2/pages/compare_{new_competitor}.php ```",
          "```php '{slug}' => [ 'slug' => '{slug}', 'name' => '{Name}', 'rating' => '{rating}', // Real value, not placeholder 'reviews' => '{count}', // Real count, not placeholder 'description' => '{...}', // Min 200 words, competitor-specific 'category' => '{Category}', 'focus' => '{Focus}', 'target' => '{Target}', 'logo_alt' => '{Alt}', 'logo_class' => '', 'pricing' => [ 'starting_price' => '{price}', // Keep for backward compatibility 'price_unit' => '{unit}', // Fallback if no plans 'currency' => '{EUR|USD}', 'plans' => [ // Optional: array of pricing plans [ 'name' => '{Plan Name}', // Required: e.g., 'Starter', 'Basic', 'Professional' 'description' => '{Description}', // Optional: plan description 'price' => '{price}', // Required: numeric string (e.g., '89.00') or text ('Auf Anfrage', 'Kostenlos') 'price_text' => '{Formatted Price}', // Optional: formatted display text (e.g., '€89,00') 'frequency' => '{Frequency}', // Optional: e.g., '/ User / Monat', '/ Standort / Monat' 'recommendation' => '{Recommendation}', // Optional: e.g., 'Empfohlen für 1-5 User' ], // ... more plans ], ], 'faq' => [ // Min 6 items ['question' => '...', 'answer' => '...'], // ... ], 'rating_distribution' => [ // Must sum to 100% '5' => ['percentage' => X, 'count' => Y], // ... ], 'schema' => [ 'name' => '{Name}', 'description' => '{...}', 'url' => '{slug}-vergleich', ], 'has_details' => true|false, 'details' => [ // Only if has_details is true 'sections' => [ [ 'title' => '{Section Title}', 'type' => 'list'|'paragraph'|'mixed', 'items' => ['Item 1', 'Item 2'], // for list type 'content' => '{Paragraph content}', // for paragraph type 'items_with_descriptions' => [ // for mixed type [ 'title' => '{Item Title}', 'description' => '{Item Description}', ], ], ], ], ], ] ```",
          "```bash python3 scripts/data/extract_competitor_data.py ```",
          "```bash python3 scripts/data/validate_extracted_data.py php scripts/data/validate_and_compare.php ```",
          "```bash # Automated update (creates backup automatically) php scripts/data/update_competitors_data.php # Manual update may be required for some entries ```",
          "```php <div class=\"comparison-grid\" x-data=\"{ ordioDetailsHeight: 0 }\" @height-changed=\"ordioDetailsHeight = $event.detail.height\"> ```",
          "```php <div x-data=\"{ open: false }\" x-init=\" $nextTick(() => { $dispatch('height-changed', { height: $el.offsetHeight }); }); $watch('open', () => { $nextTick(() => { setTimeout(() => { $dispatch('height-changed', { height: $el.offsetHeight }); }, 350); }); }); \"> ```",
          "```bash node scripts/generate_responsive_logos.js ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [],
        "css_patterns": [],
        "meta_tag_patterns": [
          "<title>{Competitor} Alternativen: Vergleich & Bewertung 2025 - Ordio</title>",
          "<meta\n  name=\"description\"\n  content=\"{Competitor} Alternativen im Vergleich 2025: Ordio vs {Competitor}. [Focus/Use Case] für [Target Audience]. Finde die beste [Category]-Alternative.\"\n/>",
          "<meta\n  name=\"keywords\"\n  content=\"{Competitor} Alternativen, {Competitor} Vergleich, {Competitor} [Feature], {Competitor} Kosten, Ordio vs {Competitor}, [Category] Software\"\n/>"
        ]
      },
      "total_patterns": 31
    },
    {
      "file": ".cursor/rules/content-clusters.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Verify all links functional"
        ],
        "code_examples": [],
        "schema_examples": [],
        "copy_guidelines": [],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 1
    },
    {
      "file": ".cursor/rules/download-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Ordio mention natural (once)"
        ],
        "code_examples": [
          "```php <?php include '../base/include_form-gated-content.php'; ?> ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"DigitalDocument\", \"name\": \"[Document Name]\", \"description\": \"[What the document covers]\", \"fileFormat\": \"application/pdf\", \"author\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"publisher\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"datePublished\": \"2024-01-15\", \"dateModified\": \"2025-01-15\", \"about\": \"[Topic/Subject]\" } ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "du tone",
          "du tone, benefit-driven\n- [ ] Ordio mention natural (once)",
          "Ordio Mention Pattern",
          "Ordio mention natural (once)"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 7
    },
    {
      "file": ".cursor/rules/global.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Check if similar changes should apply to related pages (see Whole-Project Thinking below)",
          "- [ ] Confirm pricing and ratings are up-to-date",
          "- [ ] Twitter Card tags complete",
          "- [ ] Benefits-focused (not feature lists)",
          "- [ ] Touch targets minimum 44x44px",
          "- [ ] CSS and JavaScript deferred where appropriate",
          "- [ ] Test on desktop, tablet, mobile viewports"
        ],
        "code_examples": [
          "```php <?php $aosScript = \"true\"; // or \"false\" if no animations $swiperScript = \"false\"; // or \"true\" if carousel needed include '../base/head.php'; ?> ```",
          "```php <?php $headerwidth = \"w-full\"; // or \"max-w-7xl\" for contained include '../base/header.php'; ?> ```",
          "```php <?php $ctaPositionLeft = 'true'; // or 'false' for right $ctaButtonsAOS = 'false'; // animation $ctaButtonsLight = 'yes'; // or 'no' for dark $showCallbackButton = 'yes'; // or 'no' to hide callback include '../base/include_ctabuttons.php'; ?> ```",
          "```php <?php $color_fill = '#fff'; // fill color $color_background = '#fbfbfb'; // background color $rotate = '0'; // rotation angle $margin_bottom = 'mb-24'; // bottom margin class include '../base/footer.php'; ?> ```",
          "```html <img src=\"/v2/img/hero-640w.webp\" srcset=\"/v2/img/hero-640w.webp 640w, /v2/img/hero-1280w.webp 1280w\" sizes=\"(max-width: 768px) 640px, 1280px\" alt=\"Descriptive alt text\" width=\"1280\" height=\"720\" fetchpriority=\"high\" /> ```",
          "```php <link rel=\"stylesheet\" href=\"/v2/css/file.min.css?v=<?php echo filemtime(__DIR__ . '/../css/file.min.css'); ?>\"> <script src=\"/v2/js/file.min.js?v=<?php echo filemtime(__DIR__ . '/../js/file.min.js'); ?>\"></script> ```",
          "```php try { // Code here } catch (Exception $e) { error_log('Context: ' . $e->getMessage()); error_log('Data: ' . json_encode($context)); // Return user-friendly error, never expose internals echo json_encode(['success' => false, 'message' => 'Ein Fehler ist aufgetreten.']); } ```",
          "```php header('Content-Type: application/json'); echo json_encode([ 'success' => true|false, 'message' => 'User-friendly message', 'data' => [...] // Optional ]); ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "Du Tone (Informal \"You\")",
          "du tone, benefit-driven\n- [ ] Canonical URL absolute and correct\n- [ ] Open Graph tags complete (og:title, og:description, og:url, og:image, og:type)\n- [ ] Twitter Card tags complete",
          "du tone (informal \"du\" not formal \"Sie\")\n- [ ] Ordio mentioned naturally once per major section\n- [ ] No competitor praise\n- [ ] Paragraphs short (2-3 sentences)\n- [ ] Benefits-focused (not feature lists)",
          "du tone, Ordio mentions (once per section), no competitor praise\n3. Lead with benefits, not features",
          "Ordio Mentions",
          "Ordio mentioned naturally once per major section\n- [ ] No competitor praise\n- [ ] Paragraphs short (2-3 sentences)\n- [ ] Benefits-focused (not feature lists)",
          "Ordio mentions (once per section), no competitor praise\n3. Lead with benefits, not features",
          "No competitor praise\n- [ ] Paragraphs short (2-3 sentences)\n- [ ] Benefits-focused (not feature lists)",
          "no competitor praise\n3. Lead with benefits, not features"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 24
    },
    {
      "file": ".cursor/rules/homepage.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] No layout shifts on load (all images have dimensions)"
        ],
        "code_examples": [
          "``` [Action] [Outcome] [Target Audience] ```",
          "```html <div class=\"value-prop\"> <img src=\"[icon]\" alt=\"[benefit]\" /> <h3>[Outcome: e.g., \"Spare Zeit\"]</h3> <p> [How: e.g., \"Erstelle Dienstpläne 70% schneller mit automatischen Vorschlägen\"] </p> </div> ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"WebSite\", \"name\": \"Ordio\", \"url\": \"https://www.ordio.com\", \"potentialAction\": { \"@type\": \"SearchAction\", \"target\": \"https://www.ordio.com/search?q={search_term_string}\", \"query-input\": \"required name=search_term_string\" } } ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"Organization\", \"name\": \"Ordio GmbH\", \"url\": \"https://www.ordio.com\", \"logo\": \"https://www.ordio.com/v2/img/ordio-logo.png\", \"description\": \"[Company description]\", \"contactPoint\": { \"@type\": \"ContactPoint\", \"telephone\": \"+49-...\", \"contactType\": \"customer service\", \"areaServed\": \"DE\", \"availableLanguage\": \"German\" }, \"sameAs\": [ \"https://www.linkedin.com/company/ordio\", \"https://www.facebook.com/ordio\", \"[other social media URLs]\" ], \"address\": { \"@type\": \"PostalAddress\", \"streetAddress\": \"[Street]\", \"addressLocality\": \"[City]\", \"postalCode\": \"[ZIP]\", \"addressCountry\": \"DE\" }, \"foundingDate\": \"[YYYY-MM-DD]\" } ```",
          "```html <link rel=\"preload\" href=\"/v2/img/hero-image.webp\" as=\"image\" fetchpriority=\"high\" /> <img src=\"/v2/img/hero-640w.webp\" srcset=\"/v2/img/hero-640w.webp 640w, /v2/img/hero-1280w.webp 1280w\" sizes=\"(max-width: 768px) 640px, 1280px\" alt=\"[Descriptive alt]\" width=\"1280\" height=\"720\" fetchpriority=\"high\" /> ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "Du tone throughout",
          "du tone (3-5 items)\n- [ ] Social proof authentic and visible (logos, testimonials, ratings)\n- [ ] Performance optimized (LCP < 2.5s, CLS < 0.1, PageSpeed > 90)\n- [ ] Hero image preloaded with fetchpriority=\"high\"\n- [ ] Schema validates (WebSite, Organization, WebPage)\n- [ ] Meta tags complete and SEO-optimized\n- [ ] All CTAs functional and correctly linked\n- [ ] Copy is du tone, benefit-driven, conversion-focused\n- [ ] No layout shifts on load (all images have dimensions)",
          "Ordio mentions:**"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 9
    },
    {
      "file": ".cursor/rules/industry-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] No generic software language; industry context throughout"
        ],
        "code_examples": [
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"Industry\", \"name\": \"[Industry Name]\", \"description\": \"[Industry overview]\", \"industryCode\": { \"@type\": \"DefinedTerm\", \"termCode\": \"[NACE/WZ Code]\", \"name\": \"[Code Name]\" }, \"parentIndustry\": \"[Broader Category]\", \"subIndustries\": [\"[Sub-vertical 1]\", \"[Sub-vertical 2]\"], \"typicalChallenges\": [\"[Pain Point 1]\", \"[Pain Point 2]\", \"[Pain Point 3]\"], \"regulatoryRequirements\": [ \"[Compliance Requirement 1]\", \"[Compliance Requirement 2]\" ], \"customerCount\": \"[Number of Ordio customers in vertical]\", \"marketShare\": \"[Percentage of Ordio customer base]\" } ```",
          "```json { \"@type\": \"Service\", \"name\": \"Ordio für [Industry]\", \"description\": \"[How Ordio serves this vertical]\", \"provider\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"serviceType\": \"Personalverwaltungssoftware\", \"areaServed\": { \"@type\": \"Country\", \"name\": \"Deutschland\" }, \"audience\": { \"@type\": \"Audience\", \"audienceType\": \"[Industry] mit Mitarbeitern\" } } ```",
          "``` **Herausforderungen in der Gastronomie:** - **Stoßzeiten abdecken:** Montags wenig Personal, samstags Hochbetrieb - **Saisonale Schwankungen:** Im Sommer Biergarten, im Winter ruhiger - **Hohe Fluktuation:** Ständig neue Aushilfen und Teilzeitkräfte - **Trinkgeld korrekt abrechnen:** Aushändigung, Aufteilung, Dokumentation ```",
          "```html <title>Ordio für [Industry] – [Key Benefit for Vertical] - Ordio</title> ```",
          "```html <meta name=\"description\" content=\"Ordio für [Industry]: [Industry-Specific Benefit 1]. [Industry-Specific Benefit 2]. [Compliance Reference]. Kostenlos testen.\" /> ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "Ordio mention in 1-2 FAQ answers** (not all), naturally integrated with industry context."
        ],
        "css_patterns": [],
        "meta_tag_patterns": [
          "<title>Ordio für [Industry] – [Key Benefit for Vertical] - Ordio</title>",
          "<meta\n  name=\"description\"\n  content=\"Ordio für [Industry]: [Industry-Specific Benefit 1]. [Industry-Specific Benefit 2]. [Compliance Reference]. Kostenlos testen.\"\n/>"
        ]
      },
      "total_patterns": 9
    },
    {
      "file": ".cursor/rules/lead-capture.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Document any new attribution patterns in `docs/ATTRIBUTION_DEBUGGING_GUIDE.md`",
          "- [ ] Accessibility tested"
        ],
        "code_examples": [
          "```php <?php include '../components/lead-capture-popup.php'; ?> <script src=\"/v2/js/lead-capture-triggers.js\"></script> ```",
          "```html <button onclick=\"if(window.leadCapturePopup) { window.leadCapturePopup.show('manual-callback'); }\" > Rückruf anfordern </button> ```",
          "```php 'my_page' => [ 'urls' => ['/my-page', 'my_page.php'], 'headline' => 'Hast du Fragen zu [Topic]?', 'description' => 'Wir helfen dir gerne, die beste Lösung zu finden – kostenlos und unverbindlich.', 'funnel_stage' => 'MOF' // TOF, MOF, or BOF ], ```",
          "```php '/my-page' => 'my_page', ```",
          "```javascript // After page load if (window.leadCaptureTriggers) { // Remove default trigger window.leadCaptureTriggers.triggers.delete(\"time\"); // Add custom trigger window.leadCaptureTriggers.addTrigger(\"time\", { condition: () => !window.leadCaptureTriggers.hasShown, delay: 45000, // 45 seconds once: true, }); } ```",
          "```javascript // Browser console window.leadCapturePopup?.show(\"manual\"); window.resetLeadCapture(); console.log(window.leadCaptureTriggers?.hasShown); ```",
          "```bash # Server logs tail -f v2/logs/lead-capture-debug.log grep -i \"error\" v2/logs/lead-capture-debug.log ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "du tone (informal \"du\" not formal \"Sie\")\n- Include benefit",
          "du tone"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 11
    },
    {
      "file": ".cursor/rules/performance-monitoring.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] No render-blocking resources added?"
        ],
        "code_examples": [
          "```php // Always use WebP with fallbacks <img src=\"image.webp\" srcset=\"image-640w.webp 640w, image-1280w.webp 1280w\" sizes=\"(max-width: 640px) 640px, 1280px\" alt=\"Description\" width=\"1280\" height=\"720\" loading=\"lazy\"> ```",
          "```php // Defer non-critical JavaScript <script src=\"script.js\" defer></script> // Async for independent scripts <script src=\"analytics.js\" async></script> ```",
          "```php // Inline critical CSS <style><?php include 'critical.css'; ?></style> // Defer non-critical CSS <link rel=\"stylesheet\" href=\"styles.css\" media=\"print\" onload=\"this.media='all'\"> ```",
          "```php [ 'id' => 'render-blocking-resources', 'title' => 'Eliminate render-blocking resources', 'impact' => 'high', 'total_savings_ms' => 1200, 'total_savings_bytes' => 50000, 'affected_pages' => 15, 'urls' => ['https://www.ordio.com/', 'https://www.ordio.com/schichtplan', ...] ] ```",
          "```php [ 'id' => 'render-blocking-resources', 'title' => 'Eliminate render-blocking resources', 'description' => 'Eliminate render-blocking resources affects 15 page(s) with potential savings of 1,200ms (48.83 KB)', 'impact' => 'high', 'effort' => 'medium', // Mapped from impact 'category' => 'assets', // Determined from title/ID 'estimated_savings_ms' => 1200, 'estimated_savings_bytes' => 50000, 'affected_pages' => 15, 'affected_urls' => ['https://www.ordio.com/', ...], 'type' => 'opportunity', 'tags' => ['assets', 'high-impact'] ] ```",
          "```php [ 'id' => 'task_abc123', 'task_id' => 'task_abc123', 'title' => 'Eliminate render-blocking resources', 'description' => '...', 'status' => 'pending', 'priority' => 85, 'category' => 'assets', 'assigned_to' => 'dev@ordio.com', 'due_date' => '2025-12-15', 'created_at' => '2025-11-19T10:00:00+00:00', 'pages_affected' => ['https://www.ordio.com/', ...], 'affected_page_count' => 15, 'metadata' => [ 'source' => 'recommendation', 'recommendation_id' => 'render-blocking-resources', 'original_topIssue' => [...] ] ] ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 7
    },
    {
      "file": ".cursor/rules/performance.mdc",
      "patterns": {
        "validation_checklists": [],
        "code_examples": [
          "```html <script src=\"https://example.com/script.js\" async defer></script> ```",
          "```html <script src=\"https://example.com/script.js\" crossorigin=\"anonymous\"></script> ```",
          "```html <script src=\"https://cdn.example.com/script.js\" integrity=\"sha384-...\" crossorigin=\"anonymous\" ></script> ```",
          "```php <script src=\"/v2/js/script.js?v=<?php echo filemtime(__DIR__ . '/../js/script.js'); ?>\"></script> ```",
          "```bash npm run minify ```",
          "```php <link rel=\"stylesheet\" href=\"/v2/css/comparison-pages.min.css?v=<?php echo filemtime(__DIR__ . '/../css/comparison-pages.min.css'); ?>\"> <script src=\"/v2/js/utm-tracking.min.js?v=<?php echo filemtime(__DIR__ . '/../js/utm-tracking.min.js'); ?>\"></script> ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 6
    },
    {
      "file": ".cursor/rules/pillar-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] CTAs prominent and correctly linked"
        ],
        "code_examples": [
          "```html <details> <summary> Was ist der Unterschied zwischen Dienstplan und Schichtplan? </summary> <p> [Comprehensive 100-200 word answer explaining the difference, potentially mentioning how Ordio handles both] </p> </details> ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"Article\", \"headline\": \"[Pillar Topic]\", \"description\": \"[Overview of content]\", \"author\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"publisher\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\", \"logo\": { \"@type\": \"ImageObject\", \"url\": \"https://www.ordio.com/v2/img/ordio-logo.png\" } }, \"datePublished\": \"2023-03-01\", \"dateModified\": \"2025-01-15\", \"mainEntityOfPage\": \"https://www.ordio.com/[pillar-slug]\" } ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [ { \"@type\": \"Question\", \"name\": \"[Question text]\", \"acceptedAnswer\": { \"@type\": \"Answer\", \"text\": \"[Answer text]\" } } ] } ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "du tone in questions and answers\n- Structure: `<details>` and `<summary>` elements",
          "du tone, readable, keyword-optimized (naturally)\n- [ ] Ordio mentions natural (once per major section)\n- [ ] CTAs prominent and correctly linked",
          "Ordio mention in 1-2 FAQ answers (naturally)\n- Use du tone in questions and answers\n- Structure: `<details>` and `<summary>` elements",
          "Ordio Mention Pattern",
          "Ordio mentions natural (once per major section)\n- [ ] CTAs prominent and correctly linked"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 9
    },
    {
      "file": ".cursor/rules/product-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] No inline styles (except FAQ arrow animation)"
        ],
        "code_examples": [
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"SoftwareApplication\", \"name\": \"Ordio [Feature]\", \"applicationCategory\": \"BusinessApplication\", \"operatingSystem\": \"Web, iOS, Android\", \"offers\": { \"@type\": \"Offer\", \"price\": \"89\", \"priceCurrency\": \"EUR\", \"priceSpecification\": { \"@type\": \"UnitPriceSpecification\", \"price\": \"89\", \"priceCurrency\": \"EUR\", \"unitText\": \"per Standort per Monat\" } }, \"aggregateRating\": { \"@type\": \"AggregateRating\", \"ratingValue\": \"4.9\", \"reviewCount\": \"54\", \"bestRating\": \"5\", \"worstRating\": \"1\" }, \"featureList\": [ \"[Feature 1: specific capability]\", \"[Feature 2: specific capability]\", \"[Feature 3: specific capability]\", \"[Feature 4: specific capability]\", \"[Feature 5: specific capability]\" ], \"screenshot\": \"https://www.ordio.com/v2/img/[product-screenshot].webp\", \"author\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"publisher\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"datePublished\": \"2023-01-15\", \"softwareVersion\": \"2.0\" } ```",
          "```json { \"@type\": \"Service\", \"serviceType\": \"[Feature Name]\", \"provider\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"areaServed\": { \"@type\": \"Country\", \"name\": \"Deutschland\" }, \"audience\": { \"@type\": \"Audience\", \"audienceType\": \"Unternehmen mit Mitarbeitern\" } } ```",
          "```php <link rel=\"stylesheet\" href=\"/v2/css/product-pages.css?v=<?php echo filemtime($_SERVER['DOCUMENT_ROOT'] . '/v2/css/product-pages.css'); ?>\" media=\"print\" onload=\"this.media='all'\"> <noscript><link rel=\"stylesheet\" href=\"/v2/css/product-pages.css\"></noscript> ```",
          "```html <title>[Feature Name]: [Key Benefit] - Ordio</title> ```",
          "```html <meta name=\"description\" content=\"[Feature] von Ordio: [Primary Benefit]. [Secondary Benefit]. [Outcome]. Kostenlos testen.\" /> ```",
          "```html <div class=\"feature-icon\">[Icon]</div> <h3>Spare Zeit bei der Dienstplanung</h3> <p> Erstelle Dienstpläne 70% schneller mit automatischen Vorschlägen. Ordio berücksichtigt Verfügbarkeiten, Qualifikationen und Mitarbeiterwünsche. </p> ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "du tone, benefit-focused)\n3. **Product Screenshots** – Show feature in action (preload first image)\n4. **Social Proof** – Testimonials, ratings (4.9/5, 54 reviews), customer logos\n5. **Comparison Table** – Ordio vs. manual/competitor approaches\n6. **Detailed Features** – Secondary features with icons/images\n7. **Integrations** – Compatible systems (DATEV, Personio, etc.) if relevant\n8. **FAQ** – Common objections, implementation questions\n9. **Final CTA** – Soft close, trial/demo offer",
          "du tone, benefit-driven, include CTA",
          "Ordio mention (if not in headline)",
          "Ordio mention in 1-2 FAQ answers** (not all), naturally integrated.",
          "Ordio Mentions"
        ],
        "css_patterns": [],
        "meta_tag_patterns": [
          "<title>[Feature Name]: [Key Benefit] - Ordio</title>",
          "<meta\n  name=\"description\"\n  content=\"[Feature] von Ordio: [Primary Benefit]. [Secondary Benefit]. [Outcome]. Kostenlos testen.\"\n/>"
        ]
      },
      "total_patterns": 14
    },
    {
      "file": ".cursor/rules/product-updates.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Verify mobile responsiveness",
          "- [ ] Check analytics for anomalies"
        ],
        "code_examples": [
          "``` v2/ ├── pages/ │ ├── produkt_updates_admin.php # Admin CMS interface │ ├── produkt_updates.php # Main public listing page │ ├── produkt_updates_month.php # Monthly archive page │ ├── post.php # Individual post/feature page │ ├── produkt-updates-feed.xml.php # RSS feed │ └── sitemap-produkt-updates.xml.php # XML sitemap ├── data/ │ └── produkt_updates.json # JSON data store ├── api/ │ └── produkt-updates-upload.php # Image upload handler ├── admin/produkt-updates/ │ ├── USER_GUIDE.md # User documentation │ └── CODE_DOCUMENTATION.md # Developer documentation └── img/produkt-updates/ # Uploaded images directory ```",
          "```json { \"current_month\": \"month-slug\", \"months\": { \"month-slug\": { \"month\": \"Month Name YYYY\", \"slug\": \"month-slug\", \"published_date\": \"YYYY-MM-DD\", \"intro_text\": \"Brief description\", \"page_title\": \"Optional *blue* markup\", \"big_features\": [ { \"id\": \"unique-id\", \"number\": 1, \"title\": \"Feature Title\", \"description\": \"<p>HTML content</p>\", \"page_content\": \"<p>Full HTML content</p>\", \"read_more_link\": \"/produkt-updates/feature-slug\", \"published_date\": \"YYYY-MM-DD\", \"tag\": \"neues-feature|verbesserung|bugfix\", \"platforms\": [\"desktop\", \"mobile\"], \"featured_image\": \"/v2/img/produkt-updates/image.webp\" } ], \"small_improvements\": [ { \"id\": \"unique-id\", \"number\": 1, \"title\": \"Improvement Title\", \"description\": \"<p>HTML content</p>\", \"published_date\": \"YYYY-MM-DD\", \"tag\": \"bugfix|verbesserung\", \"platforms\": [\"desktop\", \"mobile\"] } ] } }, \"last_updated\": \"ISO 8601 timestamp\", \"settings\": { \"max_big_features\": 5, \"max_small_improvements\": 10, \"main_page_title\": \"\", \"main_page_intro_text\": \"\" } } ```",
          "```php // Plain text fields $sanitized = htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); // HTML content function sanitizeHtmlInput($html) { $allowed_tags = '<p><strong><em><h1><h2><h3><ul><ol><li><a><br>'; $html = strip_tags($html, $allowed_tags); // Remove dangerous attributes $html = preg_replace('/\\s*on\\w+\\s*=\\s*[\"\\'][^\"\\']*[\"\\']/i', '', $html); $html = preg_replace('/\\s*style\\s*=\\s*[\"\\'][^\"\\']*[\"\\']/i', '', $html); $html = preg_replace('/\\s*javascript:/i', '', $html); return trim($html); } ```",
          "```php function logError($message, $context = [], $level = 'error') { $log_dir = __DIR__ . '/../logs/'; $log_file = $log_dir . 'produkt_updates_admin_' . date('Y-m-d') . '.log'; $log_entry = date('Y-m-d H:i:s') . \" [{$level}] {$message}\"; if (!empty($context)) { $log_entry .= \" | Context: \" . json_encode($context); } error_log($log_entry . PHP_EOL, 3, $log_file); } ```",
          "```php // 1. Generate slug from month name $slug = strtolower(preg_replace('/[^a-z0-9]+/', '-', $month_name)); // 2. Create month structure $month_data = [ 'month' => $month_name, 'slug' => $slug, 'published_date' => date('Y-m-d'), 'intro_text' => '', 'page_title' => '', 'big_features' => [], 'small_improvements' => [] ]; // 3. Add to months array $data['months'][$slug] = $month_data; // 4. Update current_month if most recent if (strtotime($month_data['published_date']) > strtotime($data['months'][$data['current_month']]['published_date'])) { $data['current_month'] = $slug; } ```",
          "```php // 1. Generate unique ID $id = uniqid(); // 2. Sanitize input $title = sanitizeInput($_POST['title']); $description = sanitizeHtmlInput($_POST['description']); $page_content = sanitizeHtmlInput($_POST['page_content']); // 3. Create feature structure $feature = [ 'id' => $id, 'number' => count($month['big_features']) + 1, 'title' => $title, 'description' => $description, 'page_content' => $page_content, 'read_more_link' => '/produkt-updates/' . $slug, 'published_date' => $_POST['published_date'], 'tag' => $_POST['tag'], 'platforms' => $_POST['platforms'] ?? [], 'featured_image' => $_POST['featured_image'] ?? '' ]; // 4. Add to month's features $data['months'][$month_slug]['big_features'][] = $feature; ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "du tone\n- Schema.org CollectionPage markup\n- Open Graph tags (og:title, og:description, og:url, og:image, og:type)\n- Canonical URL: absolute path"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 9
    },
    {
      "file": ".cursor/rules/shiftops-backend.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] No console errors in JavaScript (frontend)"
        ],
        "code_examples": [],
        "schema_examples": [],
        "copy_guidelines": [],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 1
    },
    {
      "file": ".cursor/rules/shiftops-frontend.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] All error handling blocks have proper comments (no empty catch blocks)"
        ],
        "code_examples": [
          "``` Bitte gib eine gültige E-Mail-Adresse ein. ```",
          "```css .gated-content { filter: blur(5px); pointer-events: none; } ```",
          "```javascript try { const response = await fetch('/v2/api/shiftops.php', {...}); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); // Process data } catch (error) { console.error('ShiftOps API Error:', error); // Show fallback UI or error message } ```",
          "```javascript // ❌ BAD: Repeated queries const element1 = document.getElementById(\"pillar-bars-compact\"); const element2 = document.getElementById(\"pillar-bars-compact\"); // ✅ GOOD: Cached query const element = DOMCache.get(\"pillar-bars-compact\"); ```",
          "```javascript // ❌ BAD: No null check element.innerHTML = content; // ✅ GOOD: Safe operation with null check safeDOMOperation( element, (el) => { el.innerHTML = content; }, \"context description\" ); ```",
          "```javascript // ❌ BAD: Direct insertion element.innerHTML = `<div>${userContent}</div>`; // ✅ GOOD: Escaped content const safeContent = escapeHtml(userContent); element.innerHTML = `<div>${safeContent}</div>`; ```",
          "```javascript // ❌ BAD: Multiple reflows items.forEach((item) => { const el = document.createElement(\"div\"); el.innerHTML = item.content; container.appendChild(el); }); // ✅ GOOD: Single reflow const fragment = document.createDocumentFragment(); items.forEach((item) => { const el = document.createElement(\"div\"); el.innerHTML = item.content; fragment.appendChild(el); }); container.appendChild(fragment); ```",
          "```javascript // Set up lazy loading LazyLoadManager.init(); const section = DOMCache.get(\"below-fold-section\"); LazyLoadManager.observe(section, \"loadSectionFunction\"); ```",
          "```html <!-- ============================================ TRACKING SCRIPTS ============================================ All tracking pixels and scripts are grouped here for easy maintenance and consistent loading order. ============================================ --> ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "Du tone in labels and error messages\n- GDPR consent checkbox"
        ],
        "css_patterns": [
          "```css\n.gated-content {\n  filter: blur(5px);\n  pointer-events: none;\n}\n```"
        ],
        "meta_tag_patterns": []
      },
      "total_patterns": 12
    },
    {
      "file": ".cursor/rules/static-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Page loads locally without errors"
        ],
        "code_examples": [
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"AboutPage\", \"name\": \"Über Ordio\", \"description\": \"[Company overview]\", \"mainEntity\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\", \"description\": \"[What Ordio does]\" } } ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"ContactPage\", \"name\": \"Kontakt / Impressum\", \"description\": \"Kontaktinformationen und Impressum\" } ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "Du tone in copy",
          "Du tone throughout** (except in formal company registration details if needed)",
          "du tone (where appropriate – legal text may be formal)\n- [ ] CTAs functional and correctly linked\n- [ ] Page loads locally without errors"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 6
    },
    {
      "file": ".cursor/rules/templates-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] No console errors"
        ],
        "code_examples": [
          "```html <div class=\"customizer-section\"> <h3>Öffnungszeiten</h3> <input type=\"time\" id=\"opening_time\" value=\"08:00\" /> <input type=\"time\" id=\"closing_time\" value=\"22:00\" /> </div> <div class=\"customizer-section\"> <h3>Rollen hinzufügen</h3> <div id=\"roles-container\"> <input type=\"text\" placeholder=\"z.B. Servicekraft\" /> <button class=\"add-role\">+ Hinzufügen</button> </div> </div> ```",
          "```javascript const templateState = { openingHours: { start: \"08:00\", end: \"22:00\" }, roles: [\"Servicekraft\", \"Koch\"], breaks: { min: 30, max: 60 }, costs: { servicekraft: 15.0, koch: 18.0 }, }; ```",
          "```javascript function updatePreview() { // Regenerate preview based on templateState renderTemplate(templateState); } // Trigger on any customizer change document.querySelectorAll(\".customizer-input\").forEach((input) => { input.addEventListener(\"change\", updatePreview); }); ```",
          "```javascript function validateState() { if (templateState.roles.length === 0) { alert(\"Bitte mindestens eine Rolle hinzufügen\"); return false; } // More validation... return true; } ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"DigitalDocument\", \"name\": \"[Template Name]\", \"description\": \"[What the template includes]\", \"fileFormat\": [\"application/vnd.ms-excel\", \"application/pdf\", \"text/csv\"], \"encodingFormat\": [\"application/vnd.ms-excel\", \"application/pdf\", \"text/csv\"], \"author\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"publisher\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\" }, \"datePublished\": \"2023-05-01\", \"dateModified\": \"2025-01-15\" } ```",
          "```json { \"@type\": \"HowTo\", \"name\": \"[Template Name] erstellen\", \"step\": [ { \"@type\": \"HowToStep\", \"name\": \"Öffnungszeiten anpassen\", \"text\": \"Trage deine Öffnungszeiten ein\" }, { \"@type\": \"HowToStep\", \"name\": \"Rollen hinzufügen\", \"text\": \"Füge Rollen wie Servicekraft, Koch hinzu\" }, { \"@type\": \"HowToStep\", \"name\": \"Exportieren\", \"text\": \"Lade als Excel, PDF oder Ordio-CSV herunter\" } ] } ```",
          "```php <link rel=\"stylesheet\" href=\"/v2/css/templates-pages.css?v=<?php echo filemtime($_SERVER['DOCUMENT_ROOT'] . '/v2/css/templates-pages.css'); ?>\"> ```",
          "```html <title>[Template Name] Vorlage Excel: Kostenlos & rechtssicher - Ordio</title> ```",
          "```html <meta name=\"description\" content=\"[Template] Vorlage: [Benefit 1]. [Benefit 2]. [Customization note]. Als Excel, PDF oder Ordio-CSV downloaden.\" /> ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "Ordio Mention Pattern"
        ],
        "css_patterns": [],
        "meta_tag_patterns": [
          "<title>[Template Name] Vorlage Excel: Kostenlos & rechtssicher - Ordio</title>",
          "<meta\n  name=\"description\"\n  content=\"[Template] Vorlage: [Benefit 1]. [Benefit 2]. [Customization note]. Als Excel, PDF oder Ordio-CSV downloaden.\"\n/>"
        ]
      },
      "total_patterns": 13
    },
    {
      "file": ".cursor/rules/tools-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Export buttons disabled until email collected (if applicable)",
          "- [ ] Edge cases handled (division by zero, negative values, empty inputs)"
        ],
        "code_examples": [
          "```css /* Colors */ --ordio-primary: #4d8ef3; --ordio-primary-hover: #3b82f6; --ordio-text-primary: #374151; --ordio-text-secondary: #6b7280; --ordio-border: #e5e7eb; --ordio-background: #ffffff; /* Shadows (layered for depth) */ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.04); /* Standard elevation */ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04); /* Result cards */ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08); /* Tab container */ /* Spacing Scale */ --spacing-xs: 0.25rem; /* 4px */ --spacing-sm: 0.5rem; /* 8px */ --spacing-md: 1rem; /* 16px */ --spacing-lg: 1.5rem; /* 24px */ --spacing-xl: 2rem; /* 32px */ /* Border Radius */ --radius-sm: 0.5rem; /* 8px - buttons, tabs */ --radius-md: 0.75rem; /* 12px - containers */ --radius-lg: 1rem; /* 16px - cards, forms */ /* Typography Scale */ --text-xs: 0.75rem; /* 12px */ --text-sm: 0.875rem; /* 14px */ --text-base: 1rem; /* 16px */ --text-lg: 1.125rem; /* 18px */ --text-xl: 1.25rem; /* 20px */ --text-2xl: 1.5rem; /* 24px */ ```",
          "```html <div class=\"input-with-addon\"> <input type=\"number\" id=\"hourly_rate\" min=\"0\" max=\"1000\" step=\"0.01\" /> <span class=\"addon\">€</span> </div> ```",
          "```html <div class=\"ordio-tabs\" role=\"tablist\"> <button :class=\"{'active': mode === 'basic'}\" class=\"ordio-tab\" role=\"tab\"> Mode Label </button> </div> ```",
          "```css .ordio-tabs { display: flex; background: var(--ordio-background-subtle); border-radius: 0.75rem; padding: 0.25rem; margin-bottom: 1.5rem; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; scroll-snap-type: x mandatory; } .ordio-tab { font-family: \"Inter-500\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; flex: 1; padding: 0.75rem 1rem; border-radius: 0.5rem; font-size: 0.875rem; font-weight: 500; text-align: center; cursor: pointer; transition: all 0.2s ease; color: var(--ordio-text-secondary); background: transparent; border: none; white-space: nowrap; } .ordio-tab.active { background: white; color: var(--ordio-primary); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); } .ordio-tab:hover:not(.active) { color: var(--ordio-text-primary); } /* Mobile Optimization */ @media (max-width: 768px) { .ordio-tabs { flex-direction: row; flex-wrap: nowrap; gap: 0.25rem; } .ordio-tab { flex-shrink: 0; scroll-snap-align: start; min-height: 44px; } } ```",
          "```html <button class=\"ordio-tab flex items-center justify-center\"> <svg class=\"w-4 h-4 mr-2 flex-shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" > <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"...\" ></path> </svg> <span class=\"whitespace-nowrap\">Label</span> </button> ```",
          "```css .ordio-tabs { display: flex; flex-wrap: wrap; justify-content: center; background: var(--ordio-background-subtle); border-radius: 0.75rem; padding: 0.25rem; margin-bottom: 1.5rem; gap: 0.25rem; } .ordio-tab { display: flex; align-items: center; justify-content: center; flex: 0 1 auto; /* ... rest of standard styles ... */ } .ordio-tab svg { flex-shrink: 0; } @media (max-width: 768px) { .ordio-tabs { gap: 0.375rem; } .ordio-tab { min-height: 44px; font-size: 0.8125rem; padding: 0.625rem 0.875rem; } } ```",
          "```css /* Desktop: Compact inputs for better visual density */ .calculator-form input, .calculator-form select { height: 2.75rem; min-height: 2.75rem; font-size: 0.875rem; padding: 0.5rem 0.75rem; } /* Mobile: Larger for touch */ @media (max-width: 768px) { .calculator-form input, .calculator-form select { height: 3.25rem; min-height: 3.25rem; font-size: 1rem; padding: 0.75rem 1rem; } } ```",
          "```css .icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; padding: 0; border: 1px solid #e5e7eb; border-radius: 0.5rem; background: white; color: #6b7280; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .icon-btn:hover { background: #f3f4f6; color: #374151; transform: translateY(-1px); } .icon-btn svg { width: 1rem; height: 1rem; } /* Danger variant */ .icon-btn-danger { color: #ef4444; } .icon-btn-danger:hover { background: #fef2f2; color: #dc2626; } ```",
          "```css .segmented-control { display: inline-flex; background: #f3f4f6; border-radius: 0.5rem; padding: 0.25rem; gap: 0.125rem; width: 100%; /* Full width for better alignment */ box-sizing: border-box; } .segmented-control input[type=\"radio\"] { display: none; } .segmented-control label { display: flex; align-items: center; justify-content: center; padding: 0.5rem 1rem; /* Increased for better touch targets */ font-size: 0.875rem; /* Slightly larger for readability */ font-weight: 500; color: #6b7280; background: transparent; border-radius: 0.375rem; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); white-space: nowrap; flex: 1; /* Equal width segments */ min-width: 0; } .segmented-control input[type=\"radio\"]:checked + label { background: white; color: #4d8ef3; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); /* Enhanced shadow */ font-weight: 600; /* Bolder for active state */ } .segmented-control label:hover:not(:has(input:checked)) { color: #374151; background: rgba(77, 142, 243, 0.05); /* Subtle hover effect */ } ```",
          "```html <div class=\"segmented-control\"> <input type=\"radio\" id=\"mode-1\" name=\"mode\" value=\"1\" x-model=\"mode\" /> <label for=\"mode-1\">Option 1</label> <input type=\"radio\" id=\"mode-2\" name=\"mode\" value=\"2\" x-model=\"mode\" /> <label for=\"mode-2\">Option 2</label> </div> ```",
          "```html <!-- After Results Display --> <div class=\"mt-8 mb-6\"> <div class=\"bg-blue-50 border border-blue-200 rounded-lg p-4\"> <div class=\"flex items-center justify-between mb-3\"> <div class=\"flex items-center gap-2\"> <svg>...</svg> <h3 class=\"text-sm font-semibold text-gray-800\">Schnellbeispiele</h3> </div> <button @click=\"showExamples = !showExamples\" class=\"icon-btn\"> <svg>...</svg> </button> </div> <div x-show=\"showExamples\" x-cloak x-transition> <!-- Examples grid --> </div> </div> </div> <div x-show=\"calculationHistory.length > 0\" x-cloak x-transition class=\"mb-6\"> <div class=\"bg-gray-50 border border-gray-200 rounded-lg p-4\"> <div class=\"flex items-center justify-between mb-3\"> <div class=\"flex items-center gap-2\"> <svg>...</svg> <h3 class=\"text-sm font-semibold text-gray-800\">Verlauf</h3> <span class=\"text-xs text-gray-500\" x-text=\"'(' + calculationHistory.length + ')'\" ></span> </div> <div class=\"flex items-center gap-1\"> <button @click=\"exportHistoryCSV()\" class=\"icon-btn\">...</button> <button @click=\"clearHistory()\" class=\"icon-btn icon-btn-danger\"> ... </button> <button @click=\"showHistory = !showHistory\" class=\"icon-btn\"> ... </button> </div> </div> </div> </div> ```",
          "```css .calculator-form input:focus, .calculator-form select:focus { transform: translateY(-1px); box-shadow: 0 0 0 3px rgba(77, 142, 243, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } ```",
          "```css .copy-btn:hover, .example-btn:hover, .history-item:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12); } .copy-btn:active, .example-btn:active, .history-item:active { transform: translateY(0); /* Tactile press feedback */ } ```",
          "```html <div class=\"flex items-center gap-2\"> <h3 class=\"text-sm font-semibold text-gray-800\">Beispiele</h3> <span class=\"text-xs text-gray-500 bg-white px-2 py-0.5 rounded-full\" x-text=\"getExamplesForMode(calculationMode).length + ' Beispiele'\" ></span> </div> <button @click=\"showExamples = !showExamples\" :aria-expanded=\"showExamples\"> <span x-show=\"!showExamples\">Anzeigen</span> <span x-show=\"showExamples\">Ausblenden</span> </button> ```",
          "```html <!-- Only show history when it contains items --> <div x-show=\"calculationHistory.length > 0\" x-cloak class=\"mb-6\"> <!-- History content --> </div> ```",
          "```css .result-card { background: white; border: 1px solid #e5e7eb; border-radius: 1rem; padding: 2rem; /* 1.5rem mobile */ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); animation: slideInUp 0.5s ease-out; position: relative; overflow: visible; } .result-card:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.06); } @keyframes slideInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } ```",
          "```css .result-value { font-size: 1.5rem; font-weight: 700; color: #4D8EF3; line-height: 1.2; } .result-label { font-size: 0.875rem; color: #6b7280; margin-top: 0.25rem; } .result-context { font-size: 0.875rem; color: #6b7280; margin-top: 1rem; } ```",
          "```css .result-item { margin-bottom: 1rem; } .result-item:last-child { margin-bottom: 0; } .result-item-label { font-size: 0.875rem; color: #6b7280; margin-bottom: 0.5rem; font-weight: 500; } .result-item-value { font-size: 1.5rem; font-weight: 700; line-height: 1.2; } .result-item-value-primary { color: #4D8EF3; font-size: 1.5rem; } .result-item-value-secondary { color: #374151; font-size: 1.25rem; font-weight: 600; } .result-item-value-success { color: #10b981; font-size: 1.5rem; } .result-item-divider { border-top: 1px solid #e5e7eb; padding-top: 1rem; margin-top: 1rem; } .result-helper { font-size: 0.75rem; color: #9ca3af; margin-top: 0.25rem; font-weight: 400; } .result-info-box { background-color: #f9fafb; border-radius: 0.5rem; padding: 0.75rem; margin-top: 1rem; font-size: 0.75rem; color: #6b7280; } .result-info-box strong { font-weight: 600; color: #374151; } ```",
          "```javascript input.addEventListener(\"input\", (e) => { const value = parseFloat(e.target.value); if (value < min || value > max) { input.classList.add(\"error\"); // Show constraint animation } else { input.classList.remove(\"error\"); } calculateResults(); // Trigger recalculation }); ```",
          "```javascript let debounceTimer; function debouncedCalculate() { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { calculateResults(); }, 300); } ```",
          "```javascript function calculateWorkdays(year, state, includeHalfDays) { // Calculation logic return { totalDays: result, publicHolidays: holidays, workdays: workdays, }; } ```",
          "```html <!-- Gated Content Container --> <div x-show=\"calculationMode === 'pro'\" class=\"gated-content-container\" style=\"position: relative;\" > <!-- Email Form Overlay --> <div x-show=\"!emailCollected\" x-cloak class=\"email-overlay\"> <div class=\"email-form\"> <h3>Vollständige Berechnung freischalten</h3> <p>Geben Sie Ihre E-Mail-Adresse ein, um alle Abzüge zu sehen</p> <input type=\"email\" x-model=\"emailAddress\" placeholder=\"ihre-email@beispiel.de\" class=\"email-input\" @keyup.enter=\"submitEmail()\" /> <button type=\"button\" @click=\"submitEmail()\" :disabled=\"isSubmittingEmail\" class=\"email-submit-btn\" > <span x-show=\"!isSubmittingEmail\">Entsperren</span> <span x-show=\"isSubmittingEmail\">Wird entsperrt...</span> </button> <div x-show=\"emailError\" x-text=\"emailError\" class=\"text-red-600 text-sm mt-2\" ></div> <div x-show=\"emailSuccess\" class=\"text-green-600 text-sm mt-2\"> ✅ Erfolgreich entsperrt! </div> </div> </div> <!-- Gated Section Content --> <div x-show=\"calculationMode === 'pro' && results.netto\" class=\"space-y-6 gated-section\" :class=\"{ 'gated': !emailCollected }\" > <!-- Content with blurred values --> <span :class=\"{ 'gated-value': !emailCollected, 'unlocked': emailCollected }\" x-text=\"results.netto ? secureCurrency(results.netto?.nettoMonat) : ''\" ></span> </div> </div> ```",
          "```css /* Individual value blurring for gated content */ .gated-value { filter: blur(4px); transition: filter 0.3s ease; user-select: none; -webkit-user-select: none; pointer-events: none; } .gated-value.unlocked { filter: none; user-select: auto; pointer-events: auto; } .email-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.98); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; border-radius: 0.75rem; pointer-events: auto; } .gated-content-container { position: relative; overflow: visible; } .email-form { background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 20px 40px -5px rgba(0, 0, 0, 0.15); border: 2px solid #4d8ef3; max-width: 450px; width: 90%; text-align: center; } .email-input { width: 100%; padding: 0.75rem 1rem; border: 2px solid #e5e7eb; border-radius: 0.5rem; font-size: 1rem; margin-bottom: 1rem; } .email-input:focus { outline: none; border-color: #4d8ef3; box-shadow: 0 0 0 3px rgba(77, 142, 243, 0.1); } .email-submit-btn { background: #4d8ef3; color: white; padding: 0.75rem 2rem; border: none; border-radius: 0.5rem; font-weight: 600; cursor: pointer; width: 100%; min-height: 44px; } .email-submit-btn:hover:not(:disabled) { background: #3b7dd8; } .email-submit-btn:disabled { opacity: 0.6; cursor: not-allowed; } .gated-section { position: relative; } .gated-section.gated .gated-value { filter: blur(4px); transition: filter 0.3s ease; user-select: none; pointer-events: none; } /* Mobile optimization */ @media (max-width: 768px) { .email-overlay { position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; border-radius: 0 !important; } .email-form { padding: 1.5rem !important; max-width: calc(100% - 2rem) !important; } } ```",
          "```javascript <script> // Define calculator function function [calculator]Calculator() { return { // State variables input1: '', input2: '', results: {}, // Email collection for gated content (if applicable) emailCollected: false, emailAddress: '', isSubmittingEmail: false, emailError: '', emailSuccess: false, // Initialization init() { // Check if email was already collected this.checkEmailStatus(); }, // Check if email was already collected in this session checkEmailStatus() { const emailCollected = localStorage.getItem('stundenlohn_email_collected'); const emailAddress = localStorage.getItem('stundenlohn_email_address'); if (emailCollected === 'true' && emailAddress) { this.emailCollected = true; this.emailAddress = emailAddress; } }, // Email collection for gated content async submitEmail() { if (!this.emailAddress || !this.emailAddress.includes('@')) { this.emailError = 'Bitte geben Sie eine gültige E-Mail-Adresse ein.'; return; } this.isSubmittingEmail = true; this.emailError = ''; try { const response = await fetch('/v2/api/collect-lead.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: this.emailAddress, tool_name: 'ToolName', // ... other data }) }); if (response.ok) { this.emailCollected = true; this.emailSuccess = true; localStorage.setItem('stundenlohn_email_collected', 'true'); localStorage.setItem('stundenlohn_email_address', this.emailAddress); setTimeout(() => { this.emailSuccess = false; }, 3000); } else { this.emailError = 'Es gab einen Fehler beim Senden. Bitte versuchen Sie es erneut.'; } } catch (error) { this.emailError = 'Es gab einen Fehler beim Senden. Bitte versuchen Sie es erneut.'; } finally { this.isSubmittingEmail = false; } }, // Secure currency display for gated content secureCurrency(amount, placeholder = '•••• €') { if (this.emailCollected) { return this.formatCurrency(amount); } return this.formatCurrency(amount); // Show actual value but it will be blurred by CSS } }; } // CRITICAL: Assign to window immediately - Alpine.js will find it in global scope window.[calculator]Calculator = [calculator]Calculator; // Register with Alpine.data() when Alpine.js is available // Also register immediately if Alpine is already available (for edge cases) if (typeof Alpine !== 'undefined') { Alpine.data('[calculator]Calculator', [calculator]Calculator); } else { document.addEventListener('alpine:init', function() { if (typeof Alpine !== 'undefined') { Alpine.data('[calculator]Calculator', [calculator]Calculator); } }); } </script> ```",
          "````javascript { // Email collection for gated content emailCollected: false, emailAddress: '', isSubmittingEmail: false, emailError: '', emailSuccess: false, init() { // Check if email was already collected this.checkEmailStatus(); }, ### Usage Pattern **Apply blur to gated values:** ```",
          "```` **Key points:** - `secureCurrency()` always returns formatted currency (CSS handles blur) - `:class` binding applies `.gated-value` when `!emailCollected` - `.unlocked` class removes blur when `emailCollected === true` - Email overlay covers entire gated section until email submitted - localStorage persists email status across page reloads - Mobile: overlay uses `position: fixed` for full-screen coverage ### Testing Checklist - [ ] Email overlay appears when gated content should be locked - [ ] Values are blurred before email submission - [ ] Email form is functional and styled correctly - [ ] After email submission, values unblur smoothly - [ ] localStorage persists email status across page reloads - [ ] Mobile experience: overlay covers full screen, form is centered - [ ] Touch targets minimum 44x44px - [ ] No console errors - [ ] API submission works correctly - [ ] Export buttons disabled until email collected (if applicable) ### Arbeitstage Rechner Specific Notes **Value Population Approach:** - Arbeitstage Rechner uses direct DOM manipulation (`getElementById`, `textContent`) rather than Alpine.js reactive bindings - Values are updated via JavaScript functions (`displayEnhancedResult()`, `calculateWorkdays()`) - Alpine.js `:class` bindings automatically update when `emailCollected` state changes - No need to manually update classes in JavaScript - Alpine.js handles reactivity **Gated Values:** - Main result: `#resultNumber` - Summary cards: `#totalDaysSummary`, `#workdaysSummary`, `#excludedDaysSummary`, `#holidaysSummary` - Progress bar: `#maxDays`, `#progressPercentage` - Holiday count: `#holidayCount` - All use `:class=\"{ 'gated-value': !emailCollected, 'unlocked': emailCollected }\"` binding **localStorage Keys:** - `arbeitstage_email_collected` - Boolean flag - `arbeitstage_email_address` - Email address - Also syncs with `arbeitstage_export_email` for export functionality consistency --- ## Export Functionality ### Excel Export Pattern **Two Implementation Approaches:** 1. **Server-side (PhpSpreadsheet)** - Used for complex templates (Dienstplan, Schichtplan) - Requires `/v2/api/generate_excel.php` endpoint - Better for large datasets and complex formatting - Follows Ordio branding [[memory:8147019]] 2. **Client-side (XLSX.js)** - Used for simple calculators (Prozentrechner, etc.) - Load XLSX.js from CDN: `<script src=\"https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js\" defer></script>` - Generate Excel directly in browser - No server-side API needed - Faster for simple exports - Example pattern: ```",
          "``` **Follow Ordio branding [[memory:8147019]]:** - **No purple:** Use relevant brand colors (blues, neutrals) - **Consistent styling:** Ordio colors across sheets - **Proper column widths:** Based on header length - **No header borders:** Cleaner look **Include in export:** - User inputs (all form values) - Calculated results - Formulas where applicable - Date stamp - Ordio branding (logo, colors) **Filename pattern:** `[tool-name]-[date].xlsx` Example: `prozentrechner-2025-01-15.xlsx` ### PDF Export Pattern **Two Implementation Approaches:** 1. **Server-side** - Used for complex PDFs with advanced formatting - Requires server-side PDF generation library - Better for complex layouts 2. **Client-side (jsPDF)** - Used for simple calculators - Load jsPDF from CDN: `<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js\" defer></script>` - Generate PDF directly in browser - Fallback to `window.print()` if library unavailable - Example pattern: ```",
          "``` ### Export Button Styling Pattern **Enhanced Export Button Classes (Prozentrechner Pattern):** **Primary Export Button (Excel):** ```",
          "``` **Secondary Export Buttons (PDF, CSV):** ```",
          "``` **HTML Usage:** ```",
          "``` **Key Requirements:** - Minimum touch target: `44px` height (`min-h-[44px]`) - Smooth transitions: `transition-all` - Visible focus states: `focus:ring-2 focus:ring-blue-500` - Hover effects: Subtle lift (`translateY(-1px)`) with enhanced shadow **Requirements:** - **Ordio logo:** Top of document (if applicable) - **Clear layout:** Results and inputs - **Ordio branding:** Colors, fonts - **Filename pattern:** `[tool-name]-[date].pdf` ### Copy to Clipboard Pattern **Implementation:** - Clipboard API with fallback for older browsers - Three copy formats: result (number/percentage), full (complete calculation), formula (formula text) - Toast notification system for user feedback - Success/error states with visual feedback **Example:** ```",
          "``` **UI Pattern:** - Copy button in result card header - Toast notifications: Fixed position (top-right), slide-in animation, auto-dismiss after 3 seconds - Accessible: ARIA labels, keyboard navigation support ### Calculation History Pattern **Implementation:** - localStorage-based storage (max 50 items) - Automatic history addition after successful calculations (debounced 1 second) - One-click history restoration (fills inputs and recalculates) - History export as CSV - Clear all history functionality with confirmation - **Individual item deletion** (2025 update) - delete specific items before export - Relative timestamp display (e.g., \"Vor 5 Min.\", \"Gerade eben\") **Example:** ```",
          "``` **UI Pattern:** - Collapsible history section with item count badge - History items as clickable buttons - Export and clear buttons - Scrollable list (max-height: 24rem) ### Share Functionality Pattern **Implementation:** - Web Share API support (mobile devices) - Shareable URLs with URL parameters (pre-fills calculator) - Automatic URL parameter loading on page load - Fallback to clipboard copy (desktop browsers) - Share text includes calculation summary and branding **URL Parameters:** - `mode`: Calculation mode (basic, reverse, change, difference, compound) - Mode-specific parameters (e.g., `p`, `b` for basic mode) **Example:** ```",
          "``` **UI Pattern:** - Share button in export buttons section - Primary button styling (blue background) - Accessible: ARIA labels, keyboard navigation ### Quick Examples Pattern **Implementation:** - Pre-configured examples array organized by mode - One-click application to fill calculator inputs - Mode-filtered display (only show examples for current mode) - Auto-scroll to results after applying example **Example:** ```",
          "``` **UI Pattern:** - Collapsible examples section - Grid layout (1/2/3 columns responsive) - Example cards with title, description, and \"Anwenden →\" indicator - Selected example highlighted ### Multi-Mode Calculator Pattern **Implementation:** - Tab-based navigation for switching between calculation modes - Each mode has its own state variables, calculation function, and UI - Consistent pattern: `calculate[Mode]()`, `[mode]Result`, `[mode]Inputs` - Mode-specific quick examples and formula explanations **Common Modes:** - Basic Percentage (X% of Y) - Reverse Percentage (X is what % of Y) - Percentage Change (from X to Y) - Percentage Difference (between X and Y) - Discount Calculator (original price + discount % → final price) - VAT Calculator (net/gross + VAT rate → net/gross/VAT) - Profit Margin Calculator (cost + selling price → margin % + markup %) **Tab Navigation:** - Use `role=\"tablist\"` and `role=\"tab\"` for accessibility - Alpine.js `calculationMode` variable controls active mode - Tab buttons trigger mode switch and calculation - Mobile: Consider horizontal scroll for 6+ tabs **Example:** ```",
          "``` **State Management:** ```",
          "``` **Mobile Considerations:** - 5-6 tabs: Wrap to multiple rows - 7+ tabs: Use horizontal scroll (`overflow-x-auto`) - Ensure minimum 44px touch targets - Keep tab labels concise ### Formula Explanations Pattern **Implementation:** - Expandable formula sections for each calculation mode - Formula display with code formatting - Step-by-step examples for each formula - Explanatory text for each calculation type **Example:** ```",
          "``` **UI Pattern:** - Formula button in mode header - Expandable formula box with gray background - Code-formatted formula display - Example calculations with step-by-step breakdown ### CSV Export Pattern **Implementation:** - **Client-side generation:** Native JavaScript (no library needed) - **Simple format:** Headers + calculated values - **UTF-8 encoding:** German characters (ä, ö, ü, ß) - **Semicolon separator:** German CSV format (not comma) - **Example pattern:** ```",
          "``` - **Filename pattern:** `[tool-name]-[date].csv` --- ## Schema Requirements (Tools-Specific) ### HowTo Schema Required for all tools pages: ```",
          "``` **Typical steps:** 1. Enter input values 2. Review calculated results 3. Export or save results (optional) ### Standard Schemas Also include (from global.mdc): - WebPage schema - BreadcrumbList schema --- ## CSS Guidelines (Tools-Specific) ### Shared CSS File ```",
          "``` ### Dotted Background Pattern (Required) All tools pages must have dotted background: ```",
          "``` This is defined in tools-pages.css and applied to main container. ### Available CSS Classes From tools-pages.css: - `.calculator-form` – Form container - `.form-group` – Input group wrapper - `.input-with-addon` – Input with suffix (€, %) - `.radio-group` – Radio button container - `.radio-item` – Individual radio button - `.toggle-container` – Toggle switch wrapper - `.toggle-switch` – Toggle switch input - `.toggle-slider` – Toggle switch slider - `.error` – Error state for inputs ### Checkbox Styling Pattern **Required CSS:** - General checkbox styles are defined in `tools-pages.css` and apply to all checkboxes (not just those inside `.calculator-form`) - Checked state styles use `input[type=\"checkbox\"]:checked` selector for universal application - Checkboxes use `position: relative` for `::after` pseudo-element (checkmark) - Toggle switches are excluded via `.toggle-switch input` selector (inputs are hidden with `opacity:0`) **HTML Pattern:** ```",
          "``` **Alpine.js Pattern:** - Use `x-model` for two-way binding (e.g., `x-model=\"applyBreakRules\"`) - Alpine.js automatically syncs `checked` attribute when model updates - No need for explicit `:checked` binding - Alpine.js handles it automatically - Visual state updates immediately when checkbox is clicked **Visual States:** - **Unchecked:** White background (`#ffffff`), gray border (`#d1d5db`) - **Checked:** Blue background (`#4D8EF3`), blue border (`#4D8EF3`), white checkmark via `::after` pseudo-element - **Focus:** Blue ring (`rgba(77, 142, 243, 0.1)`) around checkbox **Important Notes:** - Checkboxes work regardless of parent container (`.calculator-form` not required) - Toggle switches use different styling mechanism (slider, not checkbox visual) - All checkboxes automatically get consistent styling from `tools-pages.css` - No HTML changes needed - CSS handles all visual states ### Radio Button Styling Pattern **Two Implementation Patterns:** Tools pages use two different radio button patterns: **Pattern 1: `.radio-item` pattern** (tools_stundenlohnrechner.php, tools_tvoed_sue.php, etc.) - Radio input is hidden (`display: none`) - Label is styled and gets blue background when checked - Uses `.radio-item input[type=\"radio\"]:checked + label` selector - Already has CSS in `tools-pages.css` - working correctly **Pattern 2: Direct radio in label** (tools_mehrwertsteuer_rechner.php, tools_arbeitstage_rechner.php, etc.) - Radio input is visible inside label - Label has `border-2 border-gray-200 rounded-lg` styling - Radio input uses Tailwind classes `text-blue-600 focus:ring-blue-500 w-4 h-4` - Universal CSS in `tools-pages.css` handles checked state **Required CSS:** - General radio button styles are defined in `tools-pages.css` and apply to Pattern 2 radios - Excludes Pattern 1 (`.radio-item` radios remain hidden), `.sr-only` radios, and `.custom-radio` radios - Checked state styles use `input[type=\"radio\"]:not(.sr-only):not(.custom-radio):checked` selector - Label styling uses `label:has(input[type=\"radio\"]:not(.sr-only):not(.custom-radio):checked)` selector **HTML Pattern (Pattern 2):** ```",
          "``` **Alpine.js Pattern:** - Use `x-model` for two-way binding (e.g., `x-model=\"selectedCountry\"`) - Alpine.js automatically syncs `checked` attribute when model updates - Radio groups work automatically - selecting one deselects others - Visual state updates immediately when radio is clicked **Visual States (Pattern 2):** - **Unchecked:** White background (`#ffffff`), gray border (`#d1d5db`), white radio circle - **Checked:** Blue radio circle (`#4D8EF3`) with white dot (`::before` pseudo-element), blue label border (`#4D8EF3`), light blue label background (`#f8faff`) - **Focus:** Blue ring (`rgba(77, 142, 243, 0.1)`) around radio button **Important Notes:** - Pattern 1 (`.radio-item`) continues to work correctly - radios remain hidden - Pattern 2 radios show visual checked state immediately on click - Custom radio classes (`.custom-radio`, `.radio-button-compact`) have their own CSS and are excluded from general styles - All Pattern 2 radios automatically get consistent styling from `tools-pages.css` - No HTML changes needed - CSS handles all visual states ### Form Input Heights **Consistent heights** (defined in CSS): - **Desktop:** 3rem (48px) - **Mobile:** 3.5rem (56px) – prevents zoom on focus **Never hardcode heights in HTML**; use CSS variables from tools-pages.css. ### Result Card Styling Pattern **Enhanced Result Card Styling (Prozentrechner Pattern):** ```",
          "``` **Key Requirements:** - **Animation:** Smooth `slideInUp` animation (0.5s ease-out) for result appearance - **Hover effects:** Subtle lift (`translateY(-2px)`) with enhanced shadow - **Result value color:** Use primary blue (`#4D8EF3`) for better visual hierarchy - **Accessibility:** Respect `prefers-reduced-motion` media query - **Performance:** Use GPU-accelerated properties (`transform`, `opacity`) --- ## Meta Tags (Tools-Specific) ### Title Pattern ```",
          "``` Examples: - \"Arbeitstage-Rechner 2025: Arbeitstage berechnen - Ordio\" - \"Kostenrechner 2025: Personalkosten berechnen - Ordio\" Include year (2025) for SEO relevance. ### Description Pattern ```",
          "``` 155-160 characters, mention free/no-registration, export. --- ## Copy Patterns (Tools-Specific) ### Hero Headline Pattern **Formula:** [Tool Name] – [What it calculates/does] [Year] Examples: - \"Arbeitstage-Rechner – Berechne Arbeitstage 2025 & 2026 kostenlos\" - \"Kostenrechner – Berechne deine Personalkosten pro Monat\" ### Tool Description Pattern **Explain what the tool does and why it's useful:** - ✅ GOOD: \"Berechne Arbeitstage 2025 und 2026 für jedes Bundesland. Berücksichtigt Feiertage, Wochenenden und optionale Brückentage.\" - ❌ BAD: \"Ein Tool für Berechnungen.\" ### Instructions Pattern **Keep clear and concise:** 1. \"Wähle das Jahr und Bundesland\" 2. \"Aktiviere optionale Einstellungen (Brückentage, Samstage)\" 3. \"Sieh dir die Ergebnisse an\" 4. \"Exportiere als Excel, PDF oder CSV\" ### Ordio Mention Pattern **Mention Ordio naturally once** (usually in FAQ or CTA): - FAQ: \"Kann ich Arbeitstage direkt in Ordio importieren?\" → Yes, explain how - CTA: \"Mit Ordio planst du Schichten basierend auf tatsächlichen Arbeitstagen\" **Don't force Ordio into calculator instructions**; let tool speak for itself. --- ## Validation Checklist (Tools-Specific) Beyond global validation checklist: - [ ] Calculator works with valid inputs (test multiple scenarios) - [ ] Validation triggers on invalid inputs (visual feedback: red border) - [ ] Real-time calculation updates correctly (debounced) - [ ] Export functionality works (Excel, PDF, CSV as applicable) - [ ] Excel export follows Ordio branding (no purple, proper widths, no header borders) - [ ] Responsive layout works (desktop, tablet, mobile) - [ ] Form inputs have consistent heights (3rem desktop, 3.5rem mobile) - [ ] HowTo schema validates with correct steps - [ ] Dotted background pattern present - [ ] No console errors - [ ] Edge cases handled (division by zero, negative values, empty inputs) --- ## Alpine.js Registration Best Practices ### Function Placement **ALWAYS define calculator functions in `<head>` section:** ```",
          "``` **NEVER define in `<body>` section** - Alpine.js may initialize before function is available. ### Registration Pattern **Use immediate check + event listener pattern:** ```",
          "``` **Why this pattern:** - Immediate check handles case where Alpine.js loads before script executes - Event listener handles case where script executes before Alpine.js loads - Assigning to `window` ensures function is globally available ### Troubleshooting Alpine.js Errors **Error: \"[Calculator] is not defined\"** 1. Verify function is in `<head>` section 2. Check Alpine.js registration pattern matches above 3. Clear browser cache (hard refresh) 4. Check for JavaScript syntax errors **Error: \"Unexpected token ')'\"** 1. Check for stray closing parentheses 2. Verify parenthesis balance in function 3. Check for unclosed template literals 4. Clear browser cache See `docs/tools-pages-testing.md` for detailed troubleshooting guide. ## JavaScript Extraction Pattern **Best Practice:** Extract large calculator functions (>20KB) from PHP files into external JavaScript files for better caching, maintainability, and performance. ### When to Extract ✅ **Extract when:** - Calculator function > 20KB - Function has no PHP dependencies (no `<?php ?>` tags) - Function is self-contained (all helpers included) - Function can be wrapped in IIFE ❌ **Don't extract when:** - Function uses PHP variables (`<?php echo $var; ?>`) - Function depends on PHP-generated data - Function is small (< 10KB) - Function has complex PHP dependencies ### Extraction Process 1. **Create external file:** ```",
          "``` 2. **Wrap in IIFE:** ```",
          "``` 3. **Update PHP file:** ```",
          "``` 4. **Update minification script:** Add file to `minify-assets.js`: ```",
          "``` 5. **Run minification:** ```",
          "``` 6. **Verify:** - Check browser console for function definition - Test calculator functionality - Verify no console errors - Check Network tab for file loading ### Extracted Calculators The following calculators have been extracted: 1. **arbeitszeitCalculator** → `v2/js/tools-arbeitszeit-calculator.js` (167KB) 2. **stundenlohnrechnerCalculator** → `v2/js/tools-stundenlohn-calculator.js` (91KB) 3. **mehrwertsteuerCalculator** → `v2/js/tools-mehrwertsteuer-calculator.js` (77KB) 4. **midijobCalculator** → `v2/js/tools-midijob-calculator.js` (67KB) 5. **zuschlagsrechnerCalculator** → `v2/js/tools-zuschlags-calculator.js` (29KB) 6. **paypalCalculator** → `v2/js/tools-paypal-calculator.js` (16KB) ### Helper Functions **Helper functions that remain in PHP file:** - Setup functions (e.g., `setupCalculatorWatchers`, `initialCalculation`) - Config loading (e.g., `window.paypalConfig`) - Utility functions shared across multiple calculators **Helper functions moved to external file:** - Formatting functions (`formatCurrency`, `formatNumber`) - Export functions (`exportPDF`, `exportCSV`, `generatePDFHTML`) - Calculation helpers specific to the calculator ### File Naming Convention - Pattern: `tools-[name]-calculator.js` - Examples: - `tools-arbeitszeit-calculator.js` - `tools-stundenlohn-calculator.js` - `tools-mehrwertsteuer-calculator.js` ### Benefits of Extraction 1. **Better caching:** External JS files cached by browser 2. **Maintainability:** Easier to edit and debug 3. **Performance:** Smaller PHP files, parallel JS loading 4. **Code organization:** Separation of concerns 5. **Reusability:** Can be shared across pages if needed ### Common Extraction Issues #### Syntax Errors After Extraction **Cause:** Helper functions incorrectly placed inside return object **Solution:** Move helper functions outside the main Alpine.js component return object: ```",
          "``` #### Missing Dependencies **Cause:** Function references variables/functions from PHP file **Solution:** Check dependencies before extraction: - Search for `window.` references - Check for PHP-generated data - Verify all helpers are included #### Registration Timing Issues **Cause:** Alpine.js not ready when function registers **Solution:** Use robust registration pattern: ```",
          "``` ## Tab-Based Multi-Mode Calculators Some tools pages (e.g., Prozentrechner, Mehrwertsteuer-Rechner) use tab-based interfaces for multiple calculation modes. ### Tab Navigation Pattern **HTML Structure:** ```",
          "``` **Enhanced Tab Styling (Prozentrechner Pattern):** - **Container background:** `bg-gray-50` (subtle pattern, not `bg-gray-100`) - **Active tab:** White background with blue text (`#4D8EF3`) instead of blue background - **Box-shadow on active:** `0 1px 3px 0 rgba(0, 0, 0, 0.1)` for subtle elevation - **Hover states:** Smooth transitions with `#f8faff` background - **Focus states:** Visible focus ring `0 0 0 3px rgba(77, 142, 243, 0.1)` **Tab Panel Pattern:** ```",
          "``` **Key Requirements:** - ✅ Use `x-cloak` to prevent flash of unstyled content - ✅ Add `role=\"tablist\"` and `role=\"tab\"` for accessibility - ✅ Use `aria-selected` and `aria-controls` for screen readers - ✅ Support keyboard navigation (Enter/Space) - ✅ Call `calculate()` when switching tabs - ✅ Ensure CSS includes `[x-cloak] { display: none !important; }` ### Multi-Mode State Management **Alpine.js Pattern:** ```",
          "``` ## Common Tools Page Pitfalls ### Breaking Calculation Logic ❌ **BAD:** Modifying calculation without testing edge cases ✅ **GOOD:** Test with: - Valid inputs (normal use case) - Invalid inputs (negative, zero, out of range) - Edge cases (division by zero, empty fields, very large numbers) - Boundary values (min, max, just above/below) ### Inconsistent Form Heights ❌ **BAD:** Hardcoding heights: `style=\"height: 48px\"` ✅ **GOOD:** Use CSS classes from tools-pages.css (heights auto-managed) ### Missing Validation ❌ **BAD:** No min/max on number inputs ✅ **GOOD:** ```",
          "``` ### Export Errors ❌ **BAD:** Not testing export with edge cases (empty inputs, special characters, very large numbers) ✅ **GOOD:** Test export with: - Normal inputs - Empty/zero values - Special characters (ä, ö, ü, ß) - Very large numbers (formatting) ### Mobile Zoom Issues ❌ **BAD:** Input font-size < 16px on mobile (triggers zoom) ✅ **GOOD:** Font-size ≥ 16px on mobile (defined in tools-pages.css) ### Checkbox Visual State Not Updating ❌ **BAD:** Checkbox works functionally but doesn't show checked state visually ✅ **GOOD:** - General checkbox styles in `tools-pages.css` apply to all checkboxes - Alpine.js `x-model` automatically syncs `checked` attribute - CSS `:checked` pseudo-class handles visual state - No need for `.calculator-form` parent container **Troubleshooting:** - Verify `tools-pages.css` is loaded (check Network tab) - Check browser console for CSS errors - Ensure Alpine.js is initialized before checkbox interactions - Verify `x-model` binding is correct (e.g., `x-model=\"variableName\"`) - Check that checkbox isn't inside `.toggle-switch` container (uses different styling) ### Radio Button Visual State Not Updating ❌ **BAD:** Radio button works functionally but doesn't show checked state visually ✅ **GOOD:** - General radio button styles in `tools-pages.css` apply to Pattern 2 radios (label-wrapped) - Pattern 1 (`.radio-item`) radios use different styling mechanism (hidden input, styled label) - Alpine.js `x-model` automatically syncs `checked` attribute - CSS `:checked` pseudo-class handles visual state - Label styling uses `:has()` selector for checked state **Troubleshooting:** - Verify `tools-pages.css` is loaded (check Network tab) - Check browser console for CSS errors - Ensure Alpine.js is initialized before radio interactions - Verify `x-model` binding is correct (e.g., `x-model=\"selectedCountry\"`) - Check which pattern is used - Pattern 1 (`.radio-item`) vs Pattern 2 (direct radio in label) - Verify radio doesn't have `.sr-only` or `.custom-radio` class (excluded from general styles) - Check that `:has()` selector is supported (modern browsers - Chrome 105+, Firefox 121+, Safari 15.4+) - Ensure radio group has same `name` attribute for proper group behavior --- ## Edge Cases & Gotchas ### Number Formatting **German format:** 1.234,56 (not 1,234.56) ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "Ordio Mention Pattern"
        ],
        "css_patterns": [
          "```css\n/* Colors */\n--ordio-primary: #4d8ef3;\n--ordio-primary-hover: #3b82f6;\n--ordio-text-primary: #374151;\n--ordio-text-secondary: #6b7280;\n--ordio-border: #e5e7eb;\n--ordio-background: #ffffff;\n\n/* Shadows (layered for depth) */\nbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.04); /* Standard elevation */\nbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04); /* Result cards */\nbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08); /* Tab container */\n\n/* Spacing Scale */\n--spacing-xs: 0.25rem; /* 4px */\n--spacing-sm: 0.5rem; /* 8px */\n--spacing-md: 1rem; /* 16px */\n--spacing-lg: 1.5rem; /* 24px */\n--spacing-xl: 2rem; /* 32px */\n\n/* Border Radius */\n--radius-sm: 0.5rem; /* 8px - buttons, tabs */\n--radius-md: 0.75rem; /* 12px - containers */\n--radius-lg: 1rem; /* 16px - cards, forms */\n\n/* Typography Scale */\n--text-xs: 0.75rem; /* 12px */\n--text-sm: 0.875rem; /* 14px */\n--text-base: 1rem; /* 16px */\n--text-lg: 1.125rem; /* 18px */\n--text-xl: 1.25rem; /* 20px */\n--text-2xl: 1.5rem; /* 24px */\n```",
          "```css\n.ordio-tabs {\n  display: flex;\n  background: var(--ordio-background-subtle);\n  border-radius: 0.75rem;\n  padding: 0.25rem;\n  margin-bottom: 1.5rem;\n  overflow-x: auto;\n  -webkit-overflow-scrolling: touch;\n  scrollbar-width: thin;\n  scroll-snap-type: x mandatory;\n}\n\n.ordio-tab {\n  font-family: \"Inter-500\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\n    Helvetica, Arial, sans-serif;\n  flex: 1;\n  padding: 0.75rem 1rem;\n  border-radius: 0.5rem;\n  font-size: 0.875rem;\n  font-weight: 500;\n  text-align: center;\n  cursor: pointer;\n  transition: all 0.2s ease;\n  color: var(--ordio-text-secondary);\n  background: transparent;\n  border: none;\n  white-space: nowrap;\n}\n\n.ordio-tab.active {\n  background: white;\n  color: var(--ordio-primary);\n  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);\n}\n\n.ordio-tab:hover:not(.active) {\n  color: var(--ordio-text-primary);\n}\n\n/* Mobile Optimization */\n@media (max-width: 768px) {\n  .ordio-tabs {\n    flex-direction: row;\n    flex-wrap: nowrap;\n    gap: 0.25rem;\n  }\n\n  .ordio-tab {\n    flex-shrink: 0;\n    scroll-snap-align: start;\n    min-height: 44px;\n  }\n}\n```",
          "```css\n.ordio-tabs {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: center;\n  background: var(--ordio-background-subtle);\n  border-radius: 0.75rem;\n  padding: 0.25rem;\n  margin-bottom: 1.5rem;\n  gap: 0.25rem;\n}\n\n.ordio-tab {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex: 0 1 auto;\n  /* ... rest of standard styles ... */\n}\n\n.ordio-tab svg {\n  flex-shrink: 0;\n}\n\n@media (max-width: 768px) {\n  .ordio-tabs {\n    gap: 0.375rem;\n  }\n\n  .ordio-tab {\n    min-height: 44px;\n    font-size: 0.8125rem;\n    padding: 0.625rem 0.875rem;\n  }\n}\n```",
          "```css\n/* Desktop: Compact inputs for better visual density */\n.calculator-form input,\n.calculator-form select {\n  height: 2.75rem;\n  min-height: 2.75rem;\n  font-size: 0.875rem;\n  padding: 0.5rem 0.75rem;\n}\n\n/* Mobile: Larger for touch */\n@media (max-width: 768px) {\n  .calculator-form input,\n  .calculator-form select {\n    height: 3.25rem;\n    min-height: 3.25rem;\n    font-size: 1rem;\n    padding: 0.75rem 1rem;\n  }\n}\n```",
          "```css\n.icon-btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  border: 1px solid #e5e7eb;\n  border-radius: 0.5rem;\n  background: white;\n  color: #6b7280;\n  cursor: pointer;\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.icon-btn:hover {\n  background: #f3f4f6;\n  color: #374151;\n  transform: translateY(-1px);\n}\n\n.icon-btn svg {\n  width: 1rem;\n  height: 1rem;\n}\n\n/* Danger variant */\n.icon-btn-danger {\n  color: #ef4444;\n}\n\n.icon-btn-danger:hover {\n  background: #fef2f2;\n  color: #dc2626;\n}\n```",
          "```css\n.segmented-control {\n  display: inline-flex;\n  background: #f3f4f6;\n  border-radius: 0.5rem;\n  padding: 0.25rem;\n  gap: 0.125rem;\n  width: 100%; /* Full width for better alignment */\n  box-sizing: border-box;\n}\n\n.segmented-control input[type=\"radio\"] {\n  display: none;\n}\n\n.segmented-control label {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 0.5rem 1rem; /* Increased for better touch targets */\n  font-size: 0.875rem; /* Slightly larger for readability */\n  font-weight: 500;\n  color: #6b7280;\n  background: transparent;\n  border-radius: 0.375rem;\n  cursor: pointer;\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n  white-space: nowrap;\n  flex: 1; /* Equal width segments */\n  min-width: 0;\n}\n\n.segmented-control input[type=\"radio\"]:checked + label {\n  background: white;\n  color: #4d8ef3;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); /* Enhanced shadow */\n  font-weight: 600; /* Bolder for active state */\n}\n\n.segmented-control label:hover:not(:has(input:checked)) {\n  color: #374151;\n  background: rgba(77, 142, 243, 0.05); /* Subtle hover effect */\n}\n```",
          "```css\n.calculator-form input:focus,\n.calculator-form select:focus {\n  transform: translateY(-1px);\n  box-shadow: 0 0 0 3px rgba(77, 142, 243, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n```",
          "```css\n.copy-btn:hover,\n.example-btn:hover,\n.history-item:hover {\n  transform: translateY(-2px);\n  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);\n}\n\n.copy-btn:active,\n.example-btn:active,\n.history-item:active {\n  transform: translateY(0); /* Tactile press feedback */\n}\n```",
          "```css\n.result-card {\n  background: white;\n  border: 1px solid #e5e7eb;\n  border-radius: 1rem;\n  padding: 2rem; /* 1.5rem mobile */\n  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04);\n  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n  animation: slideInUp 0.5s ease-out;\n  position: relative;\n  overflow: visible;\n}\n\n.result-card:hover {\n  transform: translateY(-2px);\n  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.06);\n}\n\n@keyframes slideInUp {\n  from {\n    opacity: 0;\n    transform: translateY(20px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n```",
          "```css\n.result-value {\n  font-size: 1.5rem;\n  font-weight: 700;\n  color: #4D8EF3;\n  line-height: 1.2;\n}\n\n.result-label {\n  font-size: 0.875rem;\n  color: #6b7280;\n  margin-top: 0.25rem;\n}\n\n.result-context {\n  font-size: 0.875rem;\n  color: #6b7280;\n  margin-top: 1rem;\n}\n```",
          "```css\n.result-item {\n  margin-bottom: 1rem;\n}\n\n.result-item:last-child {\n  margin-bottom: 0;\n}\n\n.result-item-label {\n  font-size: 0.875rem;\n  color: #6b7280;\n  margin-bottom: 0.5rem;\n  font-weight: 500;\n}\n\n.result-item-value {\n  font-size: 1.5rem;\n  font-weight: 700;\n  line-height: 1.2;\n}\n\n.result-item-value-primary {\n  color: #4D8EF3;\n  font-size: 1.5rem;\n}\n\n.result-item-value-secondary {\n  color: #374151;\n  font-size: 1.25rem;\n  font-weight: 600;\n}\n\n.result-item-value-success {\n  color: #10b981;\n  font-size: 1.5rem;\n}\n\n.result-item-divider {\n  border-top: 1px solid #e5e7eb;\n  padding-top: 1rem;\n  margin-top: 1rem;\n}\n\n.result-helper {\n  font-size: 0.75rem;\n  color: #9ca3af;\n  margin-top: 0.25rem;\n  font-weight: 400;\n}\n\n.result-info-box {\n  background-color: #f9fafb;\n  border-radius: 0.5rem;\n  padding: 0.75rem;\n  margin-top: 1rem;\n  font-size: 0.75rem;\n  color: #6b7280;\n}\n\n.result-info-box strong {\n  font-weight: 600;\n  color: #374151;\n}\n```",
          "```css\n/* Individual value blurring for gated content */\n.gated-value {\n  filter: blur(4px);\n  transition: filter 0.3s ease;\n  user-select: none;\n  -webkit-user-select: none;\n  pointer-events: none;\n}\n\n.gated-value.unlocked {\n  filter: none;\n  user-select: auto;\n  pointer-events: auto;\n}\n\n.email-overlay {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: rgba(255, 255, 255, 0.98);\n  backdrop-filter: blur(4px);\n  -webkit-backdrop-filter: blur(4px);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  z-index: 1000;\n  border-radius: 0.75rem;\n  pointer-events: auto;\n}\n\n.gated-content-container {\n  position: relative;\n  overflow: visible;\n}\n\n.email-form {\n  background: white;\n  padding: 2rem;\n  border-radius: 1rem;\n  box-shadow: 0 20px 40px -5px rgba(0, 0, 0, 0.15);\n  border: 2px solid #4d8ef3;\n  max-width: 450px;\n  width: 90%;\n  text-align: center;\n}\n\n.email-input {\n  width: 100%;\n  padding: 0.75rem 1rem;\n  border: 2px solid #e5e7eb;\n  border-radius: 0.5rem;\n  font-size: 1rem;\n  margin-bottom: 1rem;\n}\n\n.email-input:focus {\n  outline: none;\n  border-color: #4d8ef3;\n  box-shadow: 0 0 0 3px rgba(77, 142, 243, 0.1);\n}\n\n.email-submit-btn {\n  background: #4d8ef3;\n  color: white;\n  padding: 0.75rem 2rem;\n  border: none;\n  border-radius: 0.5rem;\n  font-weight: 600;\n  cursor: pointer;\n  width: 100%;\n  min-height: 44px;\n}\n\n.email-submit-btn:hover:not(:disabled) {\n  background: #3b7dd8;\n}\n\n.email-submit-btn:disabled {\n  opacity: 0.6;\n  cursor: not-allowed;\n}\n\n.gated-section {\n  position: relative;\n}\n\n.gated-section.gated .gated-value {\n  filter: blur(4px);\n  transition: filter 0.3s ease;\n  user-select: none;\n  pointer-events: none;\n}\n\n/* Mobile optimization */\n@media (max-width: 768px) {\n  .email-overlay {\n    position: fixed !important;\n    top: 0 !important;\n    left: 0 !important;\n    right: 0 !important;\n    bottom: 0 !important;\n    border-radius: 0 !important;\n  }\n\n  .email-form {\n    padding: 1.5rem !important;\n    max-width: calc(100% - 2rem) !important;\n  }\n}\n```",
          "```css\n.export-btn-primary {\n  background-color: #4d8ef3;\n}\n\n.export-btn-primary:hover {\n  background-color: #3b82f6;\n  transform: translateY(-1px);\n  box-shadow: 0 4px 12px rgba(77, 142, 243, 0.3);\n}\n```",
          "```css\n.export-btn-secondary {\n  background-color: #f3f4f6;\n  color: #374151;\n}\n\n.export-btn-secondary:hover {\n  background-color: #e5e7eb;\n  transform: translateY(-1px);\n}\n```",
          "```css\nbackground-image: radial-gradient(#e6e6e6 1px, transparent 0);\nbackground-size: 20px 20px;\n```",
          "```css\n.result-card {\n  background: white;\n  border: 1px solid #e2e8f0;\n  border-radius: 1rem;\n  padding: 1.5rem;\n  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);\n  transition: all 0.3s ease;\n  animation: slideInUp 0.5s ease-out;\n}\n\n.result-card:hover {\n  transform: translateY(-2px);\n  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\n}\n\n.result-value {\n  font-size: 1.5rem;\n  font-weight: 700;\n  color: #4d8ef3; /* Primary blue for visual hierarchy */\n  line-height: 1.2;\n}\n\n@keyframes slideInUp {\n  from {\n    opacity: 0;\n    transform: translateY(20px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n/* Respect user preferences for reduced motion */\n@media (prefers-reduced-motion: reduce) {\n  .result-card,\n  .result-card:hover {\n    animation: none;\n    transition: none;\n  }\n}\n```"
        ],
        "meta_tag_patterns": [
          "<title>[Tool Name] 2025: [Action] - Ordio</title>",
          "<meta\n  name=\"description\"\n  content=\"[Tool] 2025: [Benefit/Action]. [What it calculates]. [Export option]. Kostenlos & ohne Anmeldung.\"\n/>"
        ]
      },
      "total_patterns": 81
    },
    {
      "file": ".cursor/rules/webinar-pages.mdc",
      "patterns": {
        "validation_checklists": [
          "- [ ] Ordio mention natural (once)"
        ],
        "code_examples": [
          "```php <?php include '../base/include_form-webinar.php'; ?> ```",
          "``` BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Ordio//Webinar//DE BEGIN:VEVENT UID:[unique-id]@ordio.com DTSTAMP:[timestamp] DTSTART:[start-datetime] DTEND:[end-datetime] SUMMARY:[Webinar Title] DESCRIPTION:[Webinar Description] LOCATION:[Webinar URL] STATUS:CONFIRMED SEQUENCE:0 END:VEVENT END:VCALENDAR ```",
          "```json { \"@context\": \"https://schema.org\", \"@type\": \"Event\", \"name\": \"[Webinar Title]\", \"description\": \"[What attendees will learn]\", \"startDate\": \"2025-11-15T14:00:00+01:00\", \"endDate\": \"2025-11-15T15:30:00+01:00\", \"eventStatus\": \"https://schema.org/EventScheduled\", \"eventAttendanceMode\": \"https://schema.org/OnlineEventAttendanceMode\", \"location\": { \"@type\": \"VirtualLocation\", \"url\": \"[Webinar URL]\" }, \"organizer\": { \"@type\": \"Organization\", \"name\": \"Ordio GmbH\", \"url\": \"https://www.ordio.com\" }, \"offers\": { \"@type\": \"Offer\", \"price\": \"0\", \"priceCurrency\": \"EUR\", \"availability\": \"https://schema.org/InStock\", \"url\": \"[Registration URL]\" } } ```"
        ],
        "schema_examples": [],
        "copy_guidelines": [
          "du tone, benefit-driven\n- [ ] Ordio mention natural (once)",
          "Ordio Mention Pattern",
          "Ordio mention natural (once)"
        ],
        "css_patterns": [],
        "meta_tag_patterns": []
      },
      "total_patterns": 7
    }
  ],
  "cross_file_duplicates": {
    "validation_checklists": {
      "- [ ] Ordio mention natural (once)": [
        {
          "pattern": "- [ ] Ordio mention natural (once)",
          "file": ".cursor/rules/download-pages.mdc"
        },
        {
          "pattern": "- [ ] Ordio mention natural (once)",
          "file": ".cursor/rules/webinar-pages.mdc"
        }
      ]
    },
    "code_examples": {
      "```php header('Content-Type: application/json'); echo json_encode([ 'success' => true|false, 'messag": [
        {
          "pattern": "```php header('Content-Type: application/json'); echo json_encode([ 'success' => true|false, 'message' => 'User-friendly message (du tone)', 'data' => [...] // Optional ]); ```",
          "file": ".cursor/rules/api-endpoints.mdc"
        },
        {
          "pattern": "```php header('Content-Type: application/json'); echo json_encode([ 'success' => true|false, 'message' => 'User-friendly message', 'data' => [...] // Optional ]); ```",
          "file": ".cursor/rules/global.mdc"
        }
      ]
    },
    "copy_guidelines": {
      "du tone, benefit-driven - [ ] Ordio mention natural (once)": [
        {
          "pattern": "du tone, benefit-driven - [ ] Ordio mention natural (once)",
          "file": ".cursor/rules/download-pages.mdc"
        },
        {
          "pattern": "du tone, benefit-driven - [ ] Ordio mention natural (once)",
          "file": ".cursor/rules/webinar-pages.mdc"
        }
      ]
    },
    "meta_tag_patterns": {},
    "css_patterns": {}
  },
  "summary": {
    "total_files": 20,
    "duplicate_types": {
      "validation_checklists": 1,
      "code_examples": 1,
      "copy_guidelines": 1,
      "meta_tag_patterns": 0,
      "css_patterns": 0
    }
  }
}