Skip to Content

Developer Prompt: SAM AI Funnels - Phase 2 (Core Snippets)

Developer Prompt: SAM AI Funnels - Phase 2 (Core Snippets)

Date: 2025-12-31
Phase: 2 of 7
Scope: FUNNELS tab in website builder + 15 MVP snippets
Prerequisite: Phase 1 complete and validated


CONTEXT

Phase 1 created the module foundation with 5 data models. Now we add the website builder integration - a new "FUNNELS" tab with 15 core snippets that enable all funnel types.

Architecture Document: D:\SAMAI-18-SaaS\ai_sam\ai_sam_docs\plans\2025-12-31_sam-ai-funnels-architecture.md

Odoo 18 Snippet Pattern Reference:
- Snippets defined in XML templates
- Registered via options.xml inheriting website.snippets
- Customize options use <we-*> components
- JS/SCSS in static/src/snippets/s_<name>/


GOAL

Add a FUNNELS tab to the Odoo 18 website builder sidebar with 15 draggable snippets covering all essential funnel components.


15 MVP SNIPPETS

These 15 snippets enable building ANY of the 6 funnel types:

# Snippet Category Purpose
1 hero_minimal Hero Opening headline + CTA
2 hero_full Hero Full hero with media
3 problem_agitation Problem & Story PAS framework section
4 benefits_stack Solution & Benefits Bullet benefits list
5 testimonial_single Social Proof Single testimonial quote
6 opt_in_form CTAs & Forms Email capture form
7 cta_inline CTAs & Forms Mid-page CTA
8 final_cta CTAs & Forms Bottom page CTA
9 offer_breakdown Offers & Pricing What's included list
10 price_reveal Offers & Pricing Price with anchor
11 guarantee Offers & Pricing Risk reversal badge
12 countdown_timer Urgency & Trust Deadline timer
13 objection_handler Urgency & Trust FAQ accordion
14 spacer Utility Vertical spacing
15 video_embed Utility Video player

DELIVERABLES

1. Update manifest.py

Add snippet assets to the manifest:

{
    'name': 'SAM AI Funnels',
    'version': '18.0.1.0.0',
    # ... existing fields ...
    'data': [
        # Security
        'security/ir.model.access.csv',

        # Views
        'views/funnel_definition_views.xml',
        'views/funnel_page_views.xml',
        'views/funnel_template_views.xml',
        'views/funnel_snippet_views.xml',
        'views/funnel_menus.xml',

        # Snippets (NEW)
        'views/snippets/options.xml',
        'views/snippets/s_hero_minimal.xml',
        'views/snippets/s_hero_full.xml',
        'views/snippets/s_problem_agitation.xml',
        'views/snippets/s_benefits_stack.xml',
        'views/snippets/s_testimonial_single.xml',
        'views/snippets/s_opt_in_form.xml',
        'views/snippets/s_cta_inline.xml',
        'views/snippets/s_final_cta.xml',
        'views/snippets/s_offer_breakdown.xml',
        'views/snippets/s_price_reveal.xml',
        'views/snippets/s_guarantee.xml',
        'views/snippets/s_countdown_timer.xml',
        'views/snippets/s_objection_handler.xml',
        'views/snippets/s_spacer.xml',
        'views/snippets/s_video_embed.xml',

        # Data
        'data/funnel_snippet_data.xml',
    ],
    'assets': {
        'web.assets_frontend': [
            # Snippet styles
            'sam_ai_funnels/static/src/snippets/s_hero_minimal/000.scss',
            'sam_ai_funnels/static/src/snippets/s_hero_full/000.scss',
            'sam_ai_funnels/static/src/snippets/s_problem_agitation/000.scss',
            'sam_ai_funnels/static/src/snippets/s_benefits_stack/000.scss',
            'sam_ai_funnels/static/src/snippets/s_testimonial_single/000.scss',
            'sam_ai_funnels/static/src/snippets/s_opt_in_form/000.scss',
            'sam_ai_funnels/static/src/snippets/s_cta_inline/000.scss',
            'sam_ai_funnels/static/src/snippets/s_final_cta/000.scss',
            'sam_ai_funnels/static/src/snippets/s_offer_breakdown/000.scss',
            'sam_ai_funnels/static/src/snippets/s_price_reveal/000.scss',
            'sam_ai_funnels/static/src/snippets/s_guarantee/000.scss',
            'sam_ai_funnels/static/src/snippets/s_countdown_timer/000.scss',
            'sam_ai_funnels/static/src/snippets/s_objection_handler/000.scss',
            'sam_ai_funnels/static/src/snippets/s_spacer/000.scss',
            'sam_ai_funnels/static/src/snippets/s_video_embed/000.scss',
            # Countdown timer JS
            'sam_ai_funnels/static/src/snippets/s_countdown_timer/000.js',
            # FAQ accordion JS
            'sam_ai_funnels/static/src/snippets/s_objection_handler/000.js',
        ],
        'website.assets_wysiwyg': [
            # Editor-specific JS for snippet options
            'sam_ai_funnels/static/src/js/funnel_snippet_options.js',
        ],
    },
    # ... rest of manifest ...
}

2. options.xml - FUNNELS Tab Registration

