Centuries in the making: Singsons’ unrivaled power in Ilocos Sur
Editor’s note: This is part of a Rappler series on Filipino families that have held political power for generations. The series traces how they built their influence, where it is strongest, and how it has endured over time. Each profile examines family ties, political networks, business interests, and roles in local and national government.
The Singson clan’s rise to power was built over centuries of carefully planned moves that entrenched the family in politics.
From the Spanish colonial period, where the family marks its origins, until today, the Singsons have controlled the province of Ilocos Sur.
They trace their beginnings to the marriage of a Chinese merchant, Joaquin Ayco, and a local from Vigan named Rosa Songnio. It was during the 19th century that the family began its ascent to power through wealth. They held gobernadorcillo positions for five terms and their influence continued over the American and Japanese occupations.
In 1945, the region turned to tobacco as a means of economic recovery, which boomed and led to the conflict between the Singsons and the Crisologo family, then closely intertwined — family patriarch, former Ilocos Sur governor Luis “Chavit” Singson is the nephew of then-congressman Floro Crisologo. Both families became fierce competitors over the tobacco trade.
Floro was assassinated inside a church on October 18, 1970, and the case remains unsolved. The political leadership of the Crisologos ended a year later when the Singson brothers were elected — Chavit became governor and his elder brother Evaristo “Titong” II became Vigan mayor. The family won through Chavit’s promise of streamlining tobacco production.
The clan’s power in the province remains unmatched despite alleged involvement in plunder and graft charges. Only a few have tried to defeat the Singsons and they ultimately failed.
In May 2026, Chavit claimed that 5,500 flood control projects in the country were non-existent. This challenged President Ferdinand Marcos Jr.’s announcement at his 2024 State of the Nation Address that 5,500 projects were completed nationwide.
He was charged with a private cyber libel complaint in June but was granted bail. Afterwards, Chavit proclaimed during a press conference that he would not be stopped and doubled down on his exposé of the anomalous flood control corruption scandal.

