{
  "id": 12265,
  "name": "Create Anki language flashcards with GPT-4, DALL-E and ElevenLabs",
  "description": "Turn any topic into a ready-to-study Anki deck. This workflow generates vocabulary flashcards with AI images and native pronunciation, then sends the .apkg file straight to your inbox.\n\n**What it does**\n1. You fill out a simple form (topic, languages, difficulty)\n2. GPT-4 creates vocabulary with translations, readings, and example sentences\n3. DALL-E 3 generates a unique image for each word\n4. ElevenLabs adds native pronunciation audio (word + example)\n5. Everything gets packaged into a real .apkg file\n6. The deck lands in your email, ready to import into Anki\n7. A backup copy saves to Google Sheets\n\n**Why I built this**\nI was spending hours making flashcards by hand for language learning. Finding images, recording audio, formatting everything for Anki... it took forever. This workflow does all of that in about 3 minutes.\n\n**Setup (~15 min)**\n1. Install npm packages: `jszip` and `sql.js`\n2. Add OpenAI credentials (for GPT-4 + DALL-E)\n3. Add ElevenLabs credentials\n4. Connect Gmail and Google Sheets via OAuth\n5. Update `OPENAI_API_KEY` in the DALL-E code node\n6. Update the Spreadsheet ID in the Sheets node\n\n**Features**\n- 20 languages supported\n- 7 image styles (minimal icons, kawaii, realistic, watercolor, pixel art...)\n- 6 difficulty levels (A1 to C2)\n- Optional reverse cards (target\u2192native AND native\u2192target)\n- Works on Anki desktop and mobile",
  "categories": [
    {
      "id": 31,
      "name": "Content Creation"
    },
    {
      "id": 51,
      "name": "Multimodal AI"
    }
  ],
  "totalViews": 164,
  "createdAt": "2025-12-29T11:57:32.202Z",
  "workflow": {
    "meta": {
      "instanceId": "5a736234c697011c98e45cbb42e3fa6c7274e8d2737d845f99ebfa424b5a19fa",
      "templateCredsSetupCompleted": true
    },
    "nodes": [
      {
        "id": "3d110356-8ee6-472b-be91-c41a95d22d43",
        "name": "Sticky Note - Main",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          2496,
          448
        ],
        "parameters": {
          "width": 400,
          "height": 636,
          "content": "##  AI Flashcard Generator Pro\n\n**Anki Export with AI Images & Native Audio**\n\nGenerate complete Anki-ready flashcard decks with AI images and native pronunciation audio. Perfect for language learners using spaced repetition.\n\n### How it works\n1. User fills out form with topic, languages, and preferences\n2. GPT-4 generates vocabulary with translations and examples\n3. DALL-E 3 creates memorable illustrations\n4. ElevenLabs generates native pronunciation audio\n5. Builds real .apkg file for direct Anki import\n6. Saves backup to Google Sheets\n7. Sends .apkg via Gmail to user\n\n### Set up steps (15-20 minutes)\n1. Install npm packages: `jszip`, `sql.js`\n2. Add OpenAI API credentials\n3. Add ElevenLabs credentials\n4. Connect Gmail OAuth\n5. Connect Google Sheets OAuth\n6. Update `OPENAI_API_KEY` in DALL-E node\n7. Update `SPREADSHEET_ID` in Sheets node\n\n"
        },
        "typeVersion": 1
      },
      {
        "id": "d3145b60-aaaf-4943-b472-2150e8145054",
        "name": "Sticky Note - Config",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          4736,
          752
        ],
        "parameters": {
          "color": 3,
          "width": 380,
          "height": 380,
          "content": "## \u2699\ufe0f ElevenLabs Voice IDs\n\n```\nJapanese: yoZ06aMxZJJ28mfd3POQ\nKorean: qejjN2Yjyj8IBlZGbOGj  \nChinese: pFZP5JQG7iQjIQuC4Bku\nSpanish: bVMeCyTHy58xNoL34h3p\nFrench: 0O45CbXxNQPFeShXyQEV\nGerman: g5CIjZEefAph4nQFvHAz\nItalian: zcAOhNBS3c14rBihAFp1\nPortuguese: ODq5zmih8GrVes37Dizd\nRussian: onwK4e9ZLuTAKqWW03F9\nArabic: SOYHLrjzK2X1ezoPC6cr\nEnglish: pNInz6obpgDQGcFmaJgB\n```\n\n**Note:** Voice IDs are mapped automatically in the Validate Input node based on target language."
        },
        "typeVersion": 1
      },
      {
        "id": "4f4001e5-3c9a-44e6-a65b-9be8d6e4056f",
        "name": "Sticky Note1",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          2944,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 180,
          "height": 240,
          "content": "**Step 1: Form**\nCollects user preferences"
        },
        "typeVersion": 1
      },
      {
        "id": "c5e788b5-100e-417a-b2e4-637344f1b59d",
        "name": "Sticky Note2",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          3168,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 180,
          "height": 240,
          "content": "**Step 2: Validate**\nCheck inputs & map voices"
        },
        "typeVersion": 1
      },
      {
        "id": "738b5499-e430-4480-a184-d23a1851fc92",
        "name": "Sticky Note3",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          3392,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 200,
          "height": 240,
          "content": "**Step 3: GPT-4**\nGenerate vocabulary"
        },
        "typeVersion": 1
      },
      {
        "id": "4cdb2c43-7660-4c65-a629-7093b2b871dc",
        "name": "Sticky Note4",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          3632,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 180,
          "height": 240,
          "content": "**Step 4: Prepare**\nFormat for media gen"
        },
        "typeVersion": 1
      },
      {
        "id": "2162febf-73b6-4b68-9ff5-c82ab5be1568",
        "name": "Sticky Note5",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          3856,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 176,
          "height": 240,
          "content": "**Step 5: Loop**\nProcess each card"
        },
        "typeVersion": 1
      },
      {
        "id": "c5c25aeb-dbb9-492a-a09f-61e3ccd4d414",
        "name": "Sticky Note6",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          4048,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 196,
          "height": 240,
          "content": "**Step 6: DALL-E**\nGenerate images"
        },
        "typeVersion": 1
      },
      {
        "id": "ca9d7f0d-3cbb-4a61-a348-cf3da37a0da1",
        "name": "Sticky Note7",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          4256,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 420,
          "height": 256,
          "content": "**Step 7: ElevenLabs**\nWord pronunciation"
        },
        "typeVersion": 1
      },
      {
        "id": "37993ca7-734d-48ce-92de-6ba9bf4b0333",
        "name": "Sticky Note8",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          4720,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 404,
          "height": 256,
          "content": "**Step 8: ElevenLabs**\nExample sentence audio"
        },
        "typeVersion": 1
      },
      {
        "id": "77f36469-c80a-4627-9bc5-6ff6cfa2434b",
        "name": "Sticky Note9",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          5376,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 180,
          "height": 240,
          "content": "**Step 9: Merge**\nCombine all media"
        },
        "typeVersion": 1
      },
      {
        "id": "d37df9b5-f126-4ef3-8093-db0b25a5b481",
        "name": "Sticky Note10",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          5600,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 420,
          "height": 240,
          "content": "**Step 10: APKG**\nBuild Anki package"
        },
        "typeVersion": 1
      },
      {
        "id": "ad6e4b6e-5165-4550-91ca-59847c37e459",
        "name": "Sticky Note11",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          6256,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 200,
          "height": 240,
          "content": "**Step 11: Sheets**\nBackup data"
        },
        "typeVersion": 1
      },
      {
        "id": "39b8ef20-39dd-4d30-b961-3d865a811384",
        "name": "Sticky Note12",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          6752,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 340,
          "height": 240,
          "content": "**Step 12: Email**\nSend via Gmail"
        },
        "typeVersion": 1
      },
      {
        "id": "a5d0aff8-1b0f-4364-bab6-462fbcd8eb21",
        "name": "Sticky Note13",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          7168,
          448
        ],
        "parameters": {
          "color": 7,
          "width": 180,
          "height": 240,
          "content": "**Step 13: Response**\nReturn JSON"
        },
        "typeVersion": 1
      },
      {
        "id": "ec66ccef-e079-4fb0-9a7e-76f066dbe143",
        "name": "Flashcard Form",
        "type": "n8n-nodes-base.formTrigger",
        "position": [
          2992,
          528
        ],
        "webhookId": "ai-flashcard-generator-pro",
        "parameters": {
          "options": {
            "respondWithOptions": {
              "values": {
                "formSubmittedText": "\ud83c\udfb4 Creating your flashcards! This takes 2-5 minutes.\n\nWe're generating:\n\u2022 AI vocabulary with examples\n\u2022 Custom images for each word\n\u2022 Native pronunciation audio\n\u2022 Anki-ready .apkg file\n\nYou'll receive an email when ready!"
              }
            }
          },
          "formTitle": "\ud83c\udfb4 AI Flashcard Generator Pro",
          "formFields": {
            "values": [
              {
                "fieldType": "email",
                "fieldLabel": "Your Email",
                "placeholder": "you@example.com",
                "requiredField": true
              },
              {
                "fieldType": "textarea",
                "fieldLabel": "Topic or Word List",
                "placeholder": "Example: Common kitchen items, or list specific words: apple, banana, orange...",
                "requiredField": true
              },
              {
                "fieldType": "dropdown",
                "fieldLabel": "I speak (Native Language)",
                "fieldOptions": {
                  "values": [
                    {
                      "option": "English"
                    },
                    {
                      "option": "Spanish"
                    },
                    {
                      "option": "French"
                    },
                    {
                      "option": "German"
                    },
                    {
                      "option": "Portuguese"
                    },
                    {
                      "option": "Italian"
                    },
                    {
                      "option": "Chinese"
                    },
                    {
                      "option": "Japanese"
                    },
                    {
                      "option": "Korean"
                    },
                    {
                      "option": "Russian"
                    },
                    {
                      "option": "Arabic"
                    },
                    {
                      "option": "Hindi"
                    },
                    {
                      "option": "Dutch"
                    },
                    {
                      "option": "Polish"
                    },
                    {
                      "option": "Turkish"
                    },
                    {
                      "option": "Vietnamese"
                    },
                    {
                      "option": "Thai"
                    },
                    {
                      "option": "Indonesian"
                    },
                    {
                      "option": "Swedish"
                    },
                    {
                      "option": "Greek"
                    }
                  ]
                },
                "requiredField": true
              },
              {
                "fieldType": "dropdown",
                "fieldLabel": "I want to learn (Target Language)",
                "fieldOptions": {
                  "values": [
                    {
                      "option": "Japanese"
                    },
                    {
                      "option": "Korean"
                    },
                    {
                      "option": "Chinese (Mandarin)"
                    },
                    {
                      "option": "Spanish"
                    },
                    {
                      "option": "French"
                    },
                    {
                      "option": "German"
                    },
                    {
                      "option": "Italian"
                    },
                    {
                      "option": "Portuguese"
                    },
                    {
                      "option": "Russian"
                    },
                    {
                      "option": "Arabic"
                    },
                    {
                      "option": "English"
                    },
                    {
                      "option": "Hindi"
                    },
                    {
                      "option": "Dutch"
                    },
                    {
                      "option": "Polish"
                    },
                    {
                      "option": "Turkish"
                    },
                    {
                      "option": "Vietnamese"
                    },
                    {
                      "option": "Thai"
                    },
                    {
                      "option": "Indonesian"
                    },
                    {
                      "option": "Swedish"
                    },
                    {
                      "option": "Greek"
                    }
                  ]
                },
                "requiredField": true
              },
              {
                "fieldType": "number",
                "fieldLabel": "Number of Flashcards",
                "placeholder": "10",
                "requiredField": true
              },
              {
                "fieldType": "dropdown",
                "fieldLabel": "Difficulty Level",
                "fieldOptions": {
                  "values": [
                    {
                      "option": "Beginner (A1)"
                    },
                    {
                      "option": "Elementary (A2)"
                    },
                    {
                      "option": "Intermediate (B1)"
                    },
                    {
                      "option": "Upper Intermediate (B2)"
                    },
                    {
                      "option": "Advanced (C1)"
                    },
                    {
                      "option": "Native (C2)"
                    }
                  ]
                },
                "requiredField": true
              },
              {
                "fieldType": "dropdown",
                "fieldLabel": "Image Style",
                "fieldOptions": {
                  "values": [
                    {
                      "option": "Simple icon style, flat design, minimal"
                    },
                    {
                      "option": "Cute cartoon illustration, kawaii"
                    },
                    {
                      "option": "Realistic photograph style"
                    },
                    {
                      "option": "Watercolor painting style"
                    },
                    {
                      "option": "Pixel art, retro game style"
                    },
                    {
                      "option": "Simple line drawing, sketch"
                    },
                    {
                      "option": "3D rendered, modern"
                    }
                  ]
                },
                "requiredField": true
              },
              {
                "fieldType": "dropdown",
                "fieldLabel": "Generate Reverse Cards?",
                "fieldOptions": {
                  "values": [
                    {
                      "option": "Yes - Both directions (Target\u2192Native and Native\u2192Target)"
                    },
                    {
                      "option": "No - Only Target\u2192Native"
                    }
                  ]
                },
                "requiredField": true
              }
            ]
          },
          "formDescription": "Create vocabulary flashcards with AI-generated images and native pronunciation audio. Export directly to Anki!"
        },
        "typeVersion": 2.3
      },
      {
        "id": "aa6fc4cf-e1a1-4c81-be99-49f4d438c586",
        "name": "Validate Input",
        "type": "n8n-nodes-base.code",
        "position": [
          3232,
          528
        ],
        "parameters": {
          "jsCode": "// ============================================\n// VALIDATE INPUT & SETUP CONFIGURATION\n// ============================================\n\nconst formData = $input.first().json;\n\n// Validate number of cards (1-20)\nlet numCards = parseInt(formData['Number of Flashcards']) || 10;\nif (numCards < 1) numCards = 1;\nif (numCards > 20) numCards = 20;\n\n// Generate unique deck ID\nconst deckId = `deck_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n\n// Voice ID mapping for ElevenLabs\nconst voiceMap = {\n  'Japanese': 'yoZ06aMxZJJ28mfd3POQ',\n  'Korean': 'qejjN2Yjyj8IBlZGbOGj',\n  'Chinese (Mandarin)': 'pFZP5JQG7iQjIQuC4Bku',\n  'Chinese': 'pFZP5JQG7iQjIQuC4Bku',\n  'Spanish': 'bVMeCyTHy58xNoL34h3p',\n  'French': '0O45CbXxNQPFeShXyQEV',\n  'German': 'g5CIjZEefAph4nQFvHAz',\n  'Italian': 'zcAOhNBS3c14rBihAFp1',\n  'Portuguese': 'ODq5zmih8GrVes37Dizd',\n  'Russian': 'onwK4e9ZLuTAKqWW03F9',\n  'Arabic': 'SOYHLrjzK2X1ezoPC6cr',\n  'English': 'pNInz6obpgDQGcFmaJgB',\n  'Hindi': 'XrExE9yKIg1WjnnlVkGX',\n  'Dutch': 'g5CIjZEefAph4nQFvHAz',\n  'Polish': 'onwK4e9ZLuTAKqWW03F9',\n  'Turkish': 'bVMeCyTHy58xNoL34h3p',\n  'Vietnamese': 'pFZP5JQG7iQjIQuC4Bku',\n  'Thai': 'pFZP5JQG7iQjIQuC4Bku',\n  'Indonesian': 'ODq5zmih8GrVes37Dizd',\n  'Swedish': 'g5CIjZEefAph4nQFvHAz',\n  'Greek': 'zcAOhNBS3c14rBihAFp1'\n};\n\nconst nativeLanguage = formData['I speak (Native Language)'];\nconst targetLanguage = formData['I want to learn (Target Language)'];\n\nreturn {\n  json: {\n    email: formData['Your Email'],\n    topic: formData['Topic or Word List'],\n    nativeLanguage: nativeLanguage,\n    targetLanguage: targetLanguage,\n    numCards: numCards,\n    level: formData['Difficulty Level'],\n    imageStyle: formData['Image Style'],\n    generateReverse: formData['Generate Reverse Cards?'].includes('Yes'),\n    deckId: deckId,\n    targetVoiceId: voiceMap[targetLanguage] || 'pNInz6obpgDQGcFmaJgB',\n    nativeVoiceId: voiceMap[nativeLanguage] || '21m00Tcm4TlvDq8ikWAM',\n    createdAt: new Date().toISOString()\n  }\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "76474563-46b2-47cc-ab80-9c7f362257ef",
        "name": "Generate Flashcards (GPT-4)",
        "type": "n8n-nodes-base.httpRequest",
        "position": [
          3456,
          528
        ],
        "parameters": {
          "url": "https://api.openai.com/v1/chat/completions",
          "method": "POST",
          "options": {},
          "jsonBody": "={\n  \"model\": \"gpt-4o\",\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a language learning expert creating flashcards. Generate vocabulary appropriate for the specified level. Include accurate translations, readings/pronunciations, and natural example sentences. Make image prompts simple and visual.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Create {{ $json.numCards }} flashcards for learning {{ $json.targetLanguage }}.\\n\\nTOPIC: {{ $json.topic }}\\nLEVEL: {{ $json.level }}\\nTRANSLATIONS IN: {{ $json.nativeLanguage }}\\n\\nRULES:\\n1. Generate exactly {{ $json.numCards }} vocabulary words related to the topic\\n2. Words should match the {{ $json.level }} difficulty level\\n3. Include reading/pronunciation for non-Latin scripts\\n4. Include one natural example sentence using the word\\n5. For Japanese: include kanji, hiragana reading, and romaji\\n6. For Chinese: include hanzi and pinyin with tones\\n7. For Korean: include hangul and romanization\\n8. For Arabic/Russian: include romanization\\n9. Make image prompts simple, visual, single-object focused\\n10. Notes should include usage tips or cultural context\"\n    }\n  ],\n  \"response_format\": {\n    \"type\": \"json_schema\",\n    \"json_schema\": {\n      \"name\": \"flashcard_deck\",\n      \"strict\": true,\n      \"schema\": {\n        \"type\": \"object\",\n        \"additionalProperties\": false,\n        \"properties\": {\n          \"deckName\": { \"type\": \"string\" },\n          \"targetLanguage\": { \"type\": \"string\" },\n          \"nativeLanguage\": { \"type\": \"string\" },\n          \"level\": { \"type\": \"string\" },\n          \"totalCards\": { \"type\": \"number\" },\n          \"cards\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"additionalProperties\": false,\n              \"properties\": {\n                \"id\": { \"type\": \"number\" },\n                \"word\": { \"type\": \"string\" },\n                \"reading\": { \"type\": \"string\" },\n                \"romanization\": { \"type\": \"string\" },\n                \"translation\": { \"type\": \"string\" },\n                \"partOfSpeech\": { \"type\": \"string\" },\n                \"example\": { \"type\": \"string\" },\n                \"exampleReading\": { \"type\": \"string\" },\n                \"exampleTranslation\": { \"type\": \"string\" },\n                \"imagePrompt\": { \"type\": \"string\" },\n                \"notes\": { \"type\": \"string\" }\n              },\n              \"required\": [\"id\", \"word\", \"reading\", \"romanization\", \"translation\", \"partOfSpeech\", \"example\", \"exampleReading\", \"exampleTranslation\", \"imagePrompt\", \"notes\"]\n            }\n          }\n        },\n        \"required\": [\"deckName\", \"targetLanguage\", \"nativeLanguage\", \"level\", \"totalCards\", \"cards\"]\n      }\n    }\n  }\n}",
          "sendBody": true,
          "specifyBody": "json",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "openAiApi"
        },
        "typeVersion": 4.3
      },
      {
        "id": "9e91a9c7-5ae2-4d18-8e9f-1e68e7745c9a",
        "name": "Prepare Card Data",
        "type": "n8n-nodes-base.code",
        "position": [
          3696,
          528
        ],
        "parameters": {
          "jsCode": "// ============================================\n// PREPARE CARD DATA FOR MEDIA GENERATION\n// ============================================\n\nconst response = $input.first().json;\nconst formData = $('Validate Input').first().json;\n\nconst deck = JSON.parse(response.choices[0].message.content);\n\nfunction sanitize(text) {\n  if (!text) return '';\n  return text\n    .replace(/\\\\/g, '\\\\\\\\')\n    .replace(/\"/g, \"'\")\n    .replace(/\\n/g, ' ')\n    .replace(/[\\x00-\\x1F\\x7F]/g, '')\n    .trim();\n}\n\nconst preparedCards = deck.cards.map((card, index) => {\n  const dallePrompt = `${formData.imageStyle}, single object illustration representing \"${sanitize(card.translation)}\": ${sanitize(card.imagePrompt)}. Clean white background, centered, no text, simple and memorable, high quality.`;\n  \n  return {\n    ...card,\n    cardIndex: index,\n    dallePrompt: dallePrompt.substring(0, 4000),\n    imageBase64: null,\n    wordAudioBase64: null,\n    exampleAudioBase64: null\n  };\n});\n\nreturn {\n  json: {\n    deck: { ...deck, cards: preparedCards },\n    formData: formData,\n    deckId: formData.deckId,\n    email: formData.email,\n    targetVoiceId: formData.targetVoiceId,\n    nativeVoiceId: formData.nativeVoiceId,\n    generateReverse: formData.generateReverse,\n    totalCards: preparedCards.length,\n    createdAt: formData.createdAt\n  }\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "43902eff-338b-482e-9768-f0f3af9dcad5",
        "name": "Split Into Cards",
        "type": "n8n-nodes-base.code",
        "position": [
          3920,
          528
        ],
        "parameters": {
          "jsCode": "// Split cards into individual items for loop processing\nconst data = $input.first().json;\nconst cards = data.deck.cards;\n\nreturn cards.map((card, index) => ({\n  json: {\n    card: card,\n    cardIndex: index,\n    totalCards: data.totalCards,\n    formData: data.formData,\n    deckId: data.deckId,\n    email: data.email,\n    targetVoiceId: data.targetVoiceId,\n    nativeVoiceId: data.nativeVoiceId,\n    generateReverse: data.generateReverse,\n    deckName: data.deck.deckName,\n    targetLanguage: data.deck.targetLanguage,\n    nativeLanguage: data.deck.nativeLanguage,\n    level: data.deck.level,\n    createdAt: data.createdAt\n  }\n}));"
        },
        "typeVersion": 2
      },
      {
        "id": "e510b934-d43c-437a-ae9a-65e0bf1bc19d",
        "name": "Loop Cards",
        "type": "n8n-nodes-base.splitInBatches",
        "position": [
          4112,
          528
        ],
        "parameters": {
          "options": {}
        },
        "typeVersion": 3
      },
      {
        "id": "76f7a287-293d-4acf-b72d-626107721e25",
        "name": "Generate Image (DALL-E)",
        "type": "n8n-nodes-base.code",
        "position": [
          4304,
          528
        ],
        "parameters": {
          "jsCode": "// ============================================\n// GENERATE IMAGE WITH DALL-E 3\n// ============================================\n// \u26a0\ufe0f UPDATE THIS API KEY\nconst OPENAI_API_KEY = 'YOUR_OPENAI_API_KEY';\n// ============================================\n\nconst data = $input.first().json;\nconst card = data.card;\n\ntry {\n  const response = await fetch('https://api.openai.com/v1/images/generations', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${OPENAI_API_KEY}`,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({\n      model: 'dall-e-3',\n      prompt: card.dallePrompt,\n      n: 1,\n      size: '1024x1024',\n      quality: 'standard',\n      response_format: 'b64_json'\n    })\n  });\n  \n  const result = await response.json();\n  \n  if (result.error) {\n    return {\n      json: {\n        ...data,\n        card: {\n          ...card,\n          imageBase64: null,\n          imageError: result.error.message\n        }\n      }\n    };\n  }\n  \n  return {\n    json: {\n      ...data,\n      card: {\n        ...card,\n        imageBase64: result.data[0].b64_json,\n        imageError: null\n      }\n    }\n  };\n  \n} catch (error) {\n  return {\n    json: {\n      ...data,\n      card: {\n        ...card,\n        imageBase64: null,\n        imageError: error.message\n      }\n    }\n  };\n}"
        },
        "typeVersion": 2
      },
      {
        "id": "a510fc9a-b789-464d-aee3-ea45d697c94e",
        "name": "TTS Word (ElevenLabs)",
        "type": "@elevenlabs/n8n-nodes-elevenlabs.elevenLabs",
        "disabled": true,
        "position": [
          4528,
          528
        ],
        "parameters": {
          "resource": "textToSpeech",
          "requestOptions": {}
        },
        "typeVersion": 1
      },
      {
        "id": "0acec74f-6cee-47a4-b975-e44a3dd4b004",
        "name": "Convert Word Audio",
        "type": "n8n-nodes-base.code",
        "position": [
          4752,
          528
        ],
        "parameters": {
          "jsCode": "// Convert binary audio to base64 and merge with card data\nconst inputData = $('Generate Image (DALL-E)').first().json;\nconst binaryData = $input.first().binary;\n\nlet wordAudioBase64 = null;\nlet wordAudioError = null;\n\ntry {\n  if (binaryData && binaryData.data) {\n    wordAudioBase64 = binaryData.data.data;\n  }\n} catch (error) {\n  wordAudioError = error.message;\n}\n\nreturn {\n  json: {\n    ...inputData,\n    card: {\n      ...inputData.card,\n      wordAudioBase64: wordAudioBase64,\n      wordAudioError: wordAudioError\n    }\n  }\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "3bde1f87-a0c2-47ac-8f65-9a9070fbd510",
        "name": "TTS Example (ElevenLabs)",
        "type": "@elevenlabs/n8n-nodes-elevenlabs.elevenLabs",
        "position": [
          4976,
          528
        ],
        "parameters": {
          "resource": "textToSpeech",
          "requestOptions": {}
        },
        "typeVersion": 1
      },
      {
        "id": "d843350a-85f7-4aa3-a075-7d0645229405",
        "name": "Convert Example Audio",
        "type": "n8n-nodes-base.code",
        "position": [
          5200,
          528
        ],
        "parameters": {
          "jsCode": "// Convert binary audio to base64 and merge with card data\nconst inputData = $('Convert Word Audio').first().json;\nconst binaryData = $input.first().binary;\n\nlet exampleAudioBase64 = null;\nlet exampleAudioError = null;\n\ntry {\n  if (binaryData && binaryData.data) {\n    exampleAudioBase64 = binaryData.data.data;\n  }\n} catch (error) {\n  exampleAudioError = error.message;\n}\n\nreturn {\n  json: {\n    ...inputData,\n    card: {\n      ...inputData.card,\n      exampleAudioBase64: exampleAudioBase64,\n      exampleAudioError: exampleAudioError\n    }\n  }\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "5b522be8-d8f1-46fe-b973-6354409946dd",
        "name": "Merge Card Data",
        "type": "n8n-nodes-base.code",
        "position": [
          5424,
          528
        ],
        "parameters": {
          "jsCode": "// Pass through to next iteration or complete\nconst data = $input.first().json;\nreturn { json: data };"
        },
        "typeVersion": 2
      },
      {
        "id": "5ef440aa-10f0-42c7-aa07-e2508af4b35c",
        "name": "Create APKG Data",
        "type": "n8n-nodes-base.code",
        "position": [
          5648,
          528
        ],
        "parameters": {
          "jsCode": "// ============================================\n// CREATE ANKI .APKG FILE\n// ============================================\n\nconst allItems = $input.all();\nconst firstItem = allItems[0].json;\n\nconst processedCards = allItems.map(item => item.json.card);\n\nconst deckId = firstItem.deckId;\nconst deckName = firstItem.deckName;\nconst targetLanguage = firstItem.targetLanguage;\nconst nativeLanguage = firstItem.nativeLanguage;\nconst level = firstItem.level;\nconst email = firstItem.email;\nconst generateReverse = firstItem.generateReverse;\nconst createdAt = firstItem.createdAt;\nconst formData = firstItem.formData;\n\nconst now = Date.now();\nconst modelId = now;\nconst deckIdNum = now + 1;\n\nconst mediaFiles = {};\nconst mediaMapping = {};\nlet mediaIndex = 0;\n\nprocessedCards.forEach((card, cardIdx) => {\n  if (card.imageBase64) {\n    const imgName = `img_${cardIdx}.png`;\n    mediaMapping[mediaIndex.toString()] = imgName;\n    mediaFiles[mediaIndex.toString()] = {\n      name: imgName,\n      data: card.imageBase64,\n      type: 'image'\n    };\n    card.imageFileName = imgName;\n    mediaIndex++;\n  }\n  \n  if (card.wordAudioBase64) {\n    const audioName = `word_${cardIdx}.mp3`;\n    mediaMapping[mediaIndex.toString()] = audioName;\n    mediaFiles[mediaIndex.toString()] = {\n      name: audioName,\n      data: card.wordAudioBase64,\n      type: 'audio'\n    };\n    card.wordAudioFileName = audioName;\n    mediaIndex++;\n  }\n  \n  if (card.exampleAudioBase64) {\n    const audioName = `example_${cardIdx}.mp3`;\n    mediaMapping[mediaIndex.toString()] = audioName;\n    mediaFiles[mediaIndex.toString()] = {\n      name: audioName,\n      data: card.exampleAudioBase64,\n      type: 'audio'\n    };\n    card.exampleAudioFileName = audioName;\n    mediaIndex++;\n  }\n});\n\nconst cardCss = `\n.card {\n  font-family: 'Hiragino Sans', 'Meiryo', 'Noto Sans JP', Arial, sans-serif;\n  font-size: 24px;\n  text-align: center;\n  color: #333;\n  background-color: #fafafa;\n  padding: 20px;\n}\n.word {\n  font-size: 48px;\n  font-weight: bold;\n  color: #2563eb;\n  margin: 20px 0;\n}\n.reading {\n  font-size: 24px;\n  color: #666;\n  margin: 10px 0;\n}\n.translation {\n  font-size: 28px;\n  color: #059669;\n  margin: 15px 0;\n}\n.example {\n  font-size: 20px;\n  color: #444;\n  margin: 15px 0;\n  padding: 15px;\n  background: #f0f9ff;\n  border-radius: 8px;\n}\n.example-translation {\n  font-size: 16px;\n  color: #666;\n  font-style: italic;\n}\n.pos {\n  font-size: 14px;\n  color: #888;\n  text-transform: uppercase;\n}\n.notes {\n  font-size: 14px;\n  color: #666;\n  margin-top: 15px;\n  padding: 10px;\n  background: #fff3cd;\n  border-radius: 5px;\n}\n.image-container {\n  margin: 15px 0;\n}\n.image-container img {\n  max-width: 300px;\n  max-height: 300px;\n  border-radius: 10px;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n}\n.audio-btn {\n  margin: 10px 5px;\n}\n`;\n\nconst qfmt = `\n<div class=\"card\">\n  <div class=\"image-container\">{{Image}}</div>\n  <div class=\"word\">{{Word}}</div>\n  <div class=\"reading\">{{Reading}}</div>\n  <div class=\"audio-btn\">\ud83d\udd0a {{WordAudio}}</div>\n</div>\n`;\n\nconst afmt = `\n<div class=\"card\">\n  <div class=\"image-container\">{{Image}}</div>\n  <div class=\"word\">{{Word}}</div>\n  <div class=\"reading\">{{Reading}}</div>\n  <div class=\"audio-btn\">\ud83d\udd0a {{WordAudio}}</div>\n  <hr>\n  <div class=\"pos\">{{PartOfSpeech}}</div>\n  <div class=\"translation\">{{Translation}}</div>\n  <div class=\"example\">\n    <div>{{Example}}</div>\n    <div class=\"reading\">{{ExampleReading}}</div>\n    <div class=\"audio-btn\">\ud83d\udd0a {{ExampleAudio}}</div>\n    <div class=\"example-translation\">{{ExampleTranslation}}</div>\n  </div>\n  <div class=\"notes\">{{Notes}}</div>\n</div>\n`;\n\nconst qfmtReverse = `\n<div class=\"card\">\n  <div class=\"image-container\">{{Image}}</div>\n  <div class=\"translation\" style=\"font-size: 36px;\">{{Translation}}</div>\n  <div class=\"pos\">{{PartOfSpeech}}</div>\n</div>\n`;\n\nconst afmtReverse = `\n<div class=\"card\">\n  <div class=\"image-container\">{{Image}}</div>\n  <div class=\"translation\" style=\"font-size: 36px;\">{{Translation}}</div>\n  <hr>\n  <div class=\"word\">{{Word}}</div>\n  <div class=\"reading\">{{Reading}}</div>\n  <div class=\"audio-btn\">\ud83d\udd0a {{WordAudio}}</div>\n  <div class=\"example\">\n    <div>{{Example}}</div>\n    <div class=\"reading\">{{ExampleReading}}</div>\n    <div class=\"audio-btn\">\ud83d\udd0a {{ExampleAudio}}</div>\n    <div class=\"example-translation\">{{ExampleTranslation}}</div>\n  </div>\n  <div class=\"notes\">{{Notes}}</div>\n</div>\n`;\n\nconst templates = [\n  {\n    name: 'Card 1 (Recognition)',\n    qfmt: qfmt,\n    afmt: afmt,\n    ord: 0\n  }\n];\n\nif (generateReverse) {\n  templates.push({\n    name: 'Card 2 (Production)',\n    qfmt: qfmtReverse,\n    afmt: afmtReverse,\n    ord: 1\n  });\n}\n\nconst model = {\n  id: modelId,\n  name: `${deckName} - Vocabulary`,\n  type: 0,\n  mod: Math.floor(now / 1000),\n  usn: -1,\n  sortf: 0,\n  did: deckIdNum,\n  tmpls: templates,\n  flds: [\n    { name: 'Word', ord: 0, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'Reading', ord: 1, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'Translation', ord: 2, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'PartOfSpeech', ord: 3, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'Example', ord: 4, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'ExampleReading', ord: 5, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'ExampleTranslation', ord: 6, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'Image', ord: 7, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'WordAudio', ord: 8, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'ExampleAudio', ord: 9, sticky: false, rtl: false, font: 'Arial', size: 20 },\n    { name: 'Notes', ord: 10, sticky: false, rtl: false, font: 'Arial', size: 20 }\n  ],\n  css: cardCss,\n  latexPre: '',\n  latexPost: '',\n  latexsvg: false,\n  req: [[0, 'any', [0]]]\n};\n\nconst deck = {\n  id: deckIdNum,\n  name: deckName,\n  mod: Math.floor(now / 1000),\n  usn: -1,\n  lrnToday: [0, 0],\n  revToday: [0, 0],\n  newToday: [0, 0],\n  timeToday: [0, 0],\n  collapsed: false,\n  desc: `${targetLanguage} vocabulary - ${level}\\nGenerated by AI Flashcard Generator Pro`\n};\n\nconst notes = [];\nconst cards = [];\n\nprocessedCards.forEach((card, idx) => {\n  const noteId = now + idx + 100;\n  const noteTime = Math.floor(now / 1000) + idx;\n  \n  const imageHtml = card.imageFileName ? `<img src=\"${card.imageFileName}\">` : '';\n  const wordAudioHtml = card.wordAudioFileName ? `[sound:${card.wordAudioFileName}]` : '';\n  const exampleAudioHtml = card.exampleAudioFileName ? `[sound:${card.exampleAudioFileName}]` : '';\n  \n  const fields = [\n    card.word || '',\n    card.reading || card.romanization || '',\n    card.translation || '',\n    card.partOfSpeech || '',\n    card.example || '',\n    card.exampleReading || '',\n    card.exampleTranslation || '',\n    imageHtml,\n    wordAudioHtml,\n    exampleAudioHtml,\n    card.notes || ''\n  ];\n  \n  notes.push({\n    id: noteId,\n    guid: `note_${idx}_${now}`,\n    mid: modelId,\n    mod: noteTime,\n    usn: -1,\n    tags: `${targetLanguage} ${level}`.replace(/[()]/g, '').replace(/\\s+/g, '_'),\n    flds: fields.join('\\x1f'),\n    sfld: card.word || '',\n    csum: 0,\n    flags: 0,\n    data: ''\n  });\n  \n  const numTemplates = generateReverse ? 2 : 1;\n  for (let t = 0; t < numTemplates; t++) {\n    cards.push({\n      id: now + idx * 10 + t + 1000,\n      nid: noteId,\n      did: deckIdNum,\n      ord: t,\n      mod: noteTime,\n      usn: -1,\n      type: 0,\n      queue: 0,\n      due: idx + 1,\n      ivl: 0,\n      factor: 2500,\n      reps: 0,\n      lapses: 0,\n      left: 0,\n      odue: 0,\n      odid: 0,\n      flags: 0,\n      data: ''\n    });\n  }\n});\n\nconst conf = {\n  activeDecks: [deckIdNum],\n  curDeck: deckIdNum,\n  newSpread: 0,\n  collapseTime: 1200,\n  timeLim: 0,\n  estTimes: true,\n  dueCounts: true,\n  curModel: modelId,\n  nextPos: processedCards.length + 1,\n  sortType: 'noteFld',\n  sortBackwards: false,\n  addToCur: true\n};\n\nconst dconf = {\n  1: {\n    id: 1,\n    name: 'Default',\n    mod: 0,\n    usn: 0,\n    maxTaken: 60,\n    autoplay: true,\n    timer: 0,\n    replayq: true,\n    new: {\n      bury: true,\n      delays: [1, 10],\n      initialFactor: 2500,\n      ints: [1, 4, 7],\n      order: 1,\n      perDay: 20\n    },\n    rev: {\n      bury: true,\n      ease4: 1.3,\n      fuzz: 0.05,\n      ivlFct: 1,\n      maxIvl: 36500,\n      perDay: 200\n    },\n    lapse: {\n      delays: [10],\n      leechAction: 0,\n      leechFails: 8,\n      minInt: 1,\n      mult: 0\n    }\n  }\n};\n\nreturn {\n  json: {\n    apkgData: {\n      deckName: deckName,\n      deckId: deckIdNum,\n      modelId: modelId,\n      model: model,\n      deck: deck,\n      notes: notes,\n      cards: cards,\n      conf: conf,\n      dconf: dconf,\n      mediaMapping: mediaMapping,\n      mediaFiles: mediaFiles\n    },\n    processedCards: processedCards,\n    email: email,\n    deckName: deckName,\n    targetLanguage: targetLanguage,\n    nativeLanguage: nativeLanguage,\n    level: level,\n    formData: formData,\n    createdAt: createdAt,\n    totalCards: processedCards.length,\n    generateReverse: generateReverse\n  }\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "1a4ea429-a19b-46e7-95a5-ccc4d1b9a73b",
        "name": "Build APKG ZIP",
        "type": "n8n-nodes-base.code",
        "position": [
          5872,
          528
        ],
        "parameters": {
          "jsCode": "// ============================================\n// BUILD FINAL .APKG FILE (ZIP)\n// ============================================\n\nconst JSZip = require('jszip');\nconst initSqlJs = require('sql.js');\n\nconst data = $input.first().json;\nconst apkg = data.apkgData;\n\nconst SQL = await initSqlJs();\nconst db = new SQL.Database();\n\ndb.run(`\n  CREATE TABLE col (\n    id INTEGER PRIMARY KEY,\n    crt INTEGER NOT NULL,\n    mod INTEGER NOT NULL,\n    scm INTEGER NOT NULL,\n    ver INTEGER NOT NULL,\n    dty INTEGER NOT NULL,\n    usn INTEGER NOT NULL,\n    ls INTEGER NOT NULL,\n    conf TEXT NOT NULL,\n    models TEXT NOT NULL,\n    decks TEXT NOT NULL,\n    dconf TEXT NOT NULL,\n    tags TEXT NOT NULL\n  )\n`);\n\ndb.run(`\n  CREATE TABLE notes (\n    id INTEGER PRIMARY KEY,\n    guid TEXT NOT NULL,\n    mid INTEGER NOT NULL,\n    mod INTEGER NOT NULL,\n    usn INTEGER NOT NULL,\n    tags TEXT NOT NULL,\n    flds TEXT NOT NULL,\n    sfld TEXT NOT NULL,\n    csum INTEGER NOT NULL,\n    flags INTEGER NOT NULL,\n    data TEXT NOT NULL\n  )\n`);\n\ndb.run(`\n  CREATE TABLE cards (\n    id INTEGER PRIMARY KEY,\n    nid INTEGER NOT NULL,\n    did INTEGER NOT NULL,\n    ord INTEGER NOT NULL,\n    mod INTEGER NOT NULL,\n    usn INTEGER NOT NULL,\n    type INTEGER NOT NULL,\n    queue INTEGER NOT NULL,\n    due INTEGER NOT NULL,\n    ivl INTEGER NOT NULL,\n    factor INTEGER NOT NULL,\n    reps INTEGER NOT NULL,\n    lapses INTEGER NOT NULL,\n    left INTEGER NOT NULL,\n    odue INTEGER NOT NULL,\n    odid INTEGER NOT NULL,\n    flags INTEGER NOT NULL,\n    data TEXT NOT NULL\n  )\n`);\n\ndb.run(`\n  CREATE TABLE revlog (\n    id INTEGER PRIMARY KEY,\n    cid INTEGER NOT NULL,\n    usn INTEGER NOT NULL,\n    ease INTEGER NOT NULL,\n    ivl INTEGER NOT NULL,\n    lastIvl INTEGER NOT NULL,\n    factor INTEGER NOT NULL,\n    time INTEGER NOT NULL,\n    type INTEGER NOT NULL\n  )\n`);\n\ndb.run(`CREATE TABLE graves (usn INTEGER NOT NULL, oid INTEGER NOT NULL, type INTEGER NOT NULL)`);\n\ndb.run(`CREATE INDEX ix_notes_usn ON notes (usn)`);\ndb.run(`CREATE INDEX ix_cards_usn ON cards (usn)`);\ndb.run(`CREATE INDEX ix_cards_nid ON cards (nid)`);\ndb.run(`CREATE INDEX ix_cards_sched ON cards (did, queue, due)`);\ndb.run(`CREATE INDEX ix_revlog_usn ON revlog (usn)`);\ndb.run(`CREATE INDEX ix_revlog_cid ON revlog (cid)`);\ndb.run(`CREATE INDEX ix_notes_csum ON notes (csum)`);\n\nconst now = Date.now();\nconst models = {};\nmodels[apkg.modelId.toString()] = apkg.model;\n\nconst decks = {\n  '1': {\n    id: 1,\n    name: 'Default',\n    mod: Math.floor(now / 1000),\n    usn: -1,\n    lrnToday: [0, 0],\n    revToday: [0, 0],\n    newToday: [0, 0],\n    timeToday: [0, 0],\n    collapsed: false,\n    desc: ''\n  }\n};\ndecks[apkg.deckId.toString()] = apkg.deck;\n\ndb.run(`\n  INSERT INTO col VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n`, [\n  1,\n  Math.floor(now / 1000),\n  now,\n  now,\n  11,\n  0,\n  -1,\n  0,\n  JSON.stringify(apkg.conf),\n  JSON.stringify(models),\n  JSON.stringify(decks),\n  JSON.stringify(apkg.dconf),\n  '{}'\n]);\n\nfor (const note of apkg.notes) {\n  db.run(`\n    INSERT INTO notes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n  `, [\n    note.id,\n    note.guid,\n    note.mid,\n    note.mod,\n    note.usn,\n    note.tags,\n    note.flds,\n    note.sfld,\n    note.csum,\n    note.flags,\n    note.data\n  ]);\n}\n\nfor (const card of apkg.cards) {\n  db.run(`\n    INSERT INTO cards VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n  `, [\n    card.id,\n    card.nid,\n    card.did,\n    card.ord,\n    card.mod,\n    card.usn,\n    card.type,\n    card.queue,\n    card.due,\n    card.ivl,\n    card.factor,\n    card.reps,\n    card.lapses,\n    card.left,\n    card.odue,\n    card.odid,\n    card.flags,\n    card.data\n  ]);\n}\n\nconst dbData = db.export();\nconst dbBuffer = Buffer.from(dbData);\n\nconst zip = new JSZip();\n\nzip.file('collection.anki21', dbBuffer, { compression: 'DEFLATE' });\nzip.file('media', JSON.stringify(apkg.mediaMapping));\n\nfor (const [index, mediaInfo] of Object.entries(apkg.mediaFiles)) {\n  const buffer = Buffer.from(mediaInfo.data, 'base64');\n  zip.file(index, buffer);\n}\n\nconst zipBuffer = await zip.generateAsync({ \n  type: 'nodebuffer',\n  compression: 'DEFLATE',\n  compressionOptions: { level: 6 }\n});\n\nconst apkgBase64 = zipBuffer.toString('base64');\n\nconst sheetRows = data.processedCards.map((card, idx) => ({\n  word: card.word || '',\n  reading: card.reading || '',\n  romanization: card.romanization || '',\n  translation: card.translation || '',\n  part_of_speech: card.partOfSpeech || '',\n  example: card.example || '',\n  example_translation: card.exampleTranslation || '',\n  has_image: card.imageBase64 ? 'Yes' : 'No',\n  has_word_audio: card.wordAudioBase64 ? 'Yes' : 'No',\n  has_example_audio: card.exampleAudioBase64 ? 'Yes' : 'No',\n  notes: card.notes || '',\n  level: data.level,\n  target_language: data.targetLanguage,\n  native_language: data.nativeLanguage,\n  deck_name: data.deckName,\n  created_at: data.createdAt\n}));\n\nreturn {\n  json: {\n    apkgBase64: apkgBase64,\n    apkgFileName: `${data.deckName.replace(/[^a-zA-Z0-9]/g, '_')}.apkg`,\n    email: data.email,\n    deckName: data.deckName,\n    targetLanguage: data.targetLanguage,\n    nativeLanguage: data.nativeLanguage,\n    level: data.level,\n    totalCards: data.totalCards,\n    generateReverse: data.generateReverse,\n    sheetRows: sheetRows,\n    createdAt: data.createdAt\n  }\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "aa5c1bb6-80ea-4dd6-a65f-c9904721939c",
        "name": "Split For Sheets",
        "type": "n8n-nodes-base.code",
        "position": [
          6096,
          528
        ],
        "parameters": {
          "jsCode": "// Split rows for Google Sheets\nconst data = $input.first().json;\nconst rows = data.sheetRows;\n\nreturn rows.map(row => ({\n  json: {\n    ...row,\n    _apkgData: {\n      apkgBase64: data.apkgBase64,\n      apkgFileName: data.apkgFileName,\n      email: data.email,\n      deckName: data.deckName,\n      targetLanguage: data.targetLanguage,\n      nativeLanguage: data.nativeLanguage,\n      level: data.level,\n      totalCards: data.totalCards,\n      generateReverse: data.generateReverse\n    }\n  }\n}));"
        },
        "typeVersion": 2
      },
      {
        "id": "3b61bb94-7d50-45ab-9e21-be098a3aab3c",
        "name": "Save to Google Sheets",
        "type": "n8n-nodes-base.googleSheets",
        "position": [
          6320,
          528
        ],
        "parameters": {
          "columns": {
            "value": {
              "word": "={{ $json.word }}",
              "level": "={{ $json.level }}",
              "notes": "={{ $json.notes }}",
              "example": "={{ $json.example }}",
              "reading": "={{ $json.reading }}",
              "deck_name": "={{ $json.deck_name }}",
              "has_image": "={{ $json.has_image }}",
              "created_at": "={{ $json.created_at }}",
              "translation": "={{ $json.translation }}",
              "romanization": "={{ $json.romanization }}",
              "has_word_audio": "={{ $json.has_word_audio }}",
              "part_of_speech": "={{ $json.part_of_speech }}",
              "native_language": "={{ $json.native_language }}",
              "target_language": "={{ $json.target_language }}",
              "has_example_audio": "={{ $json.has_example_audio }}",
              "example_translation": "={{ $json.example_translation }}"
            },
            "schema": [
              {
                "id": "word",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "word",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "reading",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "reading",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "romanization",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "romanization",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "translation",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "translation",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "part_of_speech",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "part_of_speech",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "example",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "example",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "example_translation",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "example_translation",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "has_image",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "has_image",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "has_word_audio",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "has_word_audio",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "has_example_audio",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "has_example_audio",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "notes",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "notes",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "level",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "level",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "target_language",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "target_language",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "native_language",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "native_language",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "deck_name",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "deck_name",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              },
              {
                "id": "created_at",
                "type": "string",
                "display": true,
                "required": false,
                "displayName": "created_at",
                "defaultMatch": false,
                "canBeUsedToMatch": true
              }
            ],
            "mappingMode": "defineBelow",
            "matchingColumns": []
          },
          "options": {},
          "operation": "append",
          "sheetName": {
            "__rl": true,
            "mode": "name",
            "value": "Flashcards"
          },
          "documentId": {
            "__rl": true,
            "mode": "id",
            "value": "YOUR_SPREADSHEET_ID"
          }
        },
        "typeVersion": 4.4
      },
      {
        "id": "20d32ede-3764-475a-b468-eef004e9d762",
        "name": "Aggregate Results",
        "type": "n8n-nodes-base.code",
        "position": [
          6544,
          528
        ],
        "parameters": {
          "jsCode": "// Aggregate results after sheets save\nconst allItems = $input.all();\nconst firstItem = allItems[0].json._apkgData;\n\nreturn {\n  json: {\n    ...firstItem,\n    savedToSheets: allItems.length\n  }\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "a83639cb-4a9b-4410-a11d-a20e5293db93",
        "name": "Prepare Attachment",
        "type": "n8n-nodes-base.code",
        "position": [
          6768,
          528
        ],
        "parameters": {
          "jsCode": "// Prepare binary data for email attachment\nconst data = $input.first().json;\n\nconst binaryData = {\n  data: {\n    data: data.apkgBase64,\n    mimeType: 'application/octet-stream',\n    fileName: data.apkgFileName\n  }\n};\n\nreturn {\n  json: data,\n  binary: binaryData\n};"
        },
        "typeVersion": 2
      },
      {
        "id": "8238fa0c-8bdf-4420-9013-da5168d92d54",
        "name": "Send Email (Gmail)",
        "type": "n8n-nodes-base.gmail",
        "position": [
          6992,
          528
        ],
        "webhookId": "bf785152-a9cc-4d3f-bf28-193c1e655acd",
        "parameters": {
          "sendTo": "={{ $json.email }}",
          "message": "=<div style=\"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;\">\n  <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 10px 10px 0 0; text-align: center;\">\n    <h1 style=\"color: white; margin: 0;\">\ud83c\udfb4 Your Flashcards Are Ready!</h1>\n  </div>\n  \n  <div style=\"background: #f8f9fa; padding: 30px; border-radius: 0 0 10px 10px;\">\n    <h2 style=\"color: #333;\">{{ $json.deckName }}</h2>\n    \n    <div style=\"background: white; padding: 20px; border-radius: 8px; margin: 20px 0;\">\n      <p><strong>\ud83d\udcda Language:</strong> {{ $json.targetLanguage }}</p>\n      <p><strong>\ud83c\udfaf Level:</strong> {{ $json.level }}</p>\n      <p><strong>\ud83d\udcca Total Cards:</strong> {{ $json.totalCards }}{{ $json.generateReverse ? ' (+ reverse cards)' : '' }}</p>\n      <p><strong>\ud83d\uddbc\ufe0f Images:</strong> AI-generated for each word</p>\n      <p><strong>\ud83d\udd0a Audio:</strong> Native pronunciation included</p>\n    </div>\n    \n    <h3 style=\"color: #333;\">\ud83d\udce5 How to Import to Anki:</h3>\n    <ol style=\"color: #555;\">\n      <li>Download the attached .apkg file</li>\n      <li>Open Anki on your computer or mobile device</li>\n      <li>Go to <strong>File \u2192 Import</strong></li>\n      <li>Select the downloaded .apkg file</li>\n      <li>Start learning! \ud83c\udf89</li>\n    </ol>\n    \n    <div style=\"background: #e8f5e9; padding: 15px; border-radius: 8px; margin-top: 20px;\">\n      <p style=\"margin: 0; color: #2e7d32;\">\ud83d\udca1 <strong>Tip:</strong> Use Anki's spaced repetition to maximize retention. Study a little bit every day!</p>\n    </div>\n    \n    <hr style=\"border: none; border-top: 1px solid #ddd; margin: 30px 0;\">\n    \n    <p style=\"color: #888; font-size: 12px; text-align: center;\">\n      Generated by AI Flashcard Generator Pro<br>\n      Powered by GPT-4, DALL-E 3, and ElevenLabs\n    </p>\n  </div>\n</div>",
          "options": {
            "attachmentsUi": {
              "attachmentsBinary": [
                {}
              ]
            }
          },
          "subject": "=\ud83c\udfb4 Your Flashcards Are Ready: {{ $json.deckName }}"
        },
        "typeVersion": 2.1
      },
      {
        "id": "80d8e0da-664f-4063-876a-c22e7f14dd6f",
        "name": "Return Response",
        "type": "n8n-nodes-base.respondToWebhook",
        "position": [
          7216,
          528
        ],
        "parameters": {
          "options": {
            "responseHeaders": {
              "entries": [
                {
                  "name": "Content-Type",
                  "value": "application/json"
                }
              ]
            }
          },
          "respondWith": "json",
          "responseBody": "={\n  \"success\": true,\n  \"message\": \"Flashcards created and sent to {{ $json.email }}\",\n  \"deck\": {\n    \"name\": \"{{ $json.deckName }}\",\n    \"targetLanguage\": \"{{ $json.targetLanguage }}\",\n    \"nativeLanguage\": \"{{ $json.nativeLanguage }}\",\n    \"level\": \"{{ $json.level }}\",\n    \"totalCards\": {{ $json.totalCards }},\n    \"includesReverse\": {{ $json.generateReverse }},\n    \"features\": {\n      \"images\": true,\n      \"wordAudio\": true,\n      \"exampleAudio\": true\n    }\n  },\n  \"savedToSheets\": {{ $json.savedToSheets }}\n}"
        },
        "typeVersion": 1.1
      },
      {
        "id": "648d1f39-1006-44f1-938f-c9e87a6f9828",
        "name": "Sticky Note - NPM",
        "type": "n8n-nodes-base.stickyNote",
        "position": [
          5152,
          752
        ],
        "parameters": {
          "color": 3,
          "width": 320,
          "height": 200,
          "content": "## \ud83d\udce6 Required npm Packages\n\n```bash\nnpm install jszip sql.js\n```\n\nOr configure in Docker:\n```\nNODE_FUNCTION_ALLOW_EXTERNAL=jszip,sql.js\n```"
        },
        "typeVersion": 1
      }
    ],
    "pinData": {},
    "connections": {
      "Loop Cards": {
        "main": [
          [
            {
              "node": "Generate Image (DALL-E)",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Create APKG Data",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Build APKG ZIP": {
        "main": [
          [
            {
              "node": "Split For Sheets",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Flashcard Form": {
        "main": [
          [
            {
              "node": "Validate Input",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Validate Input": {
        "main": [
          [
            {
              "node": "Generate Flashcards (GPT-4)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Merge Card Data": {
        "main": [
          [
            {
              "node": "Loop Cards",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Create APKG Data": {
        "main": [
          [
            {
              "node": "Build APKG ZIP",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Split For Sheets": {
        "main": [
          [
            {
              "node": "Save to Google Sheets",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Split Into Cards": {
        "main": [
          [
            {
              "node": "Loop Cards",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Aggregate Results": {
        "main": [
          [
            {
              "node": "Prepare Attachment",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Prepare Card Data": {
        "main": [
          [
            {
              "node": "Split Into Cards",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Convert Word Audio": {
        "main": [
          [
            {
              "node": "TTS Example (ElevenLabs)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Prepare Attachment": {
        "main": [
          [
            {
              "node": "Send Email (Gmail)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Send Email (Gmail)": {
        "main": [
          [
            {
              "node": "Return Response",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Convert Example Audio": {
        "main": [
          [
            {
              "node": "Merge Card Data",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Save to Google Sheets": {
        "main": [
          [
            {
              "node": "Aggregate Results",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "TTS Word (ElevenLabs)": {
        "main": [
          [
            {
              "node": "Convert Word Audio",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Generate Image (DALL-E)": {
        "main": [
          [
            {
              "node": "TTS Word (ElevenLabs)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "TTS Example (ElevenLabs)": {
        "main": [
          [
            {
              "node": "Convert Example Audio",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Generate Flashcards (GPT-4)": {
        "main": [
          [
            {
              "node": "Prepare Card Data",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    }
  },
  "workflowInfo": {
    "nodeCount": 36,
    "nodeTypes": {
      "n8n-nodes-base.code": {
        "count": 12
      },
      "n8n-nodes-base.gmail": {
        "count": 1
      },
      "n8n-nodes-base.stickyNote": {
        "count": 16
      },
      "n8n-nodes-base.formTrigger": {
        "count": 1
      },
      "n8n-nodes-base.httpRequest": {
        "count": 1
      },
      "n8n-nodes-base.googleSheets": {
        "count": 1
      },
      "n8n-nodes-base.splitInBatches": {
        "count": 1
      },
      "n8n-nodes-base.respondToWebhook": {
        "count": 1
      },
      "@elevenlabs/n8n-nodes-elevenlabs.elevenLabs": {
        "count": 2
      }
    }
  },
  "nodes": [
    "Sticky Note - Main",
    "Sticky Note - Config",
    "Sticky Note1",
    "Sticky Note2",
    "Sticky Note3",
    "Sticky Note4",
    "Sticky Note5",
    "Sticky Note6",
    "Sticky Note7",
    "Sticky Note8",
    "Sticky Note9",
    "Sticky Note10",
    "Sticky Note11",
    "Sticky Note12",
    "Sticky Note13",
    "Flashcard Form",
    "Validate Input",
    "Generate Flashcards (GPT-4)",
    "Prepare Card Data",
    "Split Into Cards",
    "Loop Cards",
    "Generate Image (DALL-E)",
    "TTS Word (ElevenLabs)",
    "Convert Word Audio",
    "TTS Example (ElevenLabs)",
    "Convert Example Audio",
    "Merge Card Data",
    "Create APKG Data",
    "Build APKG ZIP",
    "Split For Sheets",
    "Save to Google Sheets",
    "Aggregate Results",
    "Prepare Attachment",
    "Send Email (Gmail)",
    "Return Response",
    "Sticky Note - NPM"
  ],
  "nodeTypes": [
    "n8n-nodes-base.httpRequest",
    "n8n-nodes-base.splitInBatches",
    "n8n-nodes-base.code",
    "n8n-nodes-base.gmail",
    "n8n-nodes-base.stickyNote",
    "@elevenlabs/n8n-nodes-elevenlabs.elevenLabs",
    "n8n-nodes-base.formTrigger",
    "n8n-nodes-base.googleSheets",
    "n8n-nodes-base.respondToWebhook"
  ]
}