File: views/snippets/options.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <!-- ============================================
         FUNNELS TAB - Register in Website Builder
         ============================================ -->

    <!-- Register FUNNELS as a snippet group (creates the tab) -->
    <template id="snippet_groups" inherit_id="website.snippets">
        <xpath expr="//snippets[@id='snippet_groups']" position="inside">
            <t t-snippet="website.s_snippet_group"
               snippet-group="funnels"
               string="Funnels"/>
        </xpath>
    </template>

    <!-- Register all funnel snippets into the FUNNELS tab -->
    <template id="snippets" inherit_id="website.snippets">
        <xpath expr="//snippets[@id='snippet_structure']" position="inside">

            <!-- ===== HERO SECTIONS ===== -->
            <t t-snippet="sam_ai_funnels.s_hero_minimal"
               string="Minimal Hero"
               group="funnels">
                <keywords>hero, headline, minimal, funnel, opening</keywords>
            </t>

            <t t-snippet="sam_ai_funnels.s_hero_full"
               string="Full Hero"
               group="funnels">
                <keywords>hero, headline, video, image, full, media</keywords>
            </t>

            <!-- ===== PROBLEM & STORY ===== -->
            <t t-snippet="sam_ai_funnels.s_problem_agitation"
               string="Problem Agitation"
               group="funnels">
                <keywords>problem, pain, agitation, PAS, story</keywords>
            </t>

            <!-- ===== SOLUTION & BENEFITS ===== -->
            <t t-snippet="sam_ai_funnels.s_benefits_stack"
               string="Benefits Stack"
               group="funnels">
                <keywords>benefits, bullets, features, stack, checkmarks</keywords>
            </t>

            <!-- ===== SOCIAL PROOF ===== -->
            <t t-snippet="sam_ai_funnels.s_testimonial_single"
               string="Single Testimonial"
               group="funnels">
                <keywords>testimonial, quote, review, social proof</keywords>
            </t>

            <!-- ===== CTAs & FORMS ===== -->
            <t t-snippet="sam_ai_funnels.s_opt_in_form"
               string="Opt-in Form"
               group="funnels">
                <keywords>form, email, opt-in, capture, lead, subscribe</keywords>
            </t>

            <t t-snippet="sam_ai_funnels.s_cta_inline"
               string="Inline CTA"
               group="funnels">
                <keywords>cta, button, call to action, inline</keywords>
            </t>

            <t t-snippet="sam_ai_funnels.s_final_cta"
               string="Final CTA"
               group="funnels">
                <keywords>cta, final, bottom, closing, last chance</keywords>
            </t>

            <!-- ===== OFFERS & PRICING ===== -->
            <t t-snippet="sam_ai_funnels.s_offer_breakdown"
               string="Offer Breakdown"
               group="funnels">
                <keywords>offer, breakdown, included, features, value</keywords>
            </t>

            <t t-snippet="sam_ai_funnels.s_price_reveal"
               string="Price Reveal"
               group="funnels">
                <keywords>price, pricing, reveal, anchor, cost</keywords>
            </t>

            <t t-snippet="sam_ai_funnels.s_guarantee"
               string="Guarantee"
               group="funnels">
                <keywords>guarantee, risk, reversal, money back, refund</keywords>
            </t>

            <!-- ===== URGENCY & TRUST ===== -->
            <t t-snippet="sam_ai_funnels.s_countdown_timer"
               string="Countdown Timer"
               group="funnels">
                <keywords>countdown, timer, deadline, urgency, scarcity</keywords>
            </t>

            <t t-snippet="sam_ai_funnels.s_objection_handler"
               string="FAQ / Objections"
               group="funnels">
                <keywords>faq, objection, questions, accordion, answers</keywords>
            </t>

            <!-- ===== UTILITY ===== -->
            <t t-snippet="sam_ai_funnels.s_spacer"
               string="Spacer"
               group="funnels">
                <keywords>spacer, space, gap, divider, utility</keywords>
            </t>

            <t t-snippet="sam_ai_funnels.s_video_embed"
               string="Video Embed"
               group="funnels">
                <keywords>video, embed, youtube, vimeo, player</keywords>
            </t>

        </xpath>
    </template>

    <!-- ============================================
         CUSTOMIZE OPTIONS FOR EACH SNIPPET
         ============================================ -->

    <template id="snippet_options" inherit_id="website.snippet_options">
        <xpath expr="." position="inside">

            <!-- ===== HERO MINIMAL OPTIONS ===== -->
            <div data-selector=".s_hero_minimal">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-input string="Subheadline" data-attribute-name="data-subheadline"/>
                <we-input string="CTA Text" data-attribute-name="data-cta-text"/>
                <we-input string="CTA URL" data-attribute-name="data-cta-url"/>
                <we-select string="CTA Style">
                    <we-button data-select-class="s_btn_primary">Primary</we-button>
                    <we-button data-select-class="s_btn_secondary">Secondary</we-button>
                    <we-button data-select-class="s_btn_outline">Outline</we-button>
                </we-select>
                <we-button-group string="Text Align">
                    <we-button data-select-class="text-start" title="Left"/>
                    <we-button data-select-class="text-center" title="Center"/>
                    <we-button data-select-class="text-end" title="Right"/>
                </we-button-group>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-colorpicker string="Text Color" data-css-property="color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== HERO FULL OPTIONS ===== -->
            <div data-selector=".s_hero_full">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-input string="Subheadline" data-attribute-name="data-subheadline"/>
                <we-select string="Media Type">
                    <we-button data-select-class="s_media_none">None</we-button>
                    <we-button data-select-class="s_media_image">Image</we-button>
                    <we-button data-select-class="s_media_video">Video</we-button>
                </we-select>
                <we-select string="Media Position">
                    <we-button data-select-class="s_media_left">Left</we-button>
                    <we-button data-select-class="s_media_right">Right</we-button>
                    <we-button data-select-class="s_media_bg">Background</we-button>
                </we-select>
                <we-input string="CTA Text" data-attribute-name="data-cta-text"/>
                <we-input string="CTA URL" data-attribute-name="data-cta-url"/>
                <we-input string="Secondary CTA" data-attribute-name="data-cta2-text"/>
                <we-input string="Secondary URL" data-attribute-name="data-cta2-url"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== PROBLEM AGITATION OPTIONS ===== -->
            <div data-selector=".s_problem_agitation">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-colorpicker string="Text Color" data-css-property="color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== BENEFITS STACK OPTIONS ===== -->
            <div data-selector=".s_benefits_stack">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-select string="Columns">
                    <we-button data-select-class="s_cols_1">1 Column</we-button>
                    <we-button data-select-class="s_cols_2">2 Columns</we-button>
                    <we-button data-select-class="s_cols_3">3 Columns</we-button>
                </we-select>
                <we-select string="Icon Style">
                    <we-button data-select-class="s_icon_check">Checkmarks</we-button>
                    <we-button data-select-class="s_icon_bullet">Bullets</we-button>
                    <we-button data-select-class="s_icon_arrow">Arrows</we-button>
                </we-select>
                <we-colorpicker string="Icon Color" data-css-property="--icon-color"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== TESTIMONIAL SINGLE OPTIONS ===== -->
            <div data-selector=".s_testimonial_single">
                <we-select string="Style">
                    <we-button data-select-class="s_style_card">Card</we-button>
                    <we-button data-select-class="s_style_minimal">Minimal</we-button>
                    <we-button data-select-class="s_style_featured">Featured</we-button>
                </we-select>
                <we-checkbox string="Show Rating" data-attribute-name="data-show-rating"/>
                <we-checkbox string="Show Photo" data-attribute-name="data-show-photo"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== OPT-IN FORM OPTIONS ===== -->
            <div data-selector=".s_opt_in_form">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-input string="Button Text" data-attribute-name="data-button-text"/>
                <we-select string="Form Fields">
                    <we-button data-select-class="s_fields_email">Email Only</we-button>
                    <we-button data-select-class="s_fields_name_email">Name + Email</we-button>
                    <we-button data-select-class="s_fields_full">Name + Email + Phone</we-button>
                </we-select>
                <we-input string="Redirect URL" data-attribute-name="data-redirect-url"/>
                <we-select string="Integration">
                    <we-button data-set-attribute="data-integration" data-value="crm">CRM Lead</we-button>
                    <we-button data-set-attribute="data-integration" data-value="mailing">Mailing List</we-button>
                    <we-button data-set-attribute="data-integration" data-value="both">Both</we-button>
                </we-select>
                <we-input string="Lead Tag" data-attribute-name="data-lead-tag"/>
                <we-colorpicker string="Button Color" data-css-property="--btn-color"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== CTA INLINE OPTIONS ===== -->
            <div data-selector=".s_cta_inline">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-input string="Subheadline" data-attribute-name="data-subheadline"/>
                <we-input string="CTA Text" data-attribute-name="data-cta-text"/>
                <we-input string="CTA URL" data-attribute-name="data-cta-url"/>
                <we-select string="CTA Style">
                    <we-button data-select-class="s_btn_primary">Primary</we-button>
                    <we-button data-select-class="s_btn_secondary">Secondary</we-button>
                    <we-button data-select-class="s_btn_outline">Outline</we-button>
                </we-select>
                <we-button-group string="Alignment">
                    <we-button data-select-class="text-start"/>
                    <we-button data-select-class="text-center"/>
                    <we-button data-select-class="text-end"/>
                </we-button-group>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== FINAL CTA OPTIONS ===== -->
            <div data-selector=".s_final_cta">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-input string="Subheadline" data-attribute-name="data-subheadline"/>
                <we-input string="Urgency Text" data-attribute-name="data-urgency-text"/>
                <we-input string="CTA Text" data-attribute-name="data-cta-text"/>
                <we-input string="CTA URL" data-attribute-name="data-cta-url"/>
                <we-select string="CTA Size">
                    <we-button data-select-class="s_btn_md">Medium</we-button>
                    <we-button data-select-class="s_btn_lg">Large</we-button>
                </we-select>
                <we-select string="Background">
                    <we-button data-select-class="s_bg_solid">Solid Color</we-button>
                    <we-button data-select-class="s_bg_gradient">Gradient</we-button>
                </we-select>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-4">Medium</we-button>
                    <we-button data-select-class="py-6">Large</we-button>
                </we-select>
            </div>

            <!-- ===== OFFER BREAKDOWN OPTIONS ===== -->
            <div data-selector=".s_offer_breakdown">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-checkbox string="Show Values" data-attribute-name="data-show-values"/>
                <we-input string="Total Value" data-attribute-name="data-total-value"/>
                <we-select string="Checkmark Style">
                    <we-button data-select-class="s_check_default">Default</we-button>
                    <we-button data-select-class="s_check_circle">Circle</we-button>
                    <we-button data-select-class="s_check_square">Square</we-button>
                </we-select>
                <we-colorpicker string="Check Color" data-css-property="--check-color"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== PRICE REVEAL OPTIONS ===== -->
            <div data-selector=".s_price_reveal">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-input string="Anchor Price" data-attribute-name="data-anchor-price"/>
                <we-input string="Actual Price" data-attribute-name="data-actual-price"/>
                <we-input string="Price Period" data-attribute-name="data-price-period"/>
                <we-input string="Value Statement" data-attribute-name="data-value-statement"/>
                <we-input string="CTA Text" data-attribute-name="data-cta-text"/>
                <we-input string="CTA URL" data-attribute-name="data-cta-url"/>
                <we-checkbox string="Show Payment Icons" data-attribute-name="data-show-payment"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== GUARANTEE OPTIONS ===== -->
            <div data-selector=".s_guarantee">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-input string="Guarantee Period" data-attribute-name="data-period"/>
                <we-select string="Style">
                    <we-button data-select-class="s_style_badge">Badge</we-button>
                    <we-button data-select-class="s_style_card">Card</we-button>
                    <we-button data-select-class="s_style_minimal">Minimal</we-button>
                </we-select>
                <we-colorpicker string="Badge Color" data-css-property="--badge-color"/>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== COUNTDOWN TIMER OPTIONS ===== -->
            <div data-selector=".s_countdown_timer">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-select string="Deadline Type">
                    <we-button data-set-attribute="data-deadline-type" data-value="fixed">Fixed Date</we-button>
                    <we-button data-set-attribute="data-deadline-type" data-value="evergreen">Evergreen</we-button>
                </we-select>
                <we-input string="Deadline Date" data-attribute-name="data-deadline-date"/>
                <we-input string="Evergreen Hours" data-attribute-name="data-evergreen-hours"/>
                <we-select string="Expired Action">
                    <we-button data-set-attribute="data-expired-action" data-value="hide">Hide</we-button>
                    <we-button data-set-attribute="data-expired-action" data-value="message">Show Message</we-button>
                    <we-button data-set-attribute="data-expired-action" data-value="redirect">Redirect</we-button>
                </we-select>
                <we-input string="Expired Message" data-attribute-name="data-expired-message"/>
                <we-input string="Expired Redirect" data-attribute-name="data-expired-redirect"/>
                <we-select string="Style">
                    <we-button data-select-class="s_timer_minimal">Minimal</we-button>
                    <we-button data-select-class="s_timer_boxed">Boxed</we-button>
                    <we-button data-select-class="s_timer_flip">Flip</we-button>
                </we-select>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== OBJECTION HANDLER OPTIONS ===== -->
            <div data-selector=".s_objection_handler">
                <we-input string="Headline" data-attribute-name="data-headline"/>
                <we-select string="Style">
                    <we-button data-select-class="s_style_accordion">Accordion</we-button>
                    <we-button data-select-class="s_style_list">List</we-button>
                </we-select>
                <we-select string="Default Expanded">
                    <we-button data-set-attribute="data-expanded" data-value="none">None</we-button>
                    <we-button data-set-attribute="data-expanded" data-value="first">First</we-button>
                    <we-button data-set-attribute="data-expanded" data-value="all">All</we-button>
                </we-select>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

            <!-- ===== SPACER OPTIONS ===== -->
            <div data-selector=".s_spacer">
                <we-select string="Height">
                    <we-button data-select-class="s_spacer_sm">Small (32px)</we-button>
                    <we-button data-select-class="s_spacer_md">Medium (64px)</we-button>
                    <we-button data-select-class="s_spacer_lg">Large (96px)</we-button>
                    <we-button data-select-class="s_spacer_xl">Extra Large (128px)</we-button>
                </we-select>
                <we-colorpicker string="Background" data-css-property="background-color"/>
            </div>

            <!-- ===== VIDEO EMBED OPTIONS ===== -->
            <div data-selector=".s_video_embed">
                <we-input string="Video URL" data-attribute-name="data-video-url"/>
                <we-select string="Video Source">
                    <we-button data-set-attribute="data-source" data-value="youtube">YouTube</we-button>
                    <we-button data-set-attribute="data-source" data-value="vimeo">Vimeo</we-button>
                    <we-button data-set-attribute="data-source" data-value="custom">Custom</we-button>
                </we-select>
                <we-input string="Caption" data-attribute-name="data-caption"/>
                <we-checkbox string="Autoplay" data-attribute-name="data-autoplay"/>
                <we-checkbox string="Show Controls" data-attribute-name="data-controls"/>
                <we-select string="Aspect Ratio">
                    <we-button data-select-class="s_ratio_16_9">16:9</we-button>
                    <we-button data-select-class="s_ratio_4_3">4:3</we-button>
                    <we-button data-select-class="s_ratio_1_1">1:1</we-button>
                </we-select>
                <we-select string="Max Width">
                    <we-button data-select-class="s_width_sm">Small</we-button>
                    <we-button data-select-class="s_width_md">Medium</we-button>
                    <we-button data-select-class="s_width_lg">Large</we-button>
                    <we-button data-select-class="s_width_full">Full</we-button>
                </we-select>
                <we-colorpicker string="Background" data-css-property="background-color"/>
                <we-select string="Padding">
                    <we-button data-select-class="py-3">Small</we-button>
                    <we-button data-select-class="py-5">Medium</we-button>
                    <we-button data-select-class="py-7">Large</we-button>
                </we-select>
            </div>

        </xpath>
    </template>
