For the per-section mobile toggle to do anything useful, FvRE has to look at each section and decide: is this a full-width section, an inner content block, a column grid, a card grid, or a text block? The type drives the default 2-COL/STACK choice and what ratio buttons are valid.
| Signal | Weight | Detects |
|---|---|---|
data-fvw-type attribute on section |
★★★ | Explicit type (highest priority) |
.elementor-top-section vs .elementor-inner-section |
★★★ | FULL-width vs INNER/column container |
Section is direct child of main or .page |
★★ | Top-level section → FULL |
Section contains .elementor-column children |
★★ | COLS / column grid |
| Number of column children: 2 / 3 / 4 | ★★ | Card grid if 3+, 2-col if 2 |
| Column contains card-like widget (image+title+text) | ★★ | Card grid → default 2-COL on mobile |
| Section contains single text widget only | ★ | TEXT block → default STACK |
| Section contains heading + button (no image) | ★ | CTA → default STACK |
| Section has full-bleed image background | ★ | HERO → default STACK |
| Section detected as FOOTER (role=contentinfo, .site-footer, last child) | ★ | FOOTER → default STACK, links vertical |
// freshvibe-cms/fvre/detectSectionType.js function detectSectionType(sectionEl, context) { // 1. Explicit data attribute wins const explicit = sectionEl.getAttribute('data-fvw-type'); if (explicit) return { type: explicit, source: 'explicit' }; // 2. Heuristic scoring const scores = { hero: 0, full: 0, cols: 0, cards: 0, text: 0, cta: 0, footer: 0 }; // 2a. Elementor top vs inner if (sectionEl.classList.contains('elementor-inner-section')) scores.cols += 3; if (sectionEl.classList.contains('elementor-top-section')) scores.full += 3; // 2b. Direct child of main/.page if (sectionEl.parentElement?.matches('main, .page, body > *')) scores.full += 2; // 2c. Column children? const columns = sectionEl.querySelectorAll(':scope > .elementor-column, :scope > .elementor-row > .elementor-column'); if (columns.length >= 2) { scores.cols += 2; scores.cards += 1; } // 2d. Each column: card or text? let cardColumns = 0, textColumns = 0; columns.forEach(col => { const hasImage = col.querySelector('.elementor-widget-image'); const hasTitle = col.querySelector('h1, h2, h3, .elementor-widget-heading'); const hasText = col.querySelector('.elementor-widget-text-editor'); const hasButton = col.querySelector('.elementor-widget-button'); if ((hasImage || hasTitle) && hasText) cardColumns++; else if (hasText && !hasImage) textColumns++; }); if (cardColumns >= 2) scores.cards += 3; if (textColumns >= 2 && cardColumns === 0) scores.text += 2; // 2e. CTA detection (heading + button, no columns) if (columns.length === 0) { const hasHeading = sectionEl.querySelector('.elementor-widget-heading'); const hasButton = sectionEl.querySelector('.elementor-widget-button'); if (hasHeading && hasButton) scores.cta += 3; } // 2f. Hero detection (full-bleed image background) const bg = getComputedStyle(sectionEl).backgroundImage; if (bg && bg !== 'none' && sectionEl.querySelector('.elementor-widget-heading')) { scores.hero += 2; } // 2g. Footer detection (role, class, position) if (sectionEl.getAttribute('role') === 'contentinfo' || sectionEl.classList.contains('site-footer') || context.isLastSection) { scores.footer += 3; } // 3. Pick winner const winner = Object.entries(scores).sort((a, b) => b[1] - a[1])[0]; return { type: winner[0], confidence: winner[1], scores: scores }; }
| Type | Detected when... | Mobile default | Ratio buttons | Auto-collapse | Notes |
|---|---|---|---|---|---|
| HERO | Full-bleed bg image + heading | STACK | — (no ratio) | — (no collapse) | Art below text on mobile |
| FULL | elementor-top-section, no inner content | STACK | — | — | Edge-to-edge, full bleed |
| INNER | elementor-inner-section, single widget | STACK | — | — | Constrained width wrapper |
| COLS (2) | 2 column children | 2-COL | 25/75, 33/66, 50/50, 66/33, 75/25 | 480px default | Text or image+text |
| COLS (3+) | 3 or more column children | 2-COL | 50/50 only (on mobile) | 768px default | 3 cols at 50/50 means more rows |
| CARDS | 2+ columns, each has image+title+text | 2-COL | 25/75, 33/66, 50/50, 66/33, 75/25 | 480px default | Card grids, gallery, team |
| TEXT | Text widgets only, no images | STACK | — | — | Reading needs width |
| CTA | Heading + button, no columns | STACK | — | — | Centered button |
| FOOTER | role=contentinfo, .site-footer, last section | STACK (links vertical) | — | — | 5 links fit horizontal on 768+, vertical below |
detectSectionType(sectionEl, context) — produces a type (HERO/FULL/INNER/COLS/CARDS/TEXT/CTA/FOOTER) and a confidence score2-COL ✓ or STACKdata-fvw-type="cards" to any section to force the type, no heuristics neededfreshvibe-cms/fvre/detectSectionType.js — the function above (~80 lines)freshvibe-cms/fvre/sectionTypes.js — the type enum + default metadata tabledetectSectionType and attach the result to the section's trace node