No arrest, case, or political pressure will stop him from revealing the truth, he asserted.
This is not uncharted territory for him. With his family’s decades-long grip on power, he knows exactly how to play the long game.
An obese political dynasty
The Singsons are one of the biggest and oldest political families. They hold numerous seats in the Ilocos Region.
Research by the Philippine Center for Investigative Journalism revealed that in the 2025 elections, Chavit had 23 relatives running for office.
Rappler’s research shows that 16 current officials are members of the Singson clan:
- Ronald Singson – 1st District Representative (Ilocos Sur)
- Richelle Singson Michael – Partylist Representative
- Jeremias “Jerry” Crisologo Singson – Governor (Ilocos Sur)
- Ryan Luis Singson – Vice Governor (Ilocos Sur)
- Ericson Singson – 2nd District Board Member (Ilocos Sur)
- Janina Medina-Fariñas – 1st District Board Member (Ilocos Sur)
- Fayinna Zaragoza – 2nd District Board Member (Ilocos Sur)
- Eric Singson – Mayor (City of Candon)
- Randolf “Randy” Singson – Mayor (City of Vigan)
- Goulart Germelina Singson – Mayor (Caoayan)
- Kristelle Gocho Singson – Vice Mayor (City of Candon)
- Juan Paolo Singson Ancheta – Vice Mayor (Caoayan)
- Evaristo “Bobit” Singson III – Councilor (City of Vigan)
- Jaime “Jimboy” Singson – Councilor (City of Candon) & Philippine Councilors League President – Ilocos Sur Chapter
- Luis Charles Singson – Councilor (Narvacan)
- Eric “Owen” Singson Jr. – Councilor (City of Candon)
The Singson Dynasty
A timeline mapping the notable rotation and overlap of local government positions held by the Singson family across three decades.
Scroll or swipe right to see the dynasty’s full timeline. Tap or hover a bar for details, including family ties and each member’s years in power.
From
to
Until
(function(){
'use strict';
// Bind to the last un-initialised root, so several copies of this block can
// coexist on one page.
var roots = document.querySelectorAll('.pd-dynasty:not([data-pd-init])');
var root = roots[roots.length - 1];
if (!root) { return; }
root.setAttribute('data-pd-init', '1');
var timelineEl = root.querySelector('.pd-timeline');
var namesEl = root.querySelector('.pd-names');
var chartEl = root.querySelector('.pd-chart');
var yearsEl = root.querySelector('.pd-years');
var gridEl = root.querySelector('.pd-grid');
var barsEl = root.querySelector('.pd-bars');
var tipEl = root.querySelector('.pd-tooltip');
if (!timelineEl || !chartEl || !barsEl) { return; }
var tip = {
name: tipEl.querySelector('.pd-tt__name'),
relation: tipEl.querySelector('.pd-tt__relation'),
swatch: tipEl.querySelector('.pd-tt__swatch'),
pos: tipEl.querySelector('.pd-tt__pos'),
start: tipEl.querySelector('.pd-tt__start'),
end: tipEl.querySelector('.pd-tt__end'),
notes: tipEl.querySelector('.pd-tt__notes')
};
// The block ships no "loading" state: the data is inline and rendering is
// synchronous, so there is nothing to wait for. Only failure needs a notice.
function fail(msg){
timelineEl.setAttribute('data-state', 'error');
var box = document.createElement('div');
box.className = 'pd-status';
var head = document.createElement('div');
head.className = 'pd-status__err';
head.textContent = 'Timeline unavailable';
var detail = document.createElement('div');
detail.textContent = String(msg);
box.appendChild(head);
box.appendChild(detail);
timelineEl.appendChild(box);
}
// ---------------------------------------------------------------- data ---
// Base URL for the photos below. Leave '' when the files sit next to this
// page; set it to the uploads/tachyon folder when embedding. Absolute URLs win.
var IMG_BASE = '';
var PHOTOS = {
'Lilia "Nanay" Pineda': 'Lilia-Pineda.jpg',
'Dennis "Delta" Pineda': 'Dennis-Pineda.jpg',
'Mylyn Pineda Cayabyab': 'Mylyn-Pineda-Cayabyab.jpg',
'Esmeralda "Esmie" Pineda': 'Esmeralda-Pineda.jpg',
'Yolanda Miranda Pineda': 'Yolanda-Miranda-Pineda.jpg'
};
// Rows use CSV-style keys so a spreadsheet export can be pasted in as-is.
var ROWS = [
// Luis "Chavit" Singson
{ Name:'Luis "Chavit" Singson', Position:'Governor', 'Start Year':'1972', 'End Year':'1986', Relation:'Family Matriarch' },
{ Name:'Luis "Chavit" Singson', Position:'Governor', 'Start Year':'2004', 'End Year':'2007', Relation:'Family Matriarch' },
{ Name:'Luis "Chavit" Singson', Position:'Governor', 'Start Year':'1992', 'End Year':'2001', Relation:'Family Matriarch' },
{ Name:'Luis "Chavit" Singson', Position:'Governor', 'Start Year':'2010', 'End Year':'2013', Relation:'Family Matriarch' },
{ Name:'Luis "Chavit" Singson', Position:'District Representative', 'Start Year':'1987', 'End Year':'1992', Relation:'Family Matriarch' },
{ Name:'Luis "Chavit" Singson', Position:'Councilor', 'Start Year':'2016', 'End Year':'2019', Relation:'Family Matriarch' },
{ Name:'Luis "Chavit" Singson', Position:'Mayor', 'Start Year':'2019', 'End Year':'2022', Relation:'Family Matriarch' },
// Randolf "Randy" Singson
{ Name:'Randolf "Randy" Singson', Position:'Mayor (Vigan City)', 'Start Year':'2025', 'End Year':'2028', Relation:'Son of Chavit' },
{ Name:'Randolf "Randy" Singson', Position:'Vice Mayor (Vigan City)', 'Start Year':'2022', 'End Year':'2025', Relation:'Son of Chavit' },
// Ronald Singson
{ Name:'Ronald Singson', Position:'Representative', 'Start Year':'2022', 'End Year':'2028', Relation:'Son of Chavit' },
{ Name:'Ronald Singson', Position:'Representative', 'Start Year':'2007', 'End Year':'2010', Relation:'Son of Chavit' },
{ Name:'Ronald Singson', Position:'Representative', 'Start Year':'2013', 'End Year':'2016', Relation:'Son of Chavit' },
// Ryan Luis Singson
{ Name:'Ryan Luis Singson', Position:'Governor', 'Start Year':'2013', 'End Year':'2022', Relation:'Son of Chavit' },
{ Name:'Ryan Luis Singson', Position:'Vice Governor', 'Start Year':'2022', 'End Year':'2028', Relation:'Son of Chavit' },
{ Name:'Ryan Luis Singson', Position:'Representative', 'Start Year':'2011', 'End Year':'2013', Relation:'Son of Chavit' },
// Richelle Singson Michael
{ Name:'Richelle Singson Michael', Position:'Partylist Representative', 'Start Year':'2022', 'End Year':'2028', Relation:'Daughter of Chavit' },
// Luis Charles Singson
{ Name:'Luis Charles Singson', Position:'Councilor', 'Start Year':'2025', 'End Year':'2028', Relation:'Son of Chavit' },
// Evaristo "Titong" Singson II
{ Name:'Evaristo "Titong" Singson II', Position:'Mayor', 'Start Year':'1972', 'End Year':'1986', Relation:'Brother of Chavit' },
{ Name:'Evaristo "Titong" Singson II', Position:'Governor', 'Start Year':'1987', 'End Year':'1992', Relation:'Brother of Chavit' },
// Jeremias "Jerry" Crisologo Singson
{ Name:'Jeremias "Jerry" Crisologo Singson', Position:'Partylist Representative', 'Start Year':'2025', 'End Year':'2028', Relation:'Brother of Chavit' },
{ Name:'Jeremias "Jerry" Crisologo Singson', Position:'Governor', 'Start Year':'2022', 'End Year':'2025', Relation:'Brother of Chavit' },
{ Name:'Jeremias "Jerry" Crisologo Singson', Position:'Board Member', 'Start Year':'2010', 'End Year':'2016', Relation:'Brother of Chavit' },
{ Name:'Jeremias "Jerry" Crisologo Singson', Position:'Vice Governor', 'Start Year':'2016', 'End Year':'2022', Relation:'Brother of Chavit' },
// Goulart Germelina Singson
{ Name:'Goulart Germelina Singson', Position:'Mayor (Caoayan)', 'Start Year':'2010', 'End Year':'2016', Relation:'Sister of Chavit' },
{ Name:'Goulart Germelina Singson', Position:'Mayor (Caoayan)', 'Start Year':'2019', 'End Year':'2028', Relation:'Sister of Chavit' },
// Jose “Bonito” Singson Jr.
{ Name:'Jose “Bonito” Singson Jr.', Position:'Mayor (Vigan City)', 'Start Year':'2022', 'End Year':'2025', Relation:'Brother of Chavit' },
{ Name:'Jose “Bonito” Singson Jr.', Position:'Representative', 'Start Year':'2019', 'End Year':'2022', Relation:'Brother of Chavit' },
// Eva Marie Singson-Medina
{ Name:'Eva Marie Singson-Medina', Position:'Mayor', 'Start Year':'1995', 'End Year':'2004', Relation:'Niece of Chavit' },
{ Name:'Eva Marie Singson-Medina', Position:'Mayor', 'Start Year':'2007', 'End Year':'2016', Relation:'Niece of Chavit' },
// Evaristo "Bobit" Singson III.
{ Name:'Evaristo "Bobit" Singson III', Position:'Councilor', 'Start Year':'2025', 'End Year':'2028', Relation:'Son of Jerry' },
// Juan Paolo Singson Ancheta
{ Name:'Juan Paolo Singson Ancheta', Position:'Mayor (Caoayan)', 'Start Year':'2016', 'End Year':'2019', Relation:'Nephew of Chavit' },
{ Name:'Juan Paolo Singson Ancheta', Position:'Vice Mayor (Caoayan)', 'Start Year':'2022', 'End Year':'2028', Relation:'Nephew of Chavit' },
// Eric Singson
{ Name:'Eric Singson', Position:'Representative', 'Start Year':'2001', 'End Year':'2010', Relation:'Cousin of Chavit' },
{ Name:'Eric Singson', Position:'Mayor (Candon City)', 'Start Year':'2022', 'End Year':'2028', Relation:'Cousin of Chavit' },
{ Name:'Eric Singson', Position:'Representative', 'Start Year':'2013', 'End Year':'2019', Relation:'Cousin of Chavit' },
{ Name:'Eric Singson', Position:'Representative', 'Start Year':'1987', 'End Year':'1998', Relation:'Cousin of Chavit' },
// Janina Medina-Fariñas
{ Name:'Janina Medina-Fariñas', Position:'Board Member', 'Start Year':'2019', 'End Year':'2028', Relation:'Grandniece of Chavit' },
// Ericson Singson
{ Name:'Ericson Singson', Position:'Board Member', 'Start Year':'2022', 'End Year':'2028', Relation:'Son of Eric' },
{ Name:'Ericson Singson', Position:'Mayor (Candon City)', 'Start Year':'2013', 'End Year':'2022', Relation:'Son of Eric' },
// Eric "Owen" Singson Jr.
{ Name:'Eric "Owen" Singson Jr.', Position:'Representative', 'Start Year':'2010', 'End Year':'2013', Relation:'Son of Eric' },
{ Name:'Eric "Owen" Singson Jr.', Position:'Councilor', 'Start Year':'2025', 'End Year':'2028', Relation:'Son of Eric' },
// Jaime "Jimboy" Singson
{ Name:'Jaime "Jimboy" Singson', Position:'Councilor', 'Start Year':'2022', 'End Year':'2028', Relation:'Nephew of Eric' },
{ Name:'Jaime "Jimboy" Singson', Position:'Board Member', 'Start Year':'2013', 'End Year':'2022', Relation:'Nephew of Eric' },
// Fayinna Zaragoza
{ Name:'Fayinna Zaragoza', Position:'Board Member', 'Start Year':'2025', 'End Year':'2028', Relation:'Niece of Ericson' },
{ Name:'Fayinna Zaragoza', Position:'Vice Mayor', 'Start Year':'2013', 'End Year':'2019', Relation:'Niece of Ericson' },
// Kristelle Gocho Singson
{ Name:'Kristelle Gocho Singson', Position:'Vice Mayor (Candon City)', 'Start Year':'2025', 'End Year':'2028', Relation:'Niece of Ericson' },
{ Name:'Kristelle Gocho Singson', Position:'Councilor', 'Start Year':'2016', 'End Year':'2019', Relation:'Niece of Ericson' },
{ Name:'Kristelle Gocho Singson', Position:'Vice Mayor', 'Start Year':'2019', 'End Year':'2025', Relation:'Niece of Ericson' },
];
// --------------------------------------------------------------- config ---
var ROW_H = 70; // row height, keep in sync with --pd-row
var BAR_H = 32;
var config = {
minYear: 1972,
maxYear: 2028,
pixelsPerYear: 35,
yearStep: 2,
gapStart: 2100, // Pushed out of bounds; we have no empty stretch
gapEnd: 2110, // Pushed out of bounds; we have no empty stretch
gapPixelWidth: 0, // No time jump compensation needed
xOffset: 26 // recomputed per layout in render()
};
// Layout tiers, measured on the block itself rather than the viewport.
var TIER_NARROW = 560; // below this the names ride inside the scroller
var TIER_WIDE = 900; // at or above this the full desktop treatment
function blockWidth(){
var w = root.clientWidth;
if (!w) { w = window.innerWidth; }
return w;
}
// The article template caps content at roughly 650px, which leaves the
// chart little room. Set this to true to let the block span the window
// instead. Try it on staging first: a theme wrapper with overflow:hidden
// would clip the overhang. The CMS-native alternative is to wrap the
// Custom HTML block in a Group block set to Full width.
var FULL_BLEED = false;
function applyBleed(){
root.style.marginLeft = '';
root.style.marginRight = '';
root.style.width = '';
root.style.maxWidth = '';
if (!FULL_BLEED) { return; }
var docW = document.documentElement.clientWidth;
if (!docW) { return; }
if (docW < TIER_WIDE) { return; } // phones keep the normal flow var pad = 24; var rect = root.getBoundingClientRect(); // measured with margins cleared root.style.maxWidth = 'none'; root.style.width = (docW - pad * 2) + 'px'; root.style.marginLeft = (pad - rect.left) + 'px'; root.style.marginRight = '0px'; } function applyScale(){ applyBleed(); var w = blockWidth(); root.classList.remove('is-narrow'); root.classList.remove('is-mid'); root.classList.remove('is-wide'); config.rowHeight = ROW_H; if (w < TIER_NARROW) { root.classList.add('is-narrow'); config.pixelsPerYear = 22; config.yearStep = 5; } else if (w >= TIER_WIDE) {
root.classList.add('is-wide');
config.pixelsPerYear = 35;
config.yearStep = 2;
} else {
root.classList.add('is-mid');
config.pixelsPerYear = 26;
config.yearStep = 5;
}
}
// Year -> x, with the time jump bypassed since variables are out of bounds.
// The offset clears the first tick label of the left edge, and on narrow
// screens also clears the name column, which then sits inside the scroller.
var X0 = 26;
function getYearX(year){
if (year <= config.gapStart) { return config.xOffset + (year - config.minYear) * config.pixelsPerYear; } var beforeGap = (config.gapStart - config.minYear) * config.pixelsPerYear; if (year >= config.gapEnd) {
return config.xOffset + beforeGap + config.gapPixelWidth + (year - config.gapEnd) * config.pixelsPerYear;
}
var progress = (year - config.gapStart) / (config.gapEnd - config.gapStart);
return config.xOffset + beforeGap + progress * config.gapPixelWidth;
}
// --------------------------------------------------------------- colors ---
var PALETTE = [
'#606c38','#283618','#dda15e','#bc6c25','#d62828',
'#003049','#f77f00','#fcbf49','#eae2b7','#005f73'
];
// Explicit color map for roles mirroring the Pineda script styling
var ROLE_COLORS = {
'Governor': '#E05223',
'Vice Governor': '#F4A261',
'Mayor (Lubao)': '#2A9D8F',
'Board Member': '#E9C46A',
'Councilor': '#8AB17D',
'Mayor (Sta. Rita)': '#1D5464'
};
var styles = {}, colorIndex = 0;
function isBright(hex){
var h = hex.replace('#','');
if (h.length === 3) { h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2); }
var r = parseInt(h.substr(0,2),16), g = parseInt(h.substr(2,2),16), b = parseInt(h.substr(4,2),16);
return ((r*299)+(g*587)+(b*114))/1000 >= 128;
}
function styleFor(pos){
if (!styles[pos]) {
var hex = ROLE_COLORS[pos];
if (!hex) {
hex = PALETTE[colorIndex % PALETTE.length];
colorIndex++;
}
styles[pos] = { hex: hex, darkText: isBright(hex) };
}
return styles[pos];
}
// ----------------------------------------------------------- normalising ---
var records = []; // { name, pos, start, end, endLabel, isPresent, notes }
var people = []; // { name, photo }
var relations = {};
function pick(row, keys){
var rowKeys = Object.keys(row);
for (var i = 0; i < keys.length; i++) { for (var j = 0; j < rowKeys.length; j++) { if (rowKeys[j].toLowerCase().trim() === keys[i]) { var val = row[rowKeys[j]]; if (val) { return String(val).trim(); } } } } return null; } function photoUrl(name){ var file = PHOTOS[name]; if (!file) { return ''; } return /^(https?:)?\/\//.test(file) ? file : IMG_BASE + file; } function build(rows){ var thisYear = new Date().getFullYear(); var index = {}, maxYear = 0; rows.forEach(function(row){ var name = pick(row, ['name','person','official','politician']); var pos = pick(row, ['position','role','office','title']); var startRaw = pick(row, ['start','start year','from','year start']); var endRaw = pick(row, ['end','end year','to','until','year end']); var relation = pick(row, ['relation','relationship','description']); var notes = pick(row, ['notes','note']); if (!name || !pos || !startRaw) { return; } var start = parseInt(startRaw, 10); if (isNaN(start)) { return; } var isPresent = !endRaw; if (!isPresent) { if (/^(present|now)$/i.test(endRaw)) { isPresent = true; } } var end = isPresent ? thisYear + 2 : parseInt(endRaw, 10); if (isNaN(end)) { end = start; } if (end < start) { end = start; } if (end > maxYear) { maxYear = end; }
records.push({
name: name, pos: pos, start: start, end: end,
endLabel: isPresent ? 'Present' : String(end),
isPresent: isPresent, notes: notes
});
if (index[name] === undefined) {
index[name] = people.length;
people.push({ name: name, photo: photoUrl(name) });
}
if (relation) {
if (!relations[name]) { relations[name] = relation; }
}
styleFor(pos);
});
if (!records.length) { throw new Error('No timeline rows to display.'); }
// Using a predefined config.maxYear since the data is contained in one era.
}
// ---------------------------------------------------------------- render ---
function render(){
applyScale();
// Narrow: the column rides inside the scroller, so names and bars scroll
// right together. Wider: it goes back outside, staying put while the
// chart scrolls under it.
var inScroller = root.classList.contains('is-narrow');
if (inScroller) {
if (namesEl.parentNode !== chartEl) { chartEl.appendChild(namesEl); }
namesEl.style.left = '0px';
namesEl.style.top = barsEl.offsetTop + 'px';
config.xOffset = namesEl.offsetWidth + 14;
} else {
if (namesEl.parentNode !== timelineEl) { timelineEl.insertBefore(namesEl, chartEl); }
namesEl.style.left = '';
namesEl.style.top = '';
config.xOffset = X0;
}
var totalWidth = getYearX(config.maxYear) + 50;
var chartHeight = people.length * config.rowHeight;
namesEl.innerHTML = '';
yearsEl.innerHTML = '';
gridEl.innerHTML = '';
barsEl.innerHTML = '';
root.style.setProperty('--pd-row', config.rowHeight + 'px');
yearsEl.style.width = totalWidth + 'px';
gridEl.style.width = totalWidth + 'px';
barsEl.style.width = totalWidth + 'px';
barsEl.style.height = chartHeight + 'px';
namesEl.style.height = chartHeight + 'px';
// Names column
people.forEach(function(person, index){
var row = document.createElement('div');
row.className = 'pd-name-row';
row.style.top = (index * config.rowHeight) + 'px';
var img = document.createElement('img');
img.className = 'pd-photo';
img.alt = person.name;
img.loading = 'lazy';
img.onerror = function(){ this.style.display = 'none'; };
if (person.photo) {
img.src = person.photo;
} else {
img.style.display = 'none';
}
var label = document.createElement('div');
label.className = 'pd-name';
label.textContent = person.name;
row.appendChild(img);
row.appendChild(label);
namesEl.appendChild(row);
});
// Year ticks + grid lines
for (var year = config.minYear; year <= config.maxYear; year += config.yearStep) { if (year > config.gapStart) {
if (year < config.gapEnd) { continue; } // inside the time jump
}
var x = getYearX(year);
var tick = document.createElement('div');
tick.className = 'pd-year';
tick.style.left = x + 'px';
tick.textContent = year;
yearsEl.appendChild(tick);
var line = document.createElement('div');
line.className = 'pd-line';
line.style.left = x + 'px';
gridEl.appendChild(line);
}
// Term bars
var delay = 0.1;
var lastDelay = delay;
people.forEach(function(person, rowIndex){
var y = rowIndex * config.rowHeight;
var personRecords = [];
for (var i=0; i
var label = document.createElement('span');
label.className = 'pd-bar__label' + (style.darkText ? ' is-dark' : '');
label.textContent = rec.pos;
bar.appendChild(label);
}
bar.addEventListener('mouseenter', function(e){ showTip(rec, style, e); });
bar.addEventListener('mousemove', placeTip);
bar.addEventListener('mouseleave', hideTip);
// Touch and keyboard: anchor the tooltip to the bar itself, since
// there is no pointer position to follow.
bar.addEventListener('click', function(e){
e.stopPropagation();
showTipAtBar(rec, style, bar);
});
bar.addEventListener('focus', function(){ showTipAtBar(rec, style, bar); });
bar.addEventListener('blur', hideTip);
barsEl.appendChild(bar);
lastDelay = delay;
delay = Math.min(delay + 0.06, 1.6); // cap the stagger
});
});
timelineEl.setAttribute('data-state', 'ready');
updateAffordance();
reveal();
}
// Sweep the bars in when the block scrolls into view. Purely cosmetic.
var revealed = false, lastDelay = 0;
function reveal(){
if (revealed) { return; }
function go(){
if (revealed) { return; }
revealed = true;
barsEl.classList.add('is-revealing');
setTimeout(function(){ barsEl.classList.remove('is-revealing'); },
lastDelay * 1000 + 1400);
}
if (typeof IntersectionObserver !== 'function') { go(); return; }
var io = new IntersectionObserver(function(entries){
for (var i = 0; i < entries.length; i++) { if (entries[i].isIntersecting) { io.disconnect(); go(); return; } } }, { threshold: 0.15 }); io.observe(timelineEl); // Fallback for anything that keeps the observer from firing. setTimeout(function(){ io.disconnect(); go(); }, 1200); } // --------------------------------------------------------------- tooltip --- var activeBar = null; function fillTip(rec, style){ tip.name.textContent = rec.name; if (relations[rec.name]) { tip.relation.textContent = relations[rec.name]; tip.relation.hidden = false; } else { tip.relation.hidden = true; } tip.pos.textContent = rec.pos; tip.swatch.style.backgroundColor = style.hex; tip.start.textContent = rec.start; tip.end.textContent = rec.endLabel; if (rec.isPresent) { tip.end.classList.add('is-present'); } else { tip.end.classList.remove('is-present'); } if (rec.notes) { tip.notes.textContent = rec.notes; tip.notes.hidden = false; } else { tip.notes.hidden = true; } tipEl.classList.add('is-visible'); } function showTip(rec, style, e){ chartEl.classList.add('has-hover'); fillTip(rec, style); placeTip(e); } // Tap or keyboard focus: pin the tooltip over the middle of the bar. function showTipAtBar(rec, style, barEl){ clearActive(); activeBar = barEl; barEl.classList.add('is-active'); chartEl.classList.add('has-active'); fillTip(rec, style); var box = root.getBoundingClientRect(); var b = barEl.getBoundingClientRect(); positionTip(b.left - box.left + b.width / 2, b.top - box.top - 4, box); } function clearActive(){ if (activeBar) { activeBar.classList.remove('is-active'); activeBar = null; } chartEl.classList.remove('has-active'); } function hideTip(){ chartEl.classList.remove('has-hover'); clearActive(); tipEl.classList.remove('is-visible'); } // Coordinates are converted into the root's own box, so the tooltip lands // correctly no matter how the host page is scrolled or laid out. function placeTip(e){ var box = root.getBoundingClientRect(); positionTip(e.clientX - box.left, e.clientY - box.top - 12, box); } // Clamped inside the block, and flipped below the anchor when there is no // room above it. function positionTip(x, y, box){ var w = tipEl.offsetWidth, h = tipEl.offsetHeight; var min = w / 2 + 8, max = box.width - w / 2 - 8; if (max < min) { x = box.width / 2; } else { x = Math.min(Math.max(x, min), max); } if (y - h < 0) { y = y + h + 34; } tipEl.style.left = x + 'px'; tipEl.style.top = y + 'px'; } // A tap anywhere else closes a pinned tooltip. document.addEventListener('click', function(e){ var node = e.target; if (node) { if (node.closest) { if (node.closest('.pd-bar')) { return; } } } hideTip(); }); // Show the right-edge fade only while there is more timeline off-screen. function updateAffordance(){ var hidden = chartEl.scrollWidth - chartEl.clientWidth - chartEl.scrollLeft; if (hidden > 8) {
timelineEl.classList.add('has-more');
} else {
timelineEl.classList.remove('has-more');
}
}
chartEl.addEventListener('scroll', function(){
hideTip();
updateAffordance();
});
// ------------------------------------------------------- drag to scroll ---
var dragging = false, dragX = 0, dragScroll = 0;
chartEl.addEventListener('mousedown', function(e){
dragging = true;
dragX = e.pageX;
dragScroll = chartEl.scrollLeft;
chartEl.classList.add('is-dragging');
});
function endDrag(){ dragging = false; chartEl.classList.remove('is-dragging'); }
chartEl.addEventListener('mouseleave', function(){ endDrag(); hideTip(); });
chartEl.addEventListener('mouseup', endDrag);
window.addEventListener('mouseup', endDrag);
chartEl.addEventListener('mousemove', function(e){
if (!dragging) { return; }
e.preventDefault();
chartEl.scrollLeft = dragScroll - (e.pageX - dragX) * 2;
});
// ---------------------------------------------------------------- resize ---
var resizeTimer;
window.addEventListener('resize', function(){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function(){
if (!records.length) { return; }
var before = config.pixelsPerYear;
applyScale();
if (before !== config.pixelsPerYear) {
render();
} else {
updateAffordance();
}
}, 200);
});
// ------------------------------------------------------------------ boot ---
// Runs immediately — no `load`/`DOMContentLoaded` dependency, so a block
// inserted into an already-loaded page still renders.
try {
build(ROWS);
render();
} catch (err) {
fail(err ? (err.message || err) : 'unknown error');
}
})();
Attempts to challenge the family have failed. In 2019, Chavit beat long-time rival Edgardo Zaragoza for mayor. In 2023, Chavit then filed graft charges against Zaragoza and his relatives over the alleged misuse of P81 million in tobacco excise tax funds.
When Chavit backed out of his reelection bid for governor in 2013, his son, Ryan Luis, replaced him. He was challenged by former ally and aide of Chavit, Roque Verzosa Jr. Verzosa filed a case against Ryan Luis for vote-buying after he supposedly gave out at least P20,000 during a fiesta event. Yet the elections ended with the same result: the Singsons still dominated the region.

