Skip to content

Commit ed3dbe4

Browse files
Update index.html
1 parent 0304c15 commit ed3dbe4

1 file changed

Lines changed: 48 additions & 32 deletions

File tree

‎index.html‎

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7876,14 +7876,17 @@
78767876
progress: { apps: 0, time: 0, tickets: 0 },
78777877
envProgress: { appsProcessed: 0 },
78787878
calcInputs: {
7879-
users: 5000, endpoints: 6000, envs: 3, apps: 150, customApps: 30,
7879+
users: 5000, endpoints: 6000, envs: 3, apps: 150, customApps: 25,
78807880
monthlyTickets: 600, avgTicketMins: 18, pkgHours: 80, patchHours: 60,
78817881
openReqs: 0, delayedInitiatives: 0, regulated: false,
78827882
complexMixPct: 20, loadedRate: 95,
78837883
/* Currency mode (W2/W3). catalogCovered / configCovered stay null
78847884
until the AE enters a real Setup Store cross-reference. */
78857885
appsTotal: 150, appsHighChurn: 23, cycleDaysNow: 14, pctAging60Now: 35,
7886-
catalogCovered: null, configCovered: null
7886+
catalogCovered: null, configCovered: null,
7887+
/* Invariant rates. Hours and custom-app counts are always derived
7888+
from these × the current estate, never scaled incrementally. */
7889+
pkgHrsPerApp: 80 / 150, patchHrsPerApp: 60 / 150, customSharePct: 25 / 150
78877890
},
78887891
impactMode: 'effort',
78897892
exOpen: {},
@@ -13903,27 +13906,9 @@ <h2 class="text-xl font-bold tracking-tight text-[#1A1A2E]">Ask a Question</h2>
1390313906
</div>
1390413907
${idx < arr.length - 1 ? '<div class="lifecycle-connector ' + connClass + '"></div>' : ''}
1390513908
`;}).join('')}
13906-
<!-- Flow controls (icon-only) -->
13907-
${(STATE.solutionViewUser || STATE.solutionViewAdmin || STATE.solutionViewSetupStore || STATE.solutionViewMultiTenant || STATE.solutionViewDashboard || STATE.solutionViewAi || STATE.solutionViewOnboarding || STATE.solutionViewUpdate) ? (() => {
13908-
const _running =
13909-
(STATE.solutionViewSetupStore && STATE.ssRunning && !STATE.ssPaused) ||
13910-
(STATE.solutionViewMultiTenant && STATE.mtRunning && !STATE.mtPaused) ||
13911-
(STATE.solutionViewDashboard && STATE.drRunning && !STATE.drPaused) ||
13912-
(STATE.solutionViewUpdate && STATE.updRunning && !STATE.updPaused) ||
13913-
(STATE.solutionViewAi && STATE.aiRunning && !STATE.aiPaused) ||
13914-
(STATE.solutionViewOnboarding && STATE.obRunning && !STATE.obPaused) ||
13915-
(STATE.solutionViewAdmin && STATE.adminPane === 'ship' && STATE.updRunning && !STATE.updPaused) ||
13916-
((STATE.solutionViewUser || (STATE.solutionViewAdmin && STATE.adminPane !== 'ship')) && STATE.flowRunning && !STATE.flowPaused);
13917-
const _label = _running ? 'Pause' : 'Play';
13918-
const _icon = _running ? 'pause' : 'play';
13919-
return `
13920-
<div class="flex items-center gap-1.5 ml-auto pl-2">
13921-
<button type="button" data-action="toggle-flow" title="${_label}" aria-label="${_label}" class="bg-[#0000CC] text-white w-8 h-8 rounded-full hover:bg-[#0000A8] inline-flex items-center justify-center transition shadow-sm"><i data-lucide="${_icon}" class="w-3.5 h-3.5"></i></button>
13922-
<button type="button" data-action="step-flow" title="Step" aria-label="Step" class="bg-white text-[#0000CC] border border-[#0000CC] w-8 h-8 rounded-full hover:bg-[#F6F8FF] inline-flex items-center justify-center transition"><i data-lucide="step-forward" class="w-3.5 h-3.5"></i></button>
13923-
<button type="button" data-action="reset-flow" title="Reset" aria-label="Reset" class="bg-white text-[#1A1A2E] border border-[#1A1A2E]/30 w-8 h-8 rounded-full hover:bg-slate-50 hover:border-[#1A1A2E] inline-flex items-center justify-center transition"><i data-lucide="rotate-ccw" class="w-3.5 h-3.5"></i></button>
13924-
</div>
13925-
`;
13926-
})() : ''}
13909+
<!-- Play / Step / Reset live only in the pop-out header. The rail's
13910+
copy rendered under exactly the same condition as the modal, so
13911+
both clusters were always on screen at once. -->
1392713912
</div>
1392813913
</div>
1392913914
</div>
@@ -14417,6 +14402,7 @@ <h2 class="text-xl font-bold tracking-tight text-[#1A1A2E]">Ask a Question</h2>
1441714402
a driver of every hours figure on the page. */
1441814403
const PACKAGING_BENCHMARKS = {
1441914404
envRepeatShare: 0.60,
14405+
cycleGateDays: PT_LADDER.speed.rows.reduce((s, r) => s + r[2], 0),
1442014406
awStdHrs: 0.21,
1442114407
awComplexHrs: 3.0,
1442214408
tradStdHrs: 4.33,
@@ -14463,6 +14449,29 @@ <h2 class="text-xl font-bold tracking-tight text-[#1A1A2E]">Ask a Question</h2>
1446314449
else { ci.endpoints = Math.round(n * 1.3); ci.envs = 6; ci.apps = 250; ci.customApps = 50; ci.monthlyTickets = Math.round(n * 0.12); ci.avgTicketMins = 20; ci.pkgHours = 160; ci.patchHours = 110; }
1446414450
ci.appsTotal = ci.apps;
1446514451
ci.appsHighChurn = Math.round(ci.apps * 0.15);
14452+
/* Record the band's implied per-app rates. Editing the application
14453+
count then rescales hours from these, so the two inputs stay
14454+
consistent in either order and at any typing speed. */
14455+
const apps = Math.max(1, ci.apps);
14456+
ci.pkgHrsPerApp = ci.pkgHours / apps;
14457+
ci.patchHrsPerApp = ci.patchHours / apps;
14458+
ci.customSharePct = ci.customApps / apps;
14459+
}
14460+
14461+
/* Single place that turns an application count into the dependent
14462+
figures. Absolute, idempotent, and safe to call on every keystroke. */
14463+
function applyAppCount(n){
14464+
const ci = STATE.calcInputs;
14465+
const apps = Math.max(1, Math.min(100000, Math.round(n)));
14466+
if (!(ci.pkgHrsPerApp > 0)) ci.pkgHrsPerApp = (ci.pkgHours || 0) / Math.max(1, ci.apps);
14467+
if (!(ci.patchHrsPerApp > 0)) ci.patchHrsPerApp = (ci.patchHours || 0) / Math.max(1, ci.apps);
14468+
if (ci.customSharePct == null) ci.customSharePct = (ci.customApps || 0) / Math.max(1, ci.apps);
14469+
ci.apps = apps;
14470+
ci.appsTotal = apps;
14471+
ci.appsHighChurn = Math.round(apps * 0.15);
14472+
ci.customApps = Math.max(0, Math.min(apps, Math.round(apps * ci.customSharePct)));
14473+
ci.pkgHours = Math.round(apps * ci.pkgHrsPerApp * 10) / 10;
14474+
ci.patchHours = Math.round(apps * ci.patchHrsPerApp * 10) / 10;
1446614475
}
1446714476

1446814477
/* ═══════════════════════════════════════════════════════════════════
@@ -15699,7 +15708,8 @@ <h2 class="text-xl font-bold tracking-tight text-[#1A1A2E]">Ask a Question</h2>
1569915708
const tAw = rows.reduce((s, x) => s + x.awR, 0);
1570015709
const monthlyReclaim = Math.max(0, tNow - tAw);
1570115710
const annualValue = monthlyReclaim * 12 * rate;
15702-
const fte = monthlyReclaim * 12 / 1920;
15711+
const fteHours = ROI_ASSUMPTIONS[STATE.calcScenario].fteHours || 1920;
15712+
const fte = fteHours > 0 ? monthlyReclaim * 12 / fteHours : 0;
1570315713
const oneTime = r.cycleDollarsSaved || 0;
1570415714
const invest = r.awAnnualInvestment || 0;
1570515715
/* Break-even and the three-year net are recurring-only: the one-time
@@ -16048,6 +16058,10 @@ <h2 class="text-xl font-bold tracking-tight text-[#1A1A2E]">Ask a Question</h2>
1604816058
'Applied to packaging and update-validation hours and to the one-time rebuild figure. Discovery, rationalisation and licence review are counted once.'],
1604916059
['Update-workload weighting', '12 updates a year for monthly-or-faster titles, 3 for the remainder',
1605016060
'Modeling convention, stated so the weighted view can be checked.'],
16061+
['Application estate', num(ci.apps) + ' applications at ' + (ci.pkgHrsPerApp || 0).toFixed(2) + ' packaging and ' + (ci.patchHrsPerApp || 0).toFixed(2) + ' update-validation hours each a month, per environment',
16062+
'Per-application rates from the employee band. Change the application count and hours follow it; change the hours and the rate follows them.'],
16063+
['Release gate', pb.cycleGateDays.toFixed(1) + ' days minimum from request to deployed',
16064+
'Elapsed time is not touch time. This is the delivery ladder’s full post-automation residual — intake and sourcing, packaging and detection logic, the approval queue, and publish and verify — the elapsed time that remains once the effort is automated. The modelled cycle is floored there rather than scaled to a fraction of a day.'],
1605116065
['Exposure figures', 'Mean time to exploit \u22127 days in 2026, from +63 days in 2018; 48,185 CVEs published in 2025, up 20.6% year over year',
1605216066
'Mandiant M-Trends 2026; CVE program data 2025\u20132026. Shown in full under Current State.'],
1605316067
];
@@ -16809,7 +16823,9 @@ <h2 class="cs-panel-title flex-1">${ent ? 'Consolidating application delivery' :
1680916823
// the packaging card uses (PACKAGING_BENCHMARKS at the entered mix).
1681016824
const cycleDaysNow = Math.max(0, ci.cycleDaysNow ?? 14);
1681116825
const cycleRatio = tradCycleHours > 0 ? (awCycleHours / tradCycleHours) : 0;
16812-
const cycleDaysModelled = cycleDaysNow > 0 ? Math.max(0.5, Math.round(cycleDaysNow * cycleRatio * 10) / 10) : 0;
16826+
const cycleDaysModelled = cycleDaysNow > 0
16827+
? Math.min(cycleDaysNow, Math.max(pb.cycleGateDays, Math.round(cycleDaysNow * cycleRatio * 10) / 10))
16828+
: 0;
1681316829
// Projected aging: automated pipelines are assumed to clear their
1681416830
// updates inside 60 days, so aging persists only in the workload that
1681516831
// stays manual. The assumption is stated on screen.
@@ -17453,17 +17469,17 @@ <h2 id="cs-editor-title" class="text-xl font-semibold tracking-normal">Edit Narr
1745317469
if (e.target?.id === 'roi-employees') { const n = cleanNum(e.target.value); if (n > 0) { deriveFromEmployees(n); calculateImpact(); clearTimeout(_roiDebounce); _roiDebounce = setTimeout(() => render(), 600); } return; }
1745417470
if (e.target?.id === 'roi-users') { STATE.calcInputs.users = cleanNum(e.target.value); return; }
1745517471
if (e.target?.id === 'roi-endpoints') { STATE.calcInputs.endpoints = cleanNum(e.target.value); return; }
17456-
if (e.target?.id === 'roi-envs') { STATE.calcInputs.envs = cleanNum(e.target.value); return; }
17457-
if (e.target?.id === 'roi-apps') { STATE.calcInputs.apps = cleanNum(e.target.value); return; }
17458-
if (e.target?.id === 'roi-custom') { STATE.calcInputs.customApps = cleanNum(e.target.value); return; }
17472+
if (e.target?.id === 'roi-envs') { STATE.calcInputs.envs = Math.max(1, Math.min(12, cleanNum(e.target.value))); return; }
17473+
if (e.target?.id === 'roi-apps') { applyAppCount(Math.max(1, cleanNum(e.target.value))); return; }
17474+
if (e.target?.id === 'roi-custom') { const ci = STATE.calcInputs; ci.customApps = Math.max(0, Math.min(ci.apps, cleanNum(e.target.value))); ci.customSharePct = ci.customApps / Math.max(1, ci.apps); return; }
1745917475
if (e.target?.id === 'roi-tickets') { STATE.calcInputs.monthlyTickets = cleanNum(e.target.value); return; }
1746017476
if (e.target?.id === 'roi-ticketmins') { STATE.calcInputs.avgTicketMins = cleanNum(e.target.value); return; }
17461-
if (e.target?.id === 'roi-pkghrs') { STATE.calcInputs.pkgHours = cleanNum(e.target.value); return; }
17462-
if (e.target?.id === 'roi-patchhrs') { STATE.calcInputs.patchHours = cleanNum(e.target.value); return; }
17477+
if (e.target?.id === 'roi-pkghrs') { const ci = STATE.calcInputs; ci.pkgHours = Math.max(0, cleanNum(e.target.value)); ci.pkgHrsPerApp = ci.pkgHours / Math.max(1, ci.apps); return; }
17478+
if (e.target?.id === 'roi-patchhrs') { const ci = STATE.calcInputs; ci.patchHours = Math.max(0, cleanNum(e.target.value)); ci.patchHrsPerApp = ci.patchHours / Math.max(1, ci.apps); return; }
1746317479
if (e.target?.id === 'roi-reqs') { STATE.calcInputs.openReqs = cleanNum(e.target.value); return; }
1746417480
if (e.target?.id === 'roi-initiatives') { STATE.calcInputs.delayedInitiatives = cleanNum(e.target.value); return; }
17465-
if (e.target?.id === 'hero-envs') { STATE.calcInputs.envs = Math.max(1, cleanNum(e.target.value)); _curRecalc(); return; }
17466-
if (e.target?.id === 'hero-apps') { const ci = STATE.calcInputs; const n = Math.max(1, cleanNum(e.target.value)); const k = n / Math.max(1, ci.apps); ci.apps = n; ci.appsTotal = n; ci.appsHighChurn = Math.round(n * 0.15); ci.customApps = Math.max(0, Math.round(ci.customApps * k)); ci.pkgHours = Math.max(0, Math.round(ci.pkgHours * k)); ci.patchHours = Math.max(0, Math.round(ci.patchHours * k)); _curRecalc(); return; }
17481+
if (e.target?.id === 'hero-envs') { STATE.calcInputs.envs = Math.max(1, Math.min(12, cleanNum(e.target.value))); _curRecalc(); return; }
17482+
if (e.target?.id === 'hero-apps') { applyAppCount(Math.max(1, cleanNum(e.target.value))); _curRecalc(); return; }
1746717483
if (e.target?.id === 'cur-apps-total') { STATE.calcInputs.appsTotal = Math.max(1, cleanNum(e.target.value)); _curRecalc(); return; }
1746817484
if (e.target?.id === 'cur-apps-churn') { STATE.calcInputs.appsHighChurn = Math.max(0, cleanNum(e.target.value)); _curRecalc(); return; }
1746917485
if (e.target?.id === 'cur-cycle-days') { STATE.calcInputs.cycleDaysNow = Math.max(0, cleanNum(e.target.value)); _curRecalc(); return; }

0 commit comments

Comments
 (0)