{
  "version": "1.0.0",
  "exported_at": "2026-06-03T03:20:00.000Z",
  "project": {
    "name": "Trovacasa Scraper",
    "description": "Scrapes Trovacasa.it search/results pages such as https://www.trovacasa.it/case-in-affitto/milano and exports property listing data equivalent to the Octoparse Trovacasa Scraper: location, property type, title, page URL, neighborhood, surface area, rooms, price, description, image URL, and feature tags. Uses a click-next pagination loop to collect all available result pages. Sample detail URLs from the catalog currently returned 404 during analysis, so this is a best-effort live results-page scraper. Updated after test run to mark full listing cards rather than only title links.",
    "color": "bg-[#42be65]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 100,
      "position_y": 220,
      "config": {
        "url": "https://www.trovacasa.it/case-in-affitto/milano",
        "color": "bg-[#4589ff]",
        "tags": [
          "entry",
          "trovacasa"
        ]
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 460,
      "position_y": 220,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on the page",
      "position_x": 820,
      "position_y": 220,
      "config": {
        "jsCode": "(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim().toLowerCase(); const controls = Array.from(document.querySelectorAll('button,[role=button],a')); const consent = controls.find(el => /continue without|continua senza|rifiuta|reject|agree|accetta/.test(norm(el.textContent))); if (consent) { consent.click(); } return true; })()",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1180,
      "position_y": 220,
      "config": {
        "selector": "a[href*='/annunci/']",
        "timeout": 30,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on the page",
      "position_x": 1540,
      "position_y": 220,
      "config": {
        "jsCode": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const markCards = () => { document.querySelectorAll('[data-uscraper-card=true]').forEach(e => { e.removeAttribute('data-uscraper-card'); e.removeAttribute('data-uscraper-url'); }); document.querySelectorAll('[data-uscraper-next]').forEach(e => e.removeAttribute('data-uscraper-next')); const links = Array.from(document.querySelectorAll('a[href*=\"/annunci/\"]')).filter(a => a.href && !/\\.(jpg|jpeg|png|webp|gif)$/i.test(a.href)); const seenUrls = new Set(); const pickCard = a => { let best = a, bestScore = -999; for (let el = a; el && el !== document.body && el !== document.documentElement; el = el.parentElement) { const text = clean(el.innerText || el.textContent || ''); const annLinks = el.querySelectorAll ? el.querySelectorAll('a[href*=\"/annunci/\"]').length : 1; const cls = String(el.className || ''); let score = 0; if (el !== a) score += 3; if (/card|result|annunc|listing|item|realEstate|estate/i.test(cls)) score += 12; if (el.querySelector && el.querySelector('img')) score += 8; if (/€|euro/i.test(text)) score += 25; if (/\\d+[\\d.,]*\\s*(m²|mq)/i.test(text)) score += 20; if (/\\d+\\s*local[ei]|monolocale|bilocale|trilocale|quadrilocale/i.test(text)) score += 16; if (text.length > 70) score += 8; if (text.length > 180) score += 5; if (text.length > 900) score -= 12; if (text.length > 1800) score -= 35; if (annLinks > 2) score -= (annLinks - 2) * 12; if (annLinks > 8) score -= 80; if (score > bestScore) { bestScore = score; best = el; } } return best; }; links.forEach(a => { const href = a.href.split('#')[0]; if (seenUrls.has(href)) return; seenUrls.add(href); const card = pickCard(a); if (!card) return; card.setAttribute('data-uscraper-card', 'true'); card.setAttribute('data-uscraper-url', href); }); const nextCandidates = Array.from(document.querySelectorAll('a[href],button')).filter(el => { const label = ((el.textContent || '') + ' ' + (el.getAttribute('aria-label') || '') + ' ' + (el.getAttribute('title') || '') + ' ' + (el.getAttribute('rel') || '')).toLowerCase(); const disabled = el.matches('[disabled],[aria-disabled=true],.disabled,.is-disabled') || /disabled|inactive/.test(el.className || ''); return !disabled && (/\\bnext\\b|successiv|avanti/.test(label) || el.getAttribute('rel') === 'next'); }); const next = nextCandidates.find(el => el.href && el.href.split('#')[0] !== location.href.split('#')[0]) || nextCandidates[0]; if (next) next.setAttribute('data-uscraper-next', 'true'); return { cards: document.querySelectorAll('[data-uscraper-card=true]').length, next: !!next }; }; return markCards(); })()",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1900,
      "position_y": 220,
      "config": {
        "selector": "[data-uscraper-card='true']",
        "timeout": 20,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2260,
      "position_y": 220,
      "config": {
        "rowSelector": "[data-uscraper-card='true']",
        "fileName": "crawler-di-trovacasa.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "Posizione",
            "selector": "(() => { const path = decodeURIComponent(location.pathname || '').split('/').filter(Boolean); let city = path[path.length - 1] || ''; city = city.split('?')[0].replace(/-/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase()); return city; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Tipo_immobili",
            "selector": "(() => { const path = (location.pathname || '').toLowerCase(); if (path.includes('affitto')) return 'Affitto'; if (path.includes('vendita')) return 'Vendita'; if (path.includes('asta')) return 'Asta'; const txt = (ROW.textContent || '').toLowerCase(); if (txt.includes('affitto')) return 'Affitto'; if (txt.includes('vendita')) return 'Vendita'; return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Titolo",
            "selector": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const links = Array.from(ROW.querySelectorAll('a[href*=\\'/annunci/\\']')).filter(a => clean(a.textContent).length > 8); const byTitleClass = ROW.querySelector('[class*=title i] a,[class*=titolo i] a,h1 a,h2 a,h3 a,h1,h2,h3,[class*=title i],[class*=titolo i]'); const title = clean(byTitleClass && byTitleClass.textContent) || clean(links[0] && links[0].textContent); return title; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "URL_pagina",
            "selector": "(() => { const a = ROW.querySelector('a[href*=\\'/annunci/\\']'); return ROW.getAttribute('data-uscraper-url') || (a ? a.href : ''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Quartiere",
            "selector": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const el = ROW.querySelector('[class*=address i],[class*=location i],[class*=zona i],[class*=quartiere i],[class*=indirizzo i]'); let txt = clean(el && el.textContent); if (txt && txt.length < 120) return txt; const all = clean(ROW.textContent); const m = all.match(/(?:zona|quartiere)\\s*:?\\s*([^|·,;]{2,70})/i) || all.match(/Milano\\s*[-–,]\\s*([^|·,;]{2,70})/i); return m ? clean(m[1]) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Superficie",
            "selector": "(() => { const txt = (ROW.textContent || '').replace(/\\s+/g, ' '); const m = txt.match(/\\d+[\\d.,]*\\s*m²/i) || txt.match(/\\d+[\\d.,]*\\s*mq/i); return m ? m[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Locali",
            "selector": "(() => { const txt = (ROW.textContent || '').replace(/\\s+/g, ' '); const m = txt.match(/\\d+\\s*local[ei]/i) || txt.match(/monolocale|bilocale|trilocale|quadrilocale|pentalocale/i); return m ? m[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Prezzo",
            "selector": "(() => { const txt = (ROW.textContent || '').replace(/\\s+/g, ' '); const m = txt.match(/(?:€\\s*)?[\\d.]+(?:,\\d+)?\\s*€/i) || txt.match(/€\\s*[\\d.]+(?:,\\d+)?/i) || txt.match(/trattativa\\s+riservata/i); return m ? m[0].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Descrizione",
            "selector": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const titleEl = ROW.querySelector('[class*=title i] a,[class*=titolo i] a,h1 a,h2 a,h3 a,h1,h2,h3,a[href*=\\'/annunci/\\']'); const title = clean(titleEl && titleEl.textContent); const preferred = ROW.querySelector('[class*=description i],[class*=descrizione i],[class*=abstract i],[class*=excerpt i],p'); let txt = clean(preferred && preferred.textContent) || clean(ROW.textContent); if (title && txt.startsWith(title)) txt = clean(txt.slice(title.length)); txt = txt.replace(/(?:€\\s*)?[\\d.]+(?:,\\d+)?\\s*€/ig, ' ').replace(/\\d+[\\d.,]*\\s*(m²|mq)/ig, ' ').replace(/\\d+\\s*local[ei]/ig, ' ').replace(/\\s+/g, ' ').trim(); return txt.slice(0, 1000); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "URL_immagine",
            "selector": "(() => { const img = ROW.querySelector('img'); if (!img) return ''; const srcset = img.getAttribute('srcset') || img.getAttribute('data-srcset') || ''; const firstSrcset = srcset.split(',').map(s => s.trim().split(' ')[0]).filter(Boolean)[0] || ''; return img.currentSrc || img.src || img.getAttribute('data-src') || img.getAttribute('data-lazy-src') || firstSrcset || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tag1",
            "selector": "(() => { const bad = /(€|m²|mq|local|prezzo|superficie|annuncio|contatta|telefono)/i; const vals = Array.from(ROW.querySelectorAll('[class*=tag i],[class*=feature i],[class*=badge i],[class*=chip i],ul li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(v => v && v.length < 60 && !bad.test(v)); return Array.from(new Set(vals))[0] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tag2",
            "selector": "(() => { const bad = /(€|m²|mq|local|prezzo|superficie|annuncio|contatta|telefono)/i; const vals = Array.from(ROW.querySelectorAll('[class*=tag i],[class*=feature i],[class*=badge i],[class*=chip i],ul li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(v => v && v.length < 60 && !bad.test(v)); return Array.from(new Set(vals))[1] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tag3",
            "selector": "(() => { const bad = /(€|m²|mq|local|prezzo|superficie|annuncio|contatta|telefono)/i; const vals = Array.from(ROW.querySelectorAll('[class*=tag i],[class*=feature i],[class*=badge i],[class*=chip i],ul li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(v => v && v.length < 60 && !bad.test(v)); return Array.from(new Set(vals))[2] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tag4",
            "selector": "(() => { const bad = /(€|m²|mq|local|prezzo|superficie|annuncio|contatta|telefono)/i; const vals = Array.from(ROW.querySelectorAll('[class*=tag i],[class*=feature i],[class*=badge i],[class*=chip i],ul li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(v => v && v.length < 60 && !bad.test(v)); return Array.from(new Set(vals))[3] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tag5",
            "selector": "(() => { const bad = /(€|m²|mq|local|prezzo|superficie|annuncio|contatta|telefono)/i; const vals = Array.from(ROW.querySelectorAll('[class*=tag i],[class*=feature i],[class*=badge i],[class*=chip i],ul li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(v => v && v.length < 60 && !bad.test(v)); return Array.from(new Set(vals))[4] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "tag6",
            "selector": "(() => { const bad = /(€|m²|mq|local|prezzo|superficie|annuncio|contatta|telefono)/i; const vals = Array.from(ROW.querySelectorAll('[class*=tag i],[class*=feature i],[class*=badge i],[class*=chip i],ul li')).map(e => (e.textContent || '').replace(/\\s+/g, ' ').trim()).filter(v => v && v.length < 60 && !bad.test(v)); return Array.from(new Set(vals))[5] || ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 2620,
      "position_y": 220,
      "config": {
        "selector": "[data-uscraper-next='true']",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 2620,
      "position_y": 560,
      "config": {
        "selector": "[data-uscraper-next='true']",
        "timeout": 10,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 2980,
      "position_y": 560,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 3340,
      "position_y": 560,
      "config": {
        "duration": 2,
        "color": "bg-[#8d8d8d]"
      }
    },
    {
      "block_id": "inject-javascript-3",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on the page",
      "position_x": 3700,
      "position_y": 560,
      "config": {
        "jsCode": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const markCards = () => { document.querySelectorAll('[data-uscraper-card=true]').forEach(e => { e.removeAttribute('data-uscraper-card'); e.removeAttribute('data-uscraper-url'); }); document.querySelectorAll('[data-uscraper-next]').forEach(e => e.removeAttribute('data-uscraper-next')); const links = Array.from(document.querySelectorAll('a[href*=\"/annunci/\"]')).filter(a => a.href && !/\\.(jpg|jpeg|png|webp|gif)$/i.test(a.href)); const seenUrls = new Set(); const pickCard = a => { let best = a, bestScore = -999; for (let el = a; el && el !== document.body && el !== document.documentElement; el = el.parentElement) { const text = clean(el.innerText || el.textContent || ''); const annLinks = el.querySelectorAll ? el.querySelectorAll('a[href*=\"/annunci/\"]').length : 1; const cls = String(el.className || ''); let score = 0; if (el !== a) score += 3; if (/card|result|annunc|listing|item|realEstate|estate/i.test(cls)) score += 12; if (el.querySelector && el.querySelector('img')) score += 8; if (/€|euro/i.test(text)) score += 25; if (/\\d+[\\d.,]*\\s*(m²|mq)/i.test(text)) score += 20; if (/\\d+\\s*local[ei]|monolocale|bilocale|trilocale|quadrilocale/i.test(text)) score += 16; if (text.length > 70) score += 8; if (text.length > 180) score += 5; if (text.length > 900) score -= 12; if (text.length > 1800) score -= 35; if (annLinks > 2) score -= (annLinks - 2) * 12; if (annLinks > 8) score -= 80; if (score > bestScore) { bestScore = score; best = el; } } return best; }; links.forEach(a => { const href = a.href.split('#')[0]; if (seenUrls.has(href)) return; seenUrls.add(href); const card = pickCard(a); if (!card) return; card.setAttribute('data-uscraper-card', 'true'); card.setAttribute('data-uscraper-url', href); }); const nextCandidates = Array.from(document.querySelectorAll('a[href],button')).filter(el => { const label = ((el.textContent || '') + ' ' + (el.getAttribute('aria-label') || '') + ' ' + (el.getAttribute('title') || '') + ' ' + (el.getAttribute('rel') || '')).toLowerCase(); const disabled = el.matches('[disabled],[aria-disabled=true],.disabled,.is-disabled') || /disabled|inactive/.test(el.className || ''); return !disabled && (/\\bnext\\b|successiv|avanti/.test(label) || el.getAttribute('rel') === 'next'); }); const next = nextCandidates.find(el => el.href && el.href.split('#')[0] !== location.href.split('#')[0]) || nextCandidates[0]; if (next) next.setAttribute('data-uscraper-next', 'true'); return { cards: document.querySelectorAll('[data-uscraper-card=true]').length, next: !!next }; }; return markCards(); })()",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-3",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 4060,
      "position_y": 560,
      "config": {
        "selector": "[data-uscraper-card='true']",
        "timeout": 20,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 2620,
      "position_y": 900,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-2",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-2",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "element-exists-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "true",
      "to_block_id": "click-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "end-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-2",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-3",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-3",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 28,
      "position_y": 116,
      "width": 4280,
      "height": 636,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "wait-for-element-2",
          "wait-for-page-load-2",
          "sleep-1",
          "wait-for-element-3"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 748,
      "position_y": 116,
      "width": 3200,
      "height": 636,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1",
          "inject-javascript-2",
          "inject-javascript-3"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2188,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 2548,
      "position_y": 116,
      "width": 380,
      "height": 636,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "click-1"
        ]
      }
    },
    {
      "id": "group-control",
      "element_type": "group",
      "title": "Control Flow",
      "color": "#8d8d8d",
      "position_x": 2548,
      "position_y": 796,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes Trovacasa.it search/results pages such as https://www.trovacasa.it/case-in-affitto/milano and exports property listing data equivalent to the Octoparse Trovacasa Scraper: location, property type, title, page URL, neighborhood, surface area, rooms, price, description, image URL, and feature tags. Uses a click-next pagination loop to collect all available result pages. Sample detail URLs from the catalog currently returned 404 during analysis, so this is a best-effort live results-page scraper. Updated after test run to mark full listing cards rather than only title links.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const norm = s => (s || '').replace(/\\s+/g, ' ').trim().toLowerCase(); const controls = Arr...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1020,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const markCards = () => { documen...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1740,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-2"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (Posizione, Tipo_immobili, Titolo, URL_pagina, Quartiere). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2460,
      "position_y": 200,
      "width": 340,
      "height": 132,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-uscraper-next='true']`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2820,
      "position_y": 200,
      "width": 340,
      "height": 139,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-click-1",
      "element_type": "note",
      "title": "Note: Click",
      "content": "Pagination click — add waits after this block; the page reloads asynchronously.",
      "color": "#ee5396",
      "position_x": 2820,
      "position_y": 540,
      "width": 316,
      "height": 106,
      "z_index": 22,
      "data": {
        "block_id": "click-1"
      }
    }
  ]
}