// ─── Coin Writer v5: literary + factual balance ─── let gradeScore = () => -1 function setGradeScore(fn) { gradeScore = fn } function hash(str) { let h = 0 for (let i = 0; i < str.length; i++) h = ((h << 5) - h + str.charCodeAt(i)) | 0 return Math.abs(h) } function pick(arr, seed) { return arr[seed % arr.length] } function q(name) { return `«${name}»` } const PERIODS = [ { re: /николай\s*ii|николая\s*ii/i, texts: [ 'Последний российский император — его монеты неизменно возглавляют аукционные продажи.', 'Эпоха Николая II: Транссибирская магистраль, первые автомобили — и монеты, ставшие символом ушедшей империи.', 'Монеты Николая II коллекционируются по всему миру. Спрос не ослабевает десятилетиями.', 'Закат Российской империи. Его монеты — одни из самых собираемых в русской нумизматике.', ]}, { re: /пётр|петр\s*i|петра\s*i/i, from: 1682, to: 1725, texts: [ 'Пётр Великий перекроил Россию — от алфавита до монетной системы.', 'Царь, построивший новую столицу на болотах. Его монеты — среди самых желанных.', 'Реформатор, открывший России путь в Европу. Коллекционный спрос стабильно высокий.', ]}, { re: /екатерин/i, from: 1762, to: 1796, texts: [ 'При Екатерине II территория империи выросла на 500 тыс. кв. км. Монеты этого периода — классика.', 'Золотой век. 34 года правления и богатое нумизматическое наследие.', ]}, { re: /александр\s*ii/i, from: 1855, to: 1881, texts: [ 'Царь-Освободитель: отмена крепостного права, судебная реформа. Спрос на его монеты растёт.', ]}, { re: /александр\s*iii/i, from: 1881, to: 1894, texts: [ '13 лет на троне. Единственный император, при котором Россия не вела ни одной войны. Монет — немного.', ]}, { re: /павел/i, from: 1796, to: 1801, texts: [ '5 лет правления Павла I — и ограниченная чеканка. Каждый экземпляр нечаст.', ]}, { re: /анна.*иоанн/i, from: 1730, to: 1740, texts: [ 'Анна Иоанновна — эпоха дворцовых интриг. Монеты с особой притягательностью.', ]}, { re: /елизавет/i, from: 1741, to: 1762, texts: [ 'Дочь Петра Великого. При Елизавете расцвело монетное дело — изысканная чеканка, редкие тиражи.', ]}, { re: /финлянд/i, texts: [ 'Русская Финляндия — обособленная серия. Монеты чеканились для Великого княжества и обращались только на его территории.', 'Монеты Великого княжества Финляндского — компактная серия с преданным кругом ценителей.', ]}, { re: /римск|roman|денарий|антониниан/i, texts: [ 'Рим: легионы, акведуки, Колизей — и монеты, пережившие саму империю. Глобальный рынок растёт на 8-12% в год.', 'Монета цивилизации, построившей дороги, по которым ходят до сих пор.', ]}, { re: /греч|greek|драхм|обол|тетра/i, texts: [ 'Древняя Греция: Сократ, Олимпийские игры и первые в истории монеты с портретами. Рынок растёт до 15% ежегодно.', ]}, { re: /боспор|пантикапей/i, texts: [ 'Боспорское царство — античный Крым. Монеты Пантикапея — растущая ниша для ценителей причерноморской истории.', ]}, { re: /визант|byzant/i, texts: [ 'Византия просуществовала тысячу лет — дольше любой европейской империи. Доступная и перспективная античность.', ]}, { re: /осман|ottoman/i, texts: [ 'Османская империя раскинулась от Дуная до Аравии. Пока недооценённый, но быстро растущий сегмент.', ]}, { re: /сефевид|safavid/i, texts: [ 'Сефевидская Персия — империя шахов, объединившая Иран. Монеты этой династии встречаются всё реже.', ]}, { re: /смутн|владислав|лжедмитри/i, texts: [ 'Смутное время — самые драматичные годы русской истории. Эти монеты — нумизматическая элита.', 'Между царствами, между эпохами. Монеты Смутного времени доступны единицам.', ]}, { re: /медный бунт/i, texts: [ 'Медный бунт 1662 года: народное восстание против обесценивания денег. Монеты-свидетели этих событий крайне редки.', ]}, { re: /сибирск|сузун/i, texts: [ 'Сибирские монеты чеканились из меди Колывано-Воскресенских заводов с примесью серебра и золота. Ходили только за Уралом.', ]}, { re: /георгий победоносец/i, texts: [ '«Георгий Победоносец» — самая ликвидная монета из драгметаллов в России. Принимается в любом банке.', ]}, { re: /1921|1922|1923|1924|1925|1926|1927|1928|1929|1930|1931/i, materialRe: /серебро|silver/, texts: [ 'Раннее СССР серебро: последний период серебра в обращении. Большинство изъято и переплавлено в 1930-х — уцелевшие экземпляры особенно ценны.', ]}, ] function generateCoinEmail(coin, details, score) { const name = coin.name || '' const mat = (details.material || '').toLowerCase() const year = details.year_from || 0 const age = year ? 2026 - year : 0 const price = coin.price || 0 const grade = details.grade || '' const gs = gradeScore(grade) const weightG = parseFloat(details.weight) || 0 const isSilver = /серебро|silver/.test(mat) const isGold = /золото|gold/.test(mat) const isPrecious = isSilver || isGold const h = hash(coin.id || name) const totalAge = year < 0 ? Math.abs(year) + 2026 : age const p1 = [] const p2 = [] const p3 = [] // ══════════ P1: HOOK — литературно + факт ══════════ if (totalAge > 2000) { p1.push(pick([ `${q(name)}. Этой монете более ${totalAge} лет — она старше большинства государств на карте мира. Когда её чеканили, ещё существовала Римская империя.`, `Представьте: ${totalAge} лет назад кто-то расплатился этой монетой на рыночной площади древнего города. ${q(name)} — подлинный артефакт, переживший тысячелетия.`, `${q(name)}. ${totalAge} лет истории в одном небольшом кружке металла. Монета, к которой прикасались люди из совершенно другого мира.`, ], h)) } else if (totalAge > 400) { p1.push(pick([ `${q(name)} — ${totalAge} лет истории. Монета, которая пережила империи, войны и революции, и дошла до наших дней.`, `${totalAge} лет назад мастер ударил штемпелем по заготовке — так появилась ${q(name)}. С тех пор мир изменился до неузнаваемости.`, `${q(name)}. ${totalAge} лет — и монета по-прежнему существует. Каждая её потёртость — след чьей-то жизни, давно забытой историей.`, ], h)) } else if (age > 200) { p1.push(pick([ `${q(name)} — больше ${Math.floor(age / 100)} столетий истории. Монета из мира без электричества и фотографии, дошедшая до наших дней${gs >= gradeScore('VF') ? ' в достойном состоянии' : ''}.`, `${q(name)}. ${age} лет назад она была частью чьей-то повседневности. Сегодня — коллекционная ценность.`, `Больше ${Math.floor(age / 100)} веков назад эта монета зазвенела впервые. ${q(name)} — осколок эпохи, которую мы знаем лишь по книгам.`, ], h)) } else if (age > 100) { p1.push(pick([ `${q(name)}. ${year} год — мир на пороге грандиозных перемен.${isPrecious ? ` ${isGold ? 'Золотая' : 'Серебряная'} монета, ценная и как металл, и как предмет коллекционирования.` : ''}`, `${q(name)} — больше века истории. Монета, отчеканенная когда мир был совсем другим.${isPrecious ? ` ${isGold ? 'Золото' : 'Серебро'}.` : ''}`, ], h)) } else { p1.push(`${q(name)}.${year ? ` ${year} год.` : ''}${isPrecious ? ` ${isGold ? 'Золотая' : 'Серебряная'} монета.` : ''}`) } // Period context for (const period of PERIODS) { if (!period.re.test(name)) continue if (period.from && year && (year < period.from || year > period.to)) continue if (period.materialRe && !period.materialRe.test(mat)) continue p1.push(pick(period.texts, h + 7)) break } // ══════════ CONTENT BLOCKS — independent, shuffleable ══════════ const blocks = [] // Block: Grade if (gs >= gradeScore('Proof')) { blocks.push(pick([ 'Качество Proof — зеркальная поверхность, матовый рельеф. Безупречна в каждой детали.', 'Proof-чекан: полированные штемпели, идеальная заготовка — совершенство линий.', 'Proof — высшая категория качества. Зеркальное поле, матовый рельеф.', ], h + 10)) } else if (gs >= gradeScore('UNC')) { blocks.push(pick([ `Сохранность UNC — не была в обращении. Полный штемпельный блеск.${age > 50 ? ` Для ${age}-летней монеты — редкость.` : ''}`, `UNC — как в день чеканки.${age > 100 ? ` ${age} лет — и ни следа износа.` : ' Ни единого следа времени.'}`, `Не была в обращении (UNC). Оригинальный блеск, безупречный рельеф.${age > 50 ? ` Через ${age} лет — впечатляет.` : ''}`, ], h + 10)) } else if (gs >= gradeScore('AU')) { blocks.push(pick([ `AU — почти идеальное состояние. Едва заметные следы на выступающих точках.${age > 100 ? ` Для ${age}-летней монеты — отличный результат.` : ''}`, `AU — на грани между обращением и совершенством.${age > 100 ? ` Через ${age} лет — это удача.` : ''}`, `Almost Uncirculated. Минимальный износ.${age > 100 ? ` ${age} лет — и такое состояние.` : ' Блеск сохранён.'}`, ], h + 10)) } else if (gs >= gradeScore('XF')) { blocks.push(pick([ `XF — чёткий рельеф, все детали выразительны.${age > 200 ? ` Для ${age}-летней монеты — впечатляюще. Большинство ровесников дошли в худшем виде.` : ''}`, `Extremely Fine.${age > 200 ? ` ${age} лет — а рисунок практически полный.` : ' Лёгкий износ, все элементы на месте.'}`, `XF — высокая сохранность.${age > 200 ? ` Для монеты ${age}-летней давности — нечастый грейд.` : ''}`, ], h + 10)) } else if (gs >= gradeScore('VF')) { blocks.push(`Сохранность ${grade}.${age > 200 ? ` Для ${age}-летней монеты — достойно.` : ''}`) } // Block: Material + melt if (isSilver && weightG > 0 && price > 0) { const melt = Math.round(weightG * 200) const ratio = Math.round(melt / price * 100) if (ratio > 100) { blocks.push(pick([ `Серебро (${weightG}г) стоит ~${melt}₽ по курсу ЦБ — а монета продаётся дешевле. Такие аномалии встречаются нечасто.`, `${weightG}г серебра ≈ ${melt}₽. Стоимость металла превышает стоимость монеты.`, `Серебра внутри на ${melt}₽ — дороже самой монеты. Арифметика на стороне покупателя.`, ], h + 12)) } else if (ratio > 60) { blocks.push(pick([ `${weightG}г серебра (~${melt}₽) покрывают ${ratio}% стоимости. Надёжный фундамент.`, `Серебро (${weightG}г, ~${melt}₽) — ${ratio}% от стоимости. Остальное — коллекционная премия.`, ], h + 12)) } else { blocks.push(`Серебро, ${weightG}г. Драгоценный металл обеспечивает базовую поддержку стоимости.`) } } else if (isSilver) { blocks.push(pick([ 'Серебряная монета — ценность, проверенная веками.', 'Серебро — классика нумизматики.', 'Серебро обеспечивает ликвидность — драгоценный металл всегда найдёт покупателя.', ], h + 12)) } else if (isGold) { blocks.push(pick([ 'Золото — металл, который ценили во все времена и во всех цивилизациях.', 'Золотая монета — и артефакт, и ценность, проверенная тысячелетиями.', 'Золото не подвластно времени. Металл королей и императоров.', ], h + 12)) } // Block: Scarcity if (age > 200) { blocks.push(pick([ `Предложение монет ${age > 300 ? 'этой эпохи' : 'этого периода'} сокращается — утраты и оседание в коллекциях делают каждый экземпляр ценнее.`, `Число сохранившихся экземпляров может только уменьшаться. Через десять лет найти аналог будет сложнее.`, `Каждый экземпляр, уходящий в коллекцию, сужает предложение. Процесс необратим.`, ], h + 15)) } else if (age > 100) { blocks.push(pick([ 'Монет этого периода на рынке меньше с каждым десятилетием.', 'Доступных экземпляров всё меньше — их больше не чеканят.', ], h + 15)) } // Block: Collector appeal if (isPrecious && gs >= gradeScore('VF')) { blocks.push(pick([ 'Драгоценный металл и хорошая сохранность — сочетание, которое ценилось во все времена.', 'Металл и состояние — два главных фактора в нумизматике. Оба на месте.', 'Сохранность и драгоценный металл — то, за чем охотятся коллекционеры.', 'Коллекционеры ценят прежде всего металл и состояние. Здесь — и то, и другое.', ], h + 16)) } // Block: Sensory if (h % 3 === 0) { if (age > 300 && !isGold) blocks.push(pick(['Благородная патина подтверждает подлинность и придаёт монете неповторимый характер.', 'Следы ручной чеканки — каждая такая монета уникальна, двух одинаковых не существует.'], h + 18)) else if (isSilver && weightG > 3) blocks.push(pick(['Приятная тяжесть настоящего серебра в ладони — ощущение, знакомое коллекционерам по всему миру.', 'Характерный холод серебра в руке. Подлинность чувствуется на ощупь.'], h + 18)) else if (isGold) blocks.push(pick(['Тёплый блеск золота, который не тускнеет веками.', 'Особая тяжесть золота в руке — ощущение, которое ни с чем не спутать.'], h + 18)) } // Block: Collecting context — почему коллекционеры собирают именно такие if (h % 3 === 1) { if (age > 300) { blocks.push(pick([ 'Коллекционирование античных монет — одно из старейших хобби в мире. Им увлекались Петрарка, Медичи и российские императоры.', 'Античная нумизматика — область, где каждая монета является историческим документом. Надписи, портреты, символы рассказывают о правителях и событиях.', 'Каждая античная монета чеканилась вручную — поэтому двух абсолютно одинаковых не существует. Каждый экземпляр уникален.', ], h + 19)) } else if (year >= 1700 && year <= 1917) { blocks.push(pick([ 'Монеты Российской империи — одно из самых популярных направлений в отечественной нумизматике. Спрос стабилен и предсказуем.', 'Имперские монеты привлекают коллекционеров сочетанием доступности и исторической глубины. Многие серии можно собирать по годам, монетным дворам и разновидностям.', 'Русская нумизматика XVIII-XIX веков — направление с устоявшимся рынком, каталогами и аукционной историей. Риск приобрести подделку минимален при покупке у проверенных дилеров.', ], h + 19)) } else if (/серебро|silver/.test(mat)) { blocks.push(pick([ 'Серебряные монеты — один из самых ликвидных сегментов нумизматики. Их легко оценить, легко продать, легко хранить.', 'Коллекционирование серебряных монет совмещает эстетическое удовольствие и рациональный подход к сохранению ценности.', ], h + 19)) } } // Block: Technical facts — интересные факты о чеканке if (h % 3 === 2) { if (isSilver && weightG > 0) { blocks.push(pick([ `Вес монеты — ${weightG}г. В эпоху чеканки вес строго контролировался: отклонение означало фальсификацию и каралось по закону.`, `${weightG}г серебра. Монетная стопа — соотношение веса монеты к номиналу — тщательно регулировалась государством.`, `Серебро ${weightG}г. Каждая монета проходила весовой контроль — это было гарантией доверия к денежной системе.`, ], h + 20)) } else if (isGold && weightG > 0) { blocks.push(pick([ `Вес: ${weightG}г золота. Золотые монеты чеканились с особой точностью — даже минимальное отклонение веса было недопустимо.`, `${weightG}г золота. Золотая монета — это не просто деньги, это государственная гарантия пробы и веса.`, ], h + 20)) } else if (age > 200) { blocks.push(pick([ 'В эпоху чеканки этой монеты каждый экземпляр проходил ручной контроль качества. Брак уничтожался и переплавлялся.', 'Монетное дело в ту эпоху было одной из важнейших государственных функций — от качества монеты зависело доверие к экономике.', ], h + 20)) } } // Block: Gift / collection starter if (h % 5 === 0 && age > 50) { blocks.push(pick([ 'Такая монета может стать центральным экземпляром коллекции или запоминающимся подарком для ценителя истории.', 'Монета с историей — подарок, который не теряет значения с годами. Напротив — только набирает.', 'Отличный экземпляр для начала коллекции или пополнения существующей.', ], h + 21)) } // Block: Provenance / geography if (h % 5 === 1) { if (/москв|moscow|ммд/i.test(name)) blocks.push('Отчеканена на Московском монетном дворе — одном из старейших в России.') else if (/петербург|спб|спмд|ленинград/i.test(name)) blocks.push('Чеканка Санкт-Петербургского монетного двора — предприятия, основанного Петром I в 1724 году.') else if (/рига|riga/i.test(name)) blocks.push('Рижский монетный двор чеканил монету на протяжении нескольких столетий для разных государств.') else if (/сузун/i.test(name)) blocks.push('Сузунский монетный двор на Алтае работал с 1763 по 1847 год — единственное подобное предприятие за Уралом.') } // ══════════ SHUFFLE (grade stays first, rest randomized) ══════════ const first = blocks.shift() || '' const rest = [...blocks] for (let i = rest.length - 1; i > 0; i--) { const j = (h + i * 13) % (i + 1) ;[rest[i], rest[j]] = [rest[j], rest[i]] } const body = [first, ...rest].filter(Boolean) const mid = Math.ceil(body.length / 2) return [p1.join(' '), body.slice(0, mid).join(' '), body.slice(mid).join(' ')].filter(p => p && p.trim()).join('\n\n') } module.exports = { generateCoinEmail, setGradeScore }