diff --git a/app/igneum-app/ui/app.css b/app/igneum-app/ui/app.css
index 42d2b67e2..988e03240 100644
--- a/app/igneum-app/ui/app.css
+++ b/app/igneum-app/ui/app.css
@@ -717,6 +717,23 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)}
.gl-details .line.lock.searching{color:var(--molten)}
.curve-svg text{font-family:var(--mono);font-size:9px;fill:var(--ash)}
.seg.small .seg-b{padding:4px 10px;font-size:var(--t-sm)}
+/* tiers-25 (8 October 2026): the three tier buttons carry the row's rate and watts and the saving before the tap */
+.tiers{display:inline-flex;gap:3px;background:var(--obsidian);border:1px solid var(--line);border-radius:12px;padding:3px;flex-wrap:wrap}
+.tier-b{border:0;background:transparent;color:var(--ash);text-align:left;padding:7px 14px;border-radius:9px;cursor:pointer;display:flex;flex-direction:column;gap:2px;min-width:150px;transition:background .15s ease,color .15s ease}
+.tier-b .n{font-weight:600;font-size:var(--t-base);color:var(--ink-2);line-height:1.2}
+.tier-b .l{font-family:var(--mono);font-size:var(--t-sm);color:var(--bone);white-space:nowrap}
+.tier-b .s{font-family:var(--mono);font-size:var(--t-xs);color:var(--ash);white-space:nowrap}
+.tier-b:hover .n{color:var(--bone)}
+.tier-b.on{background:var(--graphite);box-shadow:0 1px 3px rgba(0,0,0,.25)}
+.tier-b.on .n{color:var(--molten)}
+.tiers.small .tier-b{min-width:128px;padding:5px 11px}
+.gl-details .tiers-ctl{grid-template-columns:120px 1fr}
+.gl-details .tiers-ctl .tier-row{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;min-width:0}
+.gl-details .tiers-ctl .tier-row .btn{margin-left:auto}
+.gl-details .tiers-ctl .tiers{justify-self:start}
+.tiers.small .tier-b .s{font-size:10px}
+.gl-details .tiers-ctl .help .mono{color:var(--ink-2)}
+.tune-strip .line-text.dim{color:var(--ash)}
.gl-details .ctl .seg{justify-self:start}
@media (max-width:1180px){
.totals{grid-template-columns:1fr 1fr 1fr 1fr}
diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js
index b519382a5..48b5230d2 100644
--- a/app/igneum-app/ui/app.js
+++ b/app/igneum-app/ui/app.js
@@ -874,6 +874,81 @@ var View = (function () {
} else out.row = 'locked at ' + cd.lock_mhz + ' MHz' + (out.kneeMhz ? ', knee at ' + out.kneeMhz + ' MHz' : '');
return out;
}
+ // ---- the three tuning tiers (tiers-25, 8 October 2026; the project lead's definition): Ember tunes a card once (the search at first steady
+ // mining, weekly, or on Tune) and the tiers are rows of that search: efficiency = the best MH/W row outright, balanced = the
+ // best MH/W row within 1% of the top rate (the default, on from install), max = the top-rate row (stock when stock is the top).
+ // A tap applies the known row at once, no re-search. The engine's fields: card.tiers [{id, clock_mhz, power_pct, w, mhs, mhw,
+ // source}], card.tier (in force), card.tier_note (why only stock exists), card.tier_at; settings.tune_tier (the fleet's) ----
+ var TIERS = [{ id: 'efficiency', name: 'Efficiency', word: 'the best hashes per watt' }, { id: 'balanced', name: 'Balanced', word: 'the best hashes per watt within 1% of the top rate' }, { id: 'max', name: 'Maximum', word: 'the top rate' }];
+ var TIER_RULE = 'Ember never locks below the knee by itself. Going lower is your choice.';
+ var DEFAULT_PRICE_MINOR = 25; // 0.25 in the currency in force, until the user sets a price (main's order, 8 October 2026)
+ function tierOf(id) { id = String(id || ''); if (id === 'rate' || id === 'max_rate' || id === 'maximum') return 'max'; for (var i = 0; i < TIERS.length; i++) if (TIERS[i].id === id) return id; return 'balanced'; }
+ function tierName(id) { for (var i = 0; i < TIERS.length; i++) if (TIERS[i].id === id) return TIERS[i].name; return 'Balanced'; }
+ function priceOrDefault(pence) { return pence > 0 ? pence : DEFAULT_PRICE_MINOR; }
+ function priceFigure(pence) { var c = currencyNow(); return fmtMoney(priceOrDefault(pence) / 100, c.code, locale) + ' per kWh, ' + (pence > 0 ? 'set in Settings' : 'the default until you set yours in Settings'); }
+ function rateText(mhs) { return mhs >= 100 ? mhs.toFixed(1) : mhs.toFixed(1); }
+ function savingText(saveW, pence) { if (!(saveW > 0)) return 'stock'; var ppd = poundsPerDay(saveW, pence); return 'saves ' + Math.round(saveW) + ' W' + (pence > 0 && ppd !== null ? ', ' + money(ppd) + ' a day' : ''); }
+ // {kind: none|nolever|measuring|unmeasured|ready, note, inForce, lock, stock: {w, mhs}|null, tiers: [{id, name, word, line, sub, on, mhz, w, mhs, mhw, saveW, source}]}
+ function tierSet(cd, s, pence) {
+ cd = cd || {}; var st = (s && s.settings) || {};
+ var out = { kind: 'none', note: '', inForce: '', lock: '', stock: null, tiers: [] };
+ if (!tunable(cd) || !Array.isArray(cd.tiers)) return out;
+ var fleetTier = tierOf(st.tune_tier || st.tune_goal || 'balanced'), inForce = cd.tier ? tierOf(cd.tier) : '';
+ if (cd.tier_note) {
+ out.kind = 'nolever'; out.note = cap(String(cd.tier_note)); out.inForce = 'max';
+ var se = cd.tiers.filter(function (t) { return t && tierOf(t.id) === 'max'; })[0] || null;
+ var sw = se && se.w > 0 ? se.w : 0, sm = se && se.mhs > 0 ? se.mhs : 0, smw = se && se.mhw > 0 ? se.mhw : (sw > 0 && sm > 0 ? sm / sw : 0);
+ out.tiers = [{ id: 'max', name: 'Stock', word: 'as the card runs', line: sw > 0 && sm > 0 ? rateText(sm) + ' MH/s · ' + Math.round(sw) + ' W' : 'as it runs', sub: smw > 0 ? smw.toFixed(3) + ' MH/W' : '', on: true, mhz: 0, w: sw, mhs: sm, mhw: smw, saveW: 0, source: 'stock' }];
+ out.stock = sw > 0 ? { w: sw, mhs: sm } : null;
+ return out;
+ }
+ if (!cd.tiers.length) {
+ var measuring = cd.state === 'tuning' || cd.sweep_state === 'running';
+ out.kind = measuring ? 'measuring' : 'unmeasured';
+ out.note = measuring ? 'Measuring' + (cd.tune_steps > 0 ? ': step ' + (cd.tune_step || 1) + ' of ' + cd.tune_steps : '') : (st.sweep === false ? 'Not measured yet: Ember Tune is off, press Tune' : 'Not measured yet: the search runs 2 min into steady mining');
+ out.tiers = TIERS.map(function (t) { return { id: t.id, name: t.name, word: t.word, line: measuring ? 'measuring' : 'not measured yet', sub: '', on: t.id === (inForce || fleetTier), mhz: 0, w: 0, mhs: 0, mhw: 0, saveW: 0, source: '' }; });
+ out.inForce = inForce || fleetTier;
+ return out;
+ }
+ var by = {}; cd.tiers.forEach(function (t) { if (t && t.id) by[tierOf(t.id)] = t; });
+ var stock = by.max || null; out.stock = stock ? { w: stock.w || 0, mhs: stock.mhs || 0 } : null;
+ out.kind = 'ready'; out.inForce = inForce || fleetTier;
+ out.tiers = TIERS.map(function (t) {
+ var r = by[t.id]; if (!r) return { id: t.id, name: t.name, word: t.word, line: 'not in this search', sub: '', on: t.id === out.inForce, mhz: 0, w: 0, mhs: 0, mhw: 0, saveW: 0, source: '' };
+ var w = r.w || 0, mhs = r.mhs || 0, mhw = r.mhw > 0 ? r.mhw : (w > 0 ? mhs / w : 0), saveW = stock && stock.w > w ? Math.round(stock.w - w) : 0;
+ return { id: t.id, name: t.name, word: t.word, line: rateText(mhs) + ' MH/s · ' + Math.round(w) + ' W', sub: mhw.toFixed(3) + ' MH/W · ' + savingText(saveW, pence), on: t.id === out.inForce, mhz: r.clock_mhz || 0, w: w, mhs: mhs, mhw: mhw, saveW: saveW, source: r.source || '' };
+ });
+ var capW = cd.lock_w > 0 ? cd.lock_w : (by[out.inForce] ? by[out.inForce].w : 0);
+ out.lock = (cd.lock_mhz > 0 ? 'locked at ' + cd.lock_mhz + ' MHz' : 'unlocked') + (capW > 0 ? ', ' + Math.round(capW) + ' W' : '') + (stock && stock.w > 0 ? '; stock ' + Math.round(stock.w) + ' W' : '');
+ return out;
+ }
+ // the strip: the measured cards summed per tier, the tier in force fleet-wide, how many cards are measured
+ // {inForce, measured, tunable, count, tiers: [{id, name, word, line, sub, on, w, mhs, saveW}]}
+ function fleetTiers(cards, s, pence) {
+ var st = (s && s.settings) || {}, inForce = tierOf(st.tune_tier || st.tune_goal || 'balanced');
+ var list = shownCards(cards).filter(tunable), sets = list.map(function (c) { return tierSet(c, s, pence); }), ready = sets.filter(function (t) { return t.kind === 'ready'; });
+ var measured = sets.filter(function (t) { return t.kind === 'ready' || (t.kind === 'nolever' && t.stock); }).length;
+ var out = { inForce: inForce, measured: measured, tunable: list.length, count: measured + ' of ' + list.length + ' card' + (list.length === 1 ? '' : 's') + ' measured', tiers: [] };
+ out.tiers = TIERS.map(function (t) {
+ var w = 0, mhs = 0, saveW = 0, n = 0;
+ ready.forEach(function (ts) { ts.tiers.forEach(function (x) { if (x.id === t.id && x.w > 0) { w += x.w; mhs += x.mhs; saveW += x.saveW; n++; } }); });
+ return { id: t.id, name: t.name, word: t.word, line: n ? rateText(mhs) + ' MH/s · ' + Math.round(w) + ' W' : (sets.some(function (x) { return x.kind === 'measuring'; }) ? 'measuring' : 'not measured yet'), sub: n ? savingText(saveW, pence) : '', on: t.id === inForce, w: w, mhs: mhs, saveW: saveW };
+ });
+ return out;
+ }
+ // the strip's one sentence: what Ember Tune is doing, did, or will do
+ function tierSentence(cards, s, now) {
+ var st = (s && s.settings) || {}, list = shownCards(cards).filter(tunable), tc = tuningCard(list);
+ if (st.tuning_off) return st.tuning_note || 'Tuning paused fleet-wide by the signed manifest.';
+ if (!list.length) return 'No card here can be tuned.';
+ if (tc) { var step = tc.tune_steps > 0 ? ': step ' + (tc.tune_step || 1) + ' of ' + tc.tune_steps : ''; return 'Measuring ' + tc.name + step + (tc.tune_eta_s > 0 ? ', mining again in ' + tuneEta(tc.tune_eta_s) : '') + '.'; }
+ if (st.sweep === false) return 'Ember Tune is off. The tiers still apply when you tap them; the automatic tune is one tap away in Settings.';
+ if (list.every(function (c) { return !!c.tier_note; })) return 'No lever on this machine: ' + String(list[0].tier_note).replace(/^no lever:\s*/i, '') + '. Ember measures and reports.';
+ var tier = tierOf(st.tune_tier || st.tune_goal || 'balanced'), last = null, n = 0;
+ list.forEach(function (c) { if (c.sweep_at > 0) { n++; if (!last || c.sweep_at > last.sweep_at) last = c; } });
+ if (last) return tierName(tier) + ' on ' + n + ' card' + (n === 1 ? '' : 's') + ', tuned ' + ago(now - last.sweep_at) + ' · ' + nextCheck(last.sweep_at, st.tune_period_s, now) + '.';
+ return 'Ember Tune is on at ' + tierName(tier) + '. The first tune runs 2 min into steady mining and sets each card’s ' + tierName(tier).toLowerCase() + ' point.';
+ }
// {kind: none|idle|measured|running|tuned|stopped|paused, fill: 0..1, label, sub, before, after, savingW, savingPct, savingText, next, points, lock}
function ember(cd, s, now) {
var tw = tuneWords(cd, s, now), out = { kind: tw.kind, fill: 0, label: '', sub: '', before: 0, after: 0, savingW: 0, savingPct: 0, savingText: '', next: '', points: [], lock: lockWords(cd) };
@@ -1334,7 +1409,7 @@ var View = (function () {
}
function addressAsk(a) { return 'Pay ' + shortHex(a, 6, 4) + ' from the next block? Mining restarts.'; }
function trustAsk(on) { return on ? 'Include proofs the node never checked? Devnet only. The node restarts.' : 'Check proofs again? The node restarts.'; }
- return { NETWORKS: NETWORKS, networkWords: networkWords, networkAsk: networkAsk, networkIdOf: networkIdOf, ISO_CURRENCY: ISO_CURRENCY, EU_MEMBERS: EU_MEMBERS, CURRENCY_CODES: CURRENCY_CODES, minorOf: minorOf, priceFactor: priceFactor, currencyInfo: currencyInfo, regionFromLocale: regionFromLocale, currencyChoice: currencyChoice, currencyAsk: currencyAsk, applyMoney: applyMoney, fmtMoney: fmtMoney, setLocale: setLocale, setCurrency: setCurrency, currencyNow: currencyNow, unitWords: unitWords, VENDORS: VENDORS, MARKS: MARKS, WELL_ALPHA_LIGHT: WELL_ALPHA_LIGHT, WELL_ALPHA_DARK: WELL_ALPHA_DARK, vendorOf: vendorOf, seriesLine: seriesLine, vendorMark: vendorMark, markHtml: markHtml, nameHtml: nameHtml, rowHead: rowHead, vendorContrast: vendorContrast, contrast: contrast, hexRgb: hexRgb, PAGES: PAGES, page: page, withCommas: withCommas, compact: compact, rel: rel, ago: ago, shortHex: shortHex, kindWord: kindWord, shownCards: shownCards, present: present, cardTitle: cardTitle, cardRow: cardRow, rowWord: rowWord, driverWords: driverWords, vendorWord: vendorWord, tuneWord: tuneWord, tuneEta: tuneEta, tuningCard: tuningCard, poundsPerDay: poundsPerDay, money: money, effOf: effOf, effText: effText, wattsTotal: wattsTotal, noReading: noReading, cells: cells, nextCheck: nextCheck, tunePoint: tunePoint, tunable: tunable, tuneWords: tuneWords, schedule: schedule, GOALS: GOALS, GOAL_NAMES: GOAL_NAMES, goalWords: goalWords, ign: ign, balanceWords: balanceWords, fleetWatts: fleetWatts, fleetPounds: fleetPounds, isMine: isMine, liveNote: liveNote, toggle: toggle, nodeWords: nodeWords, nodeLine: nodeLine, skewWord: skewWord, peersLine: peersLine, heightLine: heightLine, tipAge: tipAge, nodeSource: nodeSource, finalityWords: finalityWords, nextSwitch: nextSwitch, switchLine: switchLine, proveWords: proveWords, verifierWords: verifierWords, proveTier: proveTier, proveInsteadWords: proveInsteadWords, proveLine: proveLine, devFeeText: devFeeText, devFeeLine: devFeeLine, jobsNote: jobsNote, eventLine: eventLine, plainEvent: plainEvent, pill: pill, beforeOf: beforeOf, ember: ember, fleetSaving: fleetSaving, LOCK_RULE: LOCK_RULE, lockWords: lockWords, kneeWords: kneeWords, addressAsk: addressAsk, trustAsk: trustAsk, interfaceWords: interfaceWords, shouldReload: shouldReload, FIELDS: FIELDS, fmtIgn: fmtIgn, pct: pct, gapWords: gapWords, spanWords: spanWords, chainNums: chainNums, poisson: poisson, ignPerDay: ignPerDay, windowWords: windowWords, daysIn: daysIn, ladderRungs: ladderRungs, currentRung: currentRung, firstWait: firstWait, staleCard: staleCard, cardSeenBody: cardSeenBody, blockCard: blockCard, earningsLines: earningsLines, cardIgnDay: cardIgnDay, timeline: timeline, profileWords: profileWords, REGIONS: REGIONS, CURRENCIES: CURRENCIES, RESTRICTED_SENTENCE: RESTRICTED_SENTENCE, regionOf: regionOf, currencyOf: currencyOf, setRegion: setRegion, restrictedLine: restrictedLine, priceNote: priceNote, RENT: RENT, rentLine: rentLine, heatLine: heatLine, heatRowWords: heatRowWords };
+ return { NETWORKS: NETWORKS, networkWords: networkWords, networkAsk: networkAsk, networkIdOf: networkIdOf, ISO_CURRENCY: ISO_CURRENCY, EU_MEMBERS: EU_MEMBERS, CURRENCY_CODES: CURRENCY_CODES, minorOf: minorOf, priceFactor: priceFactor, currencyInfo: currencyInfo, regionFromLocale: regionFromLocale, currencyChoice: currencyChoice, currencyAsk: currencyAsk, applyMoney: applyMoney, fmtMoney: fmtMoney, setLocale: setLocale, setCurrency: setCurrency, currencyNow: currencyNow, unitWords: unitWords, VENDORS: VENDORS, MARKS: MARKS, WELL_ALPHA_LIGHT: WELL_ALPHA_LIGHT, WELL_ALPHA_DARK: WELL_ALPHA_DARK, vendorOf: vendorOf, seriesLine: seriesLine, vendorMark: vendorMark, markHtml: markHtml, nameHtml: nameHtml, rowHead: rowHead, vendorContrast: vendorContrast, contrast: contrast, hexRgb: hexRgb, PAGES: PAGES, page: page, withCommas: withCommas, compact: compact, rel: rel, ago: ago, shortHex: shortHex, kindWord: kindWord, shownCards: shownCards, present: present, cardTitle: cardTitle, cardRow: cardRow, rowWord: rowWord, driverWords: driverWords, vendorWord: vendorWord, tuneWord: tuneWord, tuneEta: tuneEta, tuningCard: tuningCard, poundsPerDay: poundsPerDay, money: money, effOf: effOf, effText: effText, wattsTotal: wattsTotal, noReading: noReading, cells: cells, nextCheck: nextCheck, tunePoint: tunePoint, tunable: tunable, tuneWords: tuneWords, schedule: schedule, GOALS: GOALS, GOAL_NAMES: GOAL_NAMES, goalWords: goalWords, ign: ign, balanceWords: balanceWords, fleetWatts: fleetWatts, fleetPounds: fleetPounds, isMine: isMine, liveNote: liveNote, toggle: toggle, nodeWords: nodeWords, nodeLine: nodeLine, skewWord: skewWord, peersLine: peersLine, heightLine: heightLine, tipAge: tipAge, nodeSource: nodeSource, finalityWords: finalityWords, nextSwitch: nextSwitch, switchLine: switchLine, proveWords: proveWords, verifierWords: verifierWords, proveTier: proveTier, proveInsteadWords: proveInsteadWords, proveLine: proveLine, devFeeText: devFeeText, devFeeLine: devFeeLine, jobsNote: jobsNote, eventLine: eventLine, plainEvent: plainEvent, pill: pill, beforeOf: beforeOf, ember: ember, fleetSaving: fleetSaving, LOCK_RULE: LOCK_RULE, lockWords: lockWords, kneeWords: kneeWords, TIERS: TIERS, TIER_RULE: TIER_RULE, DEFAULT_PRICE_MINOR: DEFAULT_PRICE_MINOR, tierOf: tierOf, tierName: tierName, priceOrDefault: priceOrDefault, priceFigure: priceFigure, tierSet: tierSet, fleetTiers: fleetTiers, tierSentence: tierSentence, addressAsk: addressAsk, trustAsk: trustAsk, interfaceWords: interfaceWords, shouldReload: shouldReload, FIELDS: FIELDS, fmtIgn: fmtIgn, pct: pct, gapWords: gapWords, spanWords: spanWords, chainNums: chainNums, poisson: poisson, ignPerDay: ignPerDay, windowWords: windowWords, daysIn: daysIn, ladderRungs: ladderRungs, currentRung: currentRung, firstWait: firstWait, staleCard: staleCard, cardSeenBody: cardSeenBody, blockCard: blockCard, earningsLines: earningsLines, cardIgnDay: cardIgnDay, timeline: timeline, profileWords: profileWords, REGIONS: REGIONS, CURRENCIES: CURRENCIES, RESTRICTED_SENTENCE: RESTRICTED_SENTENCE, regionOf: regionOf, currencyOf: currencyOf, setRegion: setRegion, restrictedLine: restrictedLine, priceNote: priceNote, RENT: RENT, rentLine: rentLine, heatLine: heatLine, heatRowWords: heatRowWords };
})();
/* ---------- Ember Tune: the card row's tuning line (pure; tune-line.test.mjs loads this block) ----------
One line per card from the card state (src/state.rs, src/ember.rs): running (the phase and the step), tuned
@@ -1660,6 +1735,7 @@ if (typeof document !== 'undefined') (function () {
else if (b.dataset.driverInstall !== undefined) api('api/drivers/install', { vendor: b.dataset.driverInstall }).then(function (r) { toast(r && r.ok === false ? (r.error || 'could not start the driver install') : 'Driver download started. Windows asks for permission once before the install; the workers on this vendor\u2019s cards stop for it and come back after.'); });
else if (b.dataset.driverRestart !== undefined) { if (window.confirm('Restart Windows now to finish the driver install? Mining stops until the app is back.')) api('api/drivers/restart', {}).then(function () { toast('Restarting in 20 seconds'); }); }
else if (b.dataset.price !== undefined) { showPage('settings'); $('s-price').focus(); }
+ else if (b.closest('[data-card-tier]') && b.dataset.tier) setTier(b.dataset.tier, key);
else if (b.closest('[data-card-goal]') && b.dataset.goal !== undefined) {
api('api/tune/goal', { key: key, goal: b.dataset.goal || '' }).then(function (r) { if (r && r.ok) toast(b.dataset.goal ? 'Goal for this card: ' + View.GOALS[b.dataset.goal] : 'Default: this card follows the goal set for all cards'); else toast('A goal for one card needs the next Ember Tune update'); }).catch(function () { toast('A per-card goal needs the Ember Tune update'); });
}
@@ -1674,10 +1750,22 @@ if (typeof document !== 'undefined') (function () {
list.reduce(function (p, c) { return p.then(function () { return api('api/sweep/start', { key: c.key }).then(function (r) { if (!r || r.ok !== false) n++; }); }); }, Promise.resolve()).then(function () { toast(n === 1 ? 'Tune queued for 1 card' : 'Tune queued for ' + n + ' cards, one after another'); });
});
// the goal (ember-tune: POST api/tune/goal); a build without the route says so and keeps the old value
+ // tiers-25: a tap applies the known row at once (POST api/tune/tier); an engine before 0.3.25 has no route, so the goal route
+ // takes the tier as a goal and the toast says the numbers wait for the update
+ function setTier(tier, key) {
+ var body = key ? { key: key, tier: tier } : { tier: tier };
+ api('api/tune/tier', body).then(function (r) {
+ if (r && r.ok) { if (!key) store('igneum.goal', tier === 'max' ? 'rate' : tier); toast(View.tierName(tier) + (key ? ' applied to ' + ((cardOf(key) || {}).name || 'the card') : ' applied to ' + ((r.applied || []).length || 'every') + ' card' + ((r.applied || []).length === 1 ? '' : 's'))); }
+ else if (r && r.error && !/not found|unknown|404/i.test(r.error)) toast(r.error);
+ else api('api/tune/goal', key ? { key: key, goal: tier === 'max' ? 'rate' : tier } : { goal: tier === 'max' ? 'rate' : tier }).then(function (g) { toast(g && g.ok ? View.tierName(tier) + ' set as the goal; the one-tap tiers need the 0.3.25 engine' : 'The tiers need the 0.3.25 engine'); });
+ if (state) renderTuneCard(state);
+ }).catch(function () { toast('The engine did not answer'); });
+ }
function setGoal(goal) {
api('api/tune/goal', { goal: goal }).then(function (r) { if (r && r.ok) { store('igneum.goal', goal); toast('Goal: ' + View.GOALS[goal]); } else toast('The goal needs the next Ember Tune update'); if (state) renderTuneCard(state); }).catch(function () { toast('The goal needs the Ember Tune update'); });
}
- ['goal-seg', 'goal-seg-2'].forEach(function (id) { $(id).addEventListener('click', function (e) { var b = e.target.closest('.seg-b'); if (b) setGoal(b.dataset.goal); }); });
+ ['goal-seg', 'goal-seg-2'].forEach(function (id) { var el = $(id); if (el) el.addEventListener('click', function (e) { var b = e.target.closest('.seg-b'); if (b) setGoal(b.dataset.goal); }); });
+ ['tier-seg', 'tier-seg-2'].forEach(function (id) { var el = $(id); if (el) el.addEventListener('click', function (e) { var b = e.target.closest('.tier-b'); if (b && b.dataset.tier) setTier(b.dataset.tier); }); });
function powerControl(on) { api('api/power/control', { on: on }).then(function (r) { if (r.ok) toast(on ? 'Power control on. Windows asks for administrator rights once' : 'Power control off. Nothing asks'); }); }
$('m-power-control').addEventListener('change', function () { powerControl(this.checked); });
$('s-power-control').addEventListener('change', function () { powerControl(this.checked); });
@@ -2368,6 +2456,10 @@ if (typeof document !== 'undefined') (function () {
'
' + tuneBtn + sw + chev + '
' + drv + tune +
'
' + detailsHtml(cd, s) + '
';
}
+ // tiers-25: the three tier buttons (fleet or one card): the name, the row's rate and watts, the saving against stock
+ function tierButtonsHtml(tiers, label, cardKey) {
+ return (tiers || []).map(function (t) { return ''; }).join('');
+ }
// the flame mark (the brand mark's two polygons) filled to a fraction: outline when not tuned, filling while tuning, solid when tuned
var flameN = 0;
function flameHtml(fill, kind) {
@@ -2419,8 +2511,16 @@ if (typeof document !== 'undefined') (function () {
if (tl.length) h += '
' + tl.join('') + '
';
var tm = TuneLine.model(cd, s.now), em = View.ember(cd, s, s.now);
if (View.tunable(cd)) {
- var g = cd.tune_goal || '', goalSeg = '
' + [['', 'Default', 'the goal set for all cards'], ['efficiency', 'Efficiency', 'most hashes per watt, a little rate traded'], ['balanced', 'Balanced', 'a little rate for most of the saving'], ['rate', 'Maximum', 'most hashes, watts second']].map(function (o) { return ''; }).join('') + '
' + (cd.tune_goal === undefined ? 'A goal for one card needs the next Ember Tune update. Until then every card follows the goal above.' : 'This card’s own goal. Default follows the goal set for all cards. A new goal applies from the next tune.') + '
';
+ // tiers-25: the card's own three tiers with their numbers, the one in force, the lock in words, the note for a card
+ // without a lever or a measurement; a tap applies the known row at once
+ var ts = View.tierSet(cd, s, priceNow());
+ if (ts.kind !== 'none') {
+ h += '
Tier
' + tierButtonsHtml(ts.tiers, 'Tier') + '
' +
+ (em.kind === 'running' ? '' : '') + '
' +
+ '
' + (ts.note ? esc(ts.note) + '. ' : '') + (ts.lock ? '' + esc(ts.lock.charAt(0).toUpperCase() + ts.lock.slice(1)) + '. ' : '') + (ts.kind === 'ready' ? 'A tap applies that row at once, no new search. ' + esc(View.TIER_RULE) : ts.kind === 'nolever' ? 'The card runs as the system sets it; Ember measures and reports.' : 'The tiers fill in from the card\u2019s own search.') + '
';
+ } else {
+ h += '
Tune' + (em.kind === 'running' ? '' : '') + '
The one-tap tiers need the 0.3.25 engine; this engine tunes to the goal set for all cards.
';
+ }
}
if (tm.kind === 'tuned' || tm.kind === 'measured') h += '