<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://kanak8278.github.io//feed.xml" rel="self" type="application/atom+xml" /><link href="https://kanak8278.github.io//" rel="alternate" type="text/html" /><updated>2026-06-29T17:51:11+00:00</updated><id>https://kanak8278.github.io//feed.xml</id><title type="html">Kanak Raj</title><subtitle>Personal Website</subtitle><author><name>Kanak Raj</name><email>kanak8278@gmail.com</email></author><entry><title type="html">The Disappearing Parliament: What India’s Legislature Actually Does With Its Time</title><link href="https://kanak8278.github.io//blog/india-parliament-data-audit/" rel="alternate" type="text/html" title="The Disappearing Parliament: What India’s Legislature Actually Does With Its Time" /><published>2026-04-15T00:00:00+00:00</published><updated>2026-04-15T00:00:00+00:00</updated><id>https://kanak8278.github.io//blog/india-parliament-data-audit</id><content type="html" xml:base="https://kanak8278.github.io//blog/india-parliament-data-audit/"><![CDATA[<div style="border-left:3px solid #f59e0b; padding:12px 18px; background:rgba(245,158,11,0.06); border-radius:0 6px 6px 0; margin-bottom:2rem;">
<p style="margin:0; color:#d1b07a; font-size:0.96em; line-height:1.75;">
The 17th Lok Sabha (2019–2024) averaged <strong style="color:#f59e0b">55 sitting days a year</strong> — the lowest of any full-term parliament. The National Commission to Review the Constitution recommended a minimum of 120 days. India's Constitution recommends no minimum at all. Every number in this post is sourced directly from PRS Legislative Research, PIB press releases, or official parliamentary records. Where sources disagree, both figures are shown.
</p>
</div>

<h2 id="the-same-parliament-two-sessions-apart">The Same Parliament, Two Sessions Apart</h2>

<p>2025 opened with a Budget Session that exceeded its scheduled time. Then the Monsoon Session functioned at 29% of its scheduled hours. Same parliament. Four months apart.</p>

