← back to Linkedin Like Helper
initial scaffold: LinkedIn manual like helper (batch-open, group size 1-all, progress in localStorage)
b5303bb9a1c30c0806eb4648b7b7a3f4b1f9be9c · 2026-06-30 11:24:21 -0700 · Steve
Files touched
Diff
commit b5303bb9a1c30c0806eb4648b7b7a3f4b1f9be9c
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jun 30 11:24:21 2026 -0700
initial scaffold: LinkedIn manual like helper (batch-open, group size 1-all, progress in localStorage)
---
.gitignore | 8 +++
index.html | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 212 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..9f8b355
--- /dev/null
+++ b/index.html
@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>LinkedIn Like Helper — manual, TOS-safe</title>
+<style>
+ :root{
+ --bg:#f3f2ef; --card:#fff; --ink:#1d2226; --muted:#56687a;
+ --li:#0a66c2; --li-dark:#004182; --line:#d7dce1; --done:#057642;
+ --shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.04);
+ }
+ *{box-sizing:border-box}
+ body{margin:0;font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+ background:var(--bg);color:var(--ink);padding:24px}
+ .wrap{max-width:820px;margin:0 auto}
+ header{display:flex;align-items:center;gap:12px;margin-bottom:8px}
+ header .badge{width:34px;height:34px;border-radius:7px;background:var(--li);color:#fff;
+ display:grid;place-items:center;font-weight:800;font-size:18px}
+ h1{font-size:21px;margin:0;font-weight:700}
+ .sub{color:var(--muted);margin:2px 0 20px;font-size:13.5px}
+ .card{background:var(--card);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);
+ padding:18px 20px;margin-bottom:16px}
+ label{font-weight:600;font-size:13px;display:block;margin-bottom:6px}
+ textarea{width:100%;min-height:140px;border:1px solid var(--line);border-radius:8px;padding:11px 12px;
+ font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;resize:vertical;color:var(--ink)}
+ .row{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end}
+ .grp{flex:1 1 200px}
+ select,input[type=number]{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:8px;
+ font-size:14px;background:#fff;color:var(--ink)}
+ .btns{display:flex;gap:10px;flex-wrap:wrap;margin-top:4px}
+ button{border:0;border-radius:999px;padding:10px 18px;font-size:14px;font-weight:600;cursor:pointer}
+ .primary{background:var(--li);color:#fff}
+ .primary:hover{background:var(--li-dark)}
+ .ghost{background:#fff;color:var(--li);border:1.5px solid var(--li)}
+ .ghost:hover{background:#eaf3fb}
+ .danger{background:#fff;color:#b11b1b;border:1.5px solid #e3c4c4}
+ .danger:hover{background:#fdeeee}
+ .stat{display:flex;gap:22px;margin:14px 0 4px;font-size:13.5px;color:var(--muted);flex-wrap:wrap}
+ .stat b{color:var(--ink)}
+ .bar{height:8px;background:#e6eaee;border-radius:999px;overflow:hidden;margin:10px 0 2px}
+ .bar>i{display:block;height:100%;background:var(--done);width:0;transition:width .3s}
+ ul.list{list-style:none;margin:8px 0 0;padding:0;max-height:340px;overflow:auto;border-top:1px solid var(--line)}
+ ul.list li{display:flex;align-items:center;gap:10px;padding:9px 4px;border-bottom:1px solid #eef1f4;font-size:13.5px}
+ ul.list li.done{opacity:.5}
+ ul.list li.done .url{text-decoration:line-through}
+ .num{width:26px;color:var(--muted);text-align:right;flex:0 0 auto;font-variant-numeric:tabular-nums}
+ .url{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+ .url a{color:var(--li);text-decoration:none}
+ .pill{flex:0 0 auto;font-size:11px;font-weight:700;color:var(--done);border:1px solid #bfe3cd;
+ background:#eafaf0;border-radius:999px;padding:2px 8px}
+ .mini{flex:0 0 auto;background:#fff;border:1px solid var(--line);border-radius:6px;padding:4px 9px;
+ font-size:12px;font-weight:600;cursor:pointer;color:var(--ink)}
+ .note{font-size:12.5px;color:var(--muted);background:#fffbe9;border:1px solid #f1e3a8;border-radius:8px;
+ padding:10px 12px;margin-top:14px}
+ .tos{font-size:12px;color:var(--muted);margin-top:18px;text-align:center}
+</style>
+</head>
+<body>
+<div class="wrap">
+ <header>
+ <div class="badge">in</div>
+ <div>
+ <h1>LinkedIn Like Helper</h1>
+ <div class="sub">Opens posts in batches so <b>you</b> like them by hand. No automation — fully within LinkedIn's terms.</div>
+ </div>
+ </header>
+
+ <div class="card">
+ <label for="urls">Post / profile links — one per line</label>
+ <textarea id="urls" placeholder="https://www.linkedin.com/feed/update/urn:li:activity:7300000000000000000/ https://www.linkedin.com/posts/somebody_xyz-activity-7300000000000000000-abcd/ …paste as many as you like"></textarea>
+ <div class="btns" style="margin-top:12px">
+ <button class="ghost" onclick="loadList()">Load list ↻</button>
+ <button class="danger" onclick="resetProgress()">Reset progress</button>
+ </div>
+ </div>
+
+ <div class="card">
+ <div class="row">
+ <div class="grp">
+ <label for="size">Group size</label>
+ <select id="size" onchange="syncCustom()">
+ <option value="1">1 at a time</option>
+ <option value="3">3</option>
+ <option value="5" selected>5</option>
+ <option value="10">10</option>
+ <option value="25">25</option>
+ <option value="all">All remaining</option>
+ <option value="custom">Custom…</option>
+ </select>
+ </div>
+ <div class="grp" id="customWrap" style="display:none">
+ <label for="custom">Custom count</label>
+ <input type="number" id="custom" min="1" value="8">
+ </div>
+ <div class="grp" style="flex:0 0 auto">
+ <button class="primary" onclick="openNext()">▶ Open next group</button>
+ </div>
+ </div>
+
+ <div class="stat">
+ <span>Total: <b id="cTotal">0</b></span>
+ <span>Done: <b id="cDone">0</b></span>
+ <span>Remaining: <b id="cLeft">0</b></span>
+ </div>
+ <div class="bar"><i id="barFill"></i></div>
+
+ <ul class="list" id="list"></ul>
+
+ <div class="note">
+ <b>First time:</b> Chrome will ask to <i>“allow multiple pop-ups”</i> — click Allow once.
+ Each opened tab is a real LinkedIn page; click the 👍 Like yourself, then close the tab.
+ A batch auto-marks as done when you open it (or use “done” / “Reset progress” to adjust).
+ </div>
+ </div>
+
+ <div class="tos">Manual assist only · your clicks, your account · honors the dw-linkedin “never auto-act” rule</div>
+</div>
+
+<script>
+const KEY = 'lk_like_helper_v1';
+let items = []; // {url, done}
+
+function load(){ try{ return JSON.parse(localStorage.getItem(KEY)) || {} }catch(e){ return {} } }
+function save(state){ localStorage.setItem(KEY, JSON.stringify(state)); }
+
+function loadList(){
+ const lines = document.getElementById('urls').value
+ .split(/\n+/).map(s=>s.trim()).filter(Boolean);
+ // de-dupe, preserve order
+ const seen = new Set(); const urls = [];
+ for(const u of lines){ if(!seen.has(u)){ seen.add(u); urls.push(u); } }
+ const prev = load(); const doneMap = prev.done || {};
+ items = urls.map(u => ({url:u, done: !!doneMap[u]}));
+ persist(); render();
+}
+
+function persist(){
+ const doneMap = {}; items.forEach(i=>{ if(i.done) doneMap[i.url]=1; });
+ save({ raw: document.getElementById('urls').value, done: doneMap });
+}
+
+function syncCustom(){
+ document.getElementById('customWrap').style.display =
+ document.getElementById('size').value==='custom' ? '' : 'none';
+}
+
+function groupSize(){
+ const v = document.getElementById('size').value;
+ if(v==='all') return Infinity;
+ if(v==='custom') return Math.max(1, parseInt(document.getElementById('custom').value)||1);
+ return parseInt(v);
+}
+
+function openNext(){
+ if(!items.length){ loadList(); }
+ const pending = items.filter(i=>!i.done);
+ if(!pending.length){ alert('All done 🎉 Use “Reset progress” to go again.'); return; }
+ const n = Math.min(groupSize(), pending.length);
+ const batch = pending.slice(0, n);
+ batch.forEach((it, idx) => {
+ // tiny stagger so the browser doesn't drop simultaneous opens
+ setTimeout(()=>window.open(it.url, '_blank', 'noopener'), idx*120);
+ it.done = true;
+ });
+ persist(); render();
+}
+
+function toggleDone(url){
+ const it = items.find(i=>i.url===url); if(it){ it.done=!it.done; persist(); render(); }
+}
+
+function resetProgress(){
+ items.forEach(i=>i.done=false); persist(); render();
+}
+
+function render(){
+ const total = items.length, done = items.filter(i=>i.done).length;
+ document.getElementById('cTotal').textContent = total;
+ document.getElementById('cDone').textContent = done;
+ document.getElementById('cLeft').textContent = total-done;
+ document.getElementById('barFill').style.width = total? (done/total*100)+'%' : '0';
+ const ul = document.getElementById('list'); ul.innerHTML='';
+ items.forEach((it,i)=>{
+ const li=document.createElement('li'); if(it.done) li.className='done';
+ li.innerHTML =
+ '<span class="num">'+(i+1)+'</span>'+
+ '<span class="url"><a href="'+it.url+'" target="_blank" rel="noopener">'+it.url+'</a></span>'+
+ (it.done?'<span class="pill">done</span>':'')+
+ '<button class="mini" data-u="'+encodeURIComponent(it.url)+'">'+(it.done?'undo':'done')+'</button>';
+ ul.appendChild(li);
+ });
+ ul.querySelectorAll('.mini').forEach(b=>b.onclick=()=>toggleDone(decodeURIComponent(b.dataset.u)));
+}
+
+// restore on open
+(function init(){
+ const prev = load();
+ if(prev.raw){ document.getElementById('urls').value = prev.raw; loadList(); }
+ syncCustom();
+})();
+</script>
+</body>
+</html>
(oldest)
·
back to Linkedin Like Helper
·
add Feed-pages mode: batch-open curated LinkedIn streams (ho d974f37 →