// Shared LOT price-quality indicator, used everywhere price_quality is // displayed (pricelist detail page, configurator search dropdown, configurator // table, pricing tab). price_quality is a 0-9 score set by the external pricing // tool (see bible-local/03-database.md, qt_pricelist_items.price_quality) — QF // only displays it, never computes it. Single source of the visual scale so // every view stays consistent. // // Two render modes, chosen per client via window.QF_INDICATOR_MODE (injected by // base.html from app_settings, key indicator_mode): // "color" (default) — hue scale, 0 = red, 5 = yellow, 9 = green. // "accessible" — colour-blind-safe 5-step signal-strength meter, // single neutral hue, magnitude encoded by bar count. // See bible-local/decisions/2026-08-31-accessible-indicator-mode.md. (function () { const RED = [220, 38, 38]; const YELLOW = [234, 179, 8]; const GREEN = [22, 163, 74]; function isAccessible() { return window.QF_INDICATOR_MODE === 'accessible'; } function lerp(c1, c2, t) { return c1.map((v, i) => Math.round(v + (c2[i] - v) * t)); } // Returns "r, g, b" (no rgb() wrapper) so callers can build rgb()/rgba() themselves. function priceQualityRgb(quality) { if (typeof quality !== 'number' || Number.isNaN(quality)) return null; const q = Math.max(0, Math.min(9, quality)); const [r, g, b] = q <= 5 ? lerp(RED, YELLOW, q / 5) : lerp(YELLOW, GREEN, (q - 5) / 4); return `${r}, ${g}, ${b}`; } function priceQualityColor(quality) { const rgb = priceQualityRgb(quality); return rgb ? `rgb(${rgb})` : null; } // 0-9 score -> 0-4 meter level. 0-1 / 2-3 / 4-5 / 6-7 / 8-9. function priceQualityLevel(quality) { if (typeof quality !== 'number' || Number.isNaN(quality)) return null; const q = Math.max(0, Math.min(9, quality)); return Math.min(4, Math.floor(q / 2)); } // Monochrome 5-step signal-strength meter. size: undefined | 'sm'. function qualityMeterHtml(quality, opts) { const level = priceQualityLevel(quality); if (level === null) return ''; const size = opts && opts.size === 'sm' ? ' data-size="sm"' : ''; const title = `Качество цены: ${quality}/9`; let bars = ''; for (let i = 0; i < 5; i++) { bars += ``; } return `${bars}`; } // Small indicator for compact contexts (search dropdown, configurator table cell). function qualityDotHtml(quality) { if (isAccessible()) return qualityMeterHtml(quality, { size: 'sm' }); const color = priceQualityColor(quality); if (!color) return ''; return ``; } // Indicator for a dedicated "quality" column/cell. function qualityBadgeHtml(quality) { if (typeof quality !== 'number') return '-'; if (isAccessible()) return qualityMeterHtml(quality, { size: 'sm' }); const color = priceQualityColor(quality); return `${quality}`; } // Light row-tint background for table rows, e.g. the pricing tab. Suppressed // in accessible mode — the pricing tab carries its own leading meter column there. function qualityRowStyle(quality) { if (isAccessible()) return ''; const rgb = priceQualityRgb(quality); return rgb ? `background-color: rgba(${rgb}, 0.10)` : ''; } window.priceQualityColor = priceQualityColor; window.priceQualityLevel = priceQualityLevel; window.qualityMeterHtml = qualityMeterHtml; window.qualityDotHtml = qualityDotHtml; window.qualityBadgeHtml = qualityBadgeHtml; window.qualityRowStyle = qualityRowStyle; })();