216 lines
10 KiB
HTML
216 lines
10 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Dispatch Triage Board — Andres Cruz</title>
|
||
|
|
<meta name="description" content="Andres Cruz's live route-triager: log your Bronx stops, watch deadhead and stop-density move in real time. The numbers I ran for the 8% bet.">
|
||
|
|
<meta property="og:type" content="website">
|
||
|
|
<meta property="og:title" content="Dispatch Triage Board — Andres Cruz">
|
||
|
|
<meta property="og:description" content="Log a Bronx route, watch deadhead and stop-density move. The ledger behind my 8% bet.">
|
||
|
|
<meta property="og:image" content="https://images.pexels.com/photos/11319652/pexels-photo-11319652.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940">
|
||
|
|
<style>
|
||
|
|
/* Dispatch terminal — amber on near-black, hard borders, zero radius.
|
||
|
|
This is a courier's screen, not a brochure. */
|
||
|
|
:root{
|
||
|
|
--ink:#e8c37a; /* amber incandescent */
|
||
|
|
--dim:#8a6f3f;
|
||
|
|
--bg:#0d0b06;
|
||
|
|
--panel:#16120a;
|
||
|
|
--line:#3a2f1a;
|
||
|
|
--good:#7fd08a;
|
||
|
|
--bad:#e06b5a;
|
||
|
|
--accent:#c9a227;
|
||
|
|
}
|
||
|
|
*{box-sizing:border-box}
|
||
|
|
html,body{margin:0;padding:0;background:var(--bg);color:var(--ink);
|
||
|
|
font-family:"Courier New",ui-monospace,Menlo,monospace;
|
||
|
|
font-size:15px;line-height:1.5}
|
||
|
|
.wrap{max-width:980px;margin:0 auto;padding:2rem 1.5rem 4rem}
|
||
|
|
header{border-bottom:2px solid var(--line);padding-bottom:1rem;margin-bottom:1.5rem}
|
||
|
|
.kicker{color:var(--dim);text-transform:uppercase;letter-spacing:.18em;font-size:.72rem}
|
||
|
|
h1{font-size:1.7rem;margin:.3rem 0 0;letter-spacing:.02em}
|
||
|
|
h1 em{font-style:normal;color:var(--accent)}
|
||
|
|
.sub{color:var(--dim);max-width:60ch;margin:.6rem 0 0}
|
||
|
|
.blink{animation:blink 1.1s steps(1) infinite}
|
||
|
|
@keyframes blink{50%{opacity:0}}
|
||
|
|
.grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}
|
||
|
|
@media(max-width:720px){.grid{grid-template-columns:1fr}}
|
||
|
|
.panel{background:var(--panel);border:1px solid var(--line);padding:1.1rem}
|
||
|
|
.panel h2{margin:0 0 .8rem;font-size:.85rem;text-transform:uppercase;
|
||
|
|
letter-spacing:.14em;color:var(--accent);border-bottom:1px solid var(--line);
|
||
|
|
padding-bottom:.4rem}
|
||
|
|
label{display:block;font-size:.78rem;text-transform:uppercase;
|
||
|
|
letter-spacing:.1em;color:var(--dim);margin:.7rem 0 .3rem}
|
||
|
|
input[type=text],input[type=number]{
|
||
|
|
width:100%;background:var(--bg);color:var(--ink);
|
||
|
|
border:1px solid var(--line);padding:.45rem .5rem;font:inherit}
|
||
|
|
input:focus{outline:1px solid var(--accent)}
|
||
|
|
button{background:var(--accent);color:#1a1406;border:0;font:inherit;
|
||
|
|
font-weight:bold;padding:.5rem 1rem;cursor:pointer;margin-top:.9rem}
|
||
|
|
button:hover{background:#e0b23a}
|
||
|
|
button.ghost{background:transparent;border:1px solid var(--line);
|
||
|
|
color:var(--ink);font-weight:normal}
|
||
|
|
table{width:100%;border-collapse:collapse;font-size:.82rem}
|
||
|
|
th,td{text-align:left;padding:.35rem .4rem;border-bottom:1px solid var(--line)}
|
||
|
|
th{color:var(--dim);text-transform:uppercase;font-size:.7rem;letter-spacing:.08em}
|
||
|
|
td.num{text-align:right;font-variant-numeric:tabular-nums}
|
||
|
|
.meter{height:16px;background:var(--bg);border:1px solid var(--line);margin-top:.5rem}
|
||
|
|
.meter span{display:block;height:100%;background:var(--accent);width:0}
|
||
|
|
.readout{display:flex;gap:1.5rem;flex-wrap:wrap;margin-top:.8rem}
|
||
|
|
.readout div{flex:1;min-width:120px}
|
||
|
|
.readout .val{font-size:1.5rem;font-weight:bold}
|
||
|
|
.readout .lbl{font-size:.7rem;color:var(--dim);text-transform:uppercase;letter-spacing:.08em}
|
||
|
|
.good{color:var(--good)}.bad{color:var(--bad)}
|
||
|
|
.note{margin-top:1.2rem;font-size:.8rem;color:var(--dim);border-top:1px dashed var(--line);padding-top:.7rem}
|
||
|
|
.note a{color:var(--accent)}
|
||
|
|
.hero{width:100%;display:block;border:1px solid var(--line);margin:1rem 0}
|
||
|
|
figcaption{color:var(--dim);font-size:.72rem;margin:.4rem 0 0}
|
||
|
|
.routes{font-size:.8rem;color:var(--dim);margin-top:.8rem;line-height:1.7}
|
||
|
|
.routes b{color:var(--ink)}
|
||
|
|
footer{margin-top:3rem;border-top:2px solid var(--line);padding-top:1rem;
|
||
|
|
color:var(--dim);font-size:.78rem}
|
||
|
|
footer a{color:var(--ink)}
|
||
|
|
.small{font-size:.75rem;color:var(--dim)}
|
||
|
|
</style>
|
||
|
|
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="wrap">
|
||
|
|
<header>
|
||
|
|
<div class="kicker">ANDRES CRUZ // ROUTE TRIAGE // BRONX</div>
|
||
|
|
<h1>DISPATCH <em>TRIAGE</em> BOARD</h1>
|
||
|
|
<p class="sub">Every run is a ledger. Log your stops, punch in the dead miles
|
||
|
|
between them, and watch three numbers move: stop-density, deadhead, and what
|
||
|
|
the run actually pays after the empties. This is the board behind the 8% bet —
|
||
|
|
shave deadhead under that and the run starts earning.</p>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<figure>
|
||
|
|
<img class="hero" src="https://images.pexels.com/photos/11319652/pexels-photo-11319652.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
|
||
|
|
alt="Courier rider working the night route, Bronx dispatch shift">
|
||
|
|
<figcaption>Night turn, the kind that makes or breaks a week. Photo via Pexels (RF).</figcaption>
|
||
|
|
</figure>
|
||
|
|
|
||
|
|
<div class="grid">
|
||
|
|
<section class="panel">
|
||
|
|
<h2>> LOG A STOP</h2>
|
||
|
|
<label for="where">Where (hood / block)</label>
|
||
|
|
<input id="where" type="text" placeholder="e.g. Melrose / 3rd Ave">
|
||
|
|
<label for="miles">Dead miles to this stop</label>
|
||
|
|
<input id="miles" type="number" step="0.1" min="0" placeholder="0.4" value="0.4">
|
||
|
|
<label for="pay">Pay at the stop ($)</label>
|
||
|
|
<input id="pay" type="number" step="0.25" min="0" placeholder="6.50" value="6.50">
|
||
|
|
<div style="display:flex;gap:.6rem">
|
||
|
|
<button onclick="addStop()">+ LOG STOP</button>
|
||
|
|
<button class="ghost" onclick="resetLog()">CLEAR RUN</button>
|
||
|
|
</div>
|
||
|
|
<p class="small" style="margin-top:.6rem">The first stop's dead miles count as the
|
||
|
|
trip from your staging point. That's the honest number, not the dispatch one.</p>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="panel">
|
||
|
|
<h2>> RUN READOUT</h2>
|
||
|
|
<div class="readout">
|
||
|
|
<div><div class="val" id="rStops">0</div><div class="lbl">Stops</div></div>
|
||
|
|
<div><div class="val" id="rDensity">0.0</div><div class="lbl">Stops / mi</div></div>
|
||
|
|
<div><div class="val" id="rDead">0.0%</div><div class="lbl">Deadhead</div></div>
|
||
|
|
<div><div class="val" id="rGross">$0.00</div><div class="lbl">Gross / mi</div></div>
|
||
|
|
</div>
|
||
|
|
<div class="meter"><span id="deadBar"></span></div>
|
||
|
|
<p class="small" id="deadLabel" style="margin-top:.4rem">Deadhead target: under 8%.</p>
|
||
|
|
</section>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<section class="panel" style="margin-top:1.5rem">
|
||
|
|
<h2>> STOP LOG</h2>
|
||
|
|
<table>
|
||
|
|
<thead><tr><th>Stop</th><th>Where</th><th class="num">Dead mi</th>
|
||
|
|
<th class="num">Pay/$</th><th class="num">$/dead-mi</th></tr></thead>
|
||
|
|
<tbody id="logBody"><tr><td colspan="5" style="color:var(--dim)">No stops logged yet. Punch one in.</td></tr></tbody>
|
||
|
|
</table>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="panel" style="margin-top:1.5rem">
|
||
|
|
<h2>> HOW I READ IT</h2>
|
||
|
|
<p class="routes">The math is blunt. Stop-density is <b>stops ÷ total miles</b>. Below
|
||
|
|
1.2 stops to the mile, that part of the run is all windshield and no plate.
|
||
|
|
Deadhead is <b>dead miles ÷ total miles</b> — the empties you rode to get to the
|
||
|
|
paid part. My floor is 8%; over that, I'm donating time to the dispatch forecast.</p>
|
||
|
|
<p class="routes">Gross per mile — total pay ÷ total miles — is what I actually bank.
|
||
|
|
A short dense cluster at $6.50 a stop beats a spread-out $11 stop every time,
|
||
|
|
because the empty between them eats the difference. That's the whole logic of
|
||
|
|
triage: kill the long no-pay legs first, chase density second.</p>
|
||
|
|
<p class="routes">Where it breaks: a promo dump wrecks the forecast — five $4.25
|
||
|
|
stops stacked one hood apart reads like density but pays like charity. So I log
|
||
|
|
<b>$/dead-mile</b> per stop too, and flag anything under $8. If a stop pays less
|
||
|
|
than the gas and minutes it costs, it flips from a stop to a skip.</p>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<footer>
|
||
|
|
<p>Built on the road, logged turn by turn. Tools & source on
|
||
|
|
<a href="https://4ort.dev/andres-cruz">4ort.dev</a>. Deadhead bet open —
|
||
|
|
<a href="mailto:andres@4ort.net">call me on it</a>.</p>
|
||
|
|
<p class="small">All figures are my working route notes, not a dispatch system. Your mileage, literally, may vary.</p>
|
||
|
|
</footer>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
(function(){
|
||
|
|
var log=[];
|
||
|
|
function el(id){return document.getElementById(id);}
|
||
|
|
function money(n){return '$'+n.toFixed(2);}
|
||
|
|
function render(){
|
||
|
|
if(log.length===0){
|
||
|
|
el('rStops').textContent='0';
|
||
|
|
el('rDensity').textContent='0.0';
|
||
|
|
el('rDead').textContent='0.0%';
|
||
|
|
el('rGross').textContent='$0.00';
|
||
|
|
el('deadBar').style.width='0';
|
||
|
|
el('logBody').innerHTML='<tr><td colspan="5" style="color:var(--dim)">No stops logged yet. Punch one in.</td></tr>';
|
||
|
|
el('deadLabel').textContent='Deadhead target: under 8%.';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var totMi=0, totPay=0, totDead=0;
|
||
|
|
log.forEach(function(s){totMi+=s.miles; totPay+=s.pay; totDead+=s.miles;});
|
||
|
|
// note: for this model every mile before a stop counts toward deadhead on the empty leg
|
||
|
|
var density=totPay>0?totMi>0?log.length/totMi:0:0;
|
||
|
|
var deadPct=totMi>0?(totDead/totMi)*100:0;
|
||
|
|
var gross=totMi>0?totPay/totMi:0;
|
||
|
|
el('rStops').textContent=log.length;
|
||
|
|
el('rDensity').textContent=density.toFixed(1);
|
||
|
|
el('rDead').textContent=deadPct.toFixed(1)+'%';
|
||
|
|
el('rGross').textContent=money(gross);
|
||
|
|
el('deadBar').style.width=Math.min(100,(deadPct/20)*100)+'%';
|
||
|
|
var ok=deadPct<8;
|
||
|
|
el('rDead').className=ok?'val good':'val bad';
|
||
|
|
el('deadBar').style.background=ok?'var(--good)':'var(--bad)';
|
||
|
|
el('deadLabel').textContent=ok
|
||
|
|
?'Under the 8% line. This run earns. (Deadhead '+deadPct.toFixed(1)+'%)'
|
||
|
|
:'Over the 8% line. The empties are eating the plate. (Deadhead '+deadPct.toFixed(1)+'%)';
|
||
|
|
var rows='';
|
||
|
|
for(var i=0;i<log.length;i++){
|
||
|
|
var s=log[i];
|
||
|
|
var spdmi=s.miles>0?s.pay/s.miles:999;
|
||
|
|
var flag=spdmi<8?' <span class="bad">[SKIP]</span>':'';
|
||
|
|
rows+='<tr><td>'+(i+1)+'</td><td>'+s.where+'</td><td class="num">'
|
||
|
|
+s.miles.toFixed(1)+'</td><td class="num">'+money(s.pay)+'</td><td class="num">'
|
||
|
|
+money(spdmi)+flag+'</td></tr>';
|
||
|
|
}
|
||
|
|
el('logBody').innerHTML=rows;
|
||
|
|
}
|
||
|
|
window.addStop=function(){
|
||
|
|
var where=el('where').value.trim();
|
||
|
|
if(!where){where='Unmarked stop';}
|
||
|
|
var miles=parseFloat(el('miles').value); if(isNaN(miles)||miles<0)miles=0;
|
||
|
|
var pay=parseFloat(el('pay').value); if(isNaN(pay)||pay<0)pay=0;
|
||
|
|
log.push({where:where,miles:miles,pay:pay});
|
||
|
|
el('where').value='';
|
||
|
|
render();
|
||
|
|
};
|
||
|
|
window.resetLog=function(){log=[];render();};
|
||
|
|
render();
|
||
|
|
})();
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|