<style>
#ipses-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#ipses-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 560px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="ipses-outer">
  <div id="ipses-wrap">
    <svg id="ipses-svg" viewBox="0 0 860 310" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('ipses-svg');

  /* Verified data — both figures from PRS and PIB where they differ.
     Sources:
       PRS Budget Session 2025: https://prsindia.org/parliamenttrack/vital-stats/parliament-functioning-in-budget-session-2025
       PRS Monsoon Session 2025: https://prsindia.org/parliamenttrack/vital-stats/parliament-functioning-in-monsoon-session-2025
       PIB Budget Session 2025:  https://www.pib.gov.in/PressReleasePage.aspx?PRID=2118954
       PIB Monsoon Session 2025: https://www.pib.gov.in/PressReleasePage.aspx?PRID=2159426  */
  var SESSIONS = [
    {
      label: 'Budget Session 2025',
      dates: 'Jan 31 – Apr 4, 2025',
      days: 26,
      color: '#10b981',
      lsProd: 111, lsProdSrc: 'PRS',
      lsProdAlt: 118, lsProdAltSrc: 'PIB',
      rsProd: 119, rsProdSrc: 'PIB',
      qhLS: 78, qhRS: 83,
      starredLS: 28, starredRS: 28,
      billsPassed: 10,
      hoursUsed: null,
      hoursAvail: null,
      note: 'Both houses exceeded\nscheduled time',
    },
    {
      label: 'Monsoon Session 2025',
      dates: 'Jul 21 – Aug 21, 2025',
      days: 21,
      color: '#ef4444',
      lsProd: 29, lsProdSrc: 'PRS',
      lsProdAlt: 31, lsProdAltSrc: 'PIB',
      rsProd: 34, rsProdSrc: 'PRS',
      qhLS: 23, qhRS: 6,
      starredLS: 8, starredRS: 5,
      billsPassed: 14,
      hoursUsed: 37,
      hoursAvail: 120,
      note: 'Two-thirds of planned\ntime lost to disruptions',
    },
  ];

  var W=860, H=310;
  var PW = W/2 - 20;  /* 410 per panel */
  var PH = H - 28;    /* 282 */
  var PAD = 20;       /* inner padding */

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }

  /* global bg */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* divider */
  svg.appendChild(mk('line',{x1:W/2,y1:16,x2:W/2,y2:H-16,stroke:'#1f2937','stroke-width':'1.5'}));

  SESSIONS.forEach(function(ses, si){
    var ox = si * (W/2);  /* x origin for this panel */
    var col = ses.color;

    /* panel bg tint */
    svg.appendChild(mk('rect',{x:ox+4,y:8,width:W/2-8,height:H-16,fill:col+'06',rx:6}));

    /* session label + dates */
    svg.appendChild(tx(ox+PAD, 26, ses.label,{
      fill:col,'font-size':'11.5','font-weight':'700','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(ox+PAD, 40, ses.dates + '  ·  ' + ses.days + ' sitting days',{
      fill:'#6b7280','font-size':'8.5','font-family':'system-ui,sans-serif'
    }));

    /* big LS productivity number (baseline at y=96, top of caps at ~57 — clear of dates at y=40) */
    var bigY = 96;
    svg.appendChild(tx(ox+PAD, bigY, ses.lsProd+'%',{
      fill:col,'font-size':'52','font-weight':'800','font-family':'system-ui,sans-serif'
    }));
    /* labels stack below number, no horizontal overlap */
    svg.appendChild(tx(ox+PAD, bigY+14, 'Lok Sabha · PRS ' + ses.lsProd+'%  /  PIB ~'+ses.lsProdAlt+'%',{
      fill:'#6b7280','font-size':'8','font-family':'system-ui,sans-serif'
    }));

    /* RS productivity */
    svg.appendChild(tx(ox+PAD, bigY+30, ses.rsProd+'%  Rajya Sabha ('+ses.rsProdSrc+')',{
      fill:'#3b82f6','font-size':'10.5','font-weight':'700','font-family':'system-ui,sans-serif'
    }));

    /* metric bars */
    var metrics = [
      { label:'Question Hour — LS', val:ses.qhLS, max:100 },
      { label:'Question Hour — RS', val:ses.qhRS, max:100 },
      { label:'Starred Qs answered orally (LS)', val:ses.starredLS, max:100 },
    ];
    var barY = 150;
    var barW = W/2 - PAD*2 - 54;  /* leave room for % label */
    metrics.forEach(function(m, mi){
      var y = barY + mi*30;
      svg.appendChild(tx(ox+PAD, y+5, m.label,{
        fill:'#6b7280','font-size':'8','font-family':'system-ui,sans-serif','dominant-baseline':'middle'
      }));
      /* track */
      svg.appendChild(mk('rect',{x:ox+PAD,y:y+10,width:barW,height:9,fill:'#111827',rx:4}));
      /* fill */
      var fw = (m.val/m.max)*barW;
      svg.appendChild(mk('rect',{x:ox+PAD,y:y+10,width:fw,height:9,fill:col,rx:4,opacity:'0.85'}));
      /* label */
      svg.appendChild(tx(ox+PAD+barW+6, y+15, m.val+'%',{
        fill:col,'font-size':'9','font-weight':'700','dominant-baseline':'middle',
        'font-family':'system-ui,sans-serif'
      }));
    });

    /* bottom stats row */
    var bY = 248;
    /* bills passed */
    svg.appendChild(tx(ox+PAD, bY, ses.billsPassed+'',{
      fill:'#e5e7eb','font-size':'22','font-weight':'800','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(ox+PAD, bY+14, 'bills passed',{
      fill:'#6b7280','font-size':'8','font-family':'system-ui,sans-serif'
    }));

    /* hours used (Monsoon only) */
    if(ses.hoursUsed !== null){
      svg.appendChild(tx(ox+PAD+68, bY, ses.hoursUsed+'',{
        fill:'#ef4444','font-size':'22','font-weight':'800','font-family':'system-ui,sans-serif'
      }));
      svg.appendChild(tx(ox+PAD+68, bY+14, 'of '+ses.hoursAvail+'h used (PIB)',{
        fill:'#6b7280','font-size':'8','font-family':'system-ui,sans-serif'
      }));
    }

    /* note */
    var noteLines = ses.note.split('\n');
    noteLines.forEach(function(ln, li){
      svg.appendChild(tx(ox+W/2-PAD-4, bY+(li-1)*11, ln,{
        fill:col+'88','font-size':'8','text-anchor':'end','font-family':'system-ui,sans-serif',
        'font-style':'italic'
      }));
    });
  });

  /* source */
  svg.appendChild(tx(4, H-4, 'Sources: PRS Legislative Research (prsindia.org) and Press Information Bureau (pib.gov.in) · PRS and PIB use different productivity calculation methods',{
    fill:'#1f2937','font-size':'6.5','font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>PRS and PIB use different productivity methodologies — both are shown. The gap between the two sessions, by either measure, is not marginal.</em></p>

<h2 id="the-bills-that-took-minutes">The Bills That Took Minutes</h2>

<p>In the Monsoon Session 2025, Parliament passed 14 bills (PRS count, excluding finance/appropriation). The chart below shows how much time Lok Sabha actually spent debating each of them, against the time formally allocated on the agenda.</p>

<style>
#ipbt-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#ipbt-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 580px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="ipbt-outer">
  <div id="ipbt-wrap">
    <svg id="ipbt-svg" viewBox="0 0 860 390" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('ipbt-svg');

  /* All times in minutes. allocLS = time allocated in LS agenda (minutes); null = not separately listed.
     dtp = days from first listing/introduction to passage.
     Source: PRS Legislative Research, Vital Stats Monsoon Session 2025
     URL: https://prsindia.org/parliamenttrack/vital-stats/parliament-functioning-in-monsoon-session-2025 */
  var BILLS = [
    { name:'Income-Tax (No.2) Bill, 2025',        ls:4,   rs:70,  dtp:1,   allocLS:null, note:'Replaced withdrawn IT Bill; introduced & passed same day' },
    { name:'Online Gaming Bill, 2025',             ls:6,   rs:23,  dtp:1,   allocLS:null, note:null },
    { name:'IIM (Amendment) Bill, 2025',           ls:7,   rs:74,  dtp:2,   allocLS:null, note:null },
    { name:'Merchant Shipping Bill, 2024',         ls:20,  rs:10,  dtp:244, allocLS:120,  note:'244 days since introduction' },
    { name:'Manipur GST (Amendment), 2025',        ls:22,  rs:40,  dtp:4,   allocLS:120,  note:null },
    { name:'Indian Ports Bill, 2025',              ls:24,  rs:123, dtp:143, allocLS:180,  note:'143 days since introduction' },
    { name:'Mines & Minerals (Amdt), 2025',        ls:30,  rs:154, dtp:8,   allocLS:null, note:null },
    { name:'Sports Governance Bill, 2025',         ls:34,  rs:128, dtp:20,  allocLS:480,  note:'8 hrs allocated; 34 mins used' },
    { name:'Goa ST Reservation Bill, 2024',        ls:117, rs:23,  dtp:371, allocLS:120,  note:'371 days since introduction' },
  ];

  var W=860, H=390;
  var L=222, R=158, T=56, B=28;
  var PW = W-L-R;   /* 480 */
  var NB = BILLS.length;
  var ROW_H = 30, GAP = 6;
  var MAX_MIN = 120; /* scale: 2 hours = full bar width */

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }
  function fmt(m){
    if(m<60) return m+'\u202fmin';
    var h=Math.floor(m/60), r=m%60;
    return r ? h+'h\u202f'+r+'m' : h+'h';
  }

  /* background */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 13, 'How Long Parliament Actually Debated Each Bill — Monsoon Session 2025',{
    fill:'#6b7280','font-size':'10','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* sub-note */
  svg.appendChild(tx(W/2, 24, 'Bar = time actually spent in Lok Sabha  ·  Scale 0–2 hours  ·  Rajya Sabha time shown separately on right',{
    fill:'#374151','font-size':'8',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* legend */
  var lx = L;
  svg.appendChild(mk('rect',{x:lx,y:T-18,width:10,height:8,fill:'#f59e0b',rx:2}));
  svg.appendChild(tx(lx+13,T-11,'Lok Sabha (bar)',{fill:'#9ca3af','font-size':'8','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
  lx += 90;
  svg.appendChild(mk('rect',{x:lx,y:T-18,width:10,height:8,fill:'#1f2937',rx:2}));
  svg.appendChild(mk('line',{x1:lx,y1:T-14,x2:lx+10,y2:T-14,stroke:'#374151','stroke-width':'1','stroke-dasharray':'2,2'}));
  svg.appendChild(tx(lx+13,T-11,'Time allocated on agenda',{fill:'#6b7280','font-size':'8','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));

  /* column header: days to pass */
  svg.appendChild(tx(W-R+6, T-11, 'Rajya Sabha',{
    fill:'#3b82f680','font-size':'8','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W-84, T-11, 'Days to',{
    fill:'#6b728066','font-size':'8','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W-84, T-2, 'pass',{
    fill:'#6b728066','font-size':'8','font-family':'system-ui,sans-serif'
  }));

  /* scale tick marks */
  [0, 0.25, 0.5, 0.75, 1].forEach(function(f){
    var x = L + f*PW;
    svg.appendChild(mk('line',{x1:x,y1:T-1,x2:x,y2:T+(NB*(ROW_H+GAP)-GAP)+4,
      stroke:'#1f2937','stroke-width': f===0?'1':'0.5','stroke-dasharray': f===0?'':'3,3'}));
    if(f > 0){
      svg.appendChild(tx(x, T-3, fmt(Math.round(f*MAX_MIN)),{
        fill:'#374151','font-size':'7.5','text-anchor':'middle','font-family':'system-ui,sans-serif'}));
    }
  });

  BILLS.forEach(function(b, i){
    var y  = T + i*(ROW_H+GAP);
    var cy = y + ROW_H/2;

    /* alternating row bg */
    if(i%2===1) svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:'#0e1521'}));

    /* bill name — two lines if long */
    var words = b.name.split(' ');
    var line1='', line2='';
    words.forEach(function(w){
      if((line1+' '+w).trim().length <= 30) line1 = (line1+' '+w).trim();
      else line2 = (line2+' '+w).trim();
    });
    svg.appendChild(tx(L-5, cy-(line2?4.5:0), line1,{
      fill:'#9ca3af','font-size':'8.5','text-anchor':'end','dominant-baseline':'middle',
      'font-family':'system-ui,sans-serif'
    }));
    if(line2){
      svg.appendChild(tx(L-5, cy+5.5, line2,{
        fill:'#6b7280','font-size':'8','text-anchor':'end','dominant-baseline':'middle',
        'font-family':'system-ui,sans-serif'
      }));
    }

    /* track */
    svg.appendChild(mk('rect',{x:L,y:y+8,width:PW,height:ROW_H-16,fill:'#111827',rx:3}));

    /* allocated time background (where known) */
    if(b.allocLS){
      var aw = Math.min(b.allocLS/MAX_MIN,1)*PW;
      svg.appendChild(mk('rect',{x:L,y:y+8,width:aw,height:ROW_H-16,fill:'#1a2235',rx:3}));
      /* dashed right edge */
      svg.appendChild(mk('line',{x1:L+aw,y1:y+8,x2:L+aw,y2:y+ROW_H-8,
        stroke:'#374151','stroke-width':'1','stroke-dasharray':'2,2'}));
    }

    /* LS bar */
    var lsW = Math.max((b.ls/MAX_MIN)*PW, 3);
    svg.appendChild(mk('rect',{
      x:L, y:y+8, width:lsW, height:ROW_H-16,
      fill:'#f59e0b', rx:3, opacity:'0.95'
    }));

    /* LS time label */
    var labelInside = lsW > 36;
    svg.appendChild(tx(
      labelInside ? L+lsW-4 : L+lsW+4,
      cy,
      fmt(b.ls),{
      fill: labelInside ? '#0a0e17' : '#f59e0b',
      'font-size':'9','font-weight':'700',
      'text-anchor': labelInside ? 'end' : 'start',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));

    /* RS time (right side) */
    var rsColor = '#3b82f6';
    svg.appendChild(tx(W-R+8, cy-4, fmt(b.rs),{
      fill:rsColor,'font-size':'9','font-weight':'600',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(W-R+8, cy+6, 'in RS',{
      fill:'#374151','font-size':'7.5',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));

    /* days to pass */
    var dtpColor = b.dtp >= 100 ? '#ef4444' : b.dtp >= 30 ? '#f59e0b' : '#6b7280';
    var dtpX = W-62;
    svg.appendChild(tx(dtpX, cy-4, b.dtp+'',{
      fill:dtpColor,'font-size':'13','font-weight':'800',
      'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(dtpX, cy+7, 'days',{
      fill:dtpColor+'aa','font-size':'7.5',
      'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
  });

  /* source line */
  var sy = T + NB*(ROW_H+GAP) + 8;
  svg.appendChild(tx(L, sy, 'Source: PRS Legislative Research — Vital Stats, Monsoon Session 2025 · prsindia.org',{
    fill:'#1f2937','font-size':'7','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W-4, sy, '"days to pass" = days between first introduction in any session and passage',{
    fill:'#1f2937','font-size':'7','text-anchor':'end','font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>The Income-Tax (No.2) Bill 2025 replaced the original Income Tax Bill (which had been referred to a Select Committee in the Budget Session). The replacement was introduced and passed in Lok Sabha in a single day, with 4 minutes of LS debate. The Merchant Shipping Bill waited 244 days after introduction, then received 20 minutes. Rajya Sabha, in contrast, gave several of these bills 1–2 hours each — which the Lok Sabha number obscures.</em></p>

<h2 id="the-70-year-decline">The 70-Year Decline</h2>

<p>Lok Sabha sitting days per year, from the first session in 1952 to today. Every bar extracted directly from the Ministry of Parliamentary Affairs Statistical Handbook.</p>

<style>
#iphi-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#iphi-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 600px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="iphi-outer">
  <div id="iphi-wrap">
    <svg id="iphi-svg" viewBox="0 0 860 290" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS  = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('iphi-svg');

  /* Source: MPA Statistical Handbook 2023 (PDF) — Table 3: "Dates of issue of summons,
     commencement, adjournment sine-die, prorogation, sittings and duration of various
     sessions of Lok Sabhas held since 1952."  Extracted from official PDF at
     mpa.gov.in/sites/default/files/STATISTICAL%20HANDBOOK%202023Update-2_live.pdf
     2023–24 updated from MPA dataset #45 (dataful.in/datasets/45, OGDI license).
     2025: PRS verified (Budget Session 26 days + Monsoon Session 21 days).
     Values = actual sitting days in that calendar year across all sessions. */
  var DATA = [
    [1952,124],[1953,158],[1954,168],[1955,139],[1956,151],
    [1957,106],[1958,125],[1959,123],[1960,121],[1961,102],
    [1962,116],[1963,117],[1964,122],[1965,113],[1966,119],
    [1967,110],[1968,120],[1969,120],[1970,57], [1971,59],
    [1972,111],[1973,120],[1974,119],[1975,49], [1976,87],
    [1977,38], [1978,115],[1979,66], [1980,98], [1981,105],
    [1982,92], [1983,93], [1984,77], [1985,109],[1986,98],
    [1987,78], [1988,102],[1989,80], [1990,89], [1991,82],
    [1992,98], [1993,89], [1994,77], [1995,78], [1996,67],
    [1997,64], [1998,64], [1999,51], [2000,85], [2001,81],
    [2002,84], [2003,74], [2004,48], [2005,85], [2006,67],
    [2007,49], [2008,30], [2009,64], [2010,81], [2011,73],
    [2012,73], [2013,63], [2014,67], [2015,72], [2016,70],
    [2017,61], [2018,63], [2019,67], [2020,33], [2021,59],
    [2022,56], [2023,60], [2024,51], [2025,47],
  ];

  /* Key annotations */
  var EVENTS = [
    { yr:1975, label:'Emergency\ndeclared', side:'left'  },
    { yr:2008, label:'30 days —\npost-Emergency\nlow', side:'right' },
    { yr:2020, label:'COVID', side:'right' },
    { yr:2025, label:'2025\n(partial)', side:'right' },
  ];

  var W=860, H=290;
  var L=46, R=8, T=44, B=44;
  var BAR_BOT = H-B;        /* 246 */
  var BAR_TOP = T;
  var BAR_H   = BAR_BOT - BAR_TOP;  /* 202 */
  var ND      = DATA.length;
  var PW      = W-L-R;
  var SLOT    = PW/ND;               /* ~10.9 */
  var BAR_W   = Math.max(SLOT-1.5, 6);
  var MAX_VAL = 175;

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }
  function xFor(yr){ return L + (yr-1952)*SLOT + SLOT/2; }
  function yFor(v){ return BAR_BOT - (v/MAX_VAL)*BAR_H; }
  function barColor(v){
    if(v >= 100) return '#10b981';
    if(v >= 70)  return '#f59e0b';
    if(v >= 50)  return '#f97316';
    return '#ef4444';
  }

  /* bg */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 12, 'Lok Sabha Sitting Days per Year — 1952 to 2025',{
    fill:'#6b7280','font-size':'10','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W/2, 24, '1950s–60s average: 125 days/year   ·   2020s average: 51 days/year',{
    fill:'#374151','font-size':'8',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* y-axis grid */
  [50,75,100,125,150].forEach(function(v){
    var y = yFor(v);
    svg.appendChild(mk('line',{x1:L,y1:y,x2:W-R,y2:y,
      stroke: v===75 ? '#1f2937' : '#111827',
      'stroke-width':'0.5','stroke-dasharray':'2,4'
    }));
    svg.appendChild(tx(L-3, y, v+'',{
      fill:'#1f2937','font-size':'7.5','text-anchor':'end','dominant-baseline':'middle',
      'font-family':'system-ui,sans-serif'
    }));
  });
  /* baseline */
  svg.appendChild(mk('line',{x1:L,y1:BAR_BOT,x2:W-R,y2:BAR_BOT,stroke:'#1f2937','stroke-width':'1'}));

  /* decade x-axis labels */
  for(var yr=1950; yr<=2030; yr+=10){
    if(yr < 1952 || yr > 2025) continue;
    var x = xFor(yr);
    svg.appendChild(mk('line',{x1:x,y1:BAR_BOT,x2:x,y2:BAR_BOT+4,stroke:'#374151','stroke-width':'1'}));
    svg.appendChild(tx(x, BAR_BOT+12, yr+'',{
      fill:'#374151','font-size':'8','text-anchor':'middle','font-family':'system-ui,sans-serif'
    }));
  }
  /* also label 2025 */
  svg.appendChild(tx(xFor(2025), BAR_BOT+12, '2025',{
    fill:'#6b7280','font-size':'7.5','text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* bars */
  DATA.forEach(function(d){
    var yr = d[0], val = d[1];
    var cx = xFor(yr);
    var bx = cx - BAR_W/2;
    var by = yFor(val);
    var bh = BAR_BOT - by;
    var col = yr===2025 ? '#6b7280' : barColor(val);
    svg.appendChild(mk('rect',{x:bx,y:by,width:BAR_W,height:bh,
      fill:col,rx:1,opacity: yr===2025?'0.7':'0.85'
    }));
  });

  /* 10-year rolling average line */
  var avgPoints = [];
  for(var di=4; di<DATA.length; di++){
    var window = DATA.slice(Math.max(0,di-9), di+1);
    var avg = window.reduce(function(s,d){return s+d[1];},0)/window.length;
    avgPoints.push([DATA[di][0], avg]);
  }
  var pathD = avgPoints.map(function(p,i){
    var x = xFor(p[0]), y = yFor(p[1]);
    return (i===0?'M':'L')+x.toFixed(1)+','+y.toFixed(1);
  }).join(' ');
  var pathEl = mk('path',{d:pathD,fill:'none',stroke:'#ffffff',
    'stroke-width':'1.5','stroke-opacity':'0.3','stroke-linejoin':'round'});
  svg.appendChild(pathEl);

  /* NCRWC 120-day recommendation line */
  svg.appendChild(mk('line',{
    x1:L, y1:yFor(120), x2:W-R, y2:yFor(120),
    stroke:'#ef4444','stroke-width':'0.8','stroke-dasharray':'4,4','stroke-opacity':'0.5'
  }));
  svg.appendChild(tx(W-R-2, yFor(120)-3, 'NCRWC minimum: 120 days',{
    fill:'#ef444466','font-size':'7','text-anchor':'end','font-family':'system-ui,sans-serif'
  }));

  /* Event annotations */
  EVENTS.forEach(function(ev){
    var x = xFor(ev.yr);
    var barVal = DATA.find(function(d){return d[0]===ev.yr;})[1];
    var topY = yFor(barVal);
    var lines = ev.label.split('\n');
    var annotY = topY - 6 - (lines.length-1)*9;
    if(annotY < BAR_TOP + 4) annotY = BAR_TOP + 4;
    /* vertical tick */
    svg.appendChild(mk('line',{x1:x,y1:topY-2,x2:x,y2:annotY+lines.length*9,
      stroke:'#ffffff22','stroke-width':'0.5'}));
    lines.forEach(function(ln,li){
      svg.appendChild(tx(x, annotY+li*9, ln,{
        fill:'#6b7280','font-size':'7',
        'text-anchor': ev.side==='left'?'start':'middle',
        'font-family':'system-ui,sans-serif'
      }));
    });
  });

  /* legend */
  var lx = L, ly = H-10;
  [['≥100 days','#10b981'],['70–99','#f59e0b'],['50–69','#f97316'],['<50','#ef4444']].forEach(function(p){
    svg.appendChild(mk('rect',{x:lx,y:ly-5,width:9,height:9,fill:p[1],rx:1}));
    svg.appendChild(tx(lx+12,ly,p[0],{fill:'#6b7280','font-size':'8','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
    lx += p[0].length*4.8 + 22;
  });
  svg.appendChild(mk('line',{x1:lx,y1:ly-1,x2:lx+18,y2:ly-1,stroke:'#ffffff44','stroke-width':'1.5'}));
  svg.appendChild(tx(lx+22,ly,'10-yr rolling avg',{fill:'#6b7280','font-size':'8','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
  lx += 130;
  svg.appendChild(tx(W-R, ly, 'Source: MPA Statistical Handbook 2023 (PDF) · MPA dataset #45 · PRS (2025)',{
    fill:'#1f2937','font-size':'6.5','text-anchor':'end','dominant-baseline':'middle',
    'font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>Peak: 168 days in 1954. The low points are not random — 1975–77 is the Emergency, 1977 is the election year, 2008 was 30 days (the lowest post-Emergency year), 2020 was COVID. The 10-year rolling average has not been above 100 since 1974.</em></p>

<hr />

<h2 id="session-by-session-the-full-picture">Session by Session: The Full Picture</h2>

<p>Every session of the 17th and 18th Lok Sabha — productivity, collapse points, and bills passed per sitting.</p>

<style>
#ipst-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#ipst-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 640px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="ipst-outer">
  <div id="ipst-wrap">
    <svg id="ipst-svg" viewBox="0 0 860 310" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS  = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('ipst-svg');

  /* All productivity % values verified from PRS Legislative Research individual session pages.
     Bills passed from same PRS session pages (excludes Finance & Appropriation Bills).
     null = PRS page unavailable (404) or data not stated.
     Sources: prsindia.org/parliamenttrack/vital-stats (individual session pages)
              prsindia.org/parliamenttrack/vital-stats/functioning-of-the-17th-lok-sabha
              prsindia.org/parliamenttrack/vital-stats/parliament-in-inaugural-and-budget-session-2024  */
  var SESSIONS = [
    /* ─── 17th Lok Sabha  (Jun 2019 – Feb 2024) ─── */
    { lbl:'B\'19',  full:'Budget 2019',           ls17:true,  ls:135, bills:28,  note:null },
    { lbl:'W\'19',  full:'Winter 2019',            ls17:true,  ls:111, bills:14,  note:null },
    { lbl:'B\'20',  full:'Budget 2020',            ls17:true,  ls:86,  bills:null,note:'COVID — adjourned early' },
    { lbl:'M\'20',  full:'Monsoon 2020',           ls17:true,  ls:null,bills:null,note:'PRS page unavailable' },
    { lbl:'B\'21',  full:'Budget 2021',            ls17:true,  ls:73,  bills:10,  note:null },
    { lbl:'M\'21',  full:'Monsoon 2021',           ls17:true,  ls:21,  bills:20,  note:'Pegasus · Farm Laws' },
    { lbl:'W\'21',  full:'Winter 2021',            ls17:true,  ls:77,  bills:10,  note:null },
    { lbl:'B\'22',  full:'Budget 2022',            ls17:true,  ls:123, bills:5,   note:null },
    { lbl:'M\'22',  full:'Monsoon 2022',           ls17:true,  ls:47,  bills:5,   note:'23 RS MPs suspended' },
    { lbl:'W\'22',  full:'Winter 2022',            ls17:true,  ls:88,  bills:7,   note:null },
    { lbl:'B\'23',  full:'Budget 2023',            ls17:true,  ls:33,  bills:1,   note:'Part II: 5% LS' },
    { lbl:'M\'23',  full:'Monsoon 2023',           ls17:true,  ls:43,  bills:23,  note:null },
    { lbl:'W\'23',  full:'Winter 2023',            ls17:true,  ls:74,  bills:17,  note:'146 MPs suspended' },
    { lbl:'B\'24',  full:'Budget 2024 (17th LS)',  ls17:true,  ls:null,bills:null,note:'9 days — pre-election' },
    /* ─── 18th Lok Sabha  (Jun 2024 – present) ─── */
    { lbl:'I\'24',  full:'Inaugural + Budget 2024',ls17:false, ls:123, bills:1,   note:null },
    { lbl:'W\'24',  full:'Winter 2024',            ls17:false, ls:52,  bills:1,   note:'QH absent 15/19 days' },
    { lbl:'B\'25',  full:'Budget 2025',            ls17:false, ls:111, bills:10,  note:null },
    { lbl:'M\'25',  full:'Monsoon 2025',           ls17:false, ls:29,  bills:14,  note:'Lowest 18th LS · Op Sindoor' },
  ];

  var W=860, H=310;
  var L=32, R=8;
  var T=46, B_AXIS=48;   /* top margin, bottom reserve for labels */
  var BAR_TOP = T;
  var BAR_BOT = H - B_AXIS;   /* 262 */
  var BAR_H = BAR_BOT - BAR_TOP;  /* 216 */

  var NS_CNT = SESSIONS.length;  /* 18 */
  var PW = W - L - R;            /* 820 */
  var SLOT = PW / NS_CNT;        /* ~45.6 */
  var BAR_W = Math.floor(SLOT * 0.64);  /* ~29 */
  var MAX_PCT = 145;

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }
  function barColor(pct){
    if(pct >= 100) return '#10b981';
    if(pct >= 70)  return '#f59e0b';
    if(pct >= 40)  return '#f97316';
    return '#ef4444';
  }
  function yFor(pct){ return BAR_BOT - (pct / MAX_PCT) * BAR_H; }

  /* bg */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 12, 'Lok Sabha Productivity per Session — 17th and 18th Lok Sabha',{
    fill:'#6b7280','font-size':'10','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W/2, 24, 'Bars show % of scheduled time that Lok Sabha functioned. Number below each bar = bills passed (excl. Finance/Appropriation). PRS source.',{
    fill:'#374151','font-size':'7.5',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* y-axis grid lines */
  [25, 50, 75, 100, 125].forEach(function(pct){
    var y = yFor(pct);
    var is100 = pct === 100;
    svg.appendChild(mk('line',{x1:L,y1:y,x2:W-R,y2:y,
      stroke: is100 ? '#1f2937' : '#111827',
      'stroke-width': is100 ? '1' : '0.5',
      'stroke-dasharray': is100 ? '4,3' : '2,4'
    }));
    svg.appendChild(tx(L-3, y+1, pct+'%',{
      fill: is100 ? '#4b5563' : '#1f2937',
      'font-size':'7','text-anchor':'end','dominant-baseline':'middle',
      'font-family':'system-ui,sans-serif'
    }));
  });

  /* 17th/18th LS separator */
  var sepIdx = 14; /* first 18th LS session is index 14 */
  var sepX = L + sepIdx * SLOT;
  svg.appendChild(mk('line',{x1:sepX,y1:BAR_TOP-6,x2:sepX,y2:BAR_BOT+32,
    stroke:'#1f2937','stroke-width':'1.5','stroke-dasharray':'4,3'
  }));
  svg.appendChild(tx(L + 0.5*sepIdx*SLOT, BAR_TOP-6, '17th Lok Sabha (2019–2024)',{
    fill:'#374151','font-size':'8','text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(L + sepIdx*SLOT + 0.5*(W-R-L-sepIdx*SLOT), BAR_TOP-6, '18th Lok Sabha (2024–)',{
    fill:'#374151','font-size':'8','text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* bars */
  SESSIONS.forEach(function(s, i){
    var cx = L + i * SLOT + SLOT/2;
    var bx = cx - BAR_W/2;

    /* session label (x-axis) */
    svg.appendChild(tx(cx, BAR_BOT + 10, s.lbl,{
      fill: s.ls === null ? '#374151' : '#6b7280',
      'font-size':'8','text-anchor':'middle','font-family':'system-ui,sans-serif'
    }));

    if(s.ls === null){
      /* unavailable — draw striped grey placeholder */
      svg.appendChild(mk('rect',{x:bx,y:yFor(12),width:BAR_W,height:BAR_H - yFor(12) + BAR_TOP,
        fill:'#111827',rx:2,opacity:'0.5'
      }));
      svg.appendChild(tx(cx, BAR_BOT-8, 'n/a',{
        fill:'#374151','font-size':'7','text-anchor':'middle','font-family':'system-ui,sans-serif'
      }));
    } else {
      var by = yFor(s.ls);
      var bh = BAR_BOT - by;
      var col = barColor(s.ls);

      /* bar fill */
      svg.appendChild(mk('rect',{x:bx,y:by,width:BAR_W,height:bh,
        fill:col,rx:2,opacity: s.ls17 ? '0.82' : '1'
      }));

      /* % label — inside bar if tall enough, else above */
      var pctLabel = s.ls + '%';
      if(bh > 20){
        svg.appendChild(tx(cx, by + Math.min(bh/2, 14), pctLabel,{
          fill:'#0a0e17','font-size': bh > 32 ? '8.5' : '7.5','font-weight':'700',
          'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
        }));
      } else {
        /* bar too short — put label above */
        svg.appendChild(tx(cx, by - 4, pctLabel,{
          fill:col,'font-size':'7.5','font-weight':'700',
          'text-anchor':'middle','font-family':'system-ui,sans-serif'
        }));
      }

      /* note annotation — only for notable sessions, placed above bar */
      if(s.note && bh > 0){
        var noteY = Math.min(by - 5, BAR_BOT - 8);
        /* only draw if there's room above bar */
        if(by > BAR_TOP + 22){
          svg.appendChild(tx(cx, by - 5, s.note.split('·')[0].trim(),{
            fill:col+'99','font-size':'6.5',
            'text-anchor':'middle','font-family':'system-ui,sans-serif',
            'font-style':'italic'
          }));
        }
      }
    }

    /* bills below x-axis label */
    var billsY = BAR_BOT + 22;
    if(s.bills !== null){
      svg.appendChild(tx(cx, billsY, s.bills+'',{
        fill:'#4b5563','font-size':'8','font-weight':'600',
        'text-anchor':'middle','font-family':'system-ui,sans-serif'
      }));
    } else {
      svg.appendChild(tx(cx, billsY, '—',{
        fill:'#1f2937','font-size':'8',
        'text-anchor':'middle','font-family':'system-ui,sans-serif'
      }));
    }
  });

  /* bills label on left */
  svg.appendChild(tx(L-3, BAR_BOT+22, 'bills:',{
    fill:'#374151','font-size':'7.5','text-anchor':'end','font-family':'system-ui,sans-serif'
  }));

  /* legend */
  var ly = H - 14;
  var lx = L;
  [['≥100%','#10b981'],['70–99%','#f59e0b'],['40–69%','#f97316'],['<40%','#ef4444']].forEach(function(pair){
    svg.appendChild(mk('rect',{x:lx,y:ly-5,width:9,height:9,fill:pair[1],rx:2}));
    svg.appendChild(tx(lx+12, ly, pair[0],{
      fill:'#6b7280','font-size':'8','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    lx += pair[0].length*4.8 + 22;
  });
  svg.appendChild(mk('rect',{x:lx,y:ly-5,width:9,height:9,fill:'#111827',rx:2,opacity:'0.5'}));
  svg.appendChild(tx(lx+12, ly, 'data unavailable',{
    fill:'#374151','font-size':'8','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
  }));
  lx += 100;
  svg.appendChild(tx(W-R, ly, 'Source: PRS Legislative Research · prsindia.org/parliamenttrack/vital-stats (individual session pages)',{
    fill:'#1f2937','font-size':'7','text-anchor':'end','dominant-baseline':'middle',
    'font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>Budget 2023 is the worst single session: 33% overall, with Part II running at just 5%. Monsoon 2021 at 21% — Parliament was in session but almost entirely disrupted by the Pegasus and farm laws controversies. Budget 2022 at 123% shows the same parliament can work when it chooses to.</em></p>

<hr />

<h2 id="five-years-of-the-17th-lok-sabha">Five Years of the 17th Lok Sabha</h2>

<p>These are aggregate statistics from the PRS summary of the complete 17th Lok Sabha (June 2019 – February 2024). They are not per-session estimates — they are the published totals.</p>

<style>
#ip17-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#ip17-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 560px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="ip17-outer">
  <div id="ip17-wrap">
    <svg id="ip17-svg" viewBox="0 0 860 230" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('ip17-svg');

  /* All facts from: PRS Legislative Research — "Functioning of the 17th Lok Sabha"
     URL: https://prsindia.org/parliamenttrack/vital-stats/functioning-of-the-17th-lok-sabha
     17th Lok Sabha: June 2019 – February 2024  */
  var STATS = [
    { big:'274',  sub:'sitting days',      detail:'15 sessions · 11 adjourned early', col:'#f59e0b', w:1 },
    { big:'88%',  sub:'LS functioned',     detail:'of scheduled time (73% RS)', col:'#f59e0b', w:1 },
    { big:'179',  sub:'bills passed',      detail:'excl. Finance & Appropriation', col:'#e5e7eb', w:1 },
    { big:'16%',  sub:'bills to committee',detail:'vs 71% in 15th Lok Sabha', col:'#ef4444', w:1 },
    { big:'35%',  sub:'bills < 1 hr debate',detail:'in Lok Sabha (34% in RS)', col:'#ef4444', w:1 },
    { big:'729→2',sub:'private member bills',detail:'introduced → actually discussed', col:'#ef4444', w:1 },
    { big:'60%',  sub:'QH LS worked',      detail:'52% in Rajya Sabha', col:'#6b7280', w:1 },
    { big:'206',  sub:'MP suspensions',    detail:'incl. 146 in Winter Session 2023', col:'#6b7280', w:1 },
  ];

  var W=860, H=230;
  var COLS=4, ROWS=2;
  var CW = W/COLS;  /* 215 */
  var CH = (H-36)/ROWS;  /* ~97 */
  var PAD = 16;

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }

  /* bg */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 14, '17th Lok Sabha at a Glance — June 2019 to February 2024',{
    fill:'#6b7280','font-size':'10','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W/2, 26, 'Source: PRS Legislative Research · prsindia.org/parliamenttrack/vital-stats/functioning-of-the-17th-lok-sabha',{
    fill:'#1f2937','font-size':'8',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  STATS.forEach(function(s, i){
    var col = i % COLS;
    var row = Math.floor(i / COLS);
    var cx = col * CW;
    var cy = 36 + row * CH;

    /* card bg */
    var isLast = (i === STATS.length-1);
    svg.appendChild(mk('rect',{
      x: cx+4, y: cy+4,
      width: CW-8, height: CH-8,
      fill: '#0e1521',
      rx: 6
    }));

    /* left accent bar */
    svg.appendChild(mk('rect',{
      x: cx+4, y: cy+4,
      width: 3, height: CH-8,
      fill: s.col,
      rx: 1.5
    }));

    /* big value */
    /* adjust font size for long strings */
    var bigFS = s.big.length > 4 ? '22' : '30';
    svg.appendChild(tx(cx+PAD+2, cy+CH/2-12, s.big,{
      fill:s.col,'font-size':bigFS,'font-weight':'800',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));

    /* sub label */
    svg.appendChild(tx(cx+PAD+2, cy+CH/2+12, s.sub,{
      fill:'#9ca3af','font-size':'9','font-weight':'600',
      'font-family':'system-ui,sans-serif'
    }));

    /* detail line */
    svg.appendChild(tx(cx+PAD+2, cy+CH/2+24, s.detail,{
      fill:'#4b5563','font-size':'7.5',
      'font-family':'system-ui,sans-serif'
    }));
  });

  /* grid lines */
  for(var c=1; c<COLS; c++){
    svg.appendChild(mk('line',{x1:c*CW,y1:36,x2:c*CW,y2:H,stroke:'#0a0e17','stroke-width':'1'}));
  }
  svg.appendChild(mk('line',{x1:0,y1:36+CH,x2:W,y2:36+CH,stroke:'#0a0e17','stroke-width':'1'}));

})();
</script>

<p><em>The 729 Private Member Bills introduced → 2 discussed is not a rounding error. 729 bills were formally introduced by MPs; 2 received floor time. The last Private Member Bill to actually pass Parliament was in 1970.</em></p>

<h2 id="mps-who-raised-matters-parliament-never-addressed">MPs Who Raised Matters Parliament Never Addressed</h2>

<p>Every year, MPs formally file matters they want to raise. When Parliament is disrupted, these go unaddressed. The chart counts exactly how many were filed but never taken up — under Rule 377 in Lok Sabha, and Rule 180 in Rajya Sabha.</p>

<style>
#ipua-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#ipua-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 520px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="ipua-outer">
  <div id="ipua-wrap">
    <svg id="ipua-svg" viewBox="0 0 860 290" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS  = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('ipua-svg');

  /* Sources:
     LS Rule 377: Ministry of Parliamentary Affairs — "Year-, Session- and Ministry-wise Number of
       Pending Matters Raised under Rule 377 in Lok Sabha" (dataful.in dataset #38)
       Coverage: 2019–2025 (2023 absent from dataset as published)
     RS Rule 180: Ministry of Parliamentary Affairs — "Year-, Session- and Ministry-wise Number of
       Pending Special Mentions under Rule 180 A–E in Rajya Sabha" (dataful.in dataset #57)
       Coverage: 2012–2025
     Both: Open Government Data License India  */

  /* LS data: total pending matters NOT addressed per year (Rule 377)
     RS data: total pending special mentions NOT taken up per year (Rule 180 A-E)
     2025 = Budget Session only (partial year)  */
  /* CORRECTED: original analysis double-counted by summing both the "Total" row
     and individual ministry rows. Correct values use only the "Total" row per session.
     LS 2025 = Budget Session only (partial year). LS 2023 absent from published dataset. */
  var DATA = [
    { yr:2012, ls:null, rs:2   },
    { yr:2013, ls:null, rs:1   },
    { yr:2014, ls:null, rs:2   },
    { yr:2015, ls:null, rs:2   },
    { yr:2016, ls:null, rs:2   },
    { yr:2017, ls:null, rs:10  },
    { yr:2018, ls:null, rs:6   },
    { yr:2019, ls:1,    rs:3   },
    { yr:2020, ls:1,    rs:8   },
    { yr:2021, ls:4,    rs:5   },
    { yr:2022, ls:306,  rs:206 },
    { yr:2023, ls:null, rs:12  },
    { yr:2024, ls:192,  rs:55  },
    { yr:2025, ls:473,  rs:33, partial:true },
  ];

  var W=860, H=290;
  var L=44, R=12, T=46, B=44;
  var BAR_TOP = T, BAR_BOT = H-B;
  var BAR_H   = BAR_BOT - BAR_TOP;  /* 200 */
  var ND      = DATA.length;
  var PW      = W - L - R;
  var SLOT    = PW / ND;
  var BAR_W   = 18, GAP = 6;
  var MAX_VAL = 520;

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }
  function yFor(v){ return BAR_BOT - (v / MAX_VAL) * BAR_H; }

  /* bg */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 12, 'MP-Raised Matters That Parliament Never Got Time to Address',{
    fill:'#6b7280','font-size':'10','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W/2, 24, 'LS Rule 377 (matters raised, not addressed) · RS Rule 180 A–E (special mentions not taken up)',{
    fill:'#374151','font-size':'8',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* y-axis grid */
  [100,200,300,400,500,600].forEach(function(v){
    var y = yFor(v);
    svg.appendChild(mk('line',{x1:L,y1:y,x2:W-R,y2:y,stroke:'#111827','stroke-width':'0.5','stroke-dasharray':'3,4'}));
    svg.appendChild(tx(L-3, y, v+'',{fill:'#1f2937','font-size':'7','text-anchor':'end','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
  });
  /* baseline */
  svg.appendChild(mk('line',{x1:L,y1:BAR_BOT,x2:W-R,y2:BAR_BOT,stroke:'#1f2937','stroke-width':'1'}));

  /* legend */
  var ly = T - 18;
  svg.appendChild(mk('rect',{x:L,y:ly-4,width:12,height:10,fill:'#f59e0b',rx:2}));
  svg.appendChild(tx(L+15,ly+1,'LS Rule 377 (data from 2019)',{fill:'#9ca3af','font-size':'8','font-family':'system-ui,sans-serif','dominant-baseline':'middle'}));
  svg.appendChild(mk('rect',{x:L+165,y:ly-4,width:12,height:10,fill:'#3b82f6',rx:2}));
  svg.appendChild(tx(L+180,ly+1,'RS Rule 180 (data from 2012)',{fill:'#9ca3af','font-size':'8','font-family':'system-ui,sans-serif','dominant-baseline':'middle'}));

  DATA.forEach(function(d, i){
    var cx  = L + i * SLOT + SLOT/2;
    var lsX = cx - GAP/2 - BAR_W;
    var rsX = cx + GAP/2;

    /* year label */
    svg.appendChild(tx(cx, BAR_BOT + 9, d.yr+'', {
      fill: d.yr === 2022 ? '#ef4444' : '#374151',
      'font-size':'8','font-weight': d.yr === 2022 ? '700' : '400',
      'text-anchor':'middle','font-family':'system-ui,sans-serif'
    }));
    if(d.partial){
      svg.appendChild(tx(cx, BAR_BOT + 20, 'partial',{fill:'#374151','font-size':'6.5','text-anchor':'middle','font-family':'system-ui,sans-serif'}));
    }

    /* LS bar */
    if(d.ls !== null){
      var lsY = yFor(d.ls), lsBH = BAR_BOT - lsY;
      svg.appendChild(mk('rect',{x:lsX,y:lsY,width:BAR_W,height:lsBH,fill:'#f59e0b',rx:2,opacity:'0.9'}));
      if(lsBH > 16){
        svg.appendChild(tx(lsX+BAR_W/2, lsY+lsBH/2, d.ls+'',{
          fill:'#0a0e17','font-size':'7.5','font-weight':'700',
          'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
        }));
      } else if(d.ls > 0){
        svg.appendChild(tx(lsX+BAR_W/2, lsY-4, d.ls+'',{
          fill:'#f59e0b','font-size':'7.5','font-weight':'700',
          'text-anchor':'middle','font-family':'system-ui,sans-serif'
        }));
      }
    }

    /* RS bar */
    if(d.rs !== null && d.rs > 0){
      var rsY = yFor(d.rs), rsBH = BAR_BOT - rsY;
      svg.appendChild(mk('rect',{x:rsX,y:rsY,width:BAR_W,height:rsBH,fill:'#3b82f6',rx:2,opacity:'0.85'}));
      if(rsBH > 16){
        svg.appendChild(tx(rsX+BAR_W/2, rsY+rsBH/2, d.rs+'',{
          fill:'#0a0e17','font-size':'7.5','font-weight':'700',
          'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
        }));
      } else {
        svg.appendChild(tx(rsX+BAR_W/2, rsY-4, d.rs+'',{
          fill:'#3b82f6','font-size':'7.5','font-weight':'700',
          'text-anchor':'middle','font-family':'system-ui,sans-serif'
        }));
      }
    }
  });

  /* 2022 annotation */
  var a22x = L + 9 * SLOT + SLOT/2; /* index of 2022 = 10 */
  /* recalculate properly */
  var idx2022 = DATA.findIndex(function(d){ return d.yr === 2022; });
  a22x = L + idx2022 * SLOT + SLOT/2;
  svg.appendChild(mk('line',{x1:a22x,y1:yFor(640),x2:a22x,y2:yFor(660),stroke:'#ef444488','stroke-width':'1'}));
  svg.appendChild(tx(a22x + 4, yFor(340), '← 306 LS (incl. 260 in Winter alone)  ·  412 RS',{
    fill:'#ef444488','font-size':'7','text-anchor':'start','font-style':'italic',
    'font-family':'system-ui,sans-serif'
  }));

  /* source */
  svg.appendChild(tx(4, H-4, 'Sources: MPA Statistical Handbook via dataful.in datasets #38 (Rule 377 LS) and #57 (Rule 180 RS) · Open Government Data License India · 2023 LS data absent from published dataset',{
    fill:'#1f2937','font-size':'6.5','font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>2022: 306 LS matters and 412 RS matters unaddressed. Winter 2022 alone accounts for 260 of the LS total — the session had 8 of 10 business days disrupted and 23 RS MPs suspended. The 2025 Budget figure (473 LS) is high because Rule 377 is deprioritised in busy sessions even when Parliament is functional — not a pure disruption signal. 2023 LS data absent from the Ministry’s published dataset.</em></p>

<hr />

<h2 id="when-disruptions-hit-the-two-houses-differently">When Disruptions Hit the Two Houses Differently</h2>

<p>The government tracks exact hours lost to forced adjournments per session, per house. Winter 2024 shows the sharpest contrast: Rajya Sabha lost 65.7% of its time to disruptions while Lok Sabha, in the same session, lost only 4.6%.</p>

<style>
#ipdr-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#ipdr-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 540px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="ipdr-outer">
  <div id="ipdr-wrap">
    <svg id="ipdr-svg" viewBox="0 0 860 300" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS  = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('ipdr-svg');

  /* Source: Ministry of Parliamentary Affairs Statistical Handbook 2023 (updated Jan 2026)
     "Historical Data of Parliament: Year-wise Discussion Time Allocated and Lost due to
      Interruptions in Lok Sabha and Rajya Sabha" — dataset ID 18999 via dataful.in/datasets/18999/
     Underlying source: data.gov.in (Open Government Data License India)
     NOTE: This measures time lost specifically to FORCED ADJOURNMENTS due to disruptions/uproar.
           It is NOT the same as overall "unproductive time" (which also includes Zero Hour,
           procedures, etc.). A session can be productive overall yet have some forced-adjournment time.
     For Monsoon 2025: exact disruption hours not in available preview — omitted. */
  var SESSIONS = [
    {
      label: 'Monsoon\n2023',
      short: 'M\'23',
      ls: null,          /* LS data not in preview */
      rs: { lost:45*60, avail:91*60+9, pct:49.4 },
    },
    {
      label: 'Inaugural+\nBudget 2024',
      short: 'I\'24',
      ls: { lost:5*60+37, avail:32*60+21, pct:17.4 },
      rs: { lost:43,      avail:21*60+33, pct:3.3  },
    },
    {
      label: 'Monsoon\n2024',
      short: 'M\'24',
      ls: { lost:1*60+53, avail:85*60+28, pct:2.2 },
      rs: { lost:2*60+59, avail:79*60,    pct:3.8 },
    },
    {
      label: 'Winter\n2024',
      short: 'W\'24',
      ls: { lost:5*60+15, avail:114*60+3, pct:4.6  },
      rs: { lost:71*60+43,avail:109*60+8, pct:65.7 },
      note: 'RS: VP removal + judge\nimpeachment notices'
    },
    {
      label: 'Budget\n2025',
      short: 'B\'25',
      ls: { lost:21*60+51, avail:136*60+16, pct:16.0 },
      rs: { lost:15*60+14, avail:134*60+6,  pct:11.4 },
    },
  ];

  var W=860, H=300;
  var L=40, R=12, T=48, B=52;
  var BAR_TOP = T;
  var BAR_BOT = H - B;      /* 248 */
  var BAR_H   = BAR_BOT - BAR_TOP;  /* 200 */
  var NS_CNT  = SESSIONS.length;
  var PW      = W - L - R;  /* 808 */
  var SLOT    = PW / NS_CNT;/* ~161.6 */
  var BAR_W   = 28;
  var BAR_GAP = 8;          /* gap between LS and RS bars */
  var MAX_PCT = 75;

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }
  function yFor(pct){ return BAR_BOT - (pct / MAX_PCT) * BAR_H; }
  function fmtH(mins){
    var h=Math.floor(mins/60), m=mins%60;
    return m ? h+'h\u202f'+m+'m' : h+'h';
  }

  /* bg */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 12, 'Hours Lost to Forced Adjournments — 18th Lok Sabha Sessions',{
    fill:'#6b7280','font-size':'10','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W/2, 24, 'Measures time lost specifically to disruption-forced adjournments (not all unproductive time)',{
    fill:'#374151','font-size':'8',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* y-axis grid */
  [0,25,50,75].forEach(function(pct){
    var y = yFor(pct);
    svg.appendChild(mk('line',{x1:L,y1:y,x2:W-R,y2:y,
      stroke:'#111827','stroke-width': pct===0 ? '1' : '0.5','stroke-dasharray': pct===0?'':'3,4'
    }));
    svg.appendChild(tx(L-3, y, pct+'%',{
      fill:'#374151','font-size':'7.5','text-anchor':'end','dominant-baseline':'middle',
      'font-family':'system-ui,sans-serif'
    }));
  });

  /* legend */
  var ly = T - 18;
  svg.appendChild(mk('rect',{x:L,y:ly-4,width:12,height:10,fill:'#f59e0b',rx:2}));
  svg.appendChild(tx(L+15,ly+1,'Lok Sabha',{fill:'#9ca3af','font-size':'8.5','font-family':'system-ui,sans-serif','dominant-baseline':'middle'}));
  svg.appendChild(mk('rect',{x:L+80,y:ly-4,width:12,height:10,fill:'#3b82f6',rx:2}));
  svg.appendChild(tx(L+95,ly+1,'Rajya Sabha',{fill:'#9ca3af','font-size':'8.5','font-family':'system-ui,sans-serif','dominant-baseline':'middle'}));
  svg.appendChild(tx(L+175,ly+1,'n/a = data not in available preview',{fill:'#374151','font-size':'7.5','font-family':'system-ui,sans-serif','dominant-baseline':'middle'}));

  SESSIONS.forEach(function(s, i){
    var cx = L + i * SLOT + SLOT/2;
    /* LS bar centred left-of-centre, RS bar right-of-centre */
    var lsX = cx - BAR_GAP/2 - BAR_W;
    var rsX = cx + BAR_GAP/2;

    /* session label (two lines) */
    var lines = s.short ? [s.short] : s.label.split('\n');
    var labelY = BAR_BOT + 14;
    svg.appendChild(tx(cx, labelY, s.label.split('\n')[0],{
      fill:'#6b7280','font-size':'8.5','text-anchor':'middle','font-family':'system-ui,sans-serif'
    }));
    if(s.label.indexOf('\n') > -1){
      svg.appendChild(tx(cx, labelY+11, s.label.split('\n')[1],{
        fill:'#6b7280','font-size':'8.5','text-anchor':'middle','font-family':'system-ui,sans-serif'
      }));
    }

    /* LS bar */
    if(s.ls){
      var lsY = yFor(s.ls.pct);
      var lsBH = BAR_BOT - lsY;
      svg.appendChild(mk('rect',{x:lsX,y:lsY,width:BAR_W,height:lsBH,
        fill:'#f59e0b',rx:2,opacity:'0.9'}));
      /* pct label */
      var lsLabelInside = lsBH > 22;
      svg.appendChild(tx(lsX+BAR_W/2, lsLabelInside ? lsY+lsBH/2 : lsY-5,
        s.ls.pct+'%',{
        fill: lsLabelInside ? '#0a0e17' : '#f59e0b',
        'font-size':'8','font-weight':'700',
        'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));
      /* hours lost label below bar */
      svg.appendChild(tx(lsX+BAR_W/2, BAR_BOT+3, fmtH(s.ls.lost),{
        fill:'#f59e0b88','font-size':'7',
        'text-anchor':'middle','font-family':'system-ui,sans-serif'
      }));
    } else {
      /* n/a placeholder */
      svg.appendChild(mk('rect',{x:lsX,y:BAR_BOT-18,width:BAR_W,height:18,
        fill:'#111827',rx:2,opacity:'0.5'}));
      svg.appendChild(tx(lsX+BAR_W/2, BAR_BOT-9, 'n/a',{
        fill:'#374151','font-size':'7','text-anchor':'middle','dominant-baseline':'middle',
        'font-family':'system-ui,sans-serif'
      }));
    }

    /* RS bar */
    if(s.rs && s.rs.lost !== undefined){
      var rsY = yFor(s.rs.pct);
      var rsBH = BAR_BOT - rsY;
      svg.appendChild(mk('rect',{x:rsX,y:rsY,width:BAR_W,height:rsBH,
        fill:'#3b82f6',rx:2,opacity:'0.85'}));
      var rsLabelInside = rsBH > 22;
      svg.appendChild(tx(rsX+BAR_W/2, rsLabelInside ? rsY+rsBH/2 : rsY-5,
        s.rs.pct+'%',{
        fill: rsLabelInside ? '#0a0e17' : '#3b82f6',
        'font-size':'8','font-weight':'700',
        'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));
      svg.appendChild(tx(rsX+BAR_W/2, BAR_BOT+3, fmtH(s.rs.lost),{
        fill:'#3b82f688','font-size':'7',
        'text-anchor':'middle','font-family':'system-ui,sans-serif'
      }));
    }

    /* note annotation for notable sessions */
    if(s.note){
      var nLines = s.note.split('\n');
      var noteY = yFor(Math.max(s.ls?s.ls.pct:0, s.rs?s.rs.pct:0)) - 8;
      nLines.forEach(function(ln, li){
        svg.appendChild(tx(cx, noteY-(nLines.length-1-li)*9, ln,{
          fill:'#ef444488','font-size':'7','text-anchor':'middle',
          'font-style':'italic','font-family':'system-ui,sans-serif'
        }));
      });
    }
  });

  /* source */
  svg.appendChild(tx(4, H-4, 'Source: MPA Statistical Handbook 2023 (updated Jan 2026) via dataful.in/datasets/18999 · data.gov.in · Open Government Data License India',{
    fill:'#1f2937','font-size':'6.5','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W-R, H-4, 'Monsoon 2025 disruption hours not available in preview data',{
    fill:'#1f2937','font-size':'6.5','text-anchor':'end','font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>Winter 2024 RS was disrupted by a notice to remove the Vice-President and a judge impeachment notice — neither passed, but both consumed the house. LS continued functioning. Same Parliament, same weeks, opposite outcomes.</em></p>

<hr />

<h2 id="how-india-compares">How India Compares</h2>

<p>Counting parliamentary sitting days across countries is harder than it looks — different countries measure different things. The chart below uses only verified primary-source figures, with methodology noted per row.</p>

<style>
#ipgl-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#ipgl-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 560px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="ipgl-outer">
  <div id="ipgl-wrap">
    <svg id="ipgl-svg" viewBox="0 0 860 296" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('ipgl-svg');

  /* Each entry shows a SINGLE verified number per country.
     NOTE: these measure different things — see "metric" field.
     Comparing directly is imperfect; methodology noted per row.
     Sources cited inline.  */
  var ROWS = [
    {
      flag:'🇮🇳', name:'India', sub:'17th LS avg · 2019–2024',
      val:55, valLabel:'~55 days/yr', col:'#f59e0b', hl:true,
      metric:'Sitting days (actual plenary sittings)',
      src:'PRS: "17th Lok Sabha averaged only 55 days a year" (274 days / 5 years)',
      srcUrl:'prsindia.org/parliamenttrack/vital-stats/functioning-of-the-17th-lok-sabha'
    },
    {
      flag:'🇯🇵', name:'Japan', sub:'National Diet · fixed by law',
      val:150, valLabel:'150 days', col:'#f472b6', hl:false,
      metric:'Calendar days per ordinary session (not plenary sitting days)',
      src:'Wikipedia citing official: "regular, annual sessions last for 150 days"',
      srcUrl:'en.wikipedia.org/wiki/National_Diet'
    },
    {
      flag:'🇦🇺', name:'Australia', sub:'House of Representatives · since 1901',
      val:67, valLabel:'67 days/yr', col:'#06b6d4', hl:false,
      metric:'Actual sitting days (average since 1901)',
      src:'Parliament of Australia Practice7: "67 days each year spread over 20 sitting weeks"',
      srcUrl:'aph.gov.au/About_Parliament/House_of_Representatives/Powers_practice_and_procedure/Practice7'
    },
    {
      flag:'🇨🇦', name:'Canada', sub:'House of Commons',
      val:135, valLabel:'~135 days', col:'#8b5cf6', hl:false,
      metric:'Approx. sitting days (IPU: "~27 sitting weeks/year" × 5 days)',
      src:'IPU Parline: "parliament typically sits for approximately 27 weeks per year" — converted to days',
      srcUrl:'data.ipu.org/parliament/CA/CA-LC01/working-methods/structure/'
    },
    {
      flag:'🇩🇪', name:'Germany', sub:'Bundestag',
      val:100, valLabel:'~100 days', col:'#10b981', hl:false,
      metric:'Approx. plenary days (IPU: "at least 20 sitting weeks/year" × ~5 days)',
      src:'IPU Parline: "at least 20 sitting weeks a year" — lower-bound estimate only',
      srcUrl:'data.ipu.org/parliament/DE/DE-LC01/working-methods/structure/'
    },
    {
      flag:'🇬🇧', name:'UK', sub:'House of Commons · varies by session',
      val:152, valLabel:'101–213', col:'#3b82f6', hl:false,
      metric:'Actual sitting days — varies widely (101 in election yr, 213 in 18-month session)',
      src:'UK Parliament recess dates: 2023-24=101 days, 2022-23=213 days (18-month session)',
      srcUrl:'parliament.uk/about/faqs/house-of-commons-faqs/business-faq-page/recess-dates/'
    },
  ];

  var W=860, H=296;
  var L=110, R=10, T=42, B=36;
  var PW = W-L-R;
  var NR = ROWS.length;
  var ROW_H = 28, GAP = 5;
  var MAX_VAL = 220;

  function mk(tag,a){ var e=document.createElementNS(NS,tag); for(var k in a) e.setAttribute(k,a[k]); return e; }
  function tx(x,y,s,a){ var e=mk('text',Object.assign({x:x,y:y},a)); e.textContent=s; return e; }

  /* bg */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 13, 'Parliamentary Sitting Days — Global Context',{
    fill:'#6b7280','font-size':'10','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(W/2, 25, '⚠ Caution: each country measures differently — see per-row notes. Direct comparison is approximate.',{
    fill:'#78350f','font-size':'8',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* scale lines */
  [50,100,150,200].forEach(function(v){
    var x = L + (v/MAX_VAL)*PW;
    svg.appendChild(mk('line',{x1:x,y1:T-1,x2:x,y2:T+(NR*(ROW_H+GAP)-GAP)+4,
      stroke:'#1f2937','stroke-width':'0.5','stroke-dasharray':'3,3'}));
    svg.appendChild(tx(x, T-3, v+'',{
      fill:'#374151','font-size':'7.5','text-anchor':'middle','font-family':'system-ui,sans-serif'}));
  });
  svg.appendChild(tx(L-4, T-3, '0',{
    fill:'#374151','font-size':'7.5','text-anchor':'end','font-family':'system-ui,sans-serif'}));

  ROWS.forEach(function(r, i){
    var y = T + i*(ROW_H+GAP);
    var cy = y + ROW_H/2;

    if(r.hl){
      svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:r.col+'09'}));
      svg.appendChild(mk('rect',{x:0,y:y+1,width:3,height:ROW_H-2,fill:r.col,rx:1.5}));
    } else if(i%2===1){
      svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:'#0e1521'}));
    }

    /* flag + name */
    svg.appendChild(tx(4, cy, r.flag,{
      'font-size':'13','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
    svg.appendChild(tx(22, cy-4, r.name,{
      fill: r.hl ? r.col : '#9ca3af',
      'font-size': r.hl ? '9.5':'9', 'font-weight': r.hl ? '700':'500',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
    svg.appendChild(tx(22, cy+6, r.sub,{
      fill:'#4b5563','font-size':'7','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));

    /* track */
    svg.appendChild(mk('rect',{x:L,y:y+5,width:PW,height:ROW_H-10,fill:'#111827',rx:3}));

    /* bar — for UK show range bar with labels inside */
    if(r.name === 'UK'){
      var x1 = L + (101/MAX_VAL)*PW;
      var x2 = L + (213/MAX_VAL)*PW;
      svg.appendChild(mk('rect',{x:x1,y:y+5,width:x2-x1,height:ROW_H-10,
        fill:r.col,rx:3,opacity:'0.7'}));
      /* "range" label centred inside bar */
      svg.appendChild(tx((x1+x2)/2, cy, '101 – 213 days  (varies by session length)',{
        fill:'#0a0e17','font-size':'8','font-weight':'700',
        'text-anchor':'middle','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
    } else {
      var bw = (r.val/MAX_VAL)*PW;
      svg.appendChild(mk('rect',{x:L,y:y+5,width:bw,height:ROW_H-10,
        fill:r.col,rx:3,opacity:r.hl?'1':'0.75'}));
      var labelIn = bw > 52;
      svg.appendChild(tx(
        labelIn ? L+bw-4 : L+bw+4, cy,
        r.valLabel,{
        fill: labelIn ? '#0a0e17' : r.col,
        'font-size':'9','font-weight':'700',
        'text-anchor': labelIn ? 'end' : 'start',
        'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));
    }
  });

  /* source + methodology notes */
  var sy = T + NR*(ROW_H+GAP) + 10;
  svg.appendChild(tx(4, sy, 'India: actual sitting days (PRS) · Japan: calendar days per ordinary session, not plenary sittings (IPU/Wikipedia) · Australia: sitting days since 1901 (Parliament of Australia)',{
    fill:'#1f2937','font-size':'7','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(4, sy+10, 'Canada & Germany: IPU Parline gives sitting weeks; converted to days (×5) — lower-bound estimate only · UK: varies from 101 (election year) to 213 (18-month session)',{
    fill:'#1f2937','font-size':'7','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(4, sy+20, 'Sources: PRS prsindia.org · IPU data.ipu.org · UK Parliament parliament.uk · aph.gov.au · Wikipedia (National Diet)',{
    fill:'#1f2937','font-size':'7','font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>India’s 55 days is an average across a full 5-year term. Japan’s 150 days is calendar days the session is open, not necessarily plenary sittings. Germany and Canada figures are converted from sitting weeks (IPU data) — treating 1 week as 5 days. The comparison is directionally valid, not exact.</em></p>

<hr />

<h2 id="the-data">The Data</h2>

<p>Every fact in this post is traceable to a primary source. Nothing is approximate.</p>

<div style="display:flex;gap:12px;flex-wrap:wrap;margin:1rem 0 1.5rem;">
  <a href="/assets/data/parliament_verified_facts.json" download="" style="display:inline-flex;align-items:center;gap:8px;padding:9px 16px;background:#111827;border:1px solid #1f2937;border-radius:7px;color:#e5e7eb;text-decoration:none;font-size:0.88em;font-family:system-ui,sans-serif;">
    <span style="font-size:1.1em;">⬇</span> JSON (62 verified facts with source URLs)
  </a>
  <a href="/assets/data/parliament_verified_facts.csv" download="" style="display:inline-flex;align-items:center;gap:8px;padding:9px 16px;background:#111827;border:1px solid #1f2937;border-radius:7px;color:#e5e7eb;text-decoration:none;font-size:0.88em;font-family:system-ui,sans-serif;">
    <span style="font-size:1.1em;">⬇</span> CSV (same 62 facts, flat format)
  </a>
</div>

<p><strong>Primary sources used:</strong></p>

<table>
  <thead>
    <tr>
      <th>Claim</th>
      <th>Source</th>
      <th>URL</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Both 2025 sessions — all figures</td>
      <td>PRS Legislative Research</td>
      <td>prsindia.org/parliamenttrack/vital-stats</td>
    </tr>
    <tr>
      <td>2025 session productivity (govt figures)</td>
      <td>Press Information Bureau</td>
      <td>pib.gov.in</td>
    </tr>
    <tr>
      <td>17th LS aggregate statistics</td>
      <td>PRS Legislative Research</td>
      <td>prsindia.org/parliamenttrack/vital-stats/functioning-of-the-17th-lok-sabha</td>
    </tr>
    <tr>
      <td>Year-wise sitting days 1952–2022</td>
      <td>Ministry of Parliamentary Affairs Statistical Handbook 2023</td>
      <td>mpa.gov.in (PDF, Table 3)</td>
    </tr>
    <tr>
      <td>Sitting days 2023–24</td>
      <td>Ministry of Parliamentary Affairs dataset #45</td>
      <td>data.gov.in (via dataful.in/datasets/45)</td>
    </tr>
    <tr>
      <td>Australia 67 days/year since 1901</td>
      <td>Parliament of Australia, Practice7</td>
      <td>aph.gov.au</td>
    </tr>
    <tr>
      <td>Japan 150-day ordinary session</td>
      <td>IPU / Wikipedia citing official</td>
      <td>data.ipu.org, en.wikipedia.org/wiki/National_Diet</td>
    </tr>
    <tr>
      <td>Canada ~27 sitting weeks/year</td>
      <td>IPU Parline</td>
      <td>data.ipu.org/parliament/CA</td>
    </tr>
    <tr>
      <td>Germany ≥20 sitting weeks/year</td>
      <td>IPU Parline</td>
      <td>data.ipu.org/parliament/DE</td>
    </tr>
    <tr>
      <td>UK 2022–24 session sitting days</td>
      <td>UK Parliament recess dates page</td>
      <td>parliament.uk</td>
    </tr>
  </tbody>
</table>

<p><strong>What this post does not claim:</strong>
All figures cited from secondary sources that cite PRS (e.g., The Week, Drishti IAS) are excluded from visualizations — only direct primary source fetches are used. Session productivity % for Monsoon 2020 and Special 2023 are unavailable (PRS pages returned 404).</p>]]></content><author><name>Kanak Raj</name><email>kanak8278@gmail.com</email></author><category term="blog" /><category term="india" /><category term="parliament" /><category term="democracy" /><category term="data" /><category term="visualization" /><category term="policy" /><summary type="html"><![CDATA[India's parliament sat for 55 days a year in the 17th Lok Sabha — the lowest of any full-term parliament. 35% of bills passed with under an hour of Lok Sabha debate. In Monsoon 2025, 14 bills became law; the longest LS debate was 34 minutes. Every number here is from a primary source. None are approximate.]]></summary></entry><entry><title type="html">The Fragmented Calendar: A Data Atlas of Global Stock Exchange Holidays 2025</title><link href="https://kanak8278.github.io//blog/stock-market-holidays-2025/" rel="alternate" type="text/html" title="The Fragmented Calendar: A Data Atlas of Global Stock Exchange Holidays 2025" /><published>2026-04-10T00:00:00+00:00</published><updated>2026-04-10T00:00:00+00:00</updated><id>https://kanak8278.github.io//blog/stock-market-holidays-2025</id><content type="html" xml:base="https://kanak8278.github.io//blog/stock-market-holidays-2025/"><![CDATA[<div style="border-left:3px solid #f59e0b; padding:12px 18px; background:rgba(245,158,11,0.06); border-radius:0 6px 6px 0; margin-bottom:2rem;">
<p style="margin:0; color:#d1b07a; font-size:0.96em; line-height:1.75;">
Japan loses <strong style="color:#f472b6">99 hours</strong> of trading time to holidays each year — more than any other major exchange. India loses <strong style="color:#f59e0b">87.5 hours</strong> across 14 closures. China loses only <strong style="color:#ef4444">72 hours</strong> despite having 18 holidays. Same counts, wildly different costs — because the pattern matters as much as the total. Every number below is verified from official exchange sources.
</p>
</div>

<h2 id="where-every-holiday-falls">Where Every Holiday Falls</h2>

<p>Each dot is one weekday the market is closed. Position is exact — placed by week within the year. Hover to see the holiday name.</p>

<style>
#smhc-tip {
  position: fixed;
  background: #1a2030;
  border: 1px solid #2a3348;
  border-radius: 6px;
  padding: 8px 12px;
  font-size: 11px;
  font-family: system-ui, -apple-system, sans-serif;
  color: #e4e8f0;
  pointer-events: none;
  display: none;
  z-index: 9999;
  max-width: 260px;
  line-height: 1.65;
  box-shadow: 0 4px 24px rgba(0,0,0,0.65);
}
#smhc-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#smhc-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 6px;
  min-width: 640px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="smhc-tip"></div>
<div id="smhc-outer">
  <div id="smhc-wrap">
    <div id="smhc-loading" style="padding:48px;text-align:center;color:#4b5563;font-size:12px;font-family:system-ui">Loading verified data…</div>
    <svg id="smhc-svg" viewBox="0 0 860 340" width="100%" xmlns="http://www.w3.org/2000/svg" style="display:none;"></svg>
  </div>
</div>

<script>
(function () {
  var NS   = 'http://www.w3.org/2000/svg';
  var tip  = document.getElementById('smhc-tip');
  var svg  = document.getElementById('smhc-svg');
  var load = document.getElementById('smhc-loading');

  /* ── Layout ─────────────────────────────────────── */
  var W=860, H=340, L=96, R=56, T=48, B=44;
  var PW = W-L-R;   /* 708 */
  var PH = H-T-B;   /* 248 */
  var NR = 7;
  var RH = PH/NR;   /* 35.4 */

  /* ── Exchange presentation order & colours ──────── */
  var ORDER = ['XETR','XNYS','XLON','JPX','XHKG','BSE','XSHG'];
  var COL   = {
    XETR:'#10b981', XNYS:'#8b5cf6', XLON:'#3b82f6',
    JPX :'#f472b6', XHKG:'#06b6d4', BSE :'#f59e0b', XSHG:'#ef4444'
  };
  var HL = {BSE:true, XSHG:true};   /* highlighted rows */

  /* ── Month grid ─────────────────────────────────── */
  var MONTHS=[
    {n:'Jan',d:1},{n:'Feb',d:32},{n:'Mar',d:60},{n:'Apr',d:91},
    {n:'May',d:121},{n:'Jun',d:152},{n:'Jul',d:182},{n:'Aug',d:213},
    {n:'Sep',d:244},{n:'Oct',d:274},{n:'Nov',d:305},{n:'Dec',d:335}
  ];

  /* ── Helpers ─────────────────────────────────────── */
  function doy(ds) {   /* YYYY-MM-DD → day of year (2025, non-leap) */
    var md=[0,31,28,31,30,31,30,31,31,30,31,30,31];
    var p=ds.split('-'), m=+p[1], d=+p[2], acc=0;
    for(var i=1;i<m;i++) acc+=md[i];
    return acc+d;
  }
  function xp(day){ return L+(day/365)*PW; }
  function yr(i)  { return T+i*RH+RH/2;   }
  function mk(tag,a){
    var e=document.createElementNS(NS,tag);
    for(var k in a) e.setAttribute(k,a[k]);
    return e;
  }
  function tx(x,y,s,a){
    var e=mk('text',Object.assign({x:x,y:y},a));
    e.textContent=s; return e;
  }

  /* ── Main render ─────────────────────────────────── */
  function render(data) {
    load.style.display='none';
    svg.style.display='block';

    /* background */
    svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

    /* title */
    svg.appendChild(tx(W/2,14,'Every 2025 Weekday Market Holiday — Positioned by Week',{
      fill:'#6b7280','font-size':'10','font-weight':'600',
      'text-anchor':'middle','font-family':'system-ui,sans-serif'
    }));

    /* row backgrounds + left accent bars */
    ORDER.forEach(function(code,i){
      var ry=T+i*RH, hl=HL[code];
      svg.appendChild(mk('rect',{
        x:L,y:ry,width:PW,height:RH,
        fill: hl ? COL[code]+'0a' : (i%2===1?'#0e1521':'transparent')
      }));
      if(hl) svg.appendChild(mk('rect',{x:0,y:ry+2,width:3,height:RH-4,fill:COL[code],rx:1.5}));
    });

    /* subtle week grid (every 7 days, skip if near month line) */
    for(var wd=7;wd<=364;wd+=7){
      var near=MONTHS.some(function(m){return Math.abs(m.d-wd)<3;});
      if(!near) svg.appendChild(mk('line',{x1:xp(wd),y1:T,x2:xp(wd),y2:T+PH,stroke:'#141c2a','stroke-width':'0.35'}));
    }

    /* month separator lines */
    MONTHS.forEach(function(m,mi){
      var x=xp(m.d);
      svg.appendChild(mk('line',{x1:x,y1:T-4,x2:x,y2:T+PH,stroke:'#1f2937','stroke-width':'0.6'}));
      var nd=mi<11?MONTHS[mi+1].d:366;
      svg.appendChild(tx(xp((m.d+nd)/2),T-8,m.n,{
        fill:'#4b5563','font-size':'9','text-anchor':'middle','font-family':'system-ui,sans-serif'
      }));
    });

    /* dots collector */
    var dots=[];

    ORDER.forEach(function(code,i){
      var ex=data.exchanges[code]; if(!ex) return;
      var cy=yr(i), col=COL[code], hl=HL[code], r=hl?6.5:5.5;

      /* block connector rects */
      var blocks={};
      ex.holidays.forEach(function(h){
        if(h.block){
          if(!blocks[h.block]) blocks[h.block]=[];
          blocks[h.block].push(doy(h.date));
        }
      });
      Object.keys(blocks).forEach(function(bk){
        var days=blocks[bk];
        var x1=xp(Math.min.apply(null,days))-r-1;
        var x2=xp(Math.max.apply(null,days))+r+1;
        svg.appendChild(mk('rect',{
          x:x1,y:cy-r-1,width:x2-x1,height:(r+1)*2,
          rx:r,fill:col+'18',stroke:col+'44','stroke-width':'0.8'
        }));
      });

      /* exchange label */
      svg.appendChild(tx(4,cy,ex.flag,{'font-size':'12','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
      svg.appendChild(tx(22,cy-5,ex.display_name,{
        fill:hl?col:'#9ca3af','font-size':hl?'9.5':'9','font-weight':hl?'700':'500',
        'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));
      svg.appendChild(tx(22,cy+5.5,ex.country,{
        fill:'#4b5563','font-size':'7.5','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));

      /* holiday count right */
      svg.appendChild(tx(L+PW+14,cy,ex.holiday_count+'d',{
        fill:hl?col:'#374151','font-size':'8.5','font-weight':hl?'700':'400',
        'text-anchor':'start','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));

      /* dots */
      ex.holidays.forEach(function(h){
        var dot=mk('circle',{cx:xp(doy(h.date)),cy:cy,r:r,fill:col,opacity:h.block?'0.95':'0.80',cursor:'pointer'});
        dot._d={ex:ex,h:h,col:col};
        dots.push(dot);
        svg.appendChild(dot);
      });
    });

    /* tooltip events */
    var TYPE_LABEL={
      civic:'Civic / National',cultural:'Cultural / Religious',
      regional:'Regional',mourning:'National Mourning',exchange:'Exchange-Declared'
    };
    dots.forEach(function(dot){
      dot.addEventListener('mouseenter',function(e){
        var d=this._d;
        tip.innerHTML=
          '<b style="color:#e4e8f0">'+d.ex.flag+' '+d.ex.display_name+'</b><br>'+
          '<span style="color:#6b7280">'+d.h.date+' ('+d.h.dow+')</span><br>'+
          '<span style="color:#d1d5db;font-weight:600">'+d.h.name+'</span><br>'+
          '<span style="color:'+d.col+';font-size:10px">'+(TYPE_LABEL[d.h.type]||d.h.type)+'</span>'+
          (d.h.block?'<br><span style="color:#4b5563;font-size:10px">▪ part of multi-day block</span>':'');
        tip.style.display='block'; moveTip(e);
      });
      dot.addEventListener('mousemove',moveTip);
      dot.addEventListener('mouseleave',function(){tip.style.display='none';});
    });

    /* legend */
    var ly=H-16, lx=L;
    svg.appendChild(mk('circle',{cx:lx+4,cy:ly,r:4,fill:'#6b7280',opacity:'0.8'}));
    svg.appendChild(tx(lx+11,ly,'Isolated day',{fill:'#6b7280','font-size':'8.5','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
    lx+=90;
    svg.appendChild(mk('rect',{x:lx,y:ly-4,width:19,height:9,rx:4,fill:'#ef444418',stroke:'#ef444450','stroke-width':'0.8'}));
    svg.appendChild(mk('circle',{cx:lx+5,cy:ly,r:3.5,fill:'#ef4444'}));
    svg.appendChild(mk('circle',{cx:lx+14,cy:ly,r:3.5,fill:'#ef4444'}));
    svg.appendChild(tx(lx+24,ly,'Multi-day block',{fill:'#6b7280','font-size':'8.5','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
    lx+=105;
    svg.appendChild(mk('circle',{cx:lx+4,cy:ly,r:4,fill:'#f59e0b'}));
    svg.appendChild(tx(lx+11,ly,'India',{fill:'#f59e0b','font-size':'8.5','font-weight':'700','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
    lx+=48;
    svg.appendChild(mk('circle',{cx:lx+4,cy:ly,r:4,fill:'#ef4444'}));
    svg.appendChild(tx(lx+11,ly,'China',{fill:'#ef4444','font-size':'8.5','font-weight':'700','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));

    /* source */
    svg.appendChild(tx(W-8,ly,'Data: exchange_calendars + official circulars',{
      fill:'#2d3748','font-size':'7.5','text-anchor':'end','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
  }

  function moveTip(e){
    var x=e.clientX+14, y=e.clientY-10;
    if(x+265>window.innerWidth) x=e.clientX-275;
    if(y+110>window.innerHeight) y=e.clientY-120;
    tip.style.left=x+'px'; tip.style.top=y+'px';
  }

  fetch('/assets/data/exchange_holidays_2025.json')
    .then(function(r){return r.json();})
    .then(render)
    .catch(function(){load.textContent='Could not load data.';});
})();
</script>

<p><em>India’s row is a dashed line — one dot almost every month, never clustered. China’s row is the opposite: six solid blocks you can plan around. Germany and LSE clear the entire mid-year with almost nothing.</em></p>

<h2 id="what-it-actually-costs">What It Actually Costs</h2>

<p>The count of holidays is misleading without session length. Germany and LSE each close 8 times but run 8.5-hour days. China closes 18 times but trades only 4 hours a day.</p>

<style>
#smhh-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#smhh-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 8px 0 8px;
  min-width: 560px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="smhh-outer">
  <div id="smhh-wrap">
    <div id="smhh-loading" style="padding:40px;text-align:center;color:#4b5563;font-size:12px;font-family:system-ui">Loading data…</div>
    <svg id="smhh-svg" viewBox="0 0 860 262" width="100%" xmlns="http://www.w3.org/2000/svg" style="display:none;"></svg>
  </div>
</div>

<script>
(function(){
  var NS   = 'http://www.w3.org/2000/svg';
  var svg  = document.getElementById('smhh-svg');
  var load = document.getElementById('smhh-loading');

  var W=860, H=262;
  var L=118, R=218, T=32, B=20;
  var BAR_W = W-L-R;   /* 524px for bars */
  var ORDER = ['XETR','XNYS','XLON','JPX','XHKG','BSE','XSHG'];
  var COL   = {
    XETR:'#10b981', XNYS:'#8b5cf6', XLON:'#3b82f6',
    JPX :'#f472b6', XHKG:'#06b6d4', BSE :'#f59e0b', XSHG:'#ef4444'
  };
  var HL={BSE:true,JPX:true};   /* JPX highest, BSE story */

  var NR=7, ROW_H=26, GAP=4;
  var TOTAL_H=NR*(ROW_H+GAP)-GAP;   /* 186 */
  var BAR_H=ROW_H;
  var MAX_HRS=110;   /* slightly above 99 for padding */

  function mk(tag,a){var e=document.createElementNS(NS,tag);for(var k in a)e.setAttribute(k,a[k]);return e;}
  function tx(x,y,s,a){var e=mk('text',Object.assign({x:x,y:y},a));e.textContent=s;return e;}
  function barX(hrs){ return L+((hrs/MAX_HRS)*BAR_W); }

  function render(data){
    load.style.display='none';
    svg.style.display='block';

    /* background */
    svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

    /* title */
    svg.appendChild(tx(W/2,16,'Annual Trading Hours Lost to Holidays — 2025',{
      fill:'#6b7280','font-size':'10','font-weight':'600',
      'text-anchor':'middle','font-family':'system-ui,sans-serif'
    }));

    /* sort by hours lost (desc) for this chart */
    var sorted=ORDER.map(function(c){return data.exchanges[c];})
      .sort(function(a,b){return b.annual_holiday_hours_lost-a.annual_holiday_hours_lost;});

    /* grid lines at 20h intervals */
    [20,40,60,80,100].forEach(function(hrs){
      var x=barX(hrs);
      svg.appendChild(mk('line',{x1:x,y1:T,x2:x,y2:T+TOTAL_H,stroke:'#1f2937','stroke-width':'0.5'}));
      svg.appendChild(tx(x,T-5,hrs+'h',{fill:'#374151','font-size':'8','text-anchor':'middle','font-family':'system-ui,sans-serif'}));
    });

    /* baseline */
    svg.appendChild(mk('line',{x1:L,y1:T,x2:L,y2:T+TOTAL_H,stroke:'#1f2937','stroke-width':'0.8'}));

    sorted.forEach(function(ex,i){
      if(!ex) return;
      var code=ex.code;
      var col=COL[code], hl=HL[code];
      var y=T+i*(ROW_H+GAP);
      var cy=y+ROW_H/2;
      var hrs=ex.annual_holiday_hours_lost;
      var bx=barX(hrs);

      /* row bg */
      if(i%2===1||hl){
        svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:hl?col+'08':'#0e1521'}));
      }

      /* left accent for highlighted */
      if(hl) svg.appendChild(mk('rect',{x:0,y:y+1,width:3,height:ROW_H-2,fill:col,rx:1.5}));

      /* exchange label */
      svg.appendChild(tx(5,cy,ex.flag,{'font-size':'12','dominant-baseline':'middle','font-family':'system-ui,sans-serif'}));
      svg.appendChild(tx(23,cy-4,ex.display_name,{
        fill:hl?col:'#9ca3af','font-size':hl?'9.5':'9','font-weight':hl?'700':'500',
        'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));
      svg.appendChild(tx(23,cy+5,ex.country,{
        fill:'#4b5563','font-size':'7.5','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));

      /* bar track */
      svg.appendChild(mk('rect',{x:L,y:y+4,width:BAR_W,height:ROW_H-8,fill:'#111827',rx:3}));

      /* bar fill */
      svg.appendChild(mk('rect',{x:L,y:y+4,width:bx-L,height:ROW_H-8,fill:col,opacity:hl?'1':'0.75',rx:3}));

      /* hours value label inside/outside bar */
      var labelX = (bx-L) > 40 ? bx-6 : bx+5;
      var labelAnchor = (bx-L) > 40 ? 'end' : 'start';
      svg.appendChild(tx(labelX,cy,hrs+'h',{
        fill:(bx-L)>40?'#0a0e17':col,'font-size':'9','font-weight':'700',
        'text-anchor':labelAnchor,'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));

      /* right annotation: breakdown */
      var ann=ex.holiday_count+' holidays × '+ex.trading_hours_per_day+'h/day';
      svg.appendChild(tx(L+BAR_W+10,cy-3,ann,{
        fill:hl?col:'#4b5563','font-size':'8.5','font-weight':hl?'600':'400',
        'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));
      /* trading days */
      svg.appendChild(tx(L+BAR_W+10,cy+6,ex.trading_days_2025+' trading days/yr',{
        fill:'#374151','font-size':'7.5','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
      }));
    });

    /* bottom source */
    var sy=T+TOTAL_H+12;
    svg.appendChild(tx(L,sy,'Source: exchange_calendars (github.com/gerrymanoim/exchange_calendars) + official exchange circulars, 2025. JPX hours reflect Nov 2024 extension to 5.5h/day.',{
      fill:'#2d3748','font-size':'7.2','font-family':'system-ui,sans-serif'
    }));
  }

  fetch('/assets/data/exchange_holidays_2025.json')
    .then(function(r){return r.json();})
    .then(render)
    .catch(function(){load.textContent='Could not load data.';});
})();
</script>

<p><em>Japan’s 2024 extension to 5.5-hour sessions (removing the lunch break, effective Nov 5 2024) pushed its annual hours-lost above everyone else — a fact absent from most market commentary.</em></p>

<h2 id="what-each-country-decides-is-worth-stopping-for">What Each Country Decides Is Worth Stopping For</h2>

<p>Japan closes for 13 civic holidays and only 2 cultural ones. India is the opposite — 10 religious festivals and 3 civic days. The breakdown reveals institutional character more than any count does.</p>

<style>
#smht-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#smht-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 10px 0 10px;
  min-width: 580px;
  font-family: system-ui, -apple-system, sans-serif;
}
</style>

<div id="smht-outer">
  <div id="smht-wrap">
    <svg id="smht-svg" viewBox="0 0 860 374" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('smht-svg');

  /* ── Verified data ── */
  /* Sorted: most civic → most cultural (left to right on cultural axis) */
  var ROWS = [
    { name:'JPX (Tokyo)',       country:'Japan',          flag:'🇯🇵', col:'#f472b6',
      types:{ civic:13, cultural:2, exchange:3 } },
    { name:'NYSE / NASDAQ',     country:'United States',  flag:'🇺🇸', col:'#8b5cf6',
      types:{ civic:9, cultural:1, mourning:1 } },
    { name:'SSE (Shanghai)',    country:'China',          flag:'🇨🇳', col:'#ef4444',
      types:{ civic:10, cultural:8 } },
    { name:'LSE',               country:'United Kingdom', flag:'🇬🇧', col:'#3b82f6',
      types:{ civic:4, cultural:4 } },
    { name:'Xetra (Frankfurt)', country:'Germany',        flag:'🇩🇪', col:'#10b981',
      types:{ civic:3, cultural:5 } },
    { name:'HKEX',              country:'Hong Kong',      flag:'🇭🇰', col:'#06b6d4',
      types:{ civic:4, cultural:11 } },
    { name:'NSE / BSE',         country:'India',          flag:'🇮🇳', col:'#f59e0b', hl:true,
      types:{ civic:3, cultural:10, regional:1 } },
  ];

  var TYPE_COL = {
    civic:'#3b82f6', cultural:'#f59e0b',
    regional:'#06b6d4', mourning:'#6b7280', exchange:'#8b5cf6'
  };
  var TYPE_ORDER = ['civic','cultural','regional','mourning','exchange'];
  var TYPE_LABEL = {
    civic:'Civic / National', cultural:'Cultural / Religious',
    regional:'Regional', mourning:'National Mourning', exchange:'Exchange-Declared'
  };

  /* ── Layout ──
     NR=7, ROW_H=34, GAP=8
     TOTAL_H = 7*(34+8)-8 = 294-8 = 286
     T=44, rows end at 44+286=330
     Legend at 338, source at 350
     H=360 — but let's trim to 338 since legend fits at bottom padding
  */
  var W=860, H=374;
  var L=118, R=240, T=44, B=0;
  var PW = W-L-R;   /* 502 px for bars */
  var NR=7, ROW_H=34, GAP=8;
  var TOTAL_H = NR*(ROW_H+GAP)-GAP;   /* 286 px — rows end at T+TOTAL_H = 330 */

  function mk(tag,a){
    var e=document.createElementNS(NS,tag);
    for(var k in a) e.setAttribute(k,a[k]);
    return e;
  }
  function tx(x,y,s,a){
    var e=mk('text',Object.assign({x:x,y:y},a));
    e.textContent=s; return e;
  }

  /* background */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 18, 'What Each Exchange Decides Is Worth Stopping Trade For — 2025',{
    fill:'#6b7280','font-size':'11','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* axis labels */
  svg.appendChild(tx(L+PW*0.12, T-10, '← Civic / National',{
    fill:'#3b82f660','font-size':'9','font-style':'italic',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));
  svg.appendChild(tx(L+PW*0.80, T-10, 'Cultural / Religious →',{
    fill:'#f59e0b60','font-size':'9','font-style':'italic',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  ROWS.forEach(function(row, i){
    var y  = T + i*(ROW_H+GAP);
    var cy = y + ROW_H/2;
    var total = Object.values(row.types).reduce(function(a,b){return a+b;},0);

    /* row background */
    if(row.hl){
      svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:row.col+'0a'}));
      svg.appendChild(mk('rect',{x:0,y:y+2,width:3,height:ROW_H-4,fill:row.col,rx:1.5}));
    } else if(i%2===1){
      svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:'#0e1521'}));
    }

    /* exchange label — two lines */
    svg.appendChild(tx(6, cy, row.flag,{
      'font-size':'13','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(26, cy-5.5, row.name,{
      fill: row.hl ? row.col : '#9ca3af',
      'font-size': row.hl ? '9.5' : '9',
      'font-weight': row.hl ? '700' : '500',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(26, cy+6, row.country,{
      fill:'#4b5563','font-size':'8',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));

    /* bar track */
    svg.appendChild(mk('rect',{
      x:L, y:y+5, width:PW, height:ROW_H-10,
      fill:'#111827', rx:4
    }));

    /* stacked segments */
    var xOff = L;
    TYPE_ORDER.forEach(function(t){
      var n = row.types[t] || 0;
      if(!n) return;
      var w = (n/total)*PW;
      svg.appendChild(mk('rect',{
        x:xOff, y:y+5, width:w, height:ROW_H-10,
        fill:TYPE_COL[t], opacity:row.hl?'1':'0.82', rx:0
      }));
      /* count inside segment if wide enough */
      if(w > 22){
        svg.appendChild(tx(xOff+w/2, cy, ''+n,{
          fill:'#000', 'font-size':'9.5','font-weight':'700',
          'text-anchor':'middle','dominant-baseline':'middle',
          'font-family':'system-ui,sans-serif', opacity:'0.75'
        }));
      }
      xOff += w;
    });

    /* round-corner overlay mask */
    svg.appendChild(mk('rect',{
      x:L, y:y+5, width:PW, height:ROW_H-10,
      fill:'none', rx:4, stroke:'#0a0e17','stroke-width':'1'
    }));

    /* right annotation — colored tspan per type */
    var annX = L + PW + 16;
    var annEl = mk('text',{
      x:annX, y:cy,
      'font-size':'8.5','dominant-baseline':'middle',
      'font-family':'system-ui,sans-serif'
    });
    var first = true;
    TYPE_ORDER.forEach(function(t){
      var n = row.types[t];
      if(!n) return;
      if(!first){
        var sep = document.createElementNS(NS,'tspan');
        sep.setAttribute('fill','#374151');
        sep.textContent = '  ';
        annEl.appendChild(sep);
      }
      var ts1 = document.createElementNS(NS,'tspan');
      ts1.setAttribute('fill', TYPE_COL[t]);
      ts1.setAttribute('font-weight','700');
      ts1.textContent = ''+n;
      annEl.appendChild(ts1);
      var ts2 = document.createElementNS(NS,'tspan');
      ts2.setAttribute('fill','#5b6880');
      ts2.textContent = '\u202f'+t;
      annEl.appendChild(ts2);
      first = false;
    });
    svg.appendChild(annEl);
  });

  /* ── Legend — placed after all rows with clear margin ── */
  /* Rows end at T+TOTAL_H = 330. Legend at 350 (20px gap). H=374 → 24px below legend. */
  var legendY = T + TOTAL_H + 24;   /* = 354 — well within H=374 */
  var lx = L;
  TYPE_ORDER.forEach(function(t){
    svg.appendChild(mk('rect',{x:lx,y:legendY-5,width:10,height:10,fill:TYPE_COL[t],rx:2}));
    svg.appendChild(tx(lx+13, legendY, TYPE_LABEL[t],{
      fill:'#6b7280','font-size':'8.5','dominant-baseline':'middle',
      'font-family':'system-ui,sans-serif'
    }));
    lx += TYPE_LABEL[t].length*5.1 + 24;
  });
  svg.appendChild(tx(W-6, legendY, 'Source: exchange_calendars + official circulars',{
    fill:'#1f2937','font-size':'7.5','text-anchor':'end',
    'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>JPX has a category none of the others have: 3 exchange-declared market holidays (Jan 2, Jan 3, Dec 31) with no civic or religious basis — the exchange simply decided those days don’t trade.</em></p>

<h2 id="how-many-weeks-are-actually-interrupted">How Many Weeks Are Actually Interrupted</h2>

<p>Same holiday count can mean very different disruption rhythms. Each square below is one trading week — colored if it contains at least one closure, dark if completely clean.</p>

<style>
#smhw-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.5rem 0;
}
#smhw-wrap {
  background: #0a0e17;
  border-radius: 10px;
  padding: 10px 0 10px;
  min-width: 640px;
  font-family: system-ui, -apple-system, sans-serif;
}
#smhw-tip {
  position: fixed;
  background: #1a2030;
  border: 1px solid #2a3348;
  border-radius: 6px;
  padding: 8px 11px;
  font-size: 11px;
  font-family: system-ui,-apple-system,sans-serif;
  color: #e4e8f0;
  pointer-events: none;
  display: none;
  z-index: 9999;
  max-width: 230px;
  line-height: 1.55;
  box-shadow: 0 4px 20px rgba(0,0,0,0.65);
}
</style>

<div id="smhw-tip"></div>
<div id="smhw-outer">
  <div id="smhw-wrap">
    <svg id="smhw-svg" viewBox="0 0 860 358" width="100%" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
</div>

<script>
(function(){
  var NS  = 'http://www.w3.org/2000/svg';
  var svg = document.getElementById('smhw-svg');
  var tip = document.getElementById('smhw-tip');

  /* ── Pre-computed from verified JSON (2025-04-15) ──────────────────────────
     disrupted = ISO weeks 1-52 that contain at least one holiday.
     blocks    = sets of consecutive disrupted weeks (clustered closures).
     stats     = { disrupted, clean, streak: longest consecutive clean weeks }
  ─────────────────────────────────────────────────────────────────────────── */
  var ROWS = [
    { name:'JPX (Tokyo)',       country:'Japan',          flag:'🇯🇵', col:'#f472b6',
      disrupted:[1,3,7,9,12,18,19,30,33,38,39,42,45,48],
      blocks:[[18,19],[38,39]], stats:{d:14,c:38,s:10} },
    { name:'NSE / BSE',         country:'India',          flag:'🇮🇳', col:'#f59e0b', hl:true,
      disrupted:[9,11,14,15,16,18,33,35,40,43,45,52],
      blocks:[[14,15,16]], stats:{d:12,c:40,s:14} },
    { name:'HKEX',              country:'Hong Kong',      flag:'🇭🇰', col:'#06b6d4',
      disrupted:[1,5,14,16,17,18,19,27,40,41,44,52],
      blocks:[[5],[16,17,18,19],[40,41]], stats:{d:12,c:40,s:12} },
    { name:'NYSE / NASDAQ',     country:'United States',  flag:'🇺🇸', col:'#8b5cf6',
      disrupted:[1,2,4,8,16,22,25,27,36,48,52],
      blocks:[[1,2]], stats:{d:11,c:41,s:11} },
    { name:'SSE (Shanghai)',    country:'China',          flag:'🇨🇳', col:'#ef4444', hl:true,
      disrupted:[1,5,6,14,18,19,23,40,41],
      blocks:[[5,6],[18,19],[40,41]], stats:{d:9,c:43,s:16} },
    { name:'LSE',               country:'United Kingdom', flag:'🇬🇧', col:'#3b82f6',
      disrupted:[1,16,17,19,22,35,52],
      blocks:[[16,17]], stats:{d:7,c:45,s:16} },
    { name:'Xetra (Frankfurt)', country:'Germany',        flag:'🇩🇪', col:'#10b981',
      disrupted:[1,16,17,18,52],
      blocks:[[16,17,18]], stats:{d:5,c:47,s:33} },
  ];

  /* Month start ISO weeks for 2025 */
  var MONTHS=[
    {n:'Jan',w:1},{n:'Feb',w:5},{n:'Mar',w:9},{n:'Apr',w:14},
    {n:'May',w:18},{n:'Jun',w:22},{n:'Jul',w:27},{n:'Aug',w:31},
    {n:'Sep',w:36},{n:'Oct',w:40},{n:'Nov',w:44},{n:'Dec',w:49}
  ];

  /* ── Layout ──
     SQ=10, GAP=2, SLOT=12  →  GRID_W = 52*12-2 = 622
     L=92, GRID_W=622, R=860-92-622=146 — tight for annotations.
     Use SQ=9, SLOT=11  →  GRID_W = 52*11-2 = 570
     L=92, GRID_W=570, R=860-92-570=198 — better.
     Annotation starts at 92+570+14 = 676, available = 184px.

     NR=7, ROW_H=36, GAP=7
     TOTAL_H = 7*(36+7)-7 = 301-7 = 294
     T=40, rows end at 334 — too tall for H=326.
     Use ROW_H=34, GAP=6:
     TOTAL_H = 7*(34+6)-6 = 280-6 = 274
     T=40, rows end at 314. Legend at 322. H=326. ✓
  */
  var W=860, H=358;
  var SQ=9, SLOT=11;
  var GRID_W = 52*SLOT - 2;   /* 570 */
  var L=92, T=42, B=24;
  var NR=7, ROW_H=34, GAP=6;
  var TOTAL_H = NR*(ROW_H+GAP)-GAP;   /* 274 — rows end at T+TOTAL_H=316 */
  var ANN_X = L + GRID_W + 14;        /* annotation start: 676 */

  function mk(tag,a){
    var e=document.createElementNS(NS,tag);
    for(var k in a) e.setAttribute(k,a[k]);
    return e;
  }
  function tx(x,y,s,a){
    var e=mk('text',Object.assign({x:x,y:y},a));
    e.textContent=s; return e;
  }
  function sqX(w){ return L+(w-1)*SLOT; }

  /* background */
  svg.appendChild(mk('rect',{width:W,height:H,fill:'#0a0e17'}));

  /* title */
  svg.appendChild(tx(W/2, 16, 'Clean Trading Weeks in 2025 — Every Week Either Clear or Interrupted',{
    fill:'#6b7280','font-size':'10.5','font-weight':'600',
    'text-anchor':'middle','font-family':'system-ui,sans-serif'
  }));

  /* month separator lines + labels */
  MONTHS.forEach(function(m,mi){
    var x = sqX(m.w);
    svg.appendChild(mk('line',{
      x1:x,y1:T-4,x2:x,y2:T+TOTAL_H,
      stroke:'#1a2535','stroke-width':'0.7'
    }));
    var nw = mi<11 ? MONTHS[mi+1].w : 53;
    var mid = sqX(m.w) + ((nw-m.w)*SLOT)/2;
    svg.appendChild(tx(mid, T-8, m.n,{
      fill:'#4b5563','font-size':'9','text-anchor':'middle',
      'font-family':'system-ui,sans-serif'
    }));
  });

  /* rows */
  var allSq = [];

  ROWS.forEach(function(row,i){
    var y  = T + i*(ROW_H+GAP);
    var cy = y + ROW_H/2;

    /* build lookup sets */
    var disSet={}, blockSet={};
    row.disrupted.forEach(function(w){ disSet[w]=true; });
    row.blocks.forEach(function(blk){ blk.forEach(function(w){ blockSet[w]=true; }); });

    /* row bg + accent */
    if(row.hl){
      svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:row.col+'09'}));
      svg.appendChild(mk('rect',{x:0,y:y+2,width:3,height:ROW_H-4,fill:row.col,rx:1.5}));
    } else if(i%2===1){
      svg.appendChild(mk('rect',{x:0,y:y,width:W,height:ROW_H,fill:'#0e1521'}));
    }

    /* block connector rects (drawn behind squares) */
    row.blocks.forEach(function(blk){
      if(blk.length < 2) return;
      var x1 = sqX(blk[0])-1;
      var x2 = sqX(blk[blk.length-1])+SQ+1;
      svg.appendChild(mk('rect',{
        x:x1, y:cy-SQ/2-2, width:x2-x1, height:SQ+4,
        rx:2.5, fill:row.col+'22', stroke:row.col+'50','stroke-width':'0.7'
      }));
    });

    /* 52 week squares */
    for(var w=1;w<=52;w++){
      var sx = sqX(w);
      var sy = cy - SQ/2;
      var dis = !!disSet[w];
      var sq = mk('rect',{
        x:sx, y:sy, width:SQ, height:SQ, rx:1.5,
        fill: dis ? row.col : '#141e2e',
        opacity: dis ? (blockSet[w]?'1':'0.85') : '1',
        cursor: dis ? 'pointer' : 'default'
      });
      if(dis){
        sq._d = {row:row, week:w, isBlock:!!blockSet[w]};
        allSq.push(sq);
      }
      svg.appendChild(sq);
    }

    /* exchange label */
    svg.appendChild(tx(6, cy, row.flag,{
      'font-size':'12','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(23, cy-5.5, row.name,{
      fill: row.hl ? row.col : '#9ca3af',
      'font-size': row.hl ? '9.5' : '9',
      'font-weight': row.hl ? '700' : '500',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(23, cy+5.5, row.country,{
      fill:'#4b5563','font-size':'7.5','dominant-baseline':'middle',
      'font-family':'system-ui,sans-serif'
    }));

    /* right stats — two clean lines */
    var statsColor = row.hl ? row.col : '#4b5563';
    svg.appendChild(tx(ANN_X, cy-5,
      row.stats.d+' disrupted · '+row.stats.c+' clean',{
      fill: statsColor,
      'font-size': row.hl ? '9' : '8.5',
      'font-weight': row.hl ? '700' : '400',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
    svg.appendChild(tx(ANN_X, cy+7,
      'best streak: '+row.stats.s+' wks',{
      fill:'#374151','font-size':'8',
      'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
    }));
  });

  /* tooltip */
  allSq.forEach(function(sq){
    sq.addEventListener('mouseenter',function(e){
      var d=this._d;
      tip.innerHTML=
        '<b style="color:#e4e8f0">'+d.row.flag+' '+d.row.name+'</b><br>'+
        '<span style="color:#6b7280">Week '+d.week+' of 2025</span><br>'+
        '<span style="color:'+d.row.col+'">'+(d.isBlock?'▪ Part of multi-day cluster':'◦ Isolated closure')+'</span>';
      tip.style.display='block'; moveTip(e);
    });
    sq.addEventListener('mousemove',moveTip);
    sq.addEventListener('mouseleave',function(){ tip.style.display='none'; });
  });
  function moveTip(e){
    var x=e.clientX+12, y=e.clientY-10;
    if(x+240>window.innerWidth) x=e.clientX-250;
    tip.style.left=x+'px'; tip.style.top=y+'px';
  }

  /* ── Legend — rows end at T+TOTAL_H=316. Legend at 338 (22px gap). H=358. ── */
  var ly = T + TOTAL_H + 22;   /* 42 + 274 + 22 = 338 — clearly within H=358 */
  var lx = L;

  /* isolated */
  svg.appendChild(mk('rect',{x:lx,y:ly-4,width:SQ,height:SQ,rx:1.5,fill:'#f59e0b',opacity:'0.85'}));
  svg.appendChild(tx(lx+SQ+5,ly,'Disrupted (isolated)',{
    fill:'#6b7280','font-size':'9','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
  }));
  lx += 130;

  /* block */
  svg.appendChild(mk('rect',{x:lx-2,y:ly-5,width:SQ*2+8,height:SQ+2,rx:2.5,fill:'#ef444422',stroke:'#ef444450','stroke-width':'0.7'}));
  svg.appendChild(mk('rect',{x:lx,y:ly-4,width:SQ,height:SQ,rx:1.5,fill:'#ef4444'}));
  svg.appendChild(mk('rect',{x:lx+SQ+4,y:ly-4,width:SQ,height:SQ,rx:1.5,fill:'#ef4444'}));
  svg.appendChild(tx(lx+SQ*2+14,ly,'Disrupted (cluster)',{
    fill:'#6b7280','font-size':'9','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
  }));
  lx += 140;

  /* clean */
  svg.appendChild(mk('rect',{x:lx,y:ly-4,width:SQ,height:SQ,rx:1.5,fill:'#141e2e'}));
  svg.appendChild(tx(lx+SQ+5,ly,'Clean week',{
    fill:'#6b7280','font-size':'9','dominant-baseline':'middle','font-family':'system-ui,sans-serif'
  }));

  /* source */
  svg.appendChild(tx(W-6,ly,'Computed from exchange_calendars + official circulars',{
    fill:'#1f2937','font-size':'7.5','text-anchor':'end',
    'dominant-baseline':'middle','font-family':'system-ui,sans-serif'
  }));

})();
</script>

<p><em>China has 18 holidays but only 9 disrupted weeks — clusters compress the damage. India has 14 holidays but 12 disrupted weeks because every closure is isolated. Xetra’s 33-week uninterrupted streak (late April through November) is the cleanest continuous trading window of any major exchange.</em></p>

<hr />

<h2 id="the-data">The Data</h2>

<p>Every date, name, type, and block grouping — verified from official exchange circulars and the open-source <a href="https://github.com/gerrymanoim/exchange_calendars"><code class="language-plaintext highlighter-rouge">exchange_calendars</code></a> package. No approximations.</p>

<div style="display:flex;gap:12px;flex-wrap:wrap;margin:1rem 0 1.5rem;">
  <a href="/assets/data/exchange_holidays_2025.json" download="" style="display:inline-flex;align-items:center;gap:8px;padding:9px 16px;background:#111827;border:1px solid #1f2937;border-radius:7px;color:#e5e7eb;text-decoration:none;font-size:0.88em;font-family:system-ui,sans-serif;">
    <span style="font-size:1.1em;">⬇</span> JSON (full dataset + sources)
  </a>
  <a href="/assets/data/exchange_holidays_2025.csv" download="" style="display:inline-flex;align-items:center;gap:8px;padding:9px 16px;background:#111827;border:1px solid #1f2937;border-radius:7px;color:#e5e7eb;text-decoration:none;font-size:0.88em;font-family:system-ui,sans-serif;">
    <span style="font-size:1.1em;">⬇</span> CSV (92 rows, one per exchange-holiday)
  </a>
</div>

<p><strong>Sources per exchange:</strong></p>

<table>
  <thead>
    <tr>
      <th>Exchange</th>
      <th>Holiday source</th>
      <th>Hours source</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>NSE / BSE</td>
      <td>NSE official circular (nseindia.com/resources/exchange-communication-holidays)</td>
      <td>NSE official market timings</td>
    </tr>
    <tr>
      <td>NYSE / NASDAQ</td>
      <td>NYSE/ICE press release Dec 2022 + SEC filing Jan 2025 (Carter mourning day)</td>
      <td>NYSE.com/trade/hours-calendars</td>
    </tr>
    <tr>
      <td>LSE</td>
      <td>londonstockexchange.com/equities-trading/business-days</td>
      <td>Confirmed: 08:00–16:30 daily</td>
    </tr>
    <tr>
      <td>JPX</td>
      <td>jpx.co.jp/english/corporate/about-jpx/calendar</td>
      <td>JPX press release Nov 3 2024 — extension to 12:30–15:30</td>
    </tr>
    <tr>
      <td>HKEX</td>
      <td>hkex.com.hk/services/trading/derivatives/overview/trading-calendar-and-holiday-schedule</td>
      <td>9:30–12:00 + 13:00–16:00</td>
    </tr>
    <tr>
      <td>SSE</td>
      <td>english.sse.com.cn/start/trading/schedule</td>
      <td>9:30–11:30 + 13:00–15:00</td>
    </tr>
    <tr>
      <td>Xetra</td>
      <td>Deutsche Börse 2025 calendar PDF (cashmarket.deutsche-boerse.com)</td>
      <td>“9:00–17:30 CET” — official</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Kanak Raj</name><email>kanak8278@gmail.com</email></author><category term="blog" /><category term="finance" /><category term="markets" /><category term="data" /><category term="visualization" /><category term="india" /><category term="global" /><summary type="html"><![CDATA[Japan loses more trading hours to holidays than any other major exchange. India doesn't have the most holidays — but it has the most fragmented ones. The pattern matters as much as the count.]]></summary></entry><entry><title type="html">Change Detection: Extracting Ground Truth from Noisy Edits</title><link href="https://kanak8278.github.io//blog/change-detection-ground-truth-noisy-edits/" rel="alternate" type="text/html" title="Change Detection: Extracting Ground Truth from Noisy Edits" /><published>2026-04-09T00:00:00+00:00</published><updated>2026-04-09T00:00:00+00:00</updated><id>https://kanak8278.github.io//blog/change-detection-ground-truth-noisy-edits</id><content type="html" xml:base="https://kanak8278.github.io//blog/change-detection-ground-truth-noisy-edits/"><![CDATA[<p>Published on <a href="https://medium.com/@kanak8278/change-detection-extracting-ground-truth-from-noisy-edits-97a46dace0cd">Medium</a>.</p>]]></content><author><name>Kanak Raj</name><email>kanak8278@gmail.com</email></author><category term="blog" /><category term="llm" /><category term="evaluation" /><category term="synthetic-data" /><category term="nlp" /><summary type="html"><![CDATA[You want to build an AI system that automatically updates documents when new information arrives — a court ruling updates a legal article, a product release updates documentation. But how do you extract clean ground truth from noisy human edits?]]></summary></entry><entry><title type="html">The Long Game: The Technical and Strategic Reality of India’s PFBR Criticality</title><link href="https://kanak8278.github.io//blog/pfbr-india-nuclear-three-stage/" rel="alternate" type="text/html" title="The Long Game: The Technical and Strategic Reality of India’s PFBR Criticality" /><published>2026-04-08T00:00:00+00:00</published><updated>2026-04-08T00:00:00+00:00</updated><id>https://kanak8278.github.io//blog/pfbr-india-nuclear-three-stage</id><content type="html" xml:base="https://kanak8278.github.io//blog/pfbr-india-nuclear-three-stage/"><![CDATA[<div style="border-left:3px solid #4a8fc0; padding:12px 18px; background:rgba(25,55,95,0.18); border-radius:0 6px 6px 0; margin-bottom:2rem;">
<p style="margin:0; color:#90b8d8; font-size:0.96em; line-height:1.75;">
Every PFBR headline just repeated "Three-Stage Nuclear Power Programme" without explaining what the stages actually mean, what fuel flows between them, or why India — specifically India — had no other choice. I went looking for the complete picture. This post covers India's uranium scarcity problem, Homi Bhabha's 1954 roadmap, and what first criticality at Kalpakkam actually unlocks — with interactive simulations of the <a href="#interactive-nuclear-chain-reaction" style="color:#6ab0d8;">chain reaction</a>, the <a href="#the-three-stage-fuel-relay-at-a-glance" style="color:#6ab0d8;">full fuel relay</a>, and the <a href="#the-three-loop-safety-system" style="color:#6ab0d8;">PFBR's three-loop sodium coolant system</a>.
</p>
</div>

<h2 id="i-introduction-the-moment-of-first-light">I. Introduction: The Moment of “First Light”</h2>

<p>On April 6, 2026, the 500 MWe Prototype Fast Breeder Reactor (PFBR) at Kalpakkam achieved “first criticality.” While this event generated widespread headlines, I wasn’t able to understand why it mattered and what the goal is here. I just kept reading flashy words and “Three-Stage Nuclear Power Programme.” This is what I understood after all my searches and asking Gemini to explain concepts. I hope it helps to get a complete picture.</p>

<p>First, to understand the significance of this moment, it is necessary to strip away the dramatic connotations of the word <code class="language-plaintext highlighter-rouge">criticality</code> (I thought criticality is a negative word 🙂!). In nuclear physics, it is the precise mathematical point of balance; it marks the exact moment when the nuclear fission chain reaction inside the core becomes self-sustaining. The reactor is producing exactly as many neutrons as it is losing or consuming. In engineering terms, achieving first criticality means the machine has successfully transitioned from a dormant construction project to a “living,” operational physics environment. The engine has officially started.</p>

<p>In general, how any electricity generation process works (other than solar — I always feel why we don’t have other ways) is somehow you convert other energies to rotate a turbine (wind turbine, water turbine, steam turbine, etc). Here in nuclear reactors we use fission — the breakdown of the atom — which releases a vast amount of energy to heat up a coolant, and that hot coolant passes the energy to generate steam, which rotates the turbine, and you know what is next ⚡️⚡️⚡️. However, the true weight of the PFBR lies not in the electricity it will eventually generate, but in its role as a “bridge.” India’s nuclear power strategy operates on a unique, three-stage roadmap designed to solve a severe geographical handicap: the country possesses very little high-quality uranium, but holds the world’s largest reserves of thorium. The PFBR represents the critical “Stage 2” of this roadmap. It is the industrial-scale proof of concept that will allow India to transition from a uranium-dependent past to a thorium-powered future (self-sustainability, autonomy, protection from Middle East conflict — Indian Govt will say “Atmanirbhar Bharat”, say whatever you want).</p>

<h2 id="ii-the-why-indias-unique-problem">II. The “Why”: India’s Unique Problem</h2>

<p><strong>The Resource Gap:</strong> The foundation of India’s nuclear strategy is dictated by geology. India possesses very limited domestic reserves of high-grade uranium. Relying purely on imported uranium exposes the national power grid to global market volatility, supply chain disruptions, and geopolitical pressure. However, India is home to an estimated 25% of the world’s “economically extractable” (this is important — it’s not like finding Lithium in Ladakh which might not be economically viable to extract) thorium reserves, found predominantly in the monazite sands along the coasts of Kerala, Tamil Nadu, and Odisha.</p>

<p><strong>The Strategy:</strong> Recognizing this stark resource imbalance in 1954, physicist Dr. Homi J. Bhabha formulated the “Three-Stage Nuclear Power Programme.” Rather than perpetually importing uranium to power standard reactors, Bhabha’s roadmap was designed to sequentially build the technology required to unlock the vast domestic thorium reserves. The end goal was — and remains — absolute energy autonomy.</p>

<p><strong>The Global Comparison:</strong> A common question is why technologically advanced nations like the United States or France do not use thorium. The answer lies in the Cold War (lots of our past is indirectly tied to the Cold War) and pure economics. The global nuclear infrastructure was built around the “Uranium Cycle” largely because the early reactors were tied to military programs. Today, enriched uranium is a highly commoditized, easily accessible fuel on the global market. For Western nations, it is far more economical to simply buy uranium than to spend billions of dollars developing complex thorium reactors from scratch. India, lacking domestic uranium but rich in thorium, was forced to forge its own path.</p>

<h2 id="iii-stage-1-the-foundation-the-slow-starters">III. Stage 1: The Foundation (The “Slow” Starters)</h2>

<p><strong>The Technology:</strong> To begin the three-stage process, India deployed Pressurized Heavy Water Reactors (PHWRs). Today, a fleet of approximately 18 to 20 PHWRs serves as the operational backbone of India’s civilian nuclear power grid.</p>

<p><strong>The Physics (The “Catch”):</strong> The key operational feature of a PHWR is that it uses <strong>Natural Uranium</strong>. Natural uranium is comprised of about 99.3% Uranium-238 (which is highly stable) and only 0.7% Uranium-235 (which splits easily). To sustain a nuclear fission chain reaction with such a tiny concentration of U-235, the physics must be strictly managed. The U-235 atoms require slow-moving neutrons to trigger fission.</p>

<p>To achieve this, the reactor uses <strong>Heavy Water</strong> (deuterium oxide) as a “moderator.” The heavy water acts as a physical brake. When high-speed neutrons are released from a splitting atom, they crash into the heavy water molecules, lose kinetic energy, and slow down to “thermal” speeds. This deceleration is what allows the U-235 to catch the neutrons and keep the power plant running.</p>

<p><strong>The Strategic Output:</strong> The immediate output of Stage 1 is commercial electricity. However, the long-term strategic output is found in the nuclear waste. While the reactor burns the U-235, the massive surrounding quantity of U-238 inside the fuel rods absorbs stray neutrons. In doing so, the U-238 transmutes into <strong>Plutonium-239</strong>. Once the fuel rod is “spent,” engineers chemically reprocess it to extract this plutonium. This extracted plutonium is the indispensable “starter fuel” required to ignite the Stage 2 Fast Breeder Reactors. Without Stage 1 running for decades to slowly accumulate plutonium, Stage 2 could never begin.</p>

<h3 id="interactive-nuclear-chain-reaction">Interactive: Nuclear Chain Reaction</h3>

<p>The visualization below shows the fundamental physics at work inside every stage of this programme. Each blue circle is a U-235 atom. Fire a single neutron — watch it trigger a cascade.</p>

<div class="chain-reaction-viz" style="margin: 1.5rem 0; font-family: monospace;">
  <div id="cr-wrap" style="background:#060c1a; border:1px solid #1c3a6e; border-radius:8px 8px 0 0; overflow:hidden; line-height:0; position:relative;"></div>
  <div style="display:flex; gap:10px; align-items:center; padding:10px 14px; background:#0a1529; border:1px solid #1c3a6e; border-top:none; border-radius:0 0 8px 8px; flex-wrap:wrap;">
    <button id="cr-fire-btn" style="background:#1a4a9e;color:#aef;border:1px solid #2a6ad0;padding:7px 18px;border-radius:5px;cursor:pointer;font-size:13px;font-family:monospace;">⚡ Fire Neutron</button>
    <button id="cr-reset-btn" style="background:#0e1e3e;color:#7ab;border:1px solid #1e3a6e;padding:7px 18px;border-radius:5px;cursor:pointer;font-size:13px;font-family:monospace;">↺ Reset</button>
    <span id="cr-status" style="margin-left:auto;color:#6a9fd8;font-size:12px;">Ready — fire to start</span>
  </div>
</div>

<script>
(function () {
  var fireBtn = document.getElementById('cr-fire-btn');
  var resetBtn = document.getElementById('cr-reset-btn');
  var statusEl = document.getElementById('cr-status');
  var sketch = null;

  function setStatus(msg, color) {
    if (!statusEl) return;
    statusEl.textContent = msg;
    if (color) statusEl.style.color = color;
  }

  // ── deterministic jitter so layout is stable on reset ──────────────────────
  function prand(s) { var x = Math.sin(s + 1) * 43758.5453; return x - Math.floor(x); }

  function loadP5(cb) {
    if (window.p5) { cb(); return; }
    var urls = [
      'https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js',
      'https://cdn.jsdelivr.net/npm/p5@1.9.0/lib/p5.min.js',
      'https://unpkg.com/p5@1.9.0/lib/p5.min.js'
    ];
    var i = 0;
    function tryNext() {
      if (window.p5) { cb(); return; }
      if (i >= urls.length) {
        setStatus('Interactive failed to load (p5 blocked). Refresh or disable script blocker.', '#f88');
        return;
      }
      var s = document.createElement('script');
      s.src = urls[i++];
      s.onload = function () { cb(); };
      s.onerror = tryNext;
      document.head.appendChild(s);
    }
    tryNext();
  }

  if (fireBtn) {
    fireBtn.addEventListener('click', function () {
      if (!sketch || typeof sketch.fireNeutron !== 'function') {
        setStatus('Loading simulation engine... try again in a second', '#9cf');
        return;
      }
      sketch.fireNeutron();
    });
  }
  if (resetBtn) {
    resetBtn.addEventListener('click', function () {
      if (!sketch || typeof sketch.resetSim !== 'function') return;
      sketch.resetSim();
    });
  }

  loadP5(function () {
    var sk = new p5(function (p) {

      // ── tunables ────────────────────────────────────────────────────────────
      var COLS = 9, ROWS = 5;
      var AR   = 17;          // atom radius
      var NR   = 4;           // neutron radius
      var NS   = 2.6;         // neutron base speed
      var FISSION_PROB = 0.88; // chance a neutron actually causes fission on contact
      var W, H;

      // ── state ───────────────────────────────────────────────────────────────
      var atoms, neutrons, debris, generation, stats;

      p.setup = function () {
        var wrap = document.getElementById('cr-wrap');
        W = Math.min(720, wrap.offsetWidth || 680); H = 390;
        p.createCanvas(W, H).parent('cr-wrap');
        p.textFont('monospace');
        initSim();
      };

      function initSim() {
        atoms = []; neutrons = []; debris = [];
        generation = 0;
        stats = { fissions: 0, escaped: 0, captured: 0 };

        var padX = 60, padY = 55;
        var sX = (W - padX * 2) / (COLS - 1);
        var sY = (H - padY * 2) / (ROWS - 1);

        for (var r = 0; r < ROWS; r++) {
          for (var c = 0; c < COLS; c++) {
            var idx = r * COLS + c;
            // Build nucleon positions inside each atom (8 small dots)
            var nucleons = [];
            for (var n = 0; n < 8; n++) {
              var ang = prand(idx * 10 + n) * Math.PI * 2;
              var rad = prand(idx * 10 + n + 1) * (AR * 0.55);
              nucleons.push({ dx: Math.cos(ang) * rad, dy: Math.sin(ang) * rad,
                              isProton: n < 4 });   // 4 protons (red), 4 neutrons (gray)
            }
            atoms.push({
              x:  padX + c * sX + (prand(idx * 2)     - 0.5) * 20,
              y:  padY + r * sY + (prand(idx * 2 + 1) - 0.5) * 20,
              state: 'intact',   // intact | shudder | fissioning | fissioned
              shudderT: 0,
              nucleons: nucleons,
              gp: prand(idx * 3 + 7) * Math.PI * 2,
              // fission products – filled on fission
              frags: null, ringR: AR, ringA: 0,
              gen: -1           // which generation caused this fission
            });
          }
        }
      }

      // ── draw loop ───────────────────────────────────────────────────────────
      p.draw = function () {
        p.background(6, 12, 26);
        tickDebris();
        drawAtoms();
        tickNeutrons();
        drawHUD();
      };

      // ── atoms ───────────────────────────────────────────────────────────────
      function drawAtoms() {
        for (var i = 0; i < atoms.length; i++) {
          var a = atoms[i];

          if (a.state === 'fissioned') {
            // Shockwave ring fading out
            if (a.ringA > 0) {
              a.ringR += 2.2; a.ringA = Math.max(0, a.ringA - 6);
              p.noFill(); p.stroke(255, 200, 60, a.ringA);
              p.strokeWeight(2); p.circle(a.x, a.y, a.ringR * 2);
            }
            // Two named fission fragments drifting apart
            if (a.frags) {
              for (var f = 0; f < 2; f++) {
                var fr = a.frags[f];
                fr.x += fr.vx; fr.y += fr.vy;
                fr.vx *= 0.96; fr.vy *= 0.96;
                p.noStroke();
                p.fill(fr.col[0], fr.col[1], fr.col[2]);
                p.circle(fr.x, fr.y, fr.r * 2);
                // Inner core
                p.fill(fr.col[0] * 0.5, fr.col[1] * 0.5, fr.col[2] * 0.5);
                p.circle(fr.x, fr.y, fr.r * 0.9);
                // Label
                p.fill(200, 200, 200); p.noStroke(); p.textSize(7);
                p.textAlign(p.CENTER, p.CENTER);
                p.text(fr.label, fr.x, fr.y);
              }
            }
            continue;
          }

          if (a.state === 'shudder') {
            a.shudderT += 0.4;
            if (a.shudderT > 2.5) { a.state = 'intact'; a.shudderT = 0; }
          }

          var jx = a.state === 'shudder' ? Math.sin(a.shudderT * 18) * 2.5 : 0;
          var jy = a.state === 'shudder' ? Math.cos(a.shudderT * 22) * 2  : 0;
          var g  = 0.5 + 0.5 * Math.sin(p.frameCount * 0.04 + a.gp);

          // Outer glow
          p.noStroke();
          p.fill(18, 95, 210, 20 + g * 16); p.circle(a.x + jx, a.y + jy, AR * 4.2);
          p.fill(30, 130, 250, 34 + g * 20); p.circle(a.x + jx, a.y + jy, AR * 2.9);

          // Nucleus body
          p.fill(16, 68, 148); p.stroke(54, 136, 246); p.strokeWeight(1.4);
          p.circle(a.x + jx, a.y + jy, AR * 2);

          // Nucleons (protons red, neutrons gray)
          p.noStroke();
          for (var n = 0; n < a.nucleons.length; n++) {
            var nu = a.nucleons[n];
            if (nu.isProton) p.fill(210, 80, 50);
            else             p.fill(140, 160, 180);
            p.circle(a.x + jx + nu.dx, a.y + jy + nu.dy, 4);
          }

          // Electron orbit rings
          p.noFill(); p.stroke(68, 145, 255, 48); p.strokeWeight(0.7);
          p.push(); p.translate(a.x + jx, a.y + jy);
          p.rotate(0.35);
          p.ellipse(0, 0, AR * 2.75, AR * 0.95);
          p.rotate(p.PI / 2.4);
          p.ellipse(0, 0, AR * 2.75, AR * 0.95);
          p.pop();

          // Label
          p.fill(165, 212, 255); p.noStroke(); p.textSize(7);
          p.textAlign(p.CENTER, p.CENTER);
          p.text('U-235', a.x + jx, a.y + jy);
        }
        p.textAlign(p.LEFT);
      }

      // ── neutrons ─────────────────────────────────────────────────────────────
      function tickNeutrons() {
        var spawn = [];
        for (var i = 0; i < neutrons.length; i++) {
          var n = neutrons[i];
          if (!n.active) continue;

          n.trail.push({ x: n.x, y: n.y });
          if (n.trail.length > 10) n.trail.shift();
          n.x += n.vx; n.y += n.vy;

          // Off-screen → escaped
          if (n.x < -20 || n.x > W + 20 || n.y < -20 || n.y > H + 20) {
            n.active = false; stats.escaped++;
            spawnEscapeFlash(n.x, n.y); continue;
          }

          // Collision check
          var hit = false;
          for (var j = 0; j < atoms.length; j++) {
            var a = atoms[j];
            if (a.state === 'fissioned') continue;
            var dx = n.x - a.x, dy = n.y - a.y;
            if (Math.sqrt(dx * dx + dy * dy) < AR + NR) {
              hit = true; n.active = false;

              if (Math.random() < FISSION_PROB) {
                // ── FISSION ─────────────────────────────────────────────────
                fission(a, n.gen + 1, spawn);
              } else {
                // ── CAPTURE (no fission) ─────────────────────────────────────
                stats.captured++;
                a.state = 'shudder'; a.shudderT = 0;
                spawnCaptureFlash(a.x, a.y);
              }
              break;
            }
          }
          if (hit) continue;

          // Draw trail + neutron
          for (var t = 0; t < n.trail.length; t++) {
            p.noStroke();
            p.fill(255, 255, 130, (t / n.trail.length) * 90);
            p.circle(n.trail[t].x, n.trail[t].y, 3);
          }
          p.noStroke();
          p.fill(255, 255, 120, 65); p.circle(n.x, n.y, NR * 4.5);
          p.fill(255, 255, 200);     p.circle(n.x, n.y, NR * 2);
        }
        for (var i = 0; i < spawn.length; i++) neutrons.push(spawn[i]);
        if (neutrons.length > 600)
          neutrons = neutrons.filter(function (n) { return n.active; });
      }

      function fission(a, gen, spawnList) {
        a.state   = 'fissioned';
        a.ringR   = AR; a.ringA = 255;
        a.gen     = gen;
        stats.fissions++;
        generation = Math.max(generation, gen);

        // Two real-ish fission fragments: Ba-141 (larger) and Kr-92 (smaller)
        var ang1 = Math.random() * Math.PI * 2;
        var ang2 = ang1 + Math.PI + (Math.random() - 0.5) * 0.5;
        a.frags = [
          { x: a.x, y: a.y, vx: Math.cos(ang1)*1.4, vy: Math.sin(ang1)*1.4,
            r: AR * 0.72, label: 'Ba-141', col: [200, 130, 40] },
          { x: a.x, y: a.y, vx: Math.cos(ang2)*1.8, vy: Math.sin(ang2)*1.8,
            r: AR * 0.54, label: 'Kr-92',  col: [70, 160, 220] }
        ];

        // Gamma ray sparks
        for (var k = 0; k < 20; k++) {
          var sa = Math.random() * Math.PI * 2;
          var sv = 1.5 + Math.random() * 4.5;
          debris.push({
            x: a.x, y: a.y, vx: Math.cos(sa)*sv, vy: Math.sin(sa)*sv,
            life: 1, sz: 1.5 + Math.random() * 4,
            r: 255, g: 200 + Math.random()*55, b: 50 + Math.random()*80,
            type: 'spark'
          });
        }
        // Gamma ray lines (zigzag visual)
        for (var k = 0; k < 3; k++) {
          var ga = Math.random() * Math.PI * 2;
          debris.push({
            x: a.x, y: a.y, vx: Math.cos(ga)*5, vy: Math.sin(ga)*5,
            life: 1, sz: 0, type: 'gamma', age: 0
          });
        }

        // Release 2 or 3 neutrons (avg 2.4, matching U-235 actual yield)
        var cnt  = Math.random() < 0.4 ? 3 : 2;
        var base = Math.random() * Math.PI * 2;
        for (var k = 0; k < cnt; k++) {
          var ang = base + k * Math.PI * 2 / cnt + (Math.random() - 0.5) * 0.8;
          var spd = NS * (0.85 + Math.random() * 0.35);
          spawnList.push({
            x: a.x + Math.cos(ang) * (AR + 2),
            y: a.y + Math.sin(ang) * (AR + 2),
            vx: Math.cos(ang) * spd, vy: Math.sin(ang) * spd,
            trail: [], active: true, gen: gen
          });
        }
      }

      function spawnEscapeFlash(x, y) {
        for (var k = 0; k < 5; k++) {
          var ang = Math.random() * Math.PI * 2;
          debris.push({
            x: Math.min(Math.max(x, 5), W-5),
            y: Math.min(Math.max(y, 5), H-5),
            vx: Math.cos(ang)*1.5, vy: Math.sin(ang)*1.5,
            life: 0.7, sz: 2.5, r:180, g:180, b:255, type:'spark'
          });
        }
      }

      function spawnCaptureFlash(x, y) {
        for (var k = 0; k < 8; k++) {
          var ang = Math.random() * Math.PI * 2;
          debris.push({
            x: x, y: y, vx: Math.cos(ang)*2, vy: Math.sin(ang)*2,
            life: 0.9, sz: 3, r:120, g:200, b:120, type:'spark'
          });
        }
      }

      // ── debris (sparks + gamma lines) ───────────────────────────────────────
      function tickDebris() {
        var alive = [];
        for (var i = 0; i < debris.length; i++) {
          var d = debris[i];
          if (d.type === 'spark') {
            d.x += d.vx; d.y += d.vy;
            d.vx *= 0.90; d.vy *= 0.90;
            d.life -= 0.04;
            if (d.life <= 0) continue;
            p.noStroke(); p.fill(d.r, d.g, d.b, d.life * 220);
            p.circle(d.x, d.y, d.sz * d.life * 2.5);
          } else if (d.type === 'gamma') {
            // Draw a zigzag line representing a gamma ray
            d.age += 0.15; d.life -= 0.04;
            if (d.life <= 0) continue;
            var len = d.age * 14;
            p.stroke(255, 255, 255, d.life * 160); p.strokeWeight(1);
            for (var seg = 0; seg < 5; seg++) {
              var t1 = seg / 5, t2 = (seg + 1) / 5;
              var side = (seg % 2 === 0) ? 1 : -1;
              var x1 = d.x + d.vx * len * t1 + d.vy * side * 3;
              var y1 = d.y + d.vy * len * t1 - d.vx * side * 3;
              var x2 = d.x + d.vx * len * t2 + d.vy * (side * -1) * 3;
              var y2 = d.y + d.vy * len * t2 - d.vx * (side * -1) * 3;
              p.line(x1, y1, x2, y2);
            }
          }
          alive.push(d);
        }
        debris = alive;
      }

      // ── HUD ─────────────────────────────────────────────────────────────────
      function drawHUD() {
        var intact  = 0, activeN = 0;
        for (var i = 0; i < atoms.length;   i++) if (atoms[i].state !== 'fissioned') intact++;
        for (var i = 0; i < neutrons.length; i++) if (neutrons[i].active) activeN++;

        // Panel bg
        p.noStroke(); p.fill(8, 18, 48, 210); p.rect(8, 6, 310, 62, 6);

        p.fill(100, 170, 255); p.noStroke(); p.textSize(10.5); p.textAlign(p.LEFT);
        p.text('Fissions: '      + stats.fissions + '  |  Gen: ' + generation, 16, 23);
        p.text('Neutrons flying: ' + activeN, 16, 38);
        p.text('Escaped: '       + stats.escaped  + '  |  Captured (no fission): ' + stats.captured, 16, 53);

        // Idle prompt
        if (neutrons.length === 0 && stats.fissions === 0) {
          var al = 160 + Math.sin(p.frameCount * 0.06) * 60;
          p.fill(255, 255, 100, al); p.textSize(13); p.textAlign(p.CENTER);
          p.text('Click "Fire Neutron" to begin', W / 2, H - 14);
        }

        // Legend (bottom-right)
        p.textSize(8.5); p.textAlign(p.RIGHT);
        p.fill(200, 130, 40); p.text('Ba-141  fission fragment', W - 10, H - 30);
        p.fill( 70, 160, 220); p.text('Kr-92  fission fragment',  W - 10, H - 18);
        p.fill(255, 255, 200); p.text('⚡ = free neutron',        W - 10, H -  6);

        // Update status bar
        var el = document.getElementById('cr-status');
        if (el) {
          var total = atoms.length;
          if (intact === 0) {
            el.textContent = 'Complete — all ' + total + ' atoms fissioned  |  ' + stats.escaped + ' neutrons escaped';
            el.style.color = '#ffd060';
          } else if (activeN > 0) {
            el.textContent = stats.fissions + ' fissions · gen ' + generation + ' · ' + activeN + ' neutrons flying';
            el.style.color = '#6af';
          } else if (stats.fissions > 0) {
            el.textContent = 'Chain stalled — ' + stats.fissions + '/' + total + ' fissioned  |  reset and retry';
            el.style.color = '#f96';
          } else {
            el.textContent = 'Ready — fire to start';
            el.style.color = '#6a9fd8';
          }
        }
      }

      // ── controls ────────────────────────────────────────────────────────────
      p.fireNeutron = function () {
        var targets = atoms.filter(function (a) {
          return a.state !== 'fissioned' && a.x < W / 2;
        });
        if (!targets.length) return;
        var t  = targets[Math.floor(Math.random() * targets.length)];
        var sy = t.y + (Math.random() - 0.5) * 40;
        var dx = t.x, dy = t.y - sy;
        var mg = Math.sqrt(dx * dx + dy * dy);
        neutrons.push({
          x: 0, y: sy,
          vx: (dx / mg) * NS, vy: (dy / mg) * NS + (Math.random() - 0.5) * 0.4,
          trail: [], active: true, gen: 0
        });
      };

      p.resetSim = function () { initSim(); };

    }, 'cr-wrap');
    sketch = sk;
  });
})();
</script>

<p><em>Each fission releases 2–3 neutrons. Each of those can trigger another fission. One neutron → exponential cascade. This is the chain reaction that all three stages of India’s programme depend on.</em></p>

<h3 id="the-three-stage-fuel-relay-at-a-glance">The Three-Stage Fuel Relay at a Glance</h3>

<p>Before diving into Stage 2, here’s the full fuel relay from start to finish — each stage feeds the next.</p>

<style>
.sfd-wrap { margin: 2rem 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.sfd-wrap svg { display: block; width: 100%; min-width: 580px; }
.sfd-stage { cursor: default; }
.sfd-stage .sfd-box { transition: filter 0.2s; }
.sfd-stage:hover .sfd-box { filter: brightness(1.35) drop-shadow(0 0 6px currentColor); }
@keyframes sfd-flow  { to { stroke-dashoffset: -20; } }
@keyframes sfd-pulse { 0%,100% { opacity:.6; } 50% { opacity:1; } }
.sfd-arrow  { animation: sfd-flow  1.4s linear infinite; }
.sfd-arrow2 { animation: sfd-flow  2.2s linear infinite; }
.sfd-glow   { animation: sfd-pulse 2s ease-in-out infinite; }
</style>

<div class="sfd-wrap">
<svg viewBox="0 0 860 420" xmlns="http://www.w3.org/2000/svg" aria-label="India's Three-Stage Nuclear Fuel Relay diagram">
<defs>
  <!-- Arrowheads -->
  <marker id="sfd-au" markerWidth="7" markerHeight="5" refX="6" refY="2.5" orient="auto"><path d="M0,0 L7,2.5 L0,5Z" fill="#e8891a" /></marker>
  <marker id="sfd-apu" markerWidth="7" markerHeight="5" refX="6" refY="2.5" orient="auto"><path d="M0,0 L7,2.5 L0,5Z" fill="#9b59b6" /></marker>
  <marker id="sfd-ath" markerWidth="7" markerHeight="5" refX="6" refY="2.5" orient="auto"><path d="M0,0 L7,2.5 L0,5Z" fill="#2ecc71" /></marker>
  <marker id="sfd-ael" markerWidth="7" markerHeight="5" refX="6" refY="2.5" orient="auto"><path d="M0,0 L7,2.5 L0,5Z" fill="#ffd700" /></marker>
  <marker id="sfd-alp" markerWidth="7" markerHeight="5" refX="6" refY="2.5" orient="auto"><path d="M0,0 L7,2.5 L0,5Z" fill="#2ecc71" /></marker>
</defs>

<!-- Background -->
<rect width="860" height="420" fill="#0b0f1a" rx="10" />

<!-- Title -->
<text x="430" y="26" text-anchor="middle" fill="#5a8abf" font-size="11" font-family="monospace" letter-spacing="2" font-weight="600">
  INDIA'S THREE-STAGE NUCLEAR FUEL RELAY
</text>

<!-- ──────────────────────────────────────────────────────── -->
<!-- EXTERNAL INPUTS (top row) -->
<!-- ──────────────────────────────────────────────────────── -->

<!-- Natural Uranium → Stage 1 -->
<text x="97" y="56" text-anchor="middle" fill="#e8891a" font-size="9" font-family="monospace" font-weight="700">NATURAL URANIUM</text>
<text x="97" y="68" text-anchor="middle" fill="#e8891a" font-size="8" font-family="monospace" opacity=".8">(Stage 1 only)</text>
<line x1="97" y1="72" x2="97" y2="143" stroke="#e8891a" stroke-width="2" stroke-dasharray="5 3" marker-end="url(#sfd-au)" class="sfd-arrow2" />

<!-- Thorium-232 → Stage 2B -->
<text x="527" y="56" text-anchor="middle" fill="#2ecc71" font-size="9" font-family="monospace" font-weight="700">THORIUM-232</text>
<text x="527" y="68" text-anchor="middle" fill="#2ecc71" font-size="8" font-family="monospace" opacity=".8">(new input)</text>
<line x1="527" y1="72" x2="527" y2="143" stroke="#2ecc71" stroke-width="2" stroke-dasharray="5 3" marker-end="url(#sfd-ath)" class="sfd-arrow2" />

<!-- Thorium-232 → Stage 3 -->
<text x="742" y="56" text-anchor="middle" fill="#2ecc71" font-size="9" font-family="monospace" font-weight="700">THORIUM-232</text>
<text x="742" y="68" text-anchor="middle" fill="#2ecc71" font-size="8" font-family="monospace" opacity=".8">(self-sustaining)</text>
<line x1="742" y1="72" x2="742" y2="143" stroke="#2ecc71" stroke-width="2" stroke-dasharray="5 3" marker-end="url(#sfd-ath)" class="sfd-arrow2" />

<!-- ──────────────────────────────────────────────────────── -->
<!-- BETWEEN-STAGE ARROWS -->
<!-- ──────────────────────────────────────────────────────── -->

<!-- 1 → 2A  (Plutonium-239 + U-238) -->
<line x1="178" y1="210" x2="233" y2="210" stroke="#9b59b6" stroke-width="3" stroke-dasharray="6 4" marker-end="url(#sfd-apu)" class="sfd-arrow" />
<text x="205" y="204" text-anchor="middle" fill="#b07cd4" font-size="8.5" font-family="monospace" font-weight="700">Pu-239</text>
<text x="205" y="228" text-anchor="middle" fill="#9070b0" font-size="7.5" font-family="monospace">+ U-238 waste</text>

<!-- 2A → 2B  (More Plutonium) -->
<line x1="393" y1="210" x2="448" y2="210" stroke="#9b59b6" stroke-width="3" stroke-dasharray="6 4" marker-end="url(#sfd-apu)" class="sfd-arrow" />
<text x="420" y="204" text-anchor="middle" fill="#b07cd4" font-size="8.5" font-family="monospace" font-weight="700">More Pu-239</text>
<text x="420" y="228" text-anchor="middle" fill="#9070b0" font-size="7.5" font-family="monospace">(multiplied)</text>

<!-- 2B → 3  (Uranium-233) -->
<line x1="608" y1="210" x2="663" y2="210" stroke="#2ecc71" stroke-width="3" stroke-dasharray="6 4" marker-end="url(#sfd-ath)" class="sfd-arrow" />
<text x="635" y="204" text-anchor="middle" fill="#5de890" font-size="8.5" font-family="monospace" font-weight="700">U-233</text>
<text x="635" y="228" text-anchor="middle" fill="#40b060" font-size="7.5" font-family="monospace">(superfuel)</text>

<!-- ──────────────────────────────────────────────────────── -->
<!-- STAGE 1 -->
<!-- ──────────────────────────────────────────────────────── -->
<g class="sfd-stage">
  <!-- Box -->
  <rect class="sfd-box" x="22" y="148" width="156" height="128" rx="8" fill="#16100400" stroke="#e8891a" stroke-width="1.8" />
  <rect x="22" y="148" width="156" height="28" rx="8" fill="#c4720e" />
  <rect x="22" y="164" width="156" height="12" fill="#c4720e" />
  <!-- Header -->
  <text x="100" y="166" text-anchor="middle" fill="#fff" font-size="12" font-family="monospace" font-weight="700">STAGE 1</text>
  <!-- Body -->
  <text x="100" y="192" text-anchor="middle" fill="#e8891a" font-size="9.5" font-family="monospace" font-weight="700">PHWRs</text>
  <text x="100" y="207" text-anchor="middle" fill="#c09060" font-size="8.5" font-family="monospace">Natural U → fission</text>
  <text x="100" y="220" text-anchor="middle" fill="#c09060" font-size="8.5" font-family="monospace">U-238 absorbs neutrons</text>
  <text x="100" y="233" text-anchor="middle" fill="#c09060" font-size="8.5" font-family="monospace">→ breeds Pu-239 waste</text>
  <!-- Status -->
  <rect x="38" y="245" width="124" height="18" rx="4" fill="#1a0e03" stroke="#e8891a" stroke-width="1" />
  <text x="100" y="258" text-anchor="middle" fill="#e8891a" font-size="8" font-family="monospace" font-weight="600">✓ LIVE — 18-20 reactors</text>
</g>

<!-- ──────────────────────────────────────────────────────── -->
<!-- STAGE 2A -->
<!-- ──────────────────────────────────────────────────────── -->
<g class="sfd-stage">
  <rect class="sfd-box" x="237" y="148" width="156" height="128" rx="8" fill="#12091a00" stroke="#9b59b6" stroke-width="1.8" />
  <rect x="237" y="148" width="156" height="28" rx="8" fill="#7d3ea8" />
  <rect x="237" y="164" width="156" height="12" fill="#7d3ea8" />
  <text x="315" y="166" text-anchor="middle" fill="#fff" font-size="12" font-family="monospace" font-weight="700">STAGE 2A</text>
  <text x="315" y="192" text-anchor="middle" fill="#c080e8" font-size="9.5" font-family="monospace" font-weight="700">PFBR — Multiplier</text>
  <text x="315" y="207" text-anchor="middle" fill="#a080c0" font-size="8.5" font-family="monospace">Pu-239 core fissions</text>
  <text x="315" y="220" text-anchor="middle" fill="#a080c0" font-size="8.5" font-family="monospace">U-238 blanket absorbs</text>
  <text x="315" y="233" text-anchor="middle" fill="#a080c0" font-size="8.5" font-family="monospace">→ breeds MORE Pu-239</text>
  <rect x="252" y="245" width="126" height="18" rx="4" fill="#120820" stroke="#9b59b6" stroke-width="1" />
  <text x="315" y="258" text-anchor="middle" fill="#c080e8" font-size="8" font-family="monospace" font-weight="600">⚡ NOW — Apr 2026</text>
</g>

<!-- ──────────────────────────────────────────────────────── -->
<!-- STAGE 2B -->
<!-- ──────────────────────────────────────────────────────── -->
<g class="sfd-stage">
  <rect class="sfd-box" x="452" y="148" width="156" height="128" rx="8" fill="#0a122000" stroke="#3498db" stroke-width="1.8" />
  <rect x="452" y="148" width="156" height="28" rx="8" fill="#1e6fa8" />
  <rect x="452" y="164" width="156" height="12" fill="#1e6fa8" />
  <text x="530" y="166" text-anchor="middle" fill="#fff" font-size="12" font-family="monospace" font-weight="700">STAGE 2B</text>
  <text x="530" y="192" text-anchor="middle" fill="#70c0f0" font-size="9.5" font-family="monospace" font-weight="700">PFBR — Th Bridge</text>
  <text x="530" y="207" text-anchor="middle" fill="#7090b0" font-size="8.5" font-family="monospace">Pu-239 core fissions</text>
  <text x="530" y="220" text-anchor="middle" fill="#7090b0" font-size="8.5" font-family="monospace">Th-232 blanket absorbs</text>
  <text x="530" y="233" text-anchor="middle" fill="#7090b0" font-size="8.5" font-family="monospace">→ breeds U-233 fuel</text>
  <rect x="465" y="245" width="130" height="18" rx="4" fill="#0a1628" stroke="#3498db" stroke-width="1" />
  <text x="530" y="258" text-anchor="middle" fill="#70c0f0" font-size="8" font-family="monospace" font-weight="600">⏳ NEXT — ~2030s</text>
</g>

<!-- ──────────────────────────────────────────────────────── -->
<!-- STAGE 3 -->
<!-- ──────────────────────────────────────────────────────── -->
<g class="sfd-stage">
  <rect class="sfd-box" x="667" y="148" width="156" height="128" rx="8" fill="#0a1a0f00" stroke="#27ae60" stroke-width="1.8" />
  <rect x="667" y="148" width="156" height="28" rx="8" fill="#1a8a45" />
  <rect x="667" y="164" width="156" height="12" fill="#1a8a45" />
  <text x="745" y="166" text-anchor="middle" fill="#fff" font-size="12" font-family="monospace" font-weight="700">STAGE 3</text>
  <text x="745" y="192" text-anchor="middle" fill="#60e888" font-size="9.5" font-family="monospace" font-weight="700">AHWRs — Thorium Era</text>
  <text x="745" y="207" text-anchor="middle" fill="#60a878" font-size="8.5" font-family="monospace">U-233 core fissions</text>
  <text x="745" y="220" text-anchor="middle" fill="#60a878" font-size="8.5" font-family="monospace">Th-232 blanket absorbs</text>
  <text x="745" y="233" text-anchor="middle" fill="#60a878" font-size="8.5" font-family="monospace">→ breeds its own U-233</text>
  <rect x="678" y="245" width="134" height="18" rx="4" fill="#0a1a0a" stroke="#27ae60" stroke-width="1" />
  <text x="745" y="258" text-anchor="middle" fill="#60e888" font-size="8" font-family="monospace" font-weight="600">🔮 GOAL — 2040s+</text>
</g>

<!-- ──────────────────────────────────────────────────────── -->
<!-- ELECTRICITY OUTPUTS (bottom) -->
<!-- ──────────────────────────────────────────────────────── -->
<line x1="100" y1="276" x2="100" y2="332" stroke="#ffd700" stroke-width="2" stroke-dasharray="5 3" marker-end="url(#sfd-ael)" class="sfd-arrow2" />
<text x="100" y="350" text-anchor="middle" fill="#ffd700" font-size="9" font-family="monospace">⚡ Electricity</text>

<line x1="315" y1="276" x2="315" y2="332" stroke="#ffd700" stroke-width="2" stroke-dasharray="5 3" marker-end="url(#sfd-ael)" class="sfd-arrow2" />
<text x="315" y="350" text-anchor="middle" fill="#ffd700" font-size="9" font-family="monospace">⚡ Electricity</text>

<line x1="530" y1="276" x2="530" y2="332" stroke="#ffd700" stroke-width="2" stroke-dasharray="5 3" marker-end="url(#sfd-ael)" class="sfd-arrow2" />
<text x="530" y="350" text-anchor="middle" fill="#ffd700" font-size="9" font-family="monospace">⚡ Electricity</text>

<line x1="745" y1="276" x2="745" y2="332" stroke="#ffd700" stroke-width="2" stroke-dasharray="5 3" marker-end="url(#sfd-ael)" class="sfd-arrow2" />
<text x="745" y="350" text-anchor="middle" fill="#ffd700" font-size="9" font-family="monospace">⚡ Electricity</text>

<!-- ──────────────────────────────────────────────────────── -->
<!-- STAGE 3 CLOSED-LOOP INDICATOR -->
<!-- ──────────────────────────────────────────────────────── -->
<!-- Loop arc to the right of Stage 3 -->
<path d="M 810 180 C 845 180 852 197 852 212 C 852 227 845 244 810 244" stroke="#2ecc71" stroke-width="2" fill="none" stroke-dasharray="5 3" marker-end="url(#sfd-alp)" class="sfd-arrow2" />
<text x="856" y="209" text-anchor="start" fill="#2ecc71" font-size="11" font-family="monospace" font-weight="700" class="sfd-glow">∞</text>
<text x="857" y="222" text-anchor="start" fill="#2ecc71" font-size="7" font-family="monospace">loop</text>

<!-- ──────────────────────────────────────────────────────── -->
<!-- LEGEND -->
<!-- ──────────────────────────────────────────────────────── -->
<rect x="150" y="370" width="560" height="34" rx="5" fill="#111827" stroke="#1e2d45" stroke-width="1" />

<line x1="168" y1="387" x2="195" y2="387" stroke="#e8891a" stroke-width="2" stroke-dasharray="5 3" />
<text x="200" y="391" fill="#e8891a" font-size="8" font-family="monospace">Uranium flow</text>

<line x1="278" y1="387" x2="305" y2="387" stroke="#9b59b6" stroke-width="2" stroke-dasharray="5 3" />
<text x="310" y="391" fill="#9b59b6" font-size="8" font-family="monospace">Plutonium flow</text>

<line x1="400" y1="387" x2="427" y2="387" stroke="#2ecc71" stroke-width="2" stroke-dasharray="5 3" />
<text x="432" y="391" fill="#2ecc71" font-size="8" font-family="monospace">Thorium / U-233 flow</text>

<line x1="556" y1="387" x2="583" y2="387" stroke="#ffd700" stroke-width="2" stroke-dasharray="5 3" />
<text x="588" y="391" fill="#ffd700" font-size="8" font-family="monospace">Electricity output</text>

</svg>
</div>

<p><em>Hover over each stage. Arrows show what fuel passes between stages; each stage outputs electricity as a side-effect of its primary mission.</em></p>

<h2 id="iv-stage-2-the-pfbr-the-fast-factory--where-we-are-now">IV. Stage 2: The PFBR (The “Fast” Factory) — <em>Where we are now</em></h2>

<p><strong>The “Breeder” Concept (Two Phases):</strong> With the PFBR reaching criticality, India has officially activated Stage 2. Unlike standard reactors that simply consume fuel until it is depleted, the PFBR is a “breeder” designed to produce more fissile material than it consumes. Because the ultimate goal of the programme requires a massive stockpile of new fuel, Stage 2 actually operates in two distinct, sequential phases:</p>

<ul>
  <li><strong>Stage 2A (The Multiplier):</strong> This is the current phase. The core of the reactor is fueled by the Plutonium harvested from Stage 1. Surrounding this active core is a “blanket” of Uranium-238 (the leftover waste from Stage 1). As the Plutonium undergoes fission, it releases neutrons that strike the U-238 blanket, transmuting it into <em>fresh Plutonium</em>. The goal of Phase 2A is strictly to breed enough Plutonium to build a larger fleet of breeder reactors.</li>
  <li><strong>Stage 2B (The Thorium Bridge):</strong> Once India has bred enough Plutonium to sustain a fleet of Stage 2 reactors, the “recipe” changes. Engineers will replace the U-238 blankets with blankets of <strong>Thorium-232</strong>. The Plutonium core remains the “engine,” but the fast neutrons will now bombard the Thorium, transmuting it into the man-made superfuel: <strong>Uranium-233</strong>. This U-233 is collected and stockpiled to eventually trigger Stage 3.</li>
</ul>

<p><strong>The Necessity of “Fast” Neutrons:</strong> To make both phases of this breeding process work, the physics must be the exact opposite of Stage 1. The transmutation of U-238 (and later Thorium) requires high-energy, “fast” neutrons. If the reactor used water as a coolant, the water would act as a moderator, slowing the neutrons down and instantly stopping the breeding process. Therefore, the PFBR cannot use water.</p>

<p><strong>The Sodium Secret:</strong> The solution is <strong>Liquid Sodium</strong>. Sodium is a heavy metal; when neutrons hit sodium atoms, they bounce off without losing their speed, allowing the breeding to continue. Furthermore, sodium possesses remarkable thermal properties. While water boils at 100°C and requires massive, thick pressure vessels to prevent steam explosions, liquid sodium does not boil until 882°C. The PFBR operates at around 550°C, meaning the liquid metal coolant flows through the reactor at normal atmospheric pressure, eliminating the risk of a high-pressure explosion.</p>

<p><strong>The Engineering Hurdle:</strong> The trade-off for these benefits is a severe engineering challenge. Sodium is highly reactive: it burns upon contact with air and explodes upon contact with water. Because it is an opaque metal, engineers cannot simply look inside the reactor; they must rely on advanced ultrasonic sensors and robotics for monitoring and maintenance.</p>

<p><strong>The Three-Loop Safety System:</strong> {#the-three-loop-safety-system} To safely turn this intense heat into electricity, the PFBR utilizes a strict three-loop system.</p>

<ol>
  <li><strong>The Primary Loop:</strong> Radioactive liquid sodium flows through the core, absorbing heat.</li>
  <li><strong>The Secondary Loop:</strong> This heat is transferred to a second, non-radioactive loop of liquid sodium. This acts as a physical firewall.</li>
  <li><strong>The Tertiary Loop (Water):</strong> The secondary sodium loop travels outside the reactor to a heat exchanger, where it boils water into high-pressure steam to spin the electrical turbines. If a pipe breaks in the steam generator, the water will only react with the non-radioactive secondary sodium, completely isolating the nuclear core from the chemical reaction.</li>
</ol>

<style>
  @keyframes tlc-f  { to { stroke-dashoffset: -14; } }
  @keyframes tlc-gl { 0%,100%{opacity:.65;} 50%{opacity:1;} }
  @keyframes tlc-cr { 0%,100%{filter:drop-shadow(0 0 6px #ff5500);} 50%{filter:drop-shadow(0 0 18px #ff5500);} }
  @keyframes tlc-sp { 0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);} }
  .tlc-p1h { animation: tlc-f 3.2s linear infinite; }
  .tlc-p1c { animation: tlc-f 3.2s linear infinite; }
  .tlc-p2h { animation: tlc-f 2.1s linear infinite; }
  .tlc-p2c { animation: tlc-f 2.1s linear infinite; }
  .tlc-st  { animation: tlc-f 1.1s linear infinite; }
  .tlc-fw  { animation: tlc-f 2.5s linear infinite; }
  .tlc-gl  { animation: tlc-gl 2s ease-in-out infinite; }
  .tlc-cr  { animation: tlc-cr 2.5s ease-in-out infinite; }
  .tlc-sp  { transform-origin: 668px 230px; animation: tlc-sp 1.4s linear infinite; }
</style>

<div style="margin:2rem 0; overflow-x:auto;">
<svg viewBox="0 0 860 480" xmlns="http://www.w3.org/2000/svg" style="width:100%; min-width:580px; display:block; font-family:'Courier New',monospace;" aria-label="PFBR Three-Loop Sodium Safety Coolant System">
<defs>
  <radialGradient id="tlc-rg" cx="50%" cy="50%" r="50%">
    <stop offset="0%" stop-color="#ff7030" stop-opacity=".38" />
    <stop offset="100%" stop-color="#ff3300" stop-opacity="0" />
  </radialGradient>
  <marker id="tlc-ah" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto">
    <polygon points="0,0 6,2.5 0,5" fill="#ffcc40" opacity=".75" />
  </marker>
</defs>

<!-- Background -->
<rect width="860" height="480" fill="#090c16" rx="10" />

<!-- Title -->
<text x="430" y="25" text-anchor="middle" fill="#5a8abf" font-size="11" letter-spacing="2" font-weight="600">PFBR — THREE-LOOP SODIUM SAFETY COOLANT SYSTEM</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- ZONE BACKGROUNDS                                           -->
<!-- ══════════════════════════════════════════════════════════ -->
<rect x="26" y="148" width="302" height="164" rx="7" fill="rgba(210,50,20,0.07)" stroke="#7a3020" stroke-width="1" stroke-dasharray="5 4" />
<text x="36" y="162" fill="#7a4030" font-size="8" letter-spacing="1" font-weight="700">☢  LOOP 1 — RADIOACTIVE PRIMARY</text>

<rect x="326" y="148" width="192" height="164" rx="7" fill="rgba(210,150,20,0.07)" stroke="#7a6820" stroke-width="1" stroke-dasharray="5 4" />
<text x="336" y="162" fill="#7a7030" font-size="8" letter-spacing="1" font-weight="700">⚠  LOOP 2 — SODIUM FIREWALL</text>

<rect x="516" y="148" width="314" height="240" rx="7" fill="rgba(20,90,200,0.06)" stroke="#1a5060" stroke-width="1" stroke-dasharray="5 4" />
<text x="526" y="162" fill="#2a6070" font-size="8" letter-spacing="1" font-weight="700">≋  LOOP 3 — WATER / STEAM / POWER</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- PIPES  (drawn first; opaque component boxes hide interiors)-->
<!-- ══════════════════════════════════════════════════════════ -->

<!-- LOOP 1 HOT  ▶  (orange-red, going right, y=183) -->
<line x1="30" y1="183" x2="375" y2="183" stroke="#180900" stroke-width="14" stroke-linecap="square" />
<line x1="30" y1="183" x2="375" y2="183" stroke="#e05020" stroke-width="8" stroke-dasharray="8 6" class="tlc-p1h" />

<!-- LOOP 1 COOL ◀  (darker red, going left, y=268) -->
<line x1="375" y1="268" x2="30" y2="268" stroke="#180900" stroke-width="14" stroke-linecap="square" />
<line x1="375" y1="268" x2="30" y2="268" stroke="#c03018" stroke-width="8" stroke-dasharray="8 6" class="tlc-p1c" />

<!-- LOOP 2 HOT  ▶  (amber, going right, y=210) -->
<line x1="280" y1="210" x2="565" y2="210" stroke="#140e00" stroke-width="14" stroke-linecap="square" />
<line x1="280" y1="210" x2="565" y2="210" stroke="#e0a018" stroke-width="8" stroke-dasharray="8 6" class="tlc-p2h" />

<!-- LOOP 2 COOL ◀  (darker amber, going left, y=250) -->
<line x1="565" y1="250" x2="280" y2="250" stroke="#140e00" stroke-width="14" stroke-linecap="square" />
<line x1="565" y1="250" x2="280" y2="250" stroke="#b08010" stroke-width="8" stroke-dasharray="8 6" class="tlc-p2c" />

<!-- LOOP 3 STEAM ▶  (icy blue, going right, y=224) -->
<line x1="470" y1="224" x2="635" y2="224" stroke="#04060e" stroke-width="14" stroke-linecap="square" />
<line x1="470" y1="224" x2="635" y2="224" stroke="#80c4f8" stroke-width="8" stroke-dasharray="8 6" class="tlc-st" />

<!-- LOOP 3 WATER RETURN  (dark blue path: turbine exhaust → condenser → SG) -->
<path d="M 668,285 L 668,350 L 560,350 L 560,265" stroke="#04060e" stroke-width="12" fill="none" stroke-linecap="square" stroke-linejoin="square" />
<path d="M 668,285 L 668,350 L 560,350 L 560,265" stroke="#1c4a9e" stroke-width="7" fill="none" stroke-dasharray="8 6" class="tlc-fw" />

<!-- ══════════════════════════════════════════════════════════ -->
<!-- REACTOR CORE                                               -->
<!-- ══════════════════════════════════════════════════════════ -->
<!-- Radial glow -->
<circle cx="100" cy="230" r="88" fill="url(#tlc-rg)" class="tlc-gl" />
<!-- Box -->
<rect x="30" y="158" width="140" height="147" rx="7" fill="#160a04" stroke="#d85020" stroke-width="2" />
<!-- Header -->
<rect x="30" y="158" width="140" height="25" rx="7" fill="#a83c18" />
<rect x="30" y="171" width="140" height="12" fill="#a83c18" />
<text x="100" y="175" text-anchor="middle" fill="#fff" font-size="11" font-weight="700">REACTOR CORE</text>
<!-- Nuclear symbol -->
<circle cx="100" cy="222" r="18" fill="none" stroke="#d85020" stroke-width="1.5" class="tlc-gl" />
<circle cx="100" cy="222" r="4.5" fill="#d85020" class="tlc-gl" />
<line x1="100" y1="204" x2="100" y2="240" stroke="#d85020" stroke-width="1.2" class="tlc-gl" />
<line x1="84" y1="213" x2="116" y2="231" stroke="#d85020" stroke-width="1.2" class="tlc-gl" />
<line x1="84" y1="231" x2="116" y2="213" stroke="#d85020" stroke-width="1.2" class="tlc-gl" />
<!-- Labels -->
<text x="100" y="253" text-anchor="middle" fill="#d06030" font-size="8.5">Primary Na pool</text>
<text x="100" y="266" text-anchor="middle" fill="#a04020" font-size="8">Core exit ~550°C</text>
<text x="100" y="279" text-anchor="middle" fill="#803020" font-size="8">Core entry ~370°C</text>
<text x="100" y="295" text-anchor="middle" fill="#602010" font-size="7.5">Fast neutron fission</text>

<!-- Na port labels on right wall -->
<text x="174" y="181" fill="#e06030" font-size="7" text-anchor="start">HOT →</text>
<text x="174" y="272" fill="#c04020" font-size="7" text-anchor="start">← COOL</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- IHX  (Intermediate Heat Exchanger)                        -->
<!-- ══════════════════════════════════════════════════════════ -->
<rect x="280" y="168" width="95" height="132" rx="7" fill="#0e0b06" stroke="#a87030" stroke-width="1.8" />
<!-- Header -->
<rect x="280" y="168" width="95" height="24" rx="7" fill="#7a5018" />
<rect x="280" y="180" width="95" height="12" fill="#7a5018" />
<text x="327" y="184" text-anchor="middle" fill="#fff" font-size="10" font-weight="700">IHX</text>
<!-- Vertical divider (Na / Na firewall) -->
<line x1="327" y1="192" x2="327" y2="300" stroke="#505050" stroke-width="1.8" />
<!-- Primary side fins (left) -->
<line x1="290" y1="205" x2="325" y2="205" stroke="#e05020" stroke-width="1" opacity=".4" />
<line x1="290" y1="218" x2="325" y2="218" stroke="#e05020" stroke-width="1" opacity=".4" />
<line x1="290" y1="231" x2="325" y2="231" stroke="#e05020" stroke-width="1" opacity=".4" />
<line x1="290" y1="244" x2="325" y2="244" stroke="#e05020" stroke-width="1" opacity=".4" />
<line x1="290" y1="257" x2="325" y2="257" stroke="#e05020" stroke-width="1" opacity=".4" />
<line x1="290" y1="270" x2="325" y2="270" stroke="#e05020" stroke-width="1" opacity=".4" />
<!-- Secondary side fins (right) -->
<line x1="329" y1="205" x2="365" y2="205" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="329" y1="218" x2="365" y2="218" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="329" y1="231" x2="365" y2="231" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="329" y1="244" x2="365" y2="244" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="329" y1="257" x2="365" y2="257" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="329" y1="270" x2="365" y2="270" stroke="#e0a020" stroke-width="1" opacity=".4" />
<!-- Heat transfer arrow -->
<text x="327" y="240" text-anchor="middle" fill="#ffcc40" font-size="9" font-weight="700" class="tlc-gl">→</text>
<!-- Side labels -->
<text x="303" y="298" text-anchor="middle" fill="#904030" font-size="7.5">Primary</text>
<text x="351" y="298" text-anchor="middle" fill="#908030" font-size="7.5">Secondary</text>
<text x="303" y="308" text-anchor="middle" fill="#703020" font-size="7">Na ☢</text>
<text x="351" y="308" text-anchor="middle" fill="#706020" font-size="7">Na ✓</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- STEAM GENERATOR                                            -->
<!-- ══════════════════════════════════════════════════════════ -->
<rect x="470" y="168" width="95" height="132" rx="7" fill="#080c10" stroke="#60a0c0" stroke-width="1.8" />
<!-- Header -->
<rect x="470" y="168" width="95" height="24" rx="7" fill="#186080" />
<rect x="470" y="180" width="95" height="12" fill="#186080" />
<text x="517" y="184" text-anchor="middle" fill="#fff" font-size="9.5" font-weight="700">STEAM GEN</text>
<!-- Divider: Na / Water -->
<line x1="517" y1="192" x2="517" y2="300" stroke="#505050" stroke-width="1.8" />
<!-- Na side fins -->
<line x1="480" y1="205" x2="515" y2="205" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="480" y1="218" x2="515" y2="218" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="480" y1="231" x2="515" y2="231" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="480" y1="244" x2="515" y2="244" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="480" y1="257" x2="515" y2="257" stroke="#e0a020" stroke-width="1" opacity=".4" />
<line x1="480" y1="270" x2="515" y2="270" stroke="#e0a020" stroke-width="1" opacity=".4" />
<!-- Water/steam side fins -->
<line x1="519" y1="205" x2="555" y2="205" stroke="#60a8e0" stroke-width="1" opacity=".4" />
<line x1="519" y1="218" x2="555" y2="218" stroke="#60a8e0" stroke-width="1" opacity=".4" />
<line x1="519" y1="231" x2="555" y2="231" stroke="#70c0f0" stroke-width="1" opacity=".5" />
<line x1="519" y1="244" x2="555" y2="244" stroke="#70c0f0" stroke-width="1" opacity=".5" />
<line x1="519" y1="257" x2="555" y2="257" stroke="#80d0f8" stroke-width="1" opacity=".6" />
<line x1="519" y1="270" x2="555" y2="270" stroke="#90e0ff" stroke-width="1" opacity=".6" />
<!-- Heat transfer arrow -->
<text x="517" y="240" text-anchor="middle" fill="#ffcc40" font-size="9" font-weight="700" class="tlc-gl">→</text>
<!-- Side labels -->
<text x="493" y="298" text-anchor="middle" fill="#908030" font-size="7.5">Sec. Na</text>
<text x="541" y="298" text-anchor="middle" fill="#2080a0" font-size="7.5">H₂O/Steam</text>
<!-- Steam rising indicator -->
<text x="541" y="308" text-anchor="middle" fill="#40a0c0" font-size="7">↑ boiling</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- TURBINE                                                    -->
<!-- ══════════════════════════════════════════════════════════ -->
<rect x="635" y="178" width="90" height="110" rx="7" fill="#060810" stroke="#2868b8" stroke-width="1.8" />
<!-- Header -->
<rect x="635" y="178" width="90" height="24" rx="7" fill="#163870" />
<rect x="635" y="190" width="90" height="12" fill="#163870" />
<text x="680" y="195" text-anchor="middle" fill="#fff" font-size="11" font-weight="700">TURBINE</text>
<!-- Spinning blades -->
<g class="tlc-sp">
  <circle cx="668" cy="230" r="22" fill="none" stroke="#2868b8" stroke-width="1.2" />
  <circle cx="668" cy="230" r="5" fill="#2060a0" />
  <line x1="668" y1="208" x2="668" y2="220" stroke="#2868b8" stroke-width="3.5" stroke-linecap="round" />
  <line x1="668" y1="240" x2="668" y2="252" stroke="#2868b8" stroke-width="3.5" stroke-linecap="round" />
  <line x1="646" y1="230" x2="658" y2="230" stroke="#2868b8" stroke-width="3.5" stroke-linecap="round" />
  <line x1="678" y1="230" x2="690" y2="230" stroke="#2868b8" stroke-width="3.5" stroke-linecap="round" />
  <line x1="653" y1="214" x2="661" y2="222" stroke="#2868b8" stroke-width="2.5" stroke-linecap="round" />
  <line x1="675" y1="238" x2="683" y2="246" stroke="#2868b8" stroke-width="2.5" stroke-linecap="round" />
  <line x1="683" y1="214" x2="675" y2="222" stroke="#2868b8" stroke-width="2.5" stroke-linecap="round" />
  <line x1="661" y1="238" x2="653" y2="246" stroke="#2868b8" stroke-width="2.5" stroke-linecap="round" />
</g>
<!-- Shaft connection to generator -->
<line x1="725" y1="230" x2="740" y2="230" stroke="#2060a0" stroke-width="5" />

<!-- ══════════════════════════════════════════════════════════ -->
<!-- GENERATOR                                                  -->
<!-- ══════════════════════════════════════════════════════════ -->
<rect x="740" y="200" width="68" height="60" rx="7" fill="#060810" stroke="#2868b8" stroke-width="1.8" />
<rect x="740" y="200" width="68" height="22" rx="7" fill="#163870" />
<rect x="740" y="210" width="68" height="12" fill="#163870" />
<text x="774" y="215" text-anchor="middle" fill="#fff" font-size="10" font-weight="700">GEN</text>
<circle cx="774" cy="240" r="13" fill="none" stroke="#2868b8" stroke-width="1.5" />
<text x="774" y="245" text-anchor="middle" fill="#3880c8" font-size="15" font-weight="700">G</text>
<!-- Electricity output -->
<line x1="808" y1="230" x2="830" y2="230" stroke="#ffd700" stroke-width="3" stroke-dasharray="5 3" class="tlc-st" />
<text x="836" y="236" fill="#ffd700" font-size="16" font-weight="700" class="tlc-gl">⚡</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- CONDENSER                                                  -->
<!-- ══════════════════════════════════════════════════════════ -->
<rect x="594" y="318" width="150" height="52" rx="7" fill="#060810" stroke="#1a5878" stroke-width="1.8" />
<rect x="594" y="318" width="150" height="22" rx="7" fill="#0e3850" />
<rect x="594" y="328" width="150" height="12" fill="#0e3850" />
<text x="669" y="333" text-anchor="middle" fill="#fff" font-size="10" font-weight="700">CONDENSER</text>
<text x="669" y="357" text-anchor="middle" fill="#2870a0" font-size="8.5">exhaust steam → liquid water</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- PIPE LABELS / TEMPERATURE CALLOUTS                         -->
<!-- ══════════════════════════════════════════════════════════ -->

<!-- Loop 1 hot label (between reactor and IHX) -->
<text x="226" y="174" text-anchor="middle" fill="#e06030" font-size="8" font-weight="600">~550°C  hot Na →</text>
<!-- Loop 1 cool label -->
<text x="226" y="282" text-anchor="middle" fill="#a04020" font-size="8" font-weight="600">← cool Na  ~370°C</text>

<!-- Loop 2 hot label (between IHX and SG) -->
<text x="422" y="202" text-anchor="middle" fill="#c09020" font-size="8" font-weight="600">~450°C  hot Na →</text>
<!-- Loop 2 cool label -->
<text x="422" y="263" text-anchor="middle" fill="#907010" font-size="8" font-weight="600">← cool Na  ~320°C</text>

<!-- Loop 3 steam label (between SG and turbine) -->
<text x="582" y="216" text-anchor="middle" fill="#6098d0" font-size="8" font-weight="600">~485°C steam →</text>

<!-- Water return label -->
<text x="610" y="345" text-anchor="middle" fill="#3060a0" font-size="8">← feedwater</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- SAFETY ANNOTATION (firewall explanation)                   -->
<!-- ══════════════════════════════════════════════════════════ -->
<line x1="327" y1="318" x2="327" y2="390" stroke="#cc9900" stroke-width="1" stroke-dasharray="3 3" opacity=".5" />
<rect x="240" y="390" width="175" height="62" rx="5" fill="#0e0e00" stroke="#cc9900" stroke-width="1" opacity=".85" />
<text x="327" y="404" text-anchor="middle" fill="#cc9900" font-size="8.5" font-weight="700">Safety Firewall Logic</text>
<text x="327" y="417" text-anchor="middle" fill="#aa8800" font-size="8">If SG leaks: water reacts with</text>
<text x="327" y="429" text-anchor="middle" fill="#aa8800" font-size="8">Loop 2 (non-radioactive) sodium.</text>
<text x="327" y="441" text-anchor="middle" fill="#aa8800" font-size="8">Loop 1 (radioactive core) stays</text>
<text x="327" y="453" text-anchor="middle" fill="#aa8800" font-size="8">completely isolated. ✓</text>

<!-- ══════════════════════════════════════════════════════════ -->
<!-- LEGEND                                                     -->
<!-- ══════════════════════════════════════════════════════════ -->
<rect x="30" y="460" width="800" height="14" rx="3" fill="#0a1020" stroke="#1a2840" stroke-width="1" />
<line x1="45" y1="467" x2="72" y2="467" stroke="#e05020" stroke-width="5" stroke-dasharray="8 6" />
<text x="77" y="471" fill="#c04020" font-size="7.5">Loop 1 (radioactive Na)</text>
<line x1="215" y1="467" x2="242" y2="467" stroke="#e0a020" stroke-width="5" stroke-dasharray="8 6" />
<text x="247" y="471" fill="#b08020" font-size="7.5">Loop 2 (clean Na firewall)</text>
<line x1="405" y1="467" x2="432" y2="467" stroke="#80c4f8" stroke-width="5" stroke-dasharray="8 6" />
<text x="437" y="471" fill="#5090c0" font-size="7.5">Steam (~485°C)</text>
<line x1="545" y1="467" x2="572" y2="467" stroke="#1c4a9e" stroke-width="5" stroke-dasharray="8 6" />
<text x="577" y="471" fill="#3060a0" font-size="7.5">Feedwater return</text>
<text x="720" y="471" fill="#ffd700" font-size="7.5">⚡ Electricity output</text>

</svg>
</div>

<h2 id="v-stage-3-the-thorium-era-the-end-goal">V. Stage 3: The Thorium Era (The End Goal)</h2>

<p><strong>The Paradigm Shift:</strong> It is crucial to understand that Stage 3 is not just the PFBR running on different fuel; it requires an entirely new class of reactor. Once India has used its Stage 2 breeder reactors to stockpile enough Uranium-233 (created by substituting the U-238 blankets with Thorium-232), that fuel will be transferred into <strong>Advanced Heavy Water Reactors (AHWRs)</strong>. This marks the beginning of Stage 3.</p>

<p><strong>The Closed Loop:</strong> Inside the AHWR, the operational physics finally reach their endgame. The core is fueled by the Uranium-233, and it is surrounded by a blanket of pure Thorium. As the U-233 fissions to create commercial electricity, the neutrons it releases strike the Thorium blanket, transmuting it into <em>new</em> U-233. The reactor breeds exactly enough fuel to sustain itself. At this stage, the loop is closed: the system requires only raw thorium as an input, effectively severing India’s reliance on global uranium markets forever.</p>

<p><strong>The Proof of Concept (KAMINI):</strong> The idea of running a power grid on U-233 is not a theoretical hope. India currently operates the Kalpakkam Mini reactor (KAMINI), a small research facility that holds a unique distinction: it is currently the only reactor in the world operating on Uranium-233 fuel. KAMINI proves definitively that the physics of Stage 3 are sound and achievable.</p>

<p><strong>The Decades-Long Hurdles:</strong> Reaching Stage 3 at a commercial scale is expected to take until the 2040s or beyond due to two immense, unavoidable bottlenecks:</p>

<ol>
  <li><strong>The Fuel Bank (“Doubling Time”):</strong> It takes roughly 10 to 15 years for a breeder reactor to produce enough excess fuel to start a second reactor. India must patiently wait for the Stage 2 reactors to breed a massive, national stockpile of U-233 before large-scale AHWRs can be commissioned.</li>
  <li><strong>The U-232 Robotics Problem:</strong> When Thorium is converted into U-233 inside a reactor, a side-reaction inevitably creates trace amounts of an isotope called Uranium-232. As U-232 decays, it produces Thallium-208, which emits incredibly intense, highly penetrating 2.6 MeV gamma radiation. Because of this extreme radiation, human technicians cannot safely handle or manufacture U-233 fuel rods. The transition to Stage 3 requires the development of heavily shielded “hot cells” where the fuel fabrication is handled entirely by automated robotics.</li>
</ol>

<h2 id="vi-conclusion-the-thorium-advantage-and-the-final-payoff">VI. Conclusion: The Thorium Advantage and the Final Payoff</h2>

<p>If the Three-Stage Programme takes nearly a century to complete, and involves immense engineering hurdles like liquid sodium and robotic fuel fabrication, the ultimate question is: <em>Is it worth the wait?</em> The answer lies in the massive environmental and strategic advantages of the Thorium cycle. By reaching Stage 3, India will not just achieve energy independence; it will achieve a level of nuclear sustainability that the standard “Uranium Cycle” cannot match.</p>

<p><strong>The Ultimate Recycling Machine:</strong> One of the greatest criticisms of global nuclear power is the accumulation of radioactive waste. India’s Stage 2 breeder reactors directly address this. The PFBR and its successors actively consume the “nuclear waste” (Uranium-238 and Plutonium) generated by the Stage 1 reactors over the last fifty years. Instead of burying this waste in deep geological repositories, India is using it as the primary fuel to build the bridge to Thorium.</p>

<p><strong>The Efficiency Multiplier:</strong> In a standard Stage 1 reactor, roughly 1% of the mined uranium is actually converted into energy; the rest is discarded. In the closed-loop Thorium cycle of Stage 3, because the reactor constantly breeds its own fuel, close to 100% of the mined thorium is utilized. Theoretically, one tonne of Thorium can yield the energy equivalent of 200 tonnes of conventional Uranium.</p>

<p><strong>The Waste Lifespan:</strong> Nuclear fission inherently creates radioactive byproducts, but not all waste is equal. The waste from a standard Uranium reactor contains heavy “transuranic” elements (like Plutonium, Americium, and Curium) that remain dangerously radioactive for tens of thousands of years. Thorium, however, is a lighter element. The fission byproducts of the Thorium/U-233 cycle are far less toxic, and the bulk of its radioactive waste decays back to safe, natural background radiation levels in approximately 300 years — a timeframe that human engineering and geology can easily manage.</p>

<h3 id="the-final-meaning-of-kalpakkams-criticality">The Final Meaning of Kalpakkam’s Criticality</h3>

<p>When the PFBR at Kalpakkam reached first criticality on April 6, 2026, it was not merely testing a new turbine or bringing a few more megawatts to the grid. It was proving that the hardest, most dangerous physics of the Three-Stage Programme — the fast neutrons and the liquid metal coolants — could be safely tamed on an industrial scale.</p>

<p>India still has decades of work ahead. The PFBR must now run for years to multiply its Plutonium (Stage 2A), before shifting its diet to breed Uranium-233 (Stage 2B), and eventually handing that superfuel over to the automated robotic facilities of Stage 3.</p>

<p>It is a grueling, multi-generational marathon. But with the PFBR now “awake” and self-sustaining, India has successfully crossed the hardest technical threshold. The bridge to a century of clean, independent, and sustainable Thorium power is officially open.</p>]]></content><author><name>Kanak Raj</name><email>kanak8278@gmail.com</email></author><category term="blog" /><category term="nuclear" /><category term="energy" /><category term="india" /><category term="physics" /><summary type="html"><![CDATA[Every PFBR headline just repeated 'Three-Stage Nuclear Power Programme' without explaining what the stages mean or what flows between them. This post is what I found after going looking — with interactive simulations.]]></summary></entry><entry><title type="html">Fine-Tuning LLMs with Non-Differentiable Human Feedback + RL</title><link href="https://kanak8278.github.io//fine-tuning-llms-with-non-differentiable-human-feedback-rl/" rel="alternate" type="text/html" title="Fine-Tuning LLMs with Non-Differentiable Human Feedback + RL" /><published>2025-05-04T00:00:00+00:00</published><updated>2025-05-04T00:00:00+00:00</updated><id>https://kanak8278.github.io//fine-tuning-llms-with-non-differentiable-human-feedback-rl</id><content type="html" xml:base="https://kanak8278.github.io//fine-tuning-llms-with-non-differentiable-human-feedback-rl/"><![CDATA[<p>The blog is focused on giving you an intuition of why even using non-differentiable reward function we are able to use <a href="https://arxiv.org/pdf/2402.03300">Group Relative Position Optimization (GRPO)</a> for fine-tuning LLMs.</p>

<h2 id="why-do-we-need-differentiable-loss-functions">Why Do We Need Differentiable Loss Functions?</h2>

<p>Deep learning models—like the ones powering LLMs or your favorite image generator—are trained using gradient-based optimization algorithms, stuff like gradient descent. These algorithms are basically the GPS for training: they figure out how to adjust the model’s parameters (those millions of tiny weights) to make the loss—the “how wrong are we?” score—as small as possible. To do that, they need to calculate the gradient of the loss function with respect to those parameters. Think of the gradient as a little arrow saying, “Nudge this weight up a bit, and the loss goes down,” or “Tweak that one down, it’s messing things up.”</p>

<p>Now, here’s the catch: gradients only exist if the <code class="language-plaintext highlighter-rouge">loss function is differentiable</code>. That just means it’s smooth enough that we can measure how it changes when we tweak the inputs—no sudden jumps or breaks where the slope goes haywire.</p>

<h2 id="what-happens-if-the-loss-function-isnt-differentiable">What Happens If the Loss Function Isn’t Differentiable?</h2>

<p>If the loss function isn’t differentiable, gradient-based optimization—the backbone of deep learning—falls apart. Those little arrows we rely on? Gone. Without them, we can’t tell the model which way to tweak its weights, and it’s stuck, unable to learn. Sure, there are derivative-free optimization methods out there—like random search or evolutionary algorithms. Those methods are way less efficient, especially when you’re dealing with the crazy high-dimensional parameter spaces in deep learning models.</p>

<p>Here’s something that might trip you up: in Group Relative Policy Optimization (GRPO), reward functions don’t have to be differentiable—think stuff like the length of a generated response or whether it even contains an answer. So how are we still using GRPO to fine-tune LLMs, which live and breathe gradients?</p>

<p><code class="language-plaintext highlighter-rouge">TLDR</code>:</p>

<p>“<em>The advantage (normalized reward) acts as a constant or scaling factor in the GRPO Loss”</em></p>

<p>GRPO, cooked up by the DeepSeek team, is a twist on reinforcement learning (RL) that’s all about efficiency and stability when fine-tuning LLMs. It builds on the idea of PPO—using human feedback to guide the model—but it’s got some unique tricks up its sleeve, especially with how it handles rewards. Let’s break it down.</p>

<h2 id="the-basics-of-grpo">The Basics of GRPO</h2>

<p>In GRPO, the LLM is your policy ($\pi_\theta$), spitting out responses based on prompts. You’ve got a reward model ($R_\phi$)—trained on human feedback or reward function—that scores those responses.</p>

<p>Unlike traditional RL methods like PPO, GRPO skips the critic (a separate model estimating future rewards), which cuts compute costs by about 50%. Instead, it uses a clever way to judge how good a response is by comparing it to a group of other responses for the same prompt. That’s where the “group relative” part comes in.</p>

<h2 id="the-reward-function-in-grpo">The Reward Function in GRPO</h2>

<p>Here’s where it gets interesting: the reward function in GRPO doesn’t need to be differentiable. It could be something simple and rule-based, like:</p>

<ul>
  <li>“How long is this response?” (e.g., word count)</li>
  <li>“Does it actually answer the question?” (e.g., 1 if yes, 0 if no)</li>
  <li>“Is it toxic?” (e.g., a binary flag from a rule-based checker)</li>
</ul>

<p>These are discrete, step-like signals—not smooth curves you’d expect gradients to flow from. But GRPO still can use a reward model $R_\phi$ to assign scores, and that’s often a neural network trained on human feedback (like “this response is better than that one”). So, you might have a mix: a differentiable reward model for some parts, plus these non-differentiable, rule-based rewards tossed in.</p>

<h2 id="the-grpo-loss-function">The GRPO Loss Function</h2>

<p><img src="/assets/images/posts/ppo-vs-grpo.png" alt="Demonstration of PPO and GRPO" /></p>

<p>Demonstration of PPO and GRPO. GRPO foregoes the value model, instead estimating the baseline from group scores, significantly reducing training resources. (Source: <a href="https://arxiv.org/pdf/2402.03300">DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models</a>)</p>

<p>Let’s get to the meat of it—the loss function that makes GRPO tick. The goal is to tweak the LLM’s parameters ($\theta$) to maximize rewards, but with some guardrails to keep things stable. Here’s the equation:</p>

\[\mathcal{L}_{\text{GRPO}}(\theta) = \mathcal{L}_{\text{clip}}(\theta) - w_1 \mathbb{D}_{\text{KL}}(\pi_\theta || \pi_{\text{orig}})\]

<p>$\mathcal{L}_{\text{clip}}(\theta)$: This is the clipped surrogate loss, adapted from PPO for GRPO. It encourages better responses while preventing excessive policy changes:</p>

\[\mathcal{L}_{\text{clip}}(\theta) = \mathbb{E} \left[ \min \left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1 - \epsilon, 1 + \epsilon) A_t \right) \right]\]

<p>Where:</p>

<ul>
  <li>
    <table>
      <tbody>
        <tr>
          <td>$r_t(\theta) = \frac{\pi_\theta(a_t</td>
          <td>s_t)}{\pi_{\text{old}}(a_t</td>
          <td>s_t)}$ is the probability ratio—comparing how likely the new policy is to generate this response versus the old policy</td>
        </tr>
      </tbody>
    </table>
  </li>
  <li>$A_t$ is the advantage, measuring how much better (or worse) this response is compared to others in the group</li>
  <li>The clip function keeps changes bounded (typically $\epsilon = 0.2$) to maintain stability</li>
</ul>

<table>
  <tbody>
    <tr>
      <td>$\mathbb{D}<em>{\text{KL}}(\pi</em>\theta</td>
      <td> </td>
      <td>\pi_{\text{orig}})$: This KL divergence term acts as a constraint, keeping the updated policy from straying too far from the original. The weight $w_1$ (usually around 0.1) controls this constraint’s strength.</td>
    </tr>
  </tbody>
</table>

<p>A key innovation in GRPO is how it calculates the advantage ($A_t$). Instead of using a separate critic model like PPO does, GRPO computes it by comparing a response’s reward to the group statistics:</p>

\[A_i = \frac{R_\phi(r_i) - \text{mean}(\mathcal{G})}{\text{std}(\mathcal{G})}\]

<p>Where $R_\phi(r_i)$ is the reward for response $r_i$, and $\mathcal{G}$ represents the group of responses for the same prompt.</p>

<p>The $\text{mean}(\mathcal{G})$ and $\text{std}(\mathcal{G})$ terms represent the average and standard deviation of rewards across the group, respectively. This normalization transforms raw rewards into relative advantages, making training more stable by comparing each response to its peers.</p>

<h2 id="why-non-differentiable-rewards-still-work-in-grpo">Why Non-Differentiable Rewards Still Work in GRPO</h2>

<p>Okay, so how does GRPO handle rewards like “length of the generation” or “contains an answer,” which aren’t differentiable? Here’s the trick: the reward itself doesn’t need to be differentiable—what matters is how it’s used in the optimization. Let’s unpack this:</p>

<ol>
  <li>Rewards Are Just Scores:</li>
</ol>

<ul>
  <li>In GRPO, the reward function—whether it’s a fancy neural network $R_\phi$ or a simple rule like “1 if it answers, 0 if not”—is only there to assign a number to each response. That number (the reward) isn’t differentiated directly. It’s a fixed value that feeds into the advantage calculation.</li>
</ul>

<ol>
  <li>Advantages Drive the Gradients:</li>
</ol>

<ul>
  <li>The advantage $A_t$ is computed from those rewards, and it’s just a normalized comparison (better or worse than the group). It’s not differentiated either—it’s a constant for each response.</li>
  <li>The differentiable part comes in the policy update. The loss $\mathcal{L}<em>{\text{clip}}$ depends on the LLM’s probabilities ($\pi</em>\theta$), which are differentiable. The gradients flow through the policy, not the reward or advantage directly.</li>
</ul>

<p>Now, here’s the part that clicked for me: in the GRPO loss function, the advantage $A_t$ acts like a constant or scaling factor. Check this out:</p>

\[\mathcal{L}_{\text{clip}}(\theta) = \mathbb{E} \left[ \min \left( r_t(\theta) A_t, clip(r_t(\theta), 1 - \epsilon, 1 + \epsilon) A_t \right) \right]\]

<p>The $r_t(\theta)$ part—the probability ratio—is what the LLM tweaks, and it’s totally differentiable. The advantage $A_t$? It’s just a number we calculate from the rewards, like “this response is 1.5 better than average.” It scales how much we push the model toward or away from that response, but we don’t need to differentiate it. It’s fixed for that step.</p>

<p>So, even if the reward function is something chunky and non-differentiable—like “1 if the answer’s there, 0 if not”—it doesn’t matter. The reward spits out a score, we turn it into an advantage, and that advantage just rides along as a multiplier. The gradients flow through the LLM’s policy, not the reward itself. It’s like giving the model a scorecard and saying, “Here’s how you did—now adjust!” The scorecard doesn’t need a slope; it just needs to exist.</p>

<p>For additional perspectives on GRPO, check out these papers and blog posts:</p>

<ol>
  <li><a href="https://arxiv.org/pdf/2402.03300">DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models</a></li>
  <li><a href="https://epichka.com/blog/2025/grpo/">https://epichka.com/blog/2025/grpo/</a></li>
  <li><a href="https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-reasoning-llms">https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-reasoning-llms</a></li>
  <li><a href="https://newsletter.languagemodels.co/p/the-illustrated-deepseek-r1">https://newsletter.languagemodels.co/p/the-illustrated-deepseek-r1</a></li>
</ol>]]></content><author><name>Kanak Raj &lt;kanak.raj@thomsonreuters.com&gt;</name></author><category term="llm" /><category term="rlhf" /><category term="grpo" /><category term="reinforcement learning" /><category term="human feedback" /><category term="fine-tuning" /><summary type="html"><![CDATA[The blog is focused on giving an intuition of why even using non-differentiable reward function we are able to use Group Relative Position Optimization (GRPO) for fine-tuning LLMs.]]></summary></entry><entry><title type="html">Generalized Visual Language Models</title><link href="https://kanak8278.github.io//blog/generalized-visual-language-models/" rel="alternate" type="text/html" title="Generalized Visual Language Models" /><published>2022-06-09T00:00:00+00:00</published><updated>2022-06-09T00:00:00+00:00</updated><id>https://kanak8278.github.io//blog/generalized-visual-language-models</id><content type="html" xml:base="https://kanak8278.github.io//blog/generalized-visual-language-models/"><![CDATA[<p>Processing images to generate text, such as image captioning and visual question-answering, has been studied for years. Traditionally such systems rely on an object detection network as a vision encoder to capture visual features and then produce text via a text decoder. Given a large amount of existing literature, in this post I would like to only focus on some remarkable foundation vision-language models in recent years.</p>

<h2 id="introduction-to-visual-language-models">Introduction to Visual Language Models</h2>

<p>Visual language models represent a significant advancement in AI’s ability to understand and interpret the visual world. These models integrate vision and language processing capabilities to perform tasks such as:</p>

<ol>
  <li>Image captioning</li>
  <li>Visual question answering (VQA)</li>
  <li>Image-text retrieval</li>
  <li>Visual reasoning</li>
</ol>

<p>Traditional approaches to vision-language tasks often used separate models for visual feature extraction and language processing. Modern visual language models, however, adopt an end-to-end approach, training the vision and language components jointly.</p>

<h2 id="architecture-of-visual-language-models">Architecture of Visual Language Models</h2>

<p>The general architecture of visual language models consists of three main components:</p>

<h3 id="1-visual-encoder">1. Visual Encoder</h3>

<p>The visual encoder processes the input image and extracts visual features. This can be implemented using:</p>

\[f_\text{visual}(I) = V \in \mathbb{R}^{n \times d_v}\]

<p>Where:</p>

<ul>
  <li>$I$ is the input image</li>
  <li>$V$ represents the extracted visual features</li>
  <li>$n$ is the number of visual tokens</li>
  <li>$d_v$ is the dimensionality of the visual features</li>
</ul>

<p>Common choices for visual encoders include:</p>

<ul>
  <li>Convolutional Neural Networks (CNNs)</li>
  <li>Vision Transformers (ViT)</li>
  <li>Hybrid architectures combining CNNs and transformers</li>
</ul>

<h3 id="2-language-encoderdecoder">2. Language Encoder/Decoder</h3>

<p>The language component processes textual inputs and generates textual outputs. In the encoder-decoder architecture:</p>

\[f_\text{language}(T, V) = L \in \mathbb{R}^{m \times d_l}\]

<p>Where:</p>

<ul>
  <li>$T$ is the textual input</li>
  <li>$L$ represents the language features</li>
  <li>$m$ is the number of language tokens</li>
  <li>$d_l$ is the dimensionality of the language features</li>
</ul>

<h3 id="3-cross-modal-fusion">3. Cross-Modal Fusion</h3>

<p>The cross-modal fusion component aligns visual and language features:</p>

\[f_\text{fusion}(V, L) = F \in \mathbb{R}^{k \times d_f}\]

<p>Where:</p>

<ul>
  <li>$F$ represents the fused multimodal features</li>
  <li>$k$ is the number of fused tokens</li>
  <li>$d_f$ is the dimensionality of the fused features</li>
</ul>

<h2 id="mathematical-formulations-in-visual-language-models">Mathematical Formulations in Visual Language Models</h2>

<h3 id="attention-mechanism">Attention Mechanism</h3>

<p>The attention mechanism is central to modern visual language models, allowing the model to focus on relevant parts of the image when generating text:</p>

\[\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V\]

<p>For cross-attention between vision and language:</p>

\[\text{CrossAttention}(Q_l, K_v, V_v) = \text{softmax}\left(\frac{Q_l K_v^T}{\sqrt{d_k}}\right)V_v\]

<p>Where:</p>

<ul>
  <li>$Q_l$ is the query from the language features</li>
  <li>$K_v, V_v$ are the key and value from the visual features</li>
</ul>

<h3 id="contrastive-learning">Contrastive Learning</h3>

<p>Many visual language models employ contrastive learning to align visual and textual representations:</p>

\[\mathcal{L}_\text{contrastive} = -\log \frac{\exp(s(v, t) / \tau)}{\sum_{j=1}^{N} \exp(s(v, t_j) / \tau)}\]

<p>Where:</p>

<ul>
  <li>$s(v, t)$ is the similarity score between image $v$ and text $t$</li>
  <li>$\tau$ is a temperature parameter</li>
  <li>$N$ is the number of negative samples</li>
</ul>

<h2 id="recent-advances-in-visual-language-models">Recent Advances in Visual Language Models</h2>

<p>Recent advances in visual language models have focused on creating more general-purpose models that can handle a variety of vision-language tasks. Notable examples include:</p>

<ol>
  <li>
    <p><strong>CLIP (Contrastive Language-Image Pre-training)</strong>: Trained on 400 million image-text pairs, CLIP learns visual concepts from natural language supervision.</p>
  </li>
  <li>
    <p><strong>DALL-E</strong>: Capable of generating images from textual descriptions, demonstrating the ability to understand complex concepts and compositions.</p>
  </li>
  <li>
    <p><strong>BLIP (Bootstrapping Language-Image Pre-training)</strong>: Unifies vision-language understanding and generation, achieving state-of-the-art performance across various vision-language tasks.</p>
  </li>
  <li>
    <p><strong>Florence</strong>: A unified vision-language foundation model that demonstrates strong transfer learning capabilities.</p>
  </li>
</ol>

<h2 id="applications-of-visual-language-models">Applications of Visual Language Models</h2>

<p>Visual language models have a wide range of applications:</p>

<ol>
  <li>
    <p><strong>Accessibility</strong>: Helping visually impaired individuals understand visual content through descriptions.</p>
  </li>
  <li>
    <p><strong>Content Moderation</strong>: Automatically identifying inappropriate or harmful visual content.</p>
  </li>
  <li>
    <p><strong>Search and Retrieval</strong>: Improving image search by understanding semantic content.</p>
  </li>
  <li>
    <p><strong>Creative Tools</strong>: Assisting in content creation, such as generating images from descriptions or suggesting edits.</p>
  </li>
  <li>
    <p><strong>Robotics</strong>: Helping robots understand and interact with their environment through visual and language cues.</p>
  </li>
</ol>

<h2 id="challenges-and-future-directions">Challenges and Future Directions</h2>

<p>Despite their impressive capabilities, visual language models face several challenges:</p>

<ol>
  <li>
    <p><strong>Bias</strong>: Models can perpetuate or amplify societal biases present in their training data.</p>
  </li>
  <li>
    <p><strong>Computational Efficiency</strong>: Large models require significant computational resources, making them inaccessible for many applications.</p>
  </li>
  <li>
    <p><strong>Evaluation</strong>: Properly evaluating the performance of visual language models across diverse tasks remains challenging.</p>
  </li>
  <li>
    <p><strong>Generalization</strong>: Improving the ability of models to generalize to unseen concepts and compositions.</p>
  </li>
</ol>

<p>Future directions in visual language model research include:</p>

<ol>
  <li>
    <p><strong>Multimodal Reasoning</strong>: Enhancing the models’ ability to reason about complex visual scenes.</p>
  </li>
  <li>
    <p><strong>Temporal Understanding</strong>: Extending models to understand and generate content about dynamic visual scenarios.</p>
  </li>
  <li>
    <p><strong>Interactive Learning</strong>: Developing models that can learn from interaction and feedback.</p>
  </li>
</ol>

<h2 id="conclusion">Conclusion</h2>

<p>Visual language models represent a significant step toward creating AI systems that can perceive and communicate about the visual world in ways similar to humans. As research continues to advance, we can expect these models to become more capable, efficient, and responsible, opening up new possibilities for human-AI interaction and collaboration.</p>

<p>For a detailed mathematical understanding of these models, refer to the key equations presented in this post, which highlight the fundamental principles underlying modern visual language models.</p>]]></content><author><name>Lilian Weng</name></author><category term="blog" /><category term="AI" /><category term="vision" /><category term="deep-learning" /><summary type="html"><![CDATA[Processing images to generate text, such as image captioning and visual question-answering, has been studied for years. Traditionally such systems rely on an object detection network as a vision encoder to capture visual features and then produce text via a text decoder. Given a large amount of existing literature, in this post I would like to only focus on some remarkable foundation vision-language models in recent years.]]></summary></entry></feed>