</odoo>

3. Snippet XML Templates

Create each snippet template. Here are all 15:

views/snippets/s_hero_minimal.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_hero_minimal" name="Minimal Hero">
        <section class="s_hero_minimal py-5 text-center"
                 data-snippet="sam_ai_funnels.s_hero_minimal"
                 data-name="Minimal Hero">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8">
                        <h1 class="display-4 fw-bold mb-3" data-oe-field="headline">
                            Your Compelling Headline Here
                        </h1>
                        <p class="lead mb-4" data-oe-field="subheadline">
                            A brief, powerful subheadline that expands on your promise
                        </p>
                        <a href="#" class="btn btn-primary btn-lg" data-oe-field="cta">
                            Get Started Now
                        </a>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_hero_full.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_hero_full" name="Full Hero">
        <section class="s_hero_full s_media_right py-5"
                 data-snippet="sam_ai_funnels.s_hero_full"
                 data-name="Full Hero">
            <div class="container">
                <div class="row align-items-center">
                    <div class="col-lg-6">
                        <h1 class="display-4 fw-bold mb-3" data-oe-field="headline">
                            Transform Your Results Today
                        </h1>
                        <p class="lead mb-4" data-oe-field="subheadline">
                            Discover the proven system that's helped thousands achieve extraordinary results
                        </p>
                        <div class="d-flex gap-3 flex-wrap">
                            <a href="#" class="btn btn-primary btn-lg" data-oe-field="cta">
                                Start Free Trial
                            </a>
                            <a href="#" class="btn btn-outline-secondary btn-lg" data-oe-field="cta2">
                                Watch Demo
                            </a>
                        </div>
                    </div>
                    <div class="col-lg-6 mt-4 mt-lg-0">
                        <div class="s_hero_media ratio ratio-16x9 bg-light rounded">
                            <img src="/web/image/website.s_banner_default_image"
                                 class="img-fluid rounded"
                                 alt="Hero Image"
                                 data-oe-field="media"/>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_problem_agitation.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_problem_agitation" name="Problem Agitation">
        <section class="s_problem_agitation py-5"
                 data-snippet="sam_ai_funnels.s_problem_agitation"
                 data-name="Problem Agitation">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8">
                        <h2 class="h3 mb-4" data-oe-field="headline">
                            Are You Tired Of...
                        </h2>
                        <ul class="s_pain_points list-unstyled" data-oe-field="pain_points">
                            <li class="mb-3 d-flex align-items-start">
                                <span class="text-danger me-2">&#10007;</span>
                                <span>Struggling to get consistent results despite working harder than ever?</span>
                            </li>
                            <li class="mb-3 d-flex align-items-start">
                                <span class="text-danger me-2">&#10007;</span>
                                <span>Watching competitors succeed while you feel stuck in the same place?</span>
                            </li>
                            <li class="mb-3 d-flex align-items-start">
                                <span class="text-danger me-2">&#10007;</span>
                                <span>Feeling overwhelmed by all the conflicting advice out there?</span>
                            </li>
                        </ul>
                        <div class="s_agitation mt-4 p-4 bg-light rounded" data-oe-field="agitation">
                            <p class="mb-0 fst-italic">
                                And the worst part? Every day that passes, you're falling further behind while the solution might be simpler than you think...
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_benefits_stack.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_benefits_stack" name="Benefits Stack">
        <section class="s_benefits_stack s_cols_2 s_icon_check py-5"
                 data-snippet="sam_ai_funnels.s_benefits_stack"
                 data-name="Benefits Stack">
            <div class="container">
                <h2 class="text-center mb-5" data-oe-field="headline">
                    What You'll Get
                </h2>
                <div class="row g-4">
                    <div class="col-md-6">
                        <div class="s_benefit d-flex align-items-start">
                            <span class="s_benefit_icon me-3 text-success fs-4">&#10003;</span>
                            <div>
                                <strong>Proven Framework</strong>
                                <p class="mb-0 text-muted">Step-by-step system you can implement today</p>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="s_benefit d-flex align-items-start">
                            <span class="s_benefit_icon me-3 text-success fs-4">&#10003;</span>
                            <div>
                                <strong>Expert Support</strong>
                                <p class="mb-0 text-muted">Direct access to our team whenever you need help</p>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="s_benefit d-flex align-items-start">
                            <span class="s_benefit_icon me-3 text-success fs-4">&#10003;</span>
                            <div>
                                <strong>Time-Saving Templates</strong>
                                <p class="mb-0 text-muted">Done-for-you resources to accelerate results</p>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="s_benefit d-flex align-items-start">
                            <span class="s_benefit_icon me-3 text-success fs-4">&#10003;</span>
                            <div>
                                <strong>Lifetime Updates</strong>
                                <p class="mb-0 text-muted">Always have access to the latest strategies</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_testimonial_single.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_testimonial_single" name="Single Testimonial">
        <section class="s_testimonial_single s_style_card py-5"
                 data-snippet="sam_ai_funnels.s_testimonial_single"
                 data-name="Single Testimonial">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8">
                        <div class="card border-0 shadow-sm">
                            <div class="card-body p-4 p-md-5">
                                <div class="s_rating mb-3" data-oe-field="rating">
                                    <span class="text-warning">&#9733;&#9733;&#9733;&#9733;&#9733;</span>
                                </div>
                                <blockquote class="mb-4">
                                    <p class="lead fst-italic" data-oe-field="quote">
                                        "This completely transformed how I approach my business. Within 90 days, I doubled my revenue and finally have the freedom I always dreamed of."
                                    </p>
                                </blockquote>
                                <div class="d-flex align-items-center">
                                    <img src="/web/image/website.s_banner_default_image"
                                         class="rounded-circle me-3"
                                         width="60"
                                         height="60"
                                         alt="Author"
                                         data-oe-field="photo"/>
                                    <div>
                                        <strong data-oe-field="name">Sarah Johnson</strong>
                                        <p class="mb-0 text-muted small" data-oe-field="title">
                                            CEO, Growth Marketing Co.
                                        </p>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_opt_in_form.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_opt_in_form" name="Opt-in Form">
        <section class="s_opt_in_form s_fields_name_email py-5"
                 data-snippet="sam_ai_funnels.s_opt_in_form"
                 data-name="Opt-in Form"
                 data-integration="crm">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-6">
                        <div class="card border-0 shadow">
                            <div class="card-body p-4 p-md-5">
                                <h3 class="text-center mb-4" data-oe-field="headline">
                                    Get Your Free Guide
                                </h3>
                                <form class="s_funnel_form"
                                      action="/funnel/form/submit"
                                      method="post">
                                    <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
                                    <div class="s_field_name mb-3">
                                        <input type="text"
                                               name="name"
                                               class="form-control form-control-lg"
                                               placeholder="Your Name"
                                               required="required"/>
                                    </div>
                                    <div class="s_field_email mb-3">
                                        <input type="email"
                                               name="email"
                                               class="form-control form-control-lg"
                                               placeholder="Your Email"
                                               required="required"/>
                                    </div>
                                    <div class="s_field_phone mb-3" style="display: none;">
                                        <input type="tel"
                                               name="phone"
                                               class="form-control form-control-lg"
                                               placeholder="Your Phone"/>
                                    </div>
                                    <button type="submit"
                                            class="btn btn-primary btn-lg w-100"
                                            data-oe-field="button">
                                        Get Instant Access
                                    </button>
                                    <p class="text-center text-muted small mt-3 mb-0" data-oe-field="privacy">
                                        We respect your privacy. Unsubscribe anytime.
                                    </p>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_cta_inline.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_cta_inline" name="Inline CTA">
        <section class="s_cta_inline s_btn_primary py-4 text-center"
                 data-snippet="sam_ai_funnels.s_cta_inline"
                 data-name="Inline CTA">
            <div class="container">
                <div class="row justify-content-center align-items-center">
                    <div class="col-lg-8">
                        <h3 class="mb-2" data-oe-field="headline">
                            Ready to Get Started?
                        </h3>
                        <p class="mb-3 text-muted" data-oe-field="subheadline">
                            Join thousands who've already transformed their results
                        </p>
                        <a href="#" class="btn btn-primary btn-lg" data-oe-field="cta">
                            Yes, I Want This!
                        </a>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_final_cta.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_final_cta" name="Final CTA">
        <section class="s_final_cta s_btn_lg s_bg_gradient py-5 text-center text-white"
                 data-snippet="sam_ai_funnels.s_final_cta"
                 data-name="Final CTA"
                 style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8">
                        <h2 class="display-5 fw-bold mb-3" data-oe-field="headline">
                            Don't Wait Another Day
                        </h2>
                        <p class="lead mb-2" data-oe-field="subheadline">
                            Your transformation starts with a single click
                        </p>
                        <p class="small mb-4 opacity-75" data-oe-field="urgency">
                            Limited spots available - Offer ends soon
                        </p>
                        <a href="#" class="btn btn-light btn-lg px-5 py-3" data-oe-field="cta">
                            Claim My Spot Now
                        </a>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_offer_breakdown.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_offer_breakdown" name="Offer Breakdown">
        <section class="s_offer_breakdown s_check_default py-5"
                 data-snippet="sam_ai_funnels.s_offer_breakdown"
                 data-name="Offer Breakdown"
                 data-show-values="true">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8">
                        <h2 class="text-center mb-5" data-oe-field="headline">
                            Everything You Get Today
                        </h2>
                        <ul class="list-unstyled" data-oe-field="items">
                            <li class="d-flex justify-content-between align-items-center py-3 border-bottom">
                                <div class="d-flex align-items-center">
                                    <span class="text-success me-3 fs-5">&#10003;</span>
                                    <div>
                                        <strong>Complete Video Training Course</strong>
                                        <p class="mb-0 small text-muted">12 modules of step-by-step instruction</p>
                                    </div>
                                </div>
                                <span class="s_item_value badge bg-secondary">$497 Value</span>
                            </li>
                            <li class="d-flex justify-content-between align-items-center py-3 border-bottom">
                                <div class="d-flex align-items-center">
                                    <span class="text-success me-3 fs-5">&#10003;</span>
                                    <div>
                                        <strong>Done-For-You Templates</strong>
                                        <p class="mb-0 small text-muted">Ready to use, just fill in the blanks</p>
                                    </div>
                                </div>
                                <span class="s_item_value badge bg-secondary">$297 Value</span>
                            </li>
                            <li class="d-flex justify-content-between align-items-center py-3 border-bottom">
                                <div class="d-flex align-items-center">
                                    <span class="text-success me-3 fs-5">&#10003;</span>
                                    <div>
                                        <strong>Private Community Access</strong>
                                        <p class="mb-0 small text-muted">Connect with fellow members</p>
                                    </div>
                                </div>
                                <span class="s_item_value badge bg-secondary">$197 Value</span>
                            </li>
                            <li class="d-flex justify-content-between align-items-center py-3">
                                <div class="d-flex align-items-center">
                                    <span class="text-success me-3 fs-5">&#10003;</span>
                                    <div>
                                        <strong>Lifetime Updates</strong>
                                        <p class="mb-0 small text-muted">Always get the latest version</p>
                                    </div>
                                </div>
                                <span class="s_item_value badge bg-secondary">Priceless</span>
                            </li>
                        </ul>
                        <div class="text-center mt-4 p-3 bg-light rounded">
                            <span class="text-muted">Total Value: </span>
                            <strong class="fs-4" data-oe-field="total">$991+</strong>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_price_reveal.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_price_reveal" name="Price Reveal">
        <section class="s_price_reveal py-5 text-center"
                 data-snippet="sam_ai_funnels.s_price_reveal"
                 data-name="Price Reveal">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-6">
                        <h2 class="mb-4" data-oe-field="headline">
                            Your Investment Today
                        </h2>
                        <div class="s_price_display mb-4">
                            <span class="s_anchor_price text-muted text-decoration-line-through fs-4"
                                  data-oe-field="anchor">$997</span>
                            <div class="s_actual_price">
                                <span class="display-3 fw-bold text-primary" data-oe-field="price">$297</span>
                                <span class="text-muted" data-oe-field="period">/one-time</span>
                            </div>
                        </div>
                        <p class="text-muted mb-4" data-oe-field="value_statement">
                            Less than the cost of one consultation - and you get lifetime access
                        </p>
                        <a href="#" class="btn btn-primary btn-lg px-5" data-oe-field="cta">
                            Get Instant Access
                        </a>
                        <div class="s_payment_icons mt-3" data-oe-field="payment_icons">
                            <small class="text-muted">
                                <i class="fa fa-lock me-1"></i> Secure Payment
                                <span class="mx-2">|</span>
                                <i class="fa fa-credit-card me-1"></i> All Major Cards Accepted
                            </small>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_guarantee.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_guarantee" name="Guarantee">
        <section class="s_guarantee s_style_badge py-5"
                 data-snippet="sam_ai_funnels.s_guarantee"
                 data-name="Guarantee">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8">
                        <div class="d-flex align-items-center justify-content-center flex-column flex-md-row text-center text-md-start">
                            <div class="s_guarantee_badge mb-3 mb-md-0 me-md-4">
                                <div class="rounded-circle bg-success text-white d-flex align-items-center justify-content-center"
                                     style="width: 100px; height: 100px;">
                                    <div class="text-center">
                                        <div class="fw-bold" data-oe-field="period">30-Day</div>
                                        <small>Guarantee</small>
                                    </div>
                                </div>
                            </div>
                            <div>
                                <h3 class="h4 mb-2" data-oe-field="headline">
                                    100% Money-Back Guarantee
                                </h3>
                                <p class="mb-0 text-muted" data-oe-field="text">
                                    Try it risk-free for 30 days. If you're not completely satisfied with your results, simply let us know and we'll refund every penny. No questions asked, no hard feelings.
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_countdown_timer.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_countdown_timer" name="Countdown Timer">
        <section class="s_countdown_timer s_timer_boxed py-4"
                 data-snippet="sam_ai_funnels.s_countdown_timer"
                 data-name="Countdown Timer"
                 data-deadline-type="evergreen"
                 data-evergreen-hours="48"
                 data-expired-action="hide">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8 text-center">
                        <p class="mb-3 fw-bold text-danger" data-oe-field="headline">
                            This Special Offer Expires In:
                        </p>
                        <div class="s_timer_display d-flex justify-content-center gap-3">
                            <div class="s_timer_unit bg-dark text-white rounded p-3">
                                <div class="s_timer_days display-6 fw-bold">00</div>
                                <small>Days</small>
                            </div>
                            <div class="s_timer_unit bg-dark text-white rounded p-3">
                                <div class="s_timer_hours display-6 fw-bold">00</div>
                                <small>Hours</small>
                            </div>
                            <div class="s_timer_unit bg-dark text-white rounded p-3">
                                <div class="s_timer_minutes display-6 fw-bold">00</div>
                                <small>Minutes</small>
                            </div>
                            <div class="s_timer_unit bg-dark text-white rounded p-3">
                                <div class="s_timer_seconds display-6 fw-bold">00</div>
                                <small>Seconds</small>
                            </div>
                        </div>
                        <div class="s_expired_message mt-3" style="display: none;">
                            <p class="text-danger fw-bold" data-oe-field="expired_message">
                                This offer has expired!
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_objection_handler.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_objection_handler" name="FAQ / Objections">
        <section class="s_objection_handler s_style_accordion py-5"
                 data-snippet="sam_ai_funnels.s_objection_handler"
                 data-name="FAQ / Objections"
                 data-expanded="first">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-8">
                        <h2 class="text-center mb-5" data-oe-field="headline">
                            Frequently Asked Questions
                        </h2>
                        <div class="accordion" id="faqAccordion" data-oe-field="faqs">
                            <div class="accordion-item">
                                <h3 class="accordion-header">
                                    <button class="accordion-button" type="button"
                                            data-bs-toggle="collapse" data-bs-target="#faq1">
                                        How quickly will I see results?
                                    </button>
                                </h3>
                                <div id="faq1" class="accordion-collapse collapse show"
                                     data-bs-parent="#faqAccordion">
                                    <div class="accordion-body">
                                        Most customers start seeing improvements within the first week. However, the full transformation typically happens within 30-90 days depending on how consistently you apply the system.
                                    </div>
                                </div>
                            </div>
                            <div class="accordion-item">
                                <h3 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button"
                                            data-bs-toggle="collapse" data-bs-target="#faq2">
                                        What if this doesn't work for me?
                                    </button>
                                </h3>
                                <div id="faq2" class="accordion-collapse collapse"
                                     data-bs-parent="#faqAccordion">
                                    <div class="accordion-body">
                                        We offer a full 30-day money-back guarantee. If you're not satisfied for any reason, just let us know and we'll refund your investment completely.
                                    </div>
                                </div>
                            </div>
                            <div class="accordion-item">
                                <h3 class="accordion-header">
                                    <button class="accordion-button collapsed" type="button"
                                            data-bs-toggle="collapse" data-bs-target="#faq3">
                                        How much time do I need to invest?
                                    </button>
                                </h3>
                                <div id="faq3" class="accordion-collapse collapse"
                                     data-bs-parent="#faqAccordion">
                                    <div class="accordion-body">
                                        We recommend setting aside 30-60 minutes per day to go through the training and implement what you learn. The more consistent you are, the faster you'll see results.
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

views/snippets/s_spacer.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_spacer" name="Spacer">
        <section class="s_spacer s_spacer_md"
                 data-snippet="sam_ai_funnels.s_spacer"
                 data-name="Spacer">
            <!-- Empty spacer element -->
        </section>
    </template>
</odoo>

views/snippets/s_video_embed.xml

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="s_video_embed" name="Video Embed">
        <section class="s_video_embed s_ratio_16_9 s_width_lg py-5"
                 data-snippet="sam_ai_funnels.s_video_embed"
                 data-name="Video Embed"
                 data-source="youtube"
                 data-controls="true">
            <div class="container">
                <div class="row justify-content-center">
                    <div class="col-lg-10">
                        <div class="s_video_wrapper ratio ratio-16x9 bg-dark rounded overflow-hidden">
                            <iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"
                                    title="Video"
                                    frameborder="0"
                                    allowfullscreen="allowfullscreen"
                                    data-oe-field="video"></iframe>
                        </div>
                        <p class="text-center text-muted mt-3" data-oe-field="caption">
                            Watch this short video to see how it works
                        </p>
                    </div>
                </div>
            </div>
        </section>
    </template>
</odoo>

4. SCSS Files

Create SCSS files for each snippet. Here are the key ones:

static/src/snippets/s_hero_minimal/000.scss

