FvRE section-type detection — the brains behind the toggle

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.

📋 Tree Academy trace — what FvRE sees
FVRE SCAN
S-1section<section class="elementor-section elementor-top-section">HERO
S-3section<section class="elementor-section elementor-inner-section">COLS
S-3acolumn<div class="elementor-column elementor-col-33">COL 1/3
S-3bcolumn<div class="elementor-column elementor-col-33">COL 2/3
S-3ccolumn<div class="elementor-column elementor-col-33">COL 3/3
S-5section<section class="elementor-section elementor-top-section">TEXT
S-8section<section class="elementor-section elementor-top-section">CTA
S-9section<section class="elementor-section elementor-top-section">FULL
🧠 Detection rules
HEURISTICS
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
📐 Default behaviour per type
DEFAULTS
HERO STACK single col, image below text
FULL STACK single col, full bleed
INNER STACK if text-only or single widget
COLS (2 col) 2-COL ✓ keep 2-col, ratio 50/50
COLS (3+ col) 2-COL ✓ keep 2-col, ratio 50/50
CARDS 2-COL ✓ keep 2-col, ratio 50/50
TEXT STACK single col, full width reading
CTA STACK single col, centered button
FOOTER STACK links vertical, single col
Operator can override any of these per-section
⚙️ The detection function
RECIPE
// 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 metadata — what controls show up per type
META
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

How this fits together — the chain of decisions:

  1. FvRE scans the page → builds the structure tree (sections, columns, widgets) — already done in current FvRE
  2. For each section, run detectSectionType(sectionEl, context) — produces a type (HERO/FULL/INNER/COLS/CARDS/TEXT/CTA/FOOTER) and a confidence score
  3. Type metadata lookup — pulls the defaults: mobile default (2-COL/STACK), valid ratio buttons, default auto-collapse breakpoint
  4. Operator overrides stored per-section in the structure tree, override the defaults
  5. Inspector panel shows the type badge at the top of each section's settings, with the defaults pre-filled but editable
  6. Badge on the page (top-right) reflects the current state — 2-COL ✓ or STACK

Why this is the right shape:

The FvRE work to ship this: