[object Object]

← back to Commercialrealestate

deals-flow ticker: halve default speed, constant read-pace, add stop/resume control

771efe624188f7fc5f5041c354cf99a7d1c711a9 · 2026-07-31 14:24:52 -0700 · Steve Abrams

Files touched

Diff

commit 771efe624188f7fc5f5041c354cf99a7d1c711a9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 31 14:24:52 2026 -0700

    deals-flow ticker: halve default speed, constant read-pace, add stop/resume control
---
 public/deals-flow.html | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/public/deals-flow.html b/public/deals-flow.html
index 8971197..aa9b8d4 100644
--- a/public/deals-flow.html
+++ b/public/deals-flow.html
@@ -47,8 +47,12 @@ select{background:var(--card);border:1px solid var(--line);color:var(--ink);bord
 .ticker .lbl::before{content:"";width:7px;height:7px;border-radius:50%;background:#0b0e13;animation:blip 1.1s steps(1) infinite}
 @keyframes blip{50%{opacity:.25}}
 .ticker .vp{flex:1;overflow:hidden;position:relative}
-.ticker .track{display:inline-flex;align-items:center;height:100%;white-space:nowrap;animation:tick 90s linear infinite;will-change:transform}
+.ticker .track{display:inline-flex;align-items:center;height:100%;white-space:nowrap;animation:tick 180s linear infinite;will-change:transform}
 .ticker .vp:hover .track{animation-play-state:paused}
+.ticker.is-stopped .track{animation-play-state:paused}
+.ticker .tk-ctrl{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:34px;background:#0b0e13;border:none;border-left:1px solid #20262f;color:var(--ink);font-size:13px;line-height:1;cursor:pointer;transition:background .15s,color .15s}
+.ticker .tk-ctrl:hover{background:#161b22;color:#fff}
+.ticker.is-stopped .tk-ctrl{color:var(--acc)}
 .ticker .it{display:inline-flex;align-items:center;gap:8px;padding:0 20px;font-size:12px;color:var(--ink);border-right:1px solid #20262f}
 .ticker .it .t{font-weight:800;font-size:9px;letter-spacing:.4px;padding:2px 6px;border-radius:5px}
 .ticker .it .t.listed{background:rgba(63,185,80,.18);color:var(--acc)}
@@ -88,7 +92,7 @@ select{background:var(--card);border:1px solid var(--line);color:var(--ink);bord
   <div class="nav"><a href="/just-listed.html">Just Listed</a><a href="/fha-leads.html">Assumable Leads</a><a href="/mls.html">Listings</a><a href="/">Explorer</a></div>
   <span id="auth" style="margin-left:auto;font-size:12px;color:var(--blue);cursor:pointer">Sign in</span>
 </header>
-<div class="ticker" id="ticker" style="display:none" aria-label="Breaking real estate headlines"><div class="lbl" aria-hidden="true">Breaking</div><div class="vp"><div class="track" id="tickTrack"></div></div></div>
+<div class="ticker" id="ticker" style="display:none" aria-label="Breaking real estate headlines"><div class="lbl" aria-hidden="true">Breaking</div><div class="vp"><div class="track" id="tickTrack"></div></div><button class="tk-ctrl" id="tkCtrl" type="button" aria-pressed="false" aria-label="Stop ticker" title="Stop ticker to read">⏸</button></div>
 <div class="shell">
 <aside class="rail">
   <div class="rsec" id="savedSec" style="display:none"><h4>My saved searches</h4><div class="chips" id="fSaved"></div></div>
@@ -264,8 +268,13 @@ $('#roleSkip').onclick=closeRole;
 $('#roleSkip').addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();closeRole();}});
 $('#roleModal').addEventListener('click',e=>{if(e.target.id==='roleModal')closeRole();});
 // ── breaking-news ticker ──
-async function loadTicker(){try{const r=await api('/api/ticker');const items=(r&&r.items)||[];if(!items.length)return;const html=items.map(i=>`<span class="it"><span class="t ${i.kind}">${esc(i.tag)}</span>${esc(i.text)}</span>`).join('');const dup=html.replace(/<span class="it">/g,'<span class="it" aria-hidden="true">');$('#tickTrack').innerHTML=html+dup;$('#ticker').style.display='';}catch(e){}}
+async function loadTicker(){try{const r=await api('/api/ticker');const items=(r&&r.items)||[];if(!items.length)return;const html=items.map(i=>`<span class="it"><span class="t ${i.kind}">${esc(i.tag)}</span>${esc(i.text)}</span>`).join('');const dup=html.replace(/<span class="it">/g,'<span class="it" aria-hidden="true">');const track=$('#tickTrack');track.innerHTML=html+dup;$('#ticker').style.display='';
+  // constant, comfortable read speed (~45px/s) regardless of how many deals loaded
+  requestAnimationFrame(()=>{const oneSet=track.scrollWidth/2;const dur=Math.max(60,Math.round(oneSet/45));track.style.animationDuration=dur+'s';});
+}
 loadTicker();
+// ── stop / resume (deselect) control ──
+(function(){const t=$('#ticker'),b=$('#tkCtrl');if(!b)return;b.addEventListener('click',()=>{const stopped=t.classList.toggle('is-stopped');b.textContent=stopped?'▶':'⏸';b.setAttribute('aria-pressed',stopped?'true':'false');b.setAttribute('aria-label',stopped?'Resume ticker':'Stop ticker');b.title=stopped?'Resume ticker':'Stop ticker to read';});})();
 async function loadSaved(){const r=await api('/api/saved-searches');const s=r.saved||[];window._saved=s;$('#fSaved').innerHTML=s.length?s.map(x=>`<span class="chip" data-sid="${x.id}" title="apply this search">${esc(x.name)}<span class="ct" data-del="${x.id}" title="delete">✕</span></span>`).join(''):'<span style="font-size:11px;color:var(--mut)">none yet — set filters, then ★ Save search</span>';}
 async function loadWatch(){const r=await api('/api/watchlist');WATCH=new Set(r.ids||[]);render();}
 $('#saveBtn').addEventListener('click',async()=>{if(!ME||!ME.email){signIn();return;}const name=prompt('Name this saved search:','My deal filter');if(name===null)return;const filters={src:[...F.src],use:[...F.use],year:[...F.year],conf:[...F.conf],pMin:F.pMin,uMin:F.uMin,q};await api('/api/saved-searches',{method:'POST',body:JSON.stringify({name,filters})});loadSaved();});

← daf3999 afternoon CRE update 2026-07-31  ·  back to Commercialrealestate  ·  deals-flow ticker: add reversible dismiss (deselect) control ba2514a →