In 2011, Ronald, Chavit’s other son, resigned as congressman after pleading guilty to drug trafficking in Hong Kong and was detained by authorities for 11 months. Ryan Luis, who was vice mayor of Vigan at the time, defeated Liberal Party candidate lawyer Beltrand Baterina in a special congressional election.
Then-councilor Lourdes Baquiran replaced Ryan Luis for the vice mayoral seat after his win.
The family also operates as a politicontractor. Their construction firm, Satrap Construction, secured P2.1 billion in infrastructure projects in Ilocos Sur from 2016 to 2025. They have ties, too, to DITO Telecommunity Corporation and entered the transportation sector to produce electric vehicles.
Playing ‘chess’
The family’s power did not just rely on politics. It required money and carefully thought-out moves that favored them. When the Singsons took control of the region in the 1970s, the family divided power.
Titong II replaced his brother Chavit as governor. Another brother of theirs, Jerry, became a councilor and eventually a provincial board member.


Chavit’s niece, Eva Marie Singson-Medina, became a provincial board member in 1992 and Vigan mayor from 1995 to 2004. The patriarch’s son, Randolf, became councilor for multiple terms. The influence extended to their cousins, who won seats in other towns.
Chavit also fulfilled his promise of passing Republic Act No. 7171, which mandates the allocation of excise tax on locally manufactured cigarettes for the benefit of local government units (LGUs) and tobacco farmers. This became a regular revenue for the region.
But his biggest political move happened in 2000.
Chavit’s friendship with then-president Erap Estrada fell apart after Estrada gave a gambling license to Chavit’s cousin and rival, Eric. In a press conference, Chavit exposed Estrada’s link to jueteng, accusing him of taking P130 million from the tobacco excise tax share of Ilocos Sur.
This sparked a chain of events that culminated in Estrada’s ouster during EDSA II.
In the 2010 elections, Chavit left the Lakas-Christian Muslim Democrats for the Nacionalista Party to endorse former senator Manny Villar.
The clan was also careful with its political alliances. Though Chavit joined the Uniteam tandem before, he joined the September 21, 2025 protest and called on students to skip classes and go against the government.
A group claiming to be former Marcos loyalists also pushed for a possible tandem between Vice President Sara Duterte and Chavit in the 2028 national elections.
On September 6, Chavit was also seen arriving at the Quezon City Regional Trial Court at the time Duterte posted bail for three counts of grave threats. However, he said he was only there to allegedly check on the progress of his case and not support the Vice President.
Switching sides and alliances through every election and political rift had just become second nature to the Singson family. And they have thrived. – Rappler.com