.s_hero_minimal {
    &.py-3 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
    &.py-5 { padding-top: 3rem !important; padding-bottom: 3rem !important; }
    &.py-7 { padding-top: 5rem !important; padding-bottom: 5rem !important; }

    h1 {
        line-height: 1.2;
    }

    .btn-lg {
        padding: 0.75rem 2rem;
        font-size: 1.1rem;
    }

    &.s_btn_primary .btn { @extend .btn-primary; }
    &.s_btn_secondary .btn { @extend .btn-secondary; }
    &.s_btn_outline .btn { @extend .btn-outline-primary; }
}

static/src/snippets/s_opt_in_form/000.scss

.s_opt_in_form {
    --btn-color: var(--primary);

    .form-control-lg {
        padding: 0.75rem 1rem;
        font-size: 1rem;
    }

    .btn {
        background-color: var(--btn-color);
        border-color: var(--btn-color);
    }

    // Field visibility based on class
    &.s_fields_email {
        .s_field_name, .s_field_phone { display: none !important; }
    }

    &.s_fields_name_email {
        .s_field_phone { display: none !important; }
    }

    &.s_fields_full {
        .s_field_name, .s_field_email, .s_field_phone { display: block !important; }
    }
}

static/src/snippets/s_countdown_timer/000.scss

.s_countdown_timer {
    .s_timer_unit {
        min-width: 80px;
    }

    &.s_timer_minimal {
        .s_timer_unit {
            background: transparent !important;
            color: inherit !important;
        }
    }

    &.s_timer_boxed {
        .s_timer_unit {
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
    }

    &.s_timer_flip {
        .s_timer_unit {
            perspective: 200px;
            .display-6 {
                transform-style: preserve-3d;
            }
        }
    }
}

static/src/snippets/s_spacer/000.scss

.s_spacer {
    &.s_spacer_sm { height: 32px; }
    &.s_spacer_md { height: 64px; }
    &.s_spacer_lg { height: 96px; }
    &.s_spacer_xl { height: 128px; }
}

(Create similar SCSS files for all 15 snippets)


5. JavaScript Files

static/src/snippets/s_countdown_timer/000.js

/** @odoo-module **/

import publicWidget from "@web/legacy/js/public/public_widget";

publicWidget.registry.FunnelCountdownTimer = publicWidget.Widget.extend({
    selector: '.s_countdown_timer',

    start: function () {
        this._super.apply(this, arguments);
        this._initTimer();
        return Promise.resolve();
    },

    _initTimer: function () {
        const $el = this.$el;
        const deadlineType = $el.data('deadline-type') || 'evergreen';
        const expiredAction = $el.data('expired-action') || 'hide';

        let deadline;

        if (deadlineType === 'fixed') {
            deadline = new Date($el.data('deadline-date')).getTime();
        } else {
            // Evergreen timer - uses localStorage to persist
            const storageKey = 'funnel_timer_' + $el.closest('section').attr('id');
            let stored = localStorage.getItem(storageKey);

            if (stored) {
                deadline = parseInt(stored);
            } else {
                const hours = parseInt($el.data('evergreen-hours')) || 48;
                deadline = Date.now() + (hours * 60 * 60 * 1000);
                localStorage.setItem(storageKey, deadline);
            }
        }

        this._updateTimer(deadline, expiredAction);
        this.timerInterval = setInterval(() => {
            this._updateTimer(deadline, expiredAction);
        }, 1000);
    },

    _updateTimer: function (deadline, expiredAction) {
        const now = Date.now();
        const diff = deadline - now;

        if (diff <= 0) {
            this._handleExpired(expiredAction);
            return;
        }

        const days = Math.floor(diff / (1000 * 60 * 60 * 24));
        const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
        const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
        const seconds = Math.floor((diff % (1000 * 60)) / 1000);

        this.$('.s_timer_days').text(String(days).padStart(2, '0'));
        this.$('.s_timer_hours').text(String(hours).padStart(2, '0'));
        this.$('.s_timer_minutes').text(String(minutes).padStart(2, '0'));
        this.$('.s_timer_seconds').text(String(seconds).padStart(2, '0'));
    },

    _handleExpired: function (action) {
        clearInterval(this.timerInterval);

        switch (action) {
            case 'hide':
                this.$el.slideUp();
                break;
            case 'message':
                this.$('.s_timer_display').hide();
                this.$('.s_expired_message').show();
                break;
            case 'redirect':
                const url = this.$el.data('expired-redirect');
                if (url) window.location.href = url;
                break;
        }
    },

    destroy: function () {
        if (this.timerInterval) {
            clearInterval(this.timerInterval);
        }
        this._super.apply(this, arguments);
    },
});

export default publicWidget.registry.FunnelCountdownTimer;

static/src/snippets/s_objection_handler/000.js

/** @odoo-module **/

import publicWidget from "@web/legacy/js/public/public_widget";

publicWidget.registry.FunnelObjectionHandler = publicWidget.Widget.extend({
    selector: '.s_objection_handler',

    start: function () {
        this._super.apply(this, arguments);
        this._initAccordion();
        return Promise.resolve();
    },

    _initAccordion: function () {
        const expanded = this.$el.data('expanded') || 'none';

        if (expanded === 'first') {
            this.$('.accordion-collapse').first().addClass('show');
            this.$('.accordion-button').first().removeClass('collapsed');
        } else if (expanded === 'all') {
            this.$('.accordion-collapse').addClass('show');
            this.$('.accordion-button').removeClass('collapsed');
        } else {
            this.$('.accordion-collapse').removeClass('show');
            this.$('.accordion-button').addClass('collapsed');
        }
    },
});

export default publicWidget.registry.FunnelObjectionHandler;

VALIDATION CHECKLIST

After implementation, verify:

  • [ ] Module upgrades without errors
  • [ ] FUNNELS tab appears in website builder sidebar
  • [ ] All 15 snippets appear in FUNNELS tab
  • [ ] Snippets can be dragged to page
  • [ ] Customize panel shows options when snippet selected
  • [ ] All customize options work (colors, text, selections)
  • [ ] Countdown timer counts down correctly
  • [ ] FAQ accordion expands/collapses
  • [ ] Snippets are responsive on mobile
  • [ ] No JavaScript errors in console

FILES TO CREATE (Summary)

XML (17 files):
1. views/snippets/options.xml
2. views/snippets/s_hero_minimal.xml
3. views/snippets/s_hero_full.xml
4. views/snippets/s_problem_agitation.xml
5. views/snippets/s_benefits_stack.xml
6. views/snippets/s_testimonial_single.xml
7. views/snippets/s_opt_in_form.xml
8. views/snippets/s_cta_inline.xml
9. views/snippets/s_final_cta.xml
10. views/snippets/s_offer_breakdown.xml
11. views/snippets/s_price_reveal.xml
12. views/snippets/s_guarantee.xml
13. views/snippets/s_countdown_timer.xml
14. views/snippets/s_objection_handler.xml
15. views/snippets/s_spacer.xml
16. views/snippets/s_video_embed.xml

SCSS (15 files):
17-31. static/src/snippets/s_*/000.scss (one per snippet)

JS (2 files):
32. static/src/snippets/s_countdown_timer/000.js
33. static/src/snippets/s_objection_handler/000.js

Update:
34. __manifest__.py (add new files to data and assets)


NEXT PHASE

After Phase 2 is validated, proceed to Phase 3: Form Integration.
Phase 3 will cover:
- Form submission controller
- CRM lead creation
- Mailing list integration
- UTM tracking
- Conversion events


END OF PHASE 2 DEVELOPER PROMPT

Was this helpful?