{"id":268,"date":"2026-05-29T17:42:58","date_gmt":"2026-05-29T17:42:58","guid":{"rendered":"https:\/\/fantasticfrench.com\/?page_id=268"},"modified":"2026-06-02T23:01:14","modified_gmt":"2026-06-02T22:01:14","slug":"private-french-lessons","status":"publish","type":"page","link":"https:\/\/fantasticfrench.com\/es\/private-french-lessons\/","title":{"rendered":"Book Personalized 1:1 French Lessons"},"content":{"rendered":"\n<script>\n(function() {\n    function hideNoButton() {\n        var buttons = document.querySelectorAll('button');\n        buttons.forEach(function(btn) {\n            if (btn.textContent.trim() === 'No') {\n                btn.style.setProperty('display', 'none', 'important');\n                btn.style.setProperty('visibility', 'hidden', 'important');\n                btn.style.setProperty('width', '0', 'important');\n                btn.style.setProperty('height', '0', 'important');\n                btn.style.setProperty('overflow', 'hidden', 'important');\n                btn.style.setProperty('margin', '0', 'important');\n                btn.style.setProperty('padding', '0', 'important');\n            }\n        });\n    }\n\n    \/\/ Run every 300ms for 30 seconds to catch whenever Vue renders the popup\n    var interval = setInterval(hideNoButton, 300);\n    setTimeout(function() { clearInterval(interval); }, 30000);\n\n    \/\/ Also watch DOM changes\n    var observer = new MutationObserver(hideNoButton);\n    observer.observe(document.body, { childList: true, subtree: true });\n\n    hideNoButton();\n})();\n<\/script>\n\n\n\n<script>\n(function() {\n    function hideOnOption() {\n        \/\/ Find the \"On\" radio wrapper and hide it\n        var radios = document.querySelectorAll('.am-fs__rs-ends .el-radio');\n        radios.forEach(function(radio) {\n            var input = radio.querySelector('input[value=\"On\"]');\n            if (input) {\n                \/\/ Hide the entire \"On\" radio button\n                radio.closest('.am-radio-wrapper').style.setProperty('display', 'none', 'important');\n            }\n        });\n\n        \/\/ Hide the date picker (first option in ends-options)\n        var datePicker = document.querySelector('.am-fs__rs-ends-options .am-dp__input');\n        if (datePicker) {\n            datePicker.closest('.el-tooltip__trigger').style.setProperty('display', 'none', 'important');\n        }\n\n        \/\/ Auto-click \"After\" radio if \"On\" is currently selected\n        var afterInput = document.querySelector('.am-fs__rs-ends input[value=\"After\"]');\n        var onInput = document.querySelector('.am-fs__rs-ends input[value=\"On\"]');\n        if (afterInput && onInput && onInput.checked) {\n            \/\/ Click the After label to trigger Vue's event\n            var afterLabel = afterInput.closest('.el-radio');\n            if (afterLabel) afterLabel.click();\n        }\n    }\n\n    var interval = setInterval(hideOnOption, 300);\n    setTimeout(function() { clearInterval(interval); }, 30000);\n\n    var observer = new MutationObserver(hideOnOption);\n    observer.observe(document.body, { childList: true, subtree: true });\n\n    hideOnOption();\n})();\n<\/script>\n\n\n<p data-start=\"759\" data-end=\"812\"><strong data-start=\"759\" data-end=\"812\">\u00a0 \u00a0 \u00a0 A 50-minute private lesson tailored to your goals<\/strong><\/p>\n<ul>\n<li data-start=\"814\" data-end=\"1053\">\ud83d\ude80 Progress at your own pace with personalized lessons<\/li>\n<li data-start=\"814\" data-end=\"1053\">\ud83d\udcac Discuss topics that interest you<\/li>\n<li data-start=\"814\" data-end=\"1053\">\ud83c\uddeb\ud83c\uddf7 Learn natural vocabulary and everyday expressions<\/li>\n<li data-start=\"814\" data-end=\"1053\">\ud83d\udcdd Receive personalized feedback<\/li>\n<li data-start=\"814\" data-end=\"1053\">\ud83d\udde3\ufe0f Practice speaking French in a relaxed and supportive environment<br \/><br \/><br \/>Pick a day and time, but don&#8217;t worry. It is possible to reschedule lessons later<\/li>\n<\/ul>\n\n\n<script>\n  if (typeof hasAmeliaEntitiesApiCall === 'undefined' && ('' === '' && '' !== '1')) {\n    var hasAmeliaEntitiesApiCall = true;\n  }\n  var ameliaShortcodeData = (typeof ameliaShortcodeData === 'undefined') ? [] : ameliaShortcodeData;\n  ameliaShortcodeData.push(\n    {\n      'hasApiCall': (typeof hasAmeliaEntitiesApiCall !== 'undefined') && hasAmeliaEntitiesApiCall,\n      'trigger': '',\n      'trigger_type': '',\n      'triggered_form': 'sbsNew',\n      'in_dialog': '',\n      'show': '',\n      'counter': '1000',\n      'category': '',\n      'service': '2',\n      'employee': '',\n      'location': '',\n      'package': '',\n      'layout': '',\n    }\n  );\n\n  var ameliaShortcodeDataTriggered = (typeof ameliaShortcodeDataTriggered === 'undefined') ? [] : ameliaShortcodeDataTriggered;\n  if (ameliaShortcodeData[ameliaShortcodeData.length - 1].trigger !== '') {\n    if (ameliaShortcodeDataTriggered.filter(a => a.counter === ameliaShortcodeData[ameliaShortcodeData.length - 1].counter).length === 0) {\n      ameliaShortcodeDataTriggered.push(ameliaShortcodeData.pop());\n    } else {\n      ameliaShortcodeData.pop()\n    }\n  }\n  if (typeof hasAmeliaEntitiesApiCall !== 'undefined' && hasAmeliaEntitiesApiCall) {\n    hasAmeliaEntitiesApiCall = false;\n  }\n<\/script>\n\n<div\n  id=\"amelia-v2-booking-1000\"\n  class=\"amelia-v2-booking\"\n >\n    <step-form-wrapper><\/step-form-wrapper><\/div>\n\n\n\n\n<script>\n(function() {\n    var MIN_VALUE = 3;\n    var isRunning = false;\n    var observer;\n\n    function forceValue(input, val) {\n        var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;\n        setter.call(input, val);\n        input.setAttribute('aria-valuenow', val);\n        input.dispatchEvent(new Event('input', { bubbles: true }));\n        input.dispatchEvent(new Event('change', { bubbles: true }));\n        input.dispatchEvent(new Event('blur', { bubbles: true }));\n    }\n\n    function updateMask(input) {\n        var wrapper = input.closest('.el-input__wrapper');\n        if (!wrapper) return;\n        var mask = wrapper.querySelector('.am-visual-mask');\n        if (!mask) {\n            mask = document.createElement('div');\n            mask.className = 'am-visual-mask';\n            mask.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#FFFFFF;font-size:inherit;color:#1A2C37;pointer-events:none;z-index:10;font-family:inherit';\n            wrapper.style.position = 'relative';\n            wrapper.appendChild(mask);\n        }\n        var displayVal = (parseInt(input.value) || MIN_VALUE) + 1;\n        if (mask.textContent !== String(displayVal)) {\n            mask.textContent = String(displayVal);\n        }\n    }\n\n    function updateSummary(input) {\n        var summary = document.querySelector('.am-fs__rs-summary');\n        if (!summary) return;\n        summary.querySelectorAll('span').forEach(function(span) {\n            if (span.textContent.includes('Number of Recurrences:')) {\n                var displayVal = (parseInt(input.value) || MIN_VALUE) + 1;\n                var newText = 'Number of Recurrences:  ' + displayVal;\n                if (span.textContent !== newText) {\n                    span.textContent = newText;\n                }\n            }\n        });\n    }\n\n    function tryFix() {\n        if (isRunning) return;\n        isRunning = true;\n\n        \/\/ Disconnect observer BEFORE making any DOM changes\n        if (observer) observer.disconnect();\n\n        var allInputs = document.querySelectorAll('input[type=\"number\"]');\n        allInputs.forEach(function(input) {\n            var container = input.closest('.el-input-number');\n            if (!container) return;\n            if (!input.closest('.am-fs__rs-ends-after')) return;\n\n            \/\/ Try Vue instance\n            var vueInstance = input.__vue__ || input.__vueParentComponent;\n            if (vueInstance) {\n                try {\n                    if (vueInstance.proxy && vueInstance.proxy.modelValue !== undefined) {\n                        vueInstance.proxy.$emit('update:modelValue', MIN_VALUE);\n                    }\n                    if (vueInstance.$emit) {\n                        vueInstance.$emit('input', MIN_VALUE);\n                    }\n                } catch(e) {}\n            }\n\n            \/\/ Force minimum value\n            if (parseInt(input.value) < MIN_VALUE) {\n                forceValue(input, MIN_VALUE);\n            }\n\n            \/\/ Buttons\n            var decreaseBtn = container.querySelector('[aria-label=\"decrease number\"]');\n            var increaseBtn = container.querySelector('[aria-label=\"increase number\"]');\n\n            if (decreaseBtn) {\n                if (parseInt(input.value) <= MIN_VALUE) {\n                    decreaseBtn.classList.add('is-disabled');\n                    decreaseBtn.setAttribute('aria-disabled', 'true');\n                } else {\n                    decreaseBtn.classList.remove('is-disabled');\n                    decreaseBtn.removeAttribute('aria-disabled');\n                }\n            }\n\n            if (increaseBtn) {\n                increaseBtn.classList.remove('is-disabled');\n                increaseBtn.removeAttribute('aria-disabled');\n            }\n\n            \/\/ Visual mask\n            updateMask(input);\n\n            \/\/ Summary text\n            updateSummary(input);\n\n            \/\/ Attach button listeners once\n            if (!input.dataset.maskEnforced) {\n                input.dataset.maskEnforced = 'true';\n\n                input.addEventListener('input', function() {\n                    updateMask(this);\n                    updateSummary(this);\n                });\n\n                if (increaseBtn) {\n                    increaseBtn.addEventListener('click', function() {\n                        setTimeout(function() {\n                            updateMask(input);\n                            updateSummary(input);\n                        }, 50);\n                    });\n                }\n\n                if (decreaseBtn) {\n                    decreaseBtn.addEventListener('click', function() {\n                        setTimeout(function() {\n                            updateMask(input);\n                            updateSummary(input);\n                        }, 50);\n                    });\n                }\n            }\n        });\n\n        \/\/ Reconnect observer AFTER all DOM changes done\n        if (observer) {\n            observer.observe(document.body, { childList: true, subtree: true });\n        }\n\n        isRunning = false;\n    }\n\n    \/\/ Fast then slow interval\n    var fastInterval = setInterval(tryFix, 100);\n    setTimeout(function() {\n        clearInterval(fastInterval);\n        var slowInterval = setInterval(tryFix, 500);\n        setTimeout(function() { clearInterval(slowInterval); }, 60000);\n    }, 5000);\n\n    \/\/ Observer disconnects before DOM changes, reconnects after\n    observer = new MutationObserver(tryFix);\n    observer.observe(document.body, { childList: true, subtree: true });\n})();\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>\u00a0 \u00a0 \u00a0 A 50-minute private lesson tailored to your goals \ud83d\ude80 Progress at your own pace with personalized lessons \ud83d\udcac Discuss topics that interest you \ud83c\uddeb\ud83c\uddf7 Learn natural vocabulary and everyday expressions \ud83d\udcdd Receive personalized feedback \ud83d\udde3\ufe0f Practice speaking French in a relaxed and supportive environment Pick a day and time, but don&#8217;t worry. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-268","page","type-page","status-publish","hentry"],"_hostinger_reach_plugin_has_subscription_block":false,"_hostinger_reach_plugin_is_elementor":false,"_links":{"self":[{"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/pages\/268","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/comments?post=268"}],"version-history":[{"count":33,"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/pages\/268\/revisions"}],"predecessor-version":[{"id":398,"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/pages\/268\/revisions\/398"}],"wp:attachment":[{"href":"https:\/\/fantasticfrench.com\/es\/wp-json\/wp\/v2\/media?parent=268"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}