Lane C R32 (v7.0-beta wave 1): THE BIBLE — bands derived, gems seeded, the cards get the guide stamp
bible.js: band ranges MIRRORED from E's pipeline thresholds (record 8/25/60 · book 5/15/40 · toy 6/20/50) — derived in JS off the price_band labels the packs already carry, NO pack re-emit (charter risk #3 never opens). guideText renders a RANGE ('guide $8–24' / '$60+'), never a verdict — the card never says gem; spotting asking-under-guide is the player's skill. dig.js: parody sleeves roll seeded bands (25/45/22/8) + a 10% gem on collector/grail only, asking strictly under the keeper's own bandLow offer (collector $2–9 vs $12 · grail $5–20 vs $30) — margin by construction; fixed 9-draw stream per item; rare ≡ grail (matches real packs). Real offers carry band off price_band — and real asking always sits INSIDE its band (the band derives FROM the price), so with the §9.1 basis swap every real round trip stays strictly negative: the no-pump gate holds untouched, structurally. open({gone}) omits pulled slots AFTER the draw (R27 pick-then-omit, zero collateral; o.i survives filtering — identity preserved). currentOffers() honest surface for F's gate. sell.js: the §9.1 basis swap EXECUTED exactly as pre-ratified — sellOffer(sellBasis(entry)) = min(bandLow−1, max(1, floor(bandLow·0.5))) when banded; alpha-era entries (no band) keep the pricePaid basis and sell exactly as before. The sell card carries the guide line too.
This commit is contained in:
parent
046668e5af
commit
f9517f14ab
58
web/js/interiors/bible.js
Normal file
58
web/js/interiors/bible.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// PROCITY Lane C — bible.js [v7.0-beta wave 1, R32 — THE GUIDE + THE GEM (charter system #3)]
|
||||||
|
// The in-game price guide: thriftgod's BIBLE concept as a VALUE BAND per item. The band is the
|
||||||
|
// 90s guide book, not an oracle — it names a range ("guide $25–59"), never a verdict. A **gem**
|
||||||
|
// is an item ASKING under its band's low edge; spotting one is the player's skill, so nothing in
|
||||||
|
// any UI ever says "gem".
|
||||||
|
//
|
||||||
|
// THE DERIVATION RULING (R32): band NUMBERS mirror Lane E's pipeline thresholds
|
||||||
|
// (pipeline/build_stock_pack.py price_band(): record 8/25/60 · book 5/15/40 · toy 6/20/50 —
|
||||||
|
// duplicated verbatim in godverse_stock.py, validated by validate_pack.py). We derive ranges in
|
||||||
|
// JS from the band LABEL the packs already carry instead of re-baking band data into atlases —
|
||||||
|
// so charter risk #3 (a re-emitted pack orphaning owned skus) never opens. If E's thresholds
|
||||||
|
// move, this table moves with them in the same commit (the two files name each other).
|
||||||
|
//
|
||||||
|
// THE STRUCTURAL CONSEQUENCE (why the no-pump law survives the beta, LANE_C_PUB §9.1):
|
||||||
|
// real/mint pack items derive `price_band` FROM `price`, so real asking always sits INSIDE its
|
||||||
|
// band ⇒ bandLow ≤ asking ⇒ sellOffer(bandLow) ≤ bandLow − 1 < asking: every real round trip
|
||||||
|
// stays strictly negative with the basis swapped. Only PARODY stock (proceduralSleeves) rolls a
|
||||||
|
// seeded under-band gem — and per-day scarcity (the save's `pulls`) makes each gem profit ONCE.
|
||||||
|
// Growth is bounded by finds, not arbitrage loops — charter law #2, made structural.
|
||||||
|
//
|
||||||
|
// Zero DOM, zero draws, zero fetch, zero storage: a pure table + pure functions. ?classic=1 /
|
||||||
|
// ?dig=0 never import a caller, and importing this module alone has no effect at all.
|
||||||
|
|
||||||
|
// kind → band → [low, high]; `high: null` = open-ended (the card renders "$60+").
|
||||||
|
// Lows/highs are E's threshold edges: a band [t0, t1) renders as $t0–(t1−1).
|
||||||
|
export const BANDS = {
|
||||||
|
record: { bargain: [2, 7], standard: [8, 24], collector: [25, 59], grail: [60, null] },
|
||||||
|
book: { bargain: [1, 4], standard: [5, 14], collector: [15, 39], grail: [40, null] },
|
||||||
|
toy: { bargain: [2, 5], standard: [6, 19], collector: [20, 49], grail: [50, null] },
|
||||||
|
};
|
||||||
|
export const BAND_ORDER = ['bargain', 'standard', 'collector', 'grail'];
|
||||||
|
|
||||||
|
// bandRange('record','collector') → [25, 59] | null on unknown kind/band (fail-closed: no range,
|
||||||
|
// no guide line, no basis swap — never an invented number).
|
||||||
|
export function bandRange(kind, band) {
|
||||||
|
const k = BANDS[kind];
|
||||||
|
const r = k && k[String(band || '').toLowerCase()];
|
||||||
|
return r ? [r[0], r[1]] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The guide line for a card stamp — "guide $25–59" / "guide $60+" / '' when unknowable.
|
||||||
|
export function guideText(kind, band) {
|
||||||
|
const r = bandRange(kind, band);
|
||||||
|
if (!r) return '';
|
||||||
|
return r[1] == null ? `guide $${r[0]}+` : `guide $${r[0]}–${r[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sell basis for a collection entry (LANE_C_PUB §9.1, the pre-ratified beta swap): bandLow
|
||||||
|
// when the entry carries a band the BIBLE knows, else the alpha basis (pricePaid) — so every
|
||||||
|
// alpha-era find (no band field) sells exactly as it did before this round. The sellOffer()
|
||||||
|
// CLAMP SHAPE is sell.js's and survives untouched; this function only chooses the number fed in.
|
||||||
|
export function sellBasis(entry) {
|
||||||
|
if (entry && entry.band) {
|
||||||
|
const r = bandRange(entry.type, entry.band);
|
||||||
|
if (r) return r[0];
|
||||||
|
}
|
||||||
|
return entry ? (entry.pricePaid != null ? entry.pricePaid : entry.price) : 0;
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@
|
|||||||
// runs unless the shell/test-page opens it, so `?dig=0` is byte-identical to v1.
|
// runs unless the shell/test-page opens it, so `?dig=0` is byte-identical to v1.
|
||||||
|
|
||||||
import { mulberry32, xmur3 } from '../core/prng.js';
|
import { mulberry32, xmur3 } from '../core/prng.js';
|
||||||
|
import { BANDS, guideText } from './bible.js'; // [R32] THE BIBLE — band ranges + the card's guide stamp
|
||||||
|
|
||||||
const MAXFLIP = 1.95, BACK = -0.12;
|
const MAXFLIP = 1.95, BACK = -0.12;
|
||||||
const SLEEVE = [0.31, 0.31, 0.0060]; // w, h, thickness (packed a touch chunkier than vinyl)
|
const SLEEVE = [0.31, 0.31, 0.0060]; // w, h, thickness (packed a touch chunkier than vinyl)
|
||||||
@ -23,21 +24,33 @@ const TITLES = ['Sunburnt', 'Back o’ Bourke', 'Servo at Midnight', 'Long Weeke
|
|||||||
const CONDS = ['VG+', 'VG', 'G+', 'NM', 'EX'];
|
const CONDS = ['VG+', 'VG', 'G+', 'NM', 'EX'];
|
||||||
|
|
||||||
// Deterministic sleeve list for a bin — the "existing procedural stock" as riffle-able offers.
|
// Deterministic sleeve list for a bin — the "existing procedural stock" as riffle-able offers.
|
||||||
|
// [R32 — THE BIBLE + THE GEM] Every parody sleeve now rolls a seeded BAND (the guide's range) and an
|
||||||
|
// ASKING price with seeded variance: non-gems price INSIDE their band; a 10% gem roll (collector/grail
|
||||||
|
// only — an under-band bargain is not a thrill) prices UNDER the band's low edge, which is the entire
|
||||||
|
// op-shop fantasy made mechanical. Draw COUNT is fixed per item (9 rolls, outcome-independent) so the
|
||||||
|
// stream stays deterministic and re-orderable never. `rare` (the ★) now means grail, matching the real
|
||||||
|
// packs — the card never says "gem"; spotting the asking-under-guide gap is the player's skill.
|
||||||
|
const PARODY_BAND = (roll) => (roll < 0.25 ? 'bargain' : roll < 0.70 ? 'standard' : roll < 0.92 ? 'collector' : 'grail');
|
||||||
|
const GRAIL_ASK_HI = 120; // parody grail asking ceiling (band renders "$60+")
|
||||||
function proceduralSleeves(seed, n) {
|
function proceduralSleeves(seed, n) {
|
||||||
const r = mulberry32(seed >>> 0);
|
const r = mulberry32(seed >>> 0);
|
||||||
const out = [];
|
const out = [];
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const g = GENRE[(r() * GENRE.length) | 0];
|
const g = GENRE[(r() * GENRE.length) | 0];
|
||||||
out.push({
|
const a = ARTISTS[(r() * ARTISTS.length) | 0];
|
||||||
i, kind: 'record',
|
const t = TITLES[(r() * TITLES.length) | 0];
|
||||||
a: ARTISTS[(r() * ARTISTS.length) | 0],
|
const y = 1971 + ((r() * 28) | 0);
|
||||||
t: TITLES[(r() * TITLES.length) | 0],
|
const cond = CONDS[(r() * CONDS.length) | 0];
|
||||||
y: 1971 + ((r() * 28) | 0),
|
const band = PARODY_BAND(r());
|
||||||
s: g[0], color: g[1],
|
const [lo, hi] = BANDS.record[band];
|
||||||
cond: CONDS[(r() * CONDS.length) | 0],
|
const priceRoll = r(), gemRoll = r(), gemPriceRoll = r(); // always drawn — fixed stream shape
|
||||||
price: 2 + ((r() * 28) | 0),
|
const gem = gemRoll < 0.10 && (band === 'collector' || band === 'grail');
|
||||||
rare: r() < 0.12,
|
// gem asking sits STRICTLY under the keeper's own bandLow offer (min(lo−1, floor(lo/2)) — sell.js),
|
||||||
});
|
// so a spotted gem always nets margin: collector asks $2–9 vs offer $12 · grail $5–20 vs $30.
|
||||||
|
const price = gem
|
||||||
|
? (band === 'grail' ? 5 + ((gemPriceRoll * 16) | 0) : 2 + ((gemPriceRoll * 8) | 0))
|
||||||
|
: lo + ((priceRoll * ((hi == null ? GRAIL_ASK_HI : hi) - lo + 1)) | 0);
|
||||||
|
out.push({ i, kind: 'record', a, t, y, s: g[0], color: g[1], cond, price, band, rare: band === 'grail' });
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -250,6 +263,11 @@ export function createDig(THREE, renderer) {
|
|||||||
const line = [o.y, o.s].filter(Boolean).join(' '); // "1974 SOUL" | "SOUL" | "" — never " · "
|
const line = [o.y, o.s].filter(Boolean).join(' '); // "1974 SOUL" | "SOUL" | "" — never " · "
|
||||||
if (line) stamps.push(`<span class="tag">${esc(line)}</span>`);
|
if (line) stamps.push(`<span class="tag">${esc(line)}</span>`);
|
||||||
if (o.rare) stamps.push(`<span class="star">★ ${o.real ? 'GRAIL' : 'RARE'}</span>`);
|
if (o.rare) stamps.push(`<span class="star">★ ${o.real ? 'GRAIL' : 'RARE'}</span>`);
|
||||||
|
// [R32 THE BIBLE] the guide stamp — a RANGE off the 90s guide book, never a verdict. Present-fields-
|
||||||
|
// only law holds: an unknown band leaves no stamp (guideText returns ''). The asking price sits on
|
||||||
|
// the sticker; whether it's under the guide is for the PLAYER to notice — the card never says gem.
|
||||||
|
const guide = guideText('record', o.band);
|
||||||
|
if (guide) stamps.push(`<span class="tag">${esc(guide)}</span>`);
|
||||||
panel.innerHTML = `<div class="x">✕</div>`
|
panel.innerHTML = `<div class="x">✕</div>`
|
||||||
+ `<div class="sticker">$${esc(o.price)}</div>`
|
+ `<div class="sticker">$${esc(o.price)}</div>`
|
||||||
+ `<h3>${esc(o.a)}</h3>`
|
+ `<h3>${esc(o.a)}</h3>`
|
||||||
@ -342,13 +360,16 @@ export function createDig(THREE, renderer) {
|
|||||||
// Both forms hide the sold record, so "the gone item never renders" cannot tell them apart — the
|
// Both forms hide the sold record, so "the gone item never renders" cannot tell them apart — the
|
||||||
// collateral is what a gate must assert on. F: update item FIELDS in place (live price) freely; do NOT
|
// collateral is what a gate must assert on. F: update item FIELDS in place (live price) freely; do NOT
|
||||||
// add, remove, or reorder `pack.items` — that identity/order IS the seeded pick's stability.
|
// add, remove, or reorder `pack.items` — that identity/order IS the seeded pick's stability.
|
||||||
function pickRealOffers(pack, seed, n) {
|
function pickRealOffers(pack, seed, n, pulledGone) {
|
||||||
const rr = mulberry32(seed >>> 0), out = [];
|
const rr = mulberry32(seed >>> 0), out = [];
|
||||||
const gone = pack.gone instanceof Set ? pack.gone
|
const gone = pack.gone instanceof Set ? pack.gone
|
||||||
: (Array.isArray(pack.gone) ? new Set(pack.gone) : null);
|
: (Array.isArray(pack.gone) ? new Set(pack.gone) : null);
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const it = pack.items[(rr() * pack.items.length) | 0];
|
const it = pack.items[(rr() * pack.items.length) | 0];
|
||||||
if (gone && gone.has(it.id)) continue; // sold in the real shop → this slot is simply empty
|
if (gone && gone.has(it.id)) continue; // sold in the real shop → this slot is simply empty
|
||||||
|
// [R32 scarcity] the PLAYER bought it (this day, this bin) → same pick-then-omit seam, same
|
||||||
|
// zero-collateral guarantee: the pull leaves, the survivors keep their slots.
|
||||||
|
if (pulledGone && pulledGone.has('sku:' + it.id)) continue;
|
||||||
// Real fields straight off the atlas index (§7.2c): condition/sleeve_cond are OPTIONAL — present on
|
// Real fields straight off the atlas index (§7.2c): condition/sleeve_cond are OPTIONAL — present on
|
||||||
// POS-sourced items, absent on mint ones that had no genuine grade. `y` (year) is never in the index,
|
// POS-sourced items, absent on mint ones that had no genuine grade. `y` (year) is never in the index,
|
||||||
// so it stays empty and the card simply omits it. No invented values: an unstated grade stays unstated.
|
// so it stays empty and the card simply omits it. No invented values: an unstated grade stays unstated.
|
||||||
@ -358,22 +379,29 @@ export function createDig(THREE, renderer) {
|
|||||||
out.push({ real: true, pack, item: it, a: it.artist, t: it.title, price: it.price,
|
out.push({ real: true, pack, item: it, a: it.artist, t: it.title, price: it.price,
|
||||||
s: band === 'GRAIL' ? '' : band, y: '',
|
s: band === 'GRAIL' ? '' : band, y: '',
|
||||||
cond: it.condition || '', sleeveCond: it.sleeve_cond || '',
|
cond: it.condition || '', sleeveCond: it.sleeve_cond || '',
|
||||||
|
band: it.price_band || null, // [R32] canonical band for the guide stamp + the sell basis
|
||||||
rare: it.price_band === 'grail' });
|
rare: it.price_band === 'grail' });
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// open({ seed, count, shopName, shop, stockAdapter, onClose, onBuy, getCash, emitters })
|
// open({ seed, count, shopName, shop, stockAdapter, onClose, onBuy, getCash, emitters, gone })
|
||||||
// `emitters` [R28, OPTIONAL] — pass `room.audio.emitters` and the till rings from the counter at the
|
// `emitters` [R28, OPTIONAL] — pass `room.audio.emitters` and the till rings from the counter at the
|
||||||
// right distance. Omit it and the till still rings, just un-positioned. Never required.
|
// right distance. Omit it and the till still rings, just un-positioned. Never required.
|
||||||
|
// `gone` [R32, OPTIONAL] — a Set of THIS bin's already-pulled slots ('sku:<id>' for pack items, the
|
||||||
|
// stringified offer index for parody), from the save's pull records. Omission happens AFTER the full
|
||||||
|
// seeded draw (both paths), so the survivors keep their slots — the R27 zero-collateral law. Absent
|
||||||
|
// (?game=0 / classic / fresh day) ⇒ the bin riffles exactly as it always did.
|
||||||
function open(opts = {}) {
|
function open(opts = {}) {
|
||||||
active = true;
|
active = true;
|
||||||
onCloseCb = opts.onClose || null; onBuyCb = opts.onBuy || null; getCashCb = opts.getCash || null;
|
onCloseCb = opts.onClose || null; onBuyCb = opts.onBuy || null; getCashCb = opts.getCash || null;
|
||||||
emitters = opts.emitters || null;
|
emitters = opts.emitters || null;
|
||||||
const count = opts.count || 16;
|
const count = opts.count || 16;
|
||||||
|
const gone = opts.gone instanceof Set && opts.gone.size ? opts.gone : null;
|
||||||
// riffle items: real pack (?stock=real) via the stockAdapter seam, else seeded procedural sleeves.
|
// riffle items: real pack (?stock=real) via the stockAdapter seam, else seeded procedural sleeves.
|
||||||
const src = opts.stockAdapter && opts.stockAdapter(opts.shop, 'sleeve');
|
const src = opts.stockAdapter && opts.stockAdapter(opts.shop, 'sleeve');
|
||||||
offers = (src && src.real) ? pickRealOffers(src.pack, opts.seed ?? 1, count) : proceduralSleeves(opts.seed ?? 1, count);
|
offers = (src && src.real) ? pickRealOffers(src.pack, opts.seed ?? 1, count, gone)
|
||||||
|
: proceduralSleeves(opts.seed ?? 1, count).filter((o) => !(gone && gone.has(String(o.i))));
|
||||||
curLbl.textContent = (opts.shopName ? opts.shopName + ' — ' : '');
|
curLbl.textContent = (opts.shopName ? opts.shopName + ' — ' : '');
|
||||||
[hint, curLbl, outBtn].forEach(e => e.style.display = 'block');
|
[hint, curLbl, outBtn].forEach(e => e.style.display = 'block');
|
||||||
buildStack(offers);
|
buildStack(offers);
|
||||||
@ -411,7 +439,16 @@ export function createDig(THREE, renderer) {
|
|||||||
[css, hint, curLbl, panel, outBtn, toast].forEach(e => e.remove());
|
[css, hint, curLbl, panel, outBtn, toast].forEach(e => e.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
return { scene, camera, open, close, update, dispose, get active() { return active; } };
|
// [R32 — the honest-gate surface, R24 precedent] What is in THIS open bin, BY IDENTITY: original
|
||||||
|
// offer index, asking, band, sku (pack items). Read-only snapshots; null when closed. F's bible/
|
||||||
|
// scarcity gate asserts on these instead of trusting "the crate looked right".
|
||||||
|
function currentOffers() {
|
||||||
|
if (!active) return null;
|
||||||
|
return recs.map((r) => ({ i: r.offer.i ?? null, price: r.offer.price, band: r.offer.band || null,
|
||||||
|
sku: r.offer.item ? String(r.offer.item.id) : null }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { scene, camera, open, close, update, dispose, currentOffers, get active() { return active; } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// A stable per-bin seed from the shop + a bin index, so a given bin riffles the same order every time.
|
// A stable per-bin seed from the shop + a bin index, so a given bin riffles the same order every time.
|
||||||
|
|||||||
@ -13,15 +13,20 @@
|
|||||||
// (?classic=1 / ?game=0) → the consumer never constructs/opens this, and open() itself returns false
|
// (?classic=1 / ?game=0) → the consumer never constructs/opens this, and open() itself returns false
|
||||||
// on an empty sellable list — zero errors, zero DOM.
|
// on an empty sellable list — zero errors, zero DOM.
|
||||||
|
|
||||||
|
import { sellBasis, guideText } from './bible.js'; // [R32] THE BIBLE — the §9.1 beta basis + the card's guide line
|
||||||
|
|
||||||
export const SELL_MULT = 0.5; // the keeper pays half of what he'll sticker it at (§9.1 — he eats too)
|
export const SELL_MULT = 0.5; // the keeper pays half of what he'll sticker it at (§9.1 — he eats too)
|
||||||
export const SELL_DIST = 2.0; // metres from room.counter.pose (bench centre, room-local)
|
export const SELL_DIST = 2.0; // metres from room.counter.pose (bench centre, room-local)
|
||||||
|
|
||||||
// The offer (§9.1). Alpha basis = pricePaid (the buy-side price the player actually faced — no guide
|
// The offer (§9.1). [R32 — the pre-ratified beta swap, executed] Basis = bandLow when the entry carries
|
||||||
// bands until beta; beta swaps the basis to bandLow, the clamp survives untouched). The min(p−1, …)
|
// a band the BIBLE knows (sellBasis, bible.js), else the alpha basis (pricePaid) — so alpha-era finds
|
||||||
// clamp is the law: no retune of SELL_MULT can ever push an offer to break even. p=1 → $0 (the SELL
|
// sell exactly as they always did. THE CLAMP SHAPE IS UNTOUCHED: the offer sits strictly below the
|
||||||
// button disables — the keeper won't buy what he can't sticker).
|
// BASIS for every basis ≥ 1, which with bandLow as basis is the no-pump law's beta form — "every sell
|
||||||
export function sellOffer(pricePaid) {
|
// price < the buy-side guide band, structurally" (charter law #2). A gem (bought UNDER band) nets its
|
||||||
const p = Math.floor(+pricePaid || 0);
|
// finder margin against the band, never against the keeper; scarcity (the save's pulls) makes that
|
||||||
|
// margin a finite find, not a loop. p=1 → $0 (the SELL button disables).
|
||||||
|
export function sellOffer(basis) {
|
||||||
|
const p = Math.floor(+basis || 0);
|
||||||
if (p <= 0) return 0;
|
if (p <= 0) return 0;
|
||||||
return Math.min(p - 1, Math.max(1, Math.floor(p * SELL_MULT)));
|
return Math.min(p - 1, Math.max(1, Math.floor(p * SELL_MULT)));
|
||||||
}
|
}
|
||||||
@ -92,15 +97,16 @@ export function createSell() {
|
|||||||
function render() {
|
function render() {
|
||||||
const it = items[idx];
|
const it = items[idx];
|
||||||
if (!it) { close(); return; }
|
if (!it) { close(); return; }
|
||||||
const offer = sellOffer(it.pricePaid != null ? it.pricePaid : it.price);
|
const offer = sellOffer(sellBasis(it)); // [R32] bandLow basis when banded, pricePaid else
|
||||||
const head = labelOf(it) || titleOf(it);
|
const head = labelOf(it) || titleOf(it);
|
||||||
const ttl = labelOf(it) ? titleOf(it) : '';
|
const ttl = labelOf(it) ? titleOf(it) : '';
|
||||||
const paid = it.pricePaid != null ? it.pricePaid : it.price;
|
const paid = it.pricePaid != null ? it.pricePaid : it.price;
|
||||||
|
const guide = guideText(it.type, it.band); // [R32] '' when unbanded → no stray separator (§7.2c)
|
||||||
panel.innerHTML = `<div class="x">✕</div>`
|
panel.innerHTML = `<div class="x">✕</div>`
|
||||||
+ `<div class="sticker">$${esc(offer)}</div>`
|
+ `<div class="sticker">$${esc(offer)}</div>`
|
||||||
+ `<h3>${esc(head)}</h3>`
|
+ `<h3>${esc(head)}</h3>`
|
||||||
+ (ttl ? `<div class="ttl">“${esc(ttl)}”</div>` : '')
|
+ (ttl ? `<div class="ttl">“${esc(ttl)}”</div>` : '')
|
||||||
+ `<div class="meta">${paid != null ? 'you paid $' + esc(paid) : ''}${it.dayFound != null ? ' · day ' + esc(it.dayFound) : ''}</div>`
|
+ `<div class="meta">${paid != null ? 'you paid $' + esc(paid) : ''}${guide ? ' · ' + esc(guide) : ''}${it.dayFound != null ? ' · day ' + esc(it.dayFound) : ''}</div>`
|
||||||
+ (items.length > 1 ? `<div class="nav"><span class="pv">‹</span>${idx + 1} of ${items.length}<span class="nx">›</span></div>` : '')
|
+ (items.length > 1 ? `<div class="nav"><span class="pv">‹</span>${idx + 1} of ${items.length}<span class="nx">›</span></div>` : '')
|
||||||
+ `<button ${offer <= 0 ? 'disabled' : ''}>${offer <= 0 ? 'NOT WORTH BUYING' : 'SELL — $' + esc(offer)}</button>`;
|
+ `<button ${offer <= 0 ? 'disabled' : ''}>${offer <= 0 ? 'NOT WORTH BUYING' : 'SELL — $' + esc(offer)}</button>`;
|
||||||
panel.style.display = 'block';
|
panel.style.display = 'block';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user