Explore the Realm of Digital Marketing and Tech News

Rank Tracker Sample

Nov 15, 2025 | Uncategorized | 0 comments

Written By Manisha Kamble

Hello how are you doing today?

<!--
  Paste this entire block into a Divi Code Module (or any HTML file).
  Uses Chart.js from CDN. Mock data included. Replace `mockData` with your API response.
-->

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
  /* Basic dashboard styling (adjust to match Divi theme) */
  .csp-dashboard { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; color:#222; padding:18px; max-width:1200px; margin:0 auto; }
  .csp-row { display:flex; gap:16px; margin-bottom:18px; flex-wrap:wrap; }
  .card { background:#fff; border-radius:12px; box-shadow:0 6px 20px rgba(20,20,30,0.06); padding:16px; flex:1 1 220px; min-width:220px; }
  .kpi { display:flex; align-items:center; justify-content:space-between; gap:12px; }
  .kpi .label { color:#666; font-size:13px; }
  .kpi .value { font-weight:700; font-size:20px; }
  .small-muted { color:#888; font-size:13px; margin-top:6px; }

  /* Charts area */
  #charts { display:grid; grid-template-columns: 1fr 360px; gap:16px; align-items:start; }
  .chart-card { padding:12px; }
  .legend-pill { display:inline-block; margin-right:8px; font-size:13px; color:#444; }

  /* Winners/losers */
  .wl { display:flex; gap:12px; }
  .wl .list { flex:1; }
  .wl .item { display:flex; align-items:center; justify-content:space-between; padding:8px 6px; border-radius:8px; }
  .up { color:#107c10; font-weight:700; }
  .down { color:#c32a2a; font-weight:700; }

  /* Table */
  table.csp-table { width:100%; border-collapse:collapse; margin-top:12px; font-size:14px; }
  table.csp-table th, table.csp-table td { padding:8px 6px; text-align:left; border-bottom:1px solid #f0f0f2; }
  .spark-canvas { width:100px; height:26px; }

  /* Responsive tweaks */
  @media (max-width:920px) {
    #charts { grid-template-columns: 1fr; }
    .card { min-width:100%; }
  }
</style>

<div class="csp-dashboard">
  <h2 style="margin:0 0 10px 0;">Rank Tracker — Snapshot</h2>

  <!-- KPIs -->
  <div class="csp-row">
    <div class="card kpi">
      <div>
        <div class="label">Total Keywords</div>
        <div class="value" id="kpi-total">—</div>
        <div class="small-muted">Tracked keywords in project</div>
      </div>
      <div style="font-size:28px;color:#5b8cfe;"><i class="fa fa-chart-line"></i></div>
    </div>

    <div class="card kpi">
      <div>
        <div class="label">Improved (7d)</div>
        <div class="value" id="kpi-up">—</div>
        <div class="small-muted">Keywords that moved up</div>
      </div>
      <div style="font-size:28px;color:#2dbb6b;"><i class="fa fa-arrow-up"></i></div>
    </div>

    <div class="card kpi">
      <div>
        <div class="label">Dropped (7d)</div>
        <div class="value" id="kpi-down">—</div>
        <div class="small-muted">Keywords that lost positions</div>
      </div>
      <div style="font-size:28px;color:#ff6b6b;"><i class="fa fa-arrow-down"></i></div>
    </div>

    <div class="card kpi">
      <div>
        <div class="label">Avg. Position</div>
        <div class="value" id="kpi-avg">—</div>
        <div class="small-muted">Lower is better</div>
      </div>
      <div style="font-size:28px;color:#7c3aed;"><i class="fa fa-hashtag"></i></div>
    </div>
  </div>

  <!-- Charts and side widgets -->
  <div id="charts">
    <div class="card chart-card">
      <div style="display:flex; align-items:center; justify-content:space-between;">
        <strong>Visibility & Avg. Position (30d)</strong>
        <div class="legend-pill"><small>Visibility</small></div>
      </div>
      <canvas id="lineChart" height="160"></canvas>

      <div class="csp-row" style="margin-top:12px;">
        <div style="flex:1">
          <div style="font-size:13px;color:#666">Visibility</div>
          <div style="font-weight:700;font-size:18px" id="visibility-score">—</div>
        </div>
        <div style="flex:1">
          <div style="font-size:13px;color:#666">30d Change</div>
          <div style="font-weight:700;font-size:18px" id="change-30d">—</div>
        </div>
      </div>
    </div>

    <div class="card">
      <strong>Distribution</strong>
      <canvas id="donutChart" height="260"></canvas>
      <div class="small-muted" style="margin-top:8px;">Keywords by position buckets</div>
    </div>
  </div>

  <!-- Winners / Losers -->
  <div class="csp-row" style="margin-top:14px;">
    <div class="card" style="flex:0 0 60%;">
      <strong>Top Keywords</strong>
      <div class="wl" style="margin-top:10px;">
        <div class="list" id="winners-list">
          <!-- Winners injected here -->
        </div>
        <div class="list" id="losers-list">
          <!-- Losers injected here -->
        </div>
      </div>

      <table class="csp-table" id="keywords-table">
        <thead><tr><th>Keyword</th><th>Rank</th><th>Change</th><th>Volume</th><th>Trend</th></tr></thead>
        <tbody></tbody>
      </table>
    </div>

    <div class="card" style="flex:0 0 36%;">
      <strong>Quick Insights (AI)</strong>
      <div id="ai-insights" style="margin-top:10px; color:#444">Loading AI summary…</div>

      <div style="margin-top:14px;">
        <strong>Search Features</strong>
        <div class="small-muted" style="margin-top:6px;">Featured snippet: <b id="feat-snip">2</b> • PAA: <b id="paa">5</b></div>
        <div style="margin-top:10px;">
          <strong>Top Competitors</strong>
          <ul id="top-competitors" style="margin-top:8px; padding-left:18px;"></ul>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Chart.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>

<script>
/* -------------------------
   MOCK DATA (replace with API results)
   ------------------------- */
const mockData = {
  meta: { totalKeywords: 178, visibility: 23.8, avgPosition: 17.4, change30d: "+2.3%" },
  trend: {
    days: Array.from({length:30}, (_,i)=> {
      const d = new Date(); d.setDate(d.getDate()- (29-i));
      return d.toISOString().slice(0,10);
    }),
    visibility: [13,14,13.5,14.2,14.8,15,15.8,16.3,16.9,17.1,17.7,18.2,18.9,19.5,19.7,20.1,20.4,20.9,21.2,21.6,22.0,22.6,23.0,23.5,23.7,23.9,24.1,24.3,24.5,23.8],
    avgPos: [20,19.8,20.1,19.6,19.4,19.2,18.9,18.7,18.6,18.4,18.2,18.1,18.0,17.9,17.8,17.7,17.6,17.5,17.5,17.4,17.4,17.3,17.2,17.1,17.1,17.2,17.3,17.4,17.4,17.4]
  },
  distribution: { top3: 12, top10: 44, top30: 80, beyond30: 42 },
  winners: [
    {k:"best budget smartphone 2025", pos:8, change:+6, vol:2900, trend:[12,11,10,9,8,8]},
    {k:"how to fix seo issues", pos:3, change:+5, vol:900, trend:[7,6,5,4,3,3]},
    {k:"best local seo tools india", pos:13, change:+4, vol:320, trend:[20,19,18,16,14,13]}
  ],
  losers: [
    {k:"wordpress speed tips", pos:45, change:-7, vol:1100, trend:[5,7,10,12,18,45]},
    {k:"onpage seo checklist", pos:22, change:-6, vol:780, trend:[8,10,13,18,20,22]}
  ],
  features: { featured_snippet:2, paa:5 },
  competitors: ["competitorA.com", "competitorB.com", "competitorC.com"],
  table: [
    {k:"best budget smartphone 2025", pos:8, change:+6, vol:2900, trend:[12,11,10,9,8,8]},
    {k:"how to fix seo issues", pos:3, change:+5, vol:900, trend:[7,6,5,4,3,3]},
    {k:"wordpress speed tips", pos:45, change:-7, vol:1100, trend:[5,7,10,12,18,45]},
    {k:"onpage seo checklist", pos:22, change:-6, vol:780, trend:[8,10,13,18,20,22]}
  ]
};

/* -------------------------
   Render KPIs
   ------------------------- */
document.getElementById('kpi-total').innerText = mockData.meta.totalKeywords;
document.getElementById('kpi-up').innerText = mockData.winners.length;
document.getElementById('kpi-down').innerText = mockData.losers.length;
document.getElementById('kpi-avg').innerText = mockData.meta.avgPosition;
document.getElementById('visibility-score').innerText = mockData.meta.visibility + "%";
document.getElementById('change-30d').innerText = mockData.meta.change30d;
document.getElementById('feat-snip').innerText = mockData.features.featured_snippet;
document.getElementById('paa').innerText = mockData.features.paa;

/* -------------------------
   Line Chart (Visibility + Avg pos)
   ------------------------- */
const lineCtx = document.getElementById('lineChart').getContext('2d');

const lineChart = new Chart(lineCtx, {
  type: 'line',
  data: {
    labels: mockData.trend.days,
    datasets: [
      {
        type: 'line',
        label: 'Visibility',
        data: mockData.trend.visibility,
        yAxisID: 'A',
        tension: 0.3,
        pointRadius: 0,
        borderWidth: 2,
        fill: false
      },
      {
        type: 'line',
        label: 'Avg Position',
        data: mockData.trend.avgPos,
        yAxisID: 'B',
        tension: 0.3,
        pointRadius: 0,
        borderDash: [6,4],
        borderWidth: 1.6,
        fill: false
      }
    ]
  },
  options: {
    interaction: { mode: 'index', intersect: false },
    stacked: false,
    plugins: { legend: { display:true, position:'bottom' } },
    scales: {
      A: { position: 'left', title: { display:true, text:'Visibility (%)' } },
      B: { position: 'right', title: { display:true, text:'Average Position' }, reverse: true }
    }
  }
});

/* -------------------------
   Donut Chart (distribution)
   ------------------------- */
const donutCtx = document.getElementById('donutChart').getContext('2d');
const donut = new Chart(donutCtx, {
  type: 'doughnut',
  data: {
    labels: ['Top 3','Top 10','Top 30','>30'],
    datasets: [{
      data: [mockData.distribution.top3, mockData.distribution.top10, mockData.distribution.top30, mockData.distribution.beyond30]
    }]
  },
  options: { plugins:{ legend:{position:'bottom'} } }
});

/* -------------------------
   Winners / Losers lists
   ------------------------- */
function makeWLItem(item, isUp=true) {
  const el = document.createElement('div');
  el.className = 'item';
  el.style.border = '1px dashed rgba(0,0,0,0.04)';
  el.innerHTML = `<div style="max-width:70%"><strong style="display:block">${item.k}</strong><small class="small-muted">Vol: ${item.vol}</small></div>
                  <div style="text-align:right">
                    <div class="${isUp ? 'up' : 'down'}">${isUp ? '+'+item.change : item.change}</div>
                    <div style="font-size:13px;color:#555">Pos ${item.pos}</div>
                  </div>`;
  return el;
}
const winnersWrap = document.getElementById('winners-list');
mockData.winners.forEach(w => winnersWrap.appendChild(makeWLItem(w,true)));
const losersWrap = document.getElementById('losers-list');
mockData.losers.forEach(l => losersWrap.appendChild(makeWLItem(l,false)));

/* -------------------------
   Table with mini-sparklines (creates small canvas for each row)
   ------------------------- */
const tbody = document.querySelector('#keywords-table tbody');
mockData.table.forEach((row, idx) => {
  const tr = document.createElement('tr');
  const sparkId = 'spark-'+idx;
  tr.innerHTML = `<td><strong>${row.k}</strong><div class="small-muted">${row.k.split(' ').slice(0,6).join(' ')}</div></td>
                  <td>${row.pos}</td>
                  <td style="color:${row.change>0? '#0a8b3a':'#c32a2a'}">${row.change>0? '+'+row.change:row.change}</td>
                  <td>${row.vol.toLocaleString()}</td>
                  <td><canvas id="${sparkId}" class="spark-canvas"></canvas></td>`;
  tbody.appendChild(tr);

  // sparkline chart
  const ctx = document.getElementById(sparkId).getContext('2d');
  new Chart(ctx, {
    type: 'line',
    data: { labels: row.trend.map((_,i)=>i+1), datasets:[{data: row.trend, tension:0.4, pointRadius:0, borderWidth:1.4}] },
    options: { plugins:{legend:{display:false}}, scales:{x:{display:false}, y:{display:false}} }
  });
});

/* -------------------------
   AI summary (mock) — replace with real ChatGPT response when integrated
   ------------------------- */
document.getElementById('ai-insights').innerText = "Rank drop for some keywords appears due to competitor new backlinks and a small SERP volatility spike. Focus on improving content relevance for 'wordpress speed tips' and reclaim lost topical backlinks.";

/* -------------------------
   Top competitors listing
   ------------------------- */
const compList = document.getElementById('top-competitors');
mockData.competitors.forEach(c => {
  const li = document.createElement('li'); li.innerText = c; compList.appendChild(li);
});

/* -------------------------
   Real API integration notes:
   - Replace mockData with fetched JSON from your plugin AJAX endpoint:
     fetch('/wp-json/csp/v1/rank-summary').then(r=>r.json()).then(data => { /* use data */ })
   - Ensure server returns:
     { meta:{totalKeywords, visibility, avgPosition, change30d}, trend:{days,visibility,avgPos}, distribution, winners, losers, features, competitors, table }
   - I can provide the WP REST endpoint code next.
   ------------------------- */
</script>

Written By Manisha Kamble

undefined

Explore More Insights

How does cybersecurity work

How does cyber security work for small startups?

Learn how cybersecurity works for small startups, from securing networks and data to preventing cyber threats with simple, affordable solutions. Introduction Today, digital tools are essential for how businesses operate and grow. From email and payment to customer…

Read More

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *