It looks like this is a web page, not a feed. I looked for a feed associated with this page, but couldn't find one. Please enter the address of your feed to validate.

Source: https://pharmindia.online

  1. <!DOCTYPE html>
  2. <html>
  3.  
  4. <head>
  5.  <meta charset="utf-8" />
  6.  <title>Statistiche Live per Minuto</title>
  7.  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  8.  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&amp;display=swap" rel="stylesheet">
  9.  <style>
  10.    body {
  11.      font-family: 'Montserrat', sans-serif;
  12.      margin: 0;
  13.    }
  14.  
  15.    .match-header {
  16.      display: flex;
  17.      justify-content: space-between;
  18.      align-items: center;
  19.      flex-direction: row-reverse;
  20.      font-size: 10px;
  21.      background-color: #2E5339;
  22.      padding: 10px;
  23.      border-top-right-radius: 10px;
  24.      border-top-left-radius: 10px;
  25.    }
  26.  
  27.    .teams {
  28.      font-weight: bold;
  29.      font-size: 1.1em;
  30.    }
  31.  
  32.    .score {
  33.      font-size: 1.2em;
  34.      margin: 0 10px;
  35.    }
  36.  
  37.    table {
  38.      border-collapse: collapse;
  39.      width: 100%;
  40.    }
  41.  
  42.    th,
  43.    td {
  44.      border: 1px solid #ccc;
  45.      padding: 1px;
  46.      text-align: center;
  47.      font-size: 8px;
  48.      color: black;
  49.    }
  50.  
  51.    th {
  52.      background-color: #E8E4D8;
  53.    }
  54.  
  55.    th p {
  56.      writing-mode: vertical-rl;
  57.      transform: rotate(180deg);
  58.      white-space: nowrap;
  59.      padding: 0;
  60.      margin: 0;
  61.      font-size: 11px;
  62.    }
  63.  
  64.    .team-logo {
  65.      width: 20px;
  66.      vertical-align: middle;
  67.      margin-right: 4px;
  68.    }
  69.  
  70.    .time-col {
  71.      font-weight: bold;
  72.      background-color: #f0f0f0;
  73.      min-width: 35px;
  74.    }
  75.  
  76.    .filter-container {
  77.      border-bottom: 1px solid #ccc;
  78.      padding: 10px;
  79.      /* border-radius: 8px; */
  80.      background: #fafafa;
  81.    }
  82.  
  83.    .filter-container h3 {
  84.      margin: 0 0 10px 0;
  85.    }
  86.  
  87.    .checkbox-grid {
  88.      display: grid;
  89.      grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  90.      gap: 5px;
  91.    }
  92.  
  93.    label {
  94.      font-size: 13px;
  95.      cursor: pointer;
  96.    }
  97.  
  98.    input[type="checkbox"] {
  99.      vertical-align: middle;
  100.      margin-right: 4px;
  101.      cursor: pointer;
  102.    }
  103.  
  104.    .controls {
  105.      margin-top: 8px;
  106.      text-align: center;
  107.    }
  108.  
  109.    .controls button {
  110.      margin-right: 6px;
  111.    }
  112.  
  113.    #output {
  114.      display: grid;
  115.      grid-template-columns: repeat(3, 1fr);
  116.      gap: 20px;
  117.      text-align: -webkit-center;
  118.      padding: 20px 0;
  119.      background: #A5BE00;
  120.    }
  121.  
  122.    .match-card {
  123.      color: white;
  124.      width: 95%;
  125.      border-radius: 10px;
  126.      filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.5));
  127.    }
  128.  
  129.    .table-wrapper {
  130.      border-right: 1px solid #2E5339;
  131.      border-left: 1px solid #2E5339;
  132.    }
  133.  
  134.    /* ๐Ÿ”ต Colori per squadre */
  135.    .home-row {
  136.      background-color: #f5faff;
  137.      /* Azzurrino chiaro */
  138.    }
  139.  
  140.    .away-row {
  141.      background-color: #fff7f5;
  142.      /* Rosato chiaro */
  143.    }
  144.  
  145.    .separator {
  146.      background-color: #2E5339;
  147.      border: 0;
  148.    }
  149.  
  150.    .analisi {
  151.      background-color: #2E5339;
  152.      padding: 10px;
  153.      border-bottom-left-radius: 10px;
  154.      border-bottom-right-radius: 10px;
  155.      font-size: 0.8em;
  156.    }
  157.  
  158.  
  159.    @media (max-width: 1200px) {
  160.      #output {
  161.        grid-template-columns: repeat(2, 1fr);
  162.        /* 2 schede per riga */
  163.      }
  164.    }
  165.  
  166.    @media (max-width: 768px) {
  167.      #output {
  168.        grid-template-columns: 1fr;
  169.        /* 1 scheda per riga su mobile */
  170.      }
  171.    }
  172.  </style>
  173. </head>
  174.  
  175. <body>
  176.  <div class="filter-container">
  177.    <!-- <h3>Seleziona statistiche da visualizzare:</h3> -->
  178.    <div id="checkboxContainer" class="checkbox-grid"></div>
  179.    <div class="controls">
  180.      <!-- <button id="selectAll">Seleziona tutto</button>
  181.      <button id="deselectAll">Deseleziona tutto</button> -->
  182.      <label style="margin-left:12px;">
  183.        Aggiorna ogni
  184.        <input id="refreshInterval" type="number" value="60" min="5"
  185.          style="width:70px; margin-left:6px; margin-right:6px;"> s
  186.      </label>
  187.      <button id="applyInterval">Applica</button>
  188.    </div>
  189.  </div>
  190.  
  191.  <div id="output"></div>
  192.  
  193.  <script>
  194.    const statMap = {
  195.      ball_possession: "Ball Possession",
  196.      blocked_shots: "Blocked Shots",
  197.      corner_kicks: "Corner Kicks",
  198.      expected_goals: "Expected Goals",
  199.      fouls: "Fouls",
  200.      goalkeeper_saves: "Goalkeeper Saves",
  201.      goals_prevented: "Goals Prevented",
  202.      offsides: "Offsides",
  203.      "passes_%": "Passes %",
  204.      passes_accurate: "Passes Accurate",
  205.      red_cards: "Red Cards",
  206.      shots_insidebox: "Shots Inside Box",
  207.      shots_off_goal: "Shots Off Goal",
  208.      shots_on_goal: "Shots On Goal",
  209.      shots_outsidebox: "Shots Outside Box",
  210.      total_passes: "Total Passes",
  211.      total_shots: "Total Shots",
  212.      yellow_cards: "Yellow Cards",
  213.      gol: "Goals",
  214.    };
  215.  
  216.  
  217.    const defaultStats = [
  218.      "gol",
  219.      "ball_possession",
  220.      "shots_insidebox",
  221.      "shots_on_goal",
  222.      "total_shots",
  223.      "shots_off_goal",
  224.      "shots_outsidebox",
  225.      "goalkeeper_saves",
  226.      "red_cards"
  227.    ];
  228.  
  229.    const columnOrder = [
  230.      "gol",
  231.      "total_shots",
  232.      "shots_on_goal",
  233.      "shots_off_goal",
  234.      "shots_insidebox",
  235.      "shots_outsidebox",
  236.      "ball_possession",
  237.      "goalkeeper_saves",
  238.      "red_cards"
  239.    ];
  240.  
  241.    let REFRESH_INTERVAL = 60000;
  242.    let refreshTimer;
  243.  
  244.    // โœ… Parsing sicuro dei numeri e percentuali
  245.    const parseNum = v => {
  246.      if (v === undefined || v === null) return 0;
  247.      const clean = String(v).replace("%", "").trim();
  248.      const num = parseFloat(clean);
  249.      return isNaN(num) ? 0 : num;
  250.    };
  251.  
  252.    // โœ… Calcolo indice pressione aggiornato
  253.    function calcolaIndicePressione(rows, side = "home") {
  254.      if (!rows || rows.length < 2) return 0;
  255.  
  256.      const latest = rows.at(-1);
  257.      const old = rows[rows.length - 10];
  258.  
  259.      
  260.      const dShots = parseNum(latest[`${side}_total_shots`]) - parseNum(old[`${side}_total_shots`]);
  261.      const dOnGoal = parseNum(latest[`${side}_shots_on_goal`]) - parseNum(old[`${side}_shots_on_goal`]);
  262.      const dInside = parseNum(latest[`${side}_shots_insidebox`]) - parseNum(old[`${side}_shots_insidebox`]);
  263.      const dPoss = parseNum(latest[`${side}_ball_possession`]) - parseNum(old[`${side}_ball_possession`]);
  264.      
  265.      //console.log("team:", latest[`team_${side}_name`])
  266.      //console.log("dShots:",dShots)
  267.      //console.log("dOnGoal:",dOnGoal)
  268.      //console.log("dInside:",dInside)
  269.      //console.log("dPoss:",dPoss)
  270.  
  271.      const indice = Math.max(0, dShots * 2 + dOnGoal * 3 + dInside * 2 + dPoss * 0.4);
  272.      return Number(indice.toFixed(2));
  273.    }
  274.  
  275.    // โœ… Analisi testuale migliorata
  276.    function generaAnalisi(rows) {
  277.      const ph = calcolaIndicePressione(rows, "home");
  278.      console.log("ph", ph)
  279.      const pa = calcolaIndicePressione(rows, "away");
  280.      console.log("pa", pa)
  281.  
  282.  
  283.      const soglia = 8;
  284.      let testo = "";
  285.  
  286.      if (ph >= soglia && pa >= soglia) {
  287.        if (Math.abs(ph - pa) < 2)
  288.          testo = "โš–๏ธ Entrambe le squadre in pressione simile";
  289.        else if (ph > pa)
  290.          testo = "๐Ÿ‘‰ Pressione leggermente maggiore squadra di casa";
  291.        else
  292.          testo = "โš ๏ธ Pressione leggermente maggiore squadra ospite";
  293.      } else if (ph >= soglia) {
  294.        testo = "๐Ÿ”ฅ Pressione alta squadra di casa โ€” possibile gol HOME";
  295.      } else if (pa >= soglia) {
  296.        testo = "๐Ÿšจ Pressione alta squadra ospite โ€” possibile gol AWAY";
  297.      } else {
  298.        testo = "Partita stabile, nessuna pressione marcata.";
  299.      }
  300.  
  301.      return `<div class="analisi">${testo}</div>`;
  302.    }
  303.  
  304.    // Impostazioni di aggiornamento
  305.    const restartAutoRefresh = () => {
  306.      clearInterval(refreshTimer);
  307.      refreshTimer = setInterval(caricaCSV, REFRESH_INTERVAL);
  308.    };
  309.  
  310.    function creaCheckbox() {
  311.      document.getElementById("applyInterval").onclick = () => {
  312.        REFRESH_INTERVAL = Math.max(5000, +document.getElementById("refreshInterval").value * 1000 || 60000);
  313.        restartAutoRefresh();
  314.      };
  315.    }
  316.  
  317.    async function caricaCSV() {
  318.      const scrollY = window.scrollY;
  319.      try {
  320.        const text = await (await fetch("livestats/partite_live_stats.csv?_=" + Date.now(), { cache: "no-store" })).text();
  321.        if (text.startsWith("<!DOCTYPE html>")) return console.warn("โš ๏ธ HTML ricevuto invece del CSV");
  322.  
  323.        const [header, ...rows] = text.trim().split("\n");
  324.        const headers = header.split(",");
  325.        const data = rows.map(r => Object.fromEntries(headers.map((h, i) => [h.trim(), (r.split(",")[i] || "").trim()])));
  326.  
  327.        const matches = data.reduce((acc, r) => {
  328.          (acc[r.partita_id] ??= []).push(r);
  329.          return acc;
  330.        }, {});
  331.  
  332.        const container = document.getElementById("output");
  333.        container.innerHTML = "";
  334.  
  335.        // Ordina partite per pressione totale
  336.        const matchList = Object.values(matches).map(rows => {
  337.          const ph = calcolaIndicePressione(rows, "home");
  338.          const pa = calcolaIndicePressione(rows, "away");
  339.          const pressioneTotale = ph + pa;
  340.          return { rows, pressioneTotale };
  341.        }).sort((a, b) => b.pressioneTotale - a.pressioneTotale);
  342.  
  343.        matchList.forEach(({ rows }) => {
  344.          const tabellaDiv = document.createElement("div");
  345.          tabellaDiv.innerHTML = creaTabella(rows, Object.keys(rows[0]));
  346.          container.appendChild(tabellaDiv);
  347.        });
  348.  
  349.        requestAnimationFrame(() => window.scrollTo(0, scrollY));
  350.      } catch (e) {
  351.        console.error("Errore fetch CSV:", e);
  352.      }
  353.    }
  354.  
  355.  
  356.    function creaTabella(rows, headers) {
  357.      const last = rows.at(-1);
  358.  
  359.      const homeStats = Array.from(new Set(columnOrder
  360.        .map(stat => stat === "gol_home" ? "gol_home" : "home_" + stat.replace(/^(home_|away_)/, ""))
  361.        .filter(h => headers.includes(h))
  362.      ));
  363.  
  364.      const awayStats = Array.from(new Set(columnOrder
  365.        .map(stat => stat === "gol_away" ? "gol_away" : "away_" + stat.replace(/^(home_|away_)/, ""))
  366.        .filter(h => headers.includes(h))
  367.      ));
  368.  
  369.      function parseNum(v) {
  370.        const n = parseInt(v, 10);
  371.        return isNaN(n) ? 0 : n;
  372.      }
  373.  
  374.      // Mantiene solo l'ultima riga per ogni combinazione tempo + extra-time
  375.      const uniqueByTime = Object.values(
  376.        rows.reduce((acc, r) => {
  377.          const mt = parseNum(r.match_time);
  378.          const et = parseNum(r.match_extra_time);
  379.          const key = `${mt}-${et}`;
  380.          acc[key] = r;
  381.          return acc;
  382.        }, {})
  383.      );
  384.  
  385.      // Ordina in ordine decrescente per tempo
  386.      const sorted = uniqueByTime.sort((a, b) => {
  387.        const mtA = parseNum(a.match_time);
  388.        const mtB = parseNum(b.match_time);
  389.        const etA = parseNum(a.match_extra_time);
  390.        const etB = parseNum(b.match_extra_time);
  391.        if (mtA === mtB) return etB - etA;
  392.        return mtB - mtA;
  393.      });
  394.  
  395.      // Colonne = minuti
  396.      const timeCols = sorted.map(r => {
  397.        const mt = parseNum(r.match_time);
  398.        const et = parseNum(r.match_extra_time);
  399.        return r.match_status === "FT"
  400.          ? "FT"
  401.          : mt ? `${mt}${et ? "+" + et : ""}` : "";
  402.      });
  403.  
  404.      const visibleCols = timeCols.slice(0, 10);
  405.      const visibleSorted = sorted.slice(0, 10);
  406.  
  407.      // Righe per squadra (senza nome squadra)
  408.      // ๐Ÿ” Invertito: righe = minuti, colonne = statistiche
  409.      function mkTimeRows(side, stats, colorClass) {
  410.        return visibleSorted.map((r, idx) => {
  411.          const mt = parseNum(r.match_time);
  412.          const et = parseNum(r.match_extra_time);
  413.          const label = r.match_status === "FT"
  414.            ? "FT"
  415.            : mt ? `${mt}${et ? "+" + et : ""}` : "";
  416.  
  417.          let rowHtml = `<tr class="${colorClass}"><td class="time">${label}</td>`;
  418.  
  419.          for (let s of stats) {
  420.            let rawValue = r[s];
  421.            if (rawValue === undefined || rawValue === "") {
  422.              rowHtml += "<td></td>";
  423.              continue;
  424.            }
  425.  
  426.            const isPercent = s.includes("ball_possession") || String(rawValue).includes("%");
  427.            const current = parseFloat(String(rawValue).replace("%", "")) || 0;
  428.            const prev = parseFloat(String(visibleSorted[idx + 1]?.[s] || "").replace("%", "")) || current;
  429.  
  430.            const diff = current - prev;
  431.            const diffText = diff !== 0 ? `<span style="color:${diff > 0 ? 'green' : 'red'}; font-size:9px;"> (${diff > 0 ? '+' : ''}${diff.toFixed(isPercent ? 1 : 0)})</span>` : "";
  432.  
  433.            let bg = "";
  434.            if (diff > 0) bg = "background-color: rgba(0, 200, 0, 0.15);";
  435.            else if (diff < 0) bg = "background-color: rgba(255, 0, 0, 0.1);";
  436.  
  437.            const displayValue = isPercent ? current.toFixed(1) + "%" : current;
  438.            rowHtml += `<td style="${bg}">${displayValue}${diffText}</td>`;
  439.          }
  440.  
  441.          rowHtml += "</tr>";
  442.          return rowHtml;
  443.        }).join("");
  444.      }
  445.  
  446.      const id = `tbl-${Math.random().toString(36).slice(2, 9)}`;
  447.  
  448.      return `
  449. <div class="match-card rotated">
  450.  <div class="match-header">
  451.    <div class="league">${last.league_name} | ${last.league_country}</div>
  452.    <div class="teams">
  453.      <div class="result">
  454.        <img src="${last.team_home_logo}" class="team-logo">
  455.        ${last.team_home_name} ${last.home_gol} - ${last.away_gol} ${last.team_away_name}
  456.        <img src="${last.team_away_logo}" class="team-logo">
  457.      </div>
  458.    </div>
  459.  </div>
  460.  
  461.  <div class="table-wrapper">
  462.    <table id="${id}" class="rotated-table">
  463.      <tr>
  464.        <th></th>
  465.        ${homeStats.map(s => `<th>${statMap[s.replace("home_", "")] || s}</th>`).join("")}
  466.      </tr>
  467.      ${mkTimeRows("home", homeStats, "home-row")}
  468.      <tr><td colspan="${homeStats.length + 1}" class="separator"></td></tr>
  469.      ${mkTimeRows("away", awayStats, "away-row")}
  470.    </table>
  471.  </div>
  472.  ${generaAnalisi(rows)}
  473. </div>
  474. `;
  475.  
  476.    }
  477.  
  478.    function updateTableCols(table, times, rows) {
  479.      const headerRow = table.querySelector("tr");
  480.      const statRows = [...table.querySelectorAll("tr:not(:first-child)")];
  481.      headerRow.innerHTML = `<th colspan="2"></th>${times.map(t => `<th>${t}</th>`).join("")}`;
  482.      statRows.forEach(tr => {
  483.        const statKey = tr.querySelector(".stat-name")?.textContent;
  484.        if (!statKey) return;
  485.      });
  486.    }
  487.  
  488.    // ๐Ÿ”น Gestione visibilitร  colonne
  489.    function aggiornaVisibilitaColonne() {
  490.      const selected = getCheckedStats();
  491.      document.querySelectorAll("th[data-stat], td[data-stat]").forEach(c => {
  492.        c.style.display = selected.includes(c.dataset.stat) ? "" : "none";
  493.      });
  494.      document.querySelectorAll("table").forEach(t => {
  495.        const home = t.querySelectorAll('th[data-side="home"]:not([style*="display: none"])').length;
  496.        const away = t.querySelectorAll('th[data-side="away"]:not([style*="display: none"])').length;
  497.        const ths = t.querySelectorAll("tr:first-child th");
  498.        if (ths[1]) ths[1].colSpan = home || 1;
  499.        if (ths[2]) ths[2].colSpan = away || 1;
  500.      });
  501.    }
  502.  
  503.  
  504.    creaCheckbox();
  505.    caricaCSV();
  506.    restartAutoRefresh();
  507.  </script>
  508.  
  509.  
  510. </body>
  511.  
  512. </html>
Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda