Initial commit: ASPEKTER — визуальный конструктор email-рассылок
- z51-pug-builder: Svelte 5 SPA, визуальный редактор Pug-писем - email-gen: Node.js рендерер Pug→HTML через email-templates + Juice - email-gen-api: HTTP сервер рендеринга (порт 8787) - coin-scout: сервис подбора монет из фидов - Docker Compose для dev/prod - Nginx конфиг с SSL для app.aspekter.ru
This commit is contained in:
8
coin-scout/Dockerfile
Normal file
8
coin-scout/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package.json .
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN mkdir -p data
|
||||
EXPOSE 5180
|
||||
CMD ["node", "server.js"]
|
||||
311
coin-scout/coin-writer.js
Normal file
311
coin-scout/coin-writer.js
Normal file
@@ -0,0 +1,311 @@
|
||||
// ─── 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 }
|
||||
14
coin-scout/package.json
Normal file
14
coin-scout/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "coin-scout",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"better-sqlite3": "^11.0.0",
|
||||
"node-cron": "^3.0.3",
|
||||
"node-html-parser": "^6.1.13"
|
||||
}
|
||||
}
|
||||
768
coin-scout/public/index.html
Normal file
768
coin-scout/public/index.html
Normal file
@@ -0,0 +1,768 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Coin Scout</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; color: #e1e4e8; }
|
||||
a { color: #58a6ff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
.header { background: #161b22; border-bottom: 1px solid #30363d; padding: 16px 24px; display: flex; align-items: center; gap: 16px; }
|
||||
.header h1 { font-size: 20px; font-weight: 600; }
|
||||
.header .stats { font-size: 13px; color: #8b949e; margin-left: auto; }
|
||||
|
||||
.toolbar { background: #161b22; border-bottom: 1px solid #30363d; padding: 12px 24px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
||||
.toolbar label { font-size: 13px; color: #8b949e; }
|
||||
.toolbar input, .toolbar select { background: #0d1117; border: 1px solid #30363d; color: #e1e4e8; padding: 6px 10px; border-radius: 6px; font-size: 13px; }
|
||||
.toolbar input:focus, .toolbar select:focus { border-color: #58a6ff; outline: none; }
|
||||
.toolbar input[type="number"] { width: 80px; }
|
||||
|
||||
.btn { background: #21262d; border: 1px solid #30363d; color: #e1e4e8; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
||||
.btn:hover { background: #30363d; }
|
||||
.btn-primary { background: #238636; border-color: #238636; }
|
||||
.btn-primary:hover { background: #2ea043; }
|
||||
.btn-scan { background: #1f6feb; border-color: #1f6feb; }
|
||||
.btn-scan:hover { background: #388bfd; }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.tabs { display: flex; gap: 0; border-bottom: 1px solid #30363d; background: #161b22; padding: 0 24px; }
|
||||
.tab { padding: 10px 16px; font-size: 14px; color: #8b949e; cursor: pointer; border-bottom: 2px solid transparent; }
|
||||
.tab:hover { color: #e1e4e8; }
|
||||
.tab.active { color: #e1e4e8; border-bottom-color: #f78166; }
|
||||
|
||||
.content { padding: 16px 24px; }
|
||||
|
||||
.coin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 12px; }
|
||||
.coin-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px; display: flex; gap: 12px; transition: border-color 0.2s; }
|
||||
.coin-card:hover { border-color: #58a6ff; }
|
||||
.coin-card.top { border-color: #f0883e; }
|
||||
.coin-img { width: 80px; height: 80px; border-radius: 6px; object-fit: cover; background: #0d1117; flex-shrink: 0; }
|
||||
.coin-info { flex: 1; min-width: 0; }
|
||||
.coin-name { font-size: 14px; font-weight: 500; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
||||
.coin-meta { font-size: 12px; color: #8b949e; display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
|
||||
.coin-meta .tag { background: #21262d; padding: 2px 6px; border-radius: 4px; }
|
||||
.coin-meta .tag.silver { background: #1c3a5a; color: #79c0ff; }
|
||||
.coin-meta .tag.gold { background: #3d2e00; color: #e3b341; }
|
||||
.coin-meta .tag.grade-high { background: #1a4731; color: #56d364; }
|
||||
.coin-meta .tag.grade-mid { background: #3d2e00; color: #e3b341; }
|
||||
.coin-meta .tag.no-stock { background: #490202; color: #f85149; }
|
||||
.coin-price { font-size: 16px; font-weight: 600; color: #56d364; margin-top: 6px; }
|
||||
.coin-price .old { font-size: 12px; color: #8b949e; text-decoration: line-through; margin-left: 6px; font-weight: 400; }
|
||||
.coin-score { font-size: 11px; color: #f0883e; font-weight: 600; margin-top: 4px; }
|
||||
.coin-feed { font-size: 11px; color: #8b949e; }
|
||||
|
||||
.scan-bar { background: #1c2128; border: 1px solid #30363d; border-radius: 8px; padding: 14px; margin-bottom: 16px; display: flex; align-items: center; gap: 12px; }
|
||||
.scan-bar .status { font-size: 13px; }
|
||||
.scan-bar .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
||||
.dot.green { background: #56d364; }
|
||||
.dot.yellow { background: #e3b341; animation: pulse 1s infinite; }
|
||||
.dot.red { background: #f85149; }
|
||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
||||
|
||||
.settings-panel { max-width: 500px; }
|
||||
.settings-panel .field { margin-bottom: 14px; }
|
||||
.settings-panel .field label { display: block; font-size: 13px; color: #8b949e; margin-bottom: 4px; }
|
||||
.settings-panel .field input, .settings-panel .field select { width: 100%; }
|
||||
|
||||
.empty { text-align: center; padding: 60px; color: #8b949e; }
|
||||
.empty p { margin-top: 8px; }
|
||||
|
||||
.loading { text-align: center; padding: 40px; color: #8b949e; }
|
||||
|
||||
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 20px; }
|
||||
.modal { background: #161b22; border: 1px solid #30363d; border-radius: 12px; max-width: 650px; width: 100%; max-height: 85vh; overflow-y: auto; padding: 24px; position: relative; }
|
||||
.modal-close { position: absolute; top: 12px; right: 16px; background: none; border: none; color: #8b949e; font-size: 20px; cursor: pointer; }
|
||||
.modal-close:hover { color: #e1e4e8; }
|
||||
.modal h3 { color: #e1e4e8; margin-bottom: 12px; padding-right: 30px; }
|
||||
.modal-score { font-size: 24px; font-weight: 700; color: #f0883e; margin-bottom: 16px; }
|
||||
.analysis-section { margin-bottom: 4px; }
|
||||
.analysis-section h4 { color: #58a6ff; font-size: 13px; margin: 12px 0 4px; }
|
||||
.analysis-text { color: #c9d1d9; font-size: 13px; line-height: 1.6; white-space: pre-line; }
|
||||
.analysis-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
|
||||
.analysis-tags .tag { background: #21262d; padding: 3px 8px; border-radius: 4px; font-size: 11px; color: #8b949e; }
|
||||
.coin-detail-btn { background: none; border: 1px solid #30363d; color: #58a6ff; font-size: 11px; padding: 2px 8px; border-radius: 4px; cursor: pointer; margin-top: 4px; }
|
||||
.coin-detail-btn:hover { background: #21262d; }
|
||||
|
||||
.progress-panel { background: #1c2128; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin-bottom: 16px; display: none; }
|
||||
.progress-panel.active { display: block; }
|
||||
.progress-bar-wrap { background: #21262d; border-radius: 4px; height: 8px; margin: 8px 0; overflow: hidden; }
|
||||
.progress-bar-fill { background: #1f6feb; height: 100%; border-radius: 4px; transition: width 0.3s; width: 0%; }
|
||||
.progress-log { font-family: monospace; font-size: 12px; color: #8b949e; max-height: 200px; overflow-y: auto; margin-top: 8px; }
|
||||
.progress-log div { padding: 2px 0; }
|
||||
.progress-log .ok { color: #56d364; }
|
||||
.progress-log .err { color: #f85149; }
|
||||
.progress-log .info { color: #58a6ff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Coin Scout</h1>
|
||||
<div class="stats" id="stats"></div>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-tab="hot">Горячие</div>
|
||||
<div class="tab" data-tab="new">Новые</div>
|
||||
<div class="tab" data-tab="dashboard">Дашборд</div>
|
||||
<div class="tab" data-tab="compare">Сравнение</div>
|
||||
<div class="tab" data-tab="all">Все</div>
|
||||
<div class="tab" data-tab="method">Методология</div>
|
||||
<div class="tab" data-tab="settings">Настройки</div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar" id="toolbar">
|
||||
<label>Макс. цена:
|
||||
<input type="number" id="f-price" value="3000" step="100" min="0">
|
||||
</label>
|
||||
<label>Мин. грейд:
|
||||
<select id="f-grade">
|
||||
<option value="">Любой</option>
|
||||
<option value="G">G</option>
|
||||
<option value="VG">VG</option>
|
||||
<option value="F">F</option>
|
||||
<option value="VF" selected>VF</option>
|
||||
<option value="XF">XF</option>
|
||||
<option value="AU">AU</option>
|
||||
<option value="UNC">UNC</option>
|
||||
<option value="Proof">Proof</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Материал:
|
||||
<select id="f-material">
|
||||
<option value="серебро,золото">Серебро/Золото</option>
|
||||
<option value="любой">Любой</option>
|
||||
<option value="серебро">Серебро</option>
|
||||
<option value="золото">Золото</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Магазин:
|
||||
<select id="f-feed">
|
||||
<option value="">Все</option>
|
||||
<option value="AT">numizm.at</option>
|
||||
<option value="KB">coinsbolhov.ru</option>
|
||||
<option value="RU">numizmat.ru</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" id="f-stock" checked> В наличии
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" id="f-dupes" checked> Без дублей
|
||||
</label>
|
||||
<button class="btn btn-primary" onclick="loadCoins()">Применить</button>
|
||||
<button class="btn btn-scan" id="btn-scan" onclick="startScan()">Сканировать</button>
|
||||
</div>
|
||||
|
||||
<div class="progress-panel" id="progress-panel">
|
||||
<div style="font-size: 14px; font-weight: 500;" id="progress-title">Сканирование...</div>
|
||||
<div class="progress-bar-wrap"><div class="progress-bar-fill" id="progress-bar"></div></div>
|
||||
<div class="progress-log" id="progress-log"></div>
|
||||
</div>
|
||||
<div class="content" id="content">
|
||||
<div class="loading">Загрузка...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentTab = 'hot'
|
||||
let settings = {}
|
||||
const coinStore = new Map()
|
||||
|
||||
// Modal
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.coin-detail-btn')
|
||||
if (btn) {
|
||||
const coin = coinStore.get(btn.dataset.coinId)
|
||||
if (coin) showAnalysisModal(coin)
|
||||
return
|
||||
}
|
||||
if (e.target.classList.contains('modal-overlay')) {
|
||||
e.target.remove()
|
||||
}
|
||||
})
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
const m = document.querySelector('.modal-overlay')
|
||||
if (m) m.remove()
|
||||
}
|
||||
})
|
||||
|
||||
function showAnalysisModal(c) {
|
||||
const overlay = document.createElement('div')
|
||||
overlay.className = 'modal-overlay'
|
||||
const tags = (c.reasons || []).map(r => `<span class="tag">${r}</span>`).join('')
|
||||
overlay.innerHTML = `
|
||||
<div class="modal">
|
||||
<button class="modal-close" onclick="this.closest('.modal-overlay').remove()">×</button>
|
||||
<h3>${c.name}</h3>
|
||||
<div class="modal-score">Score: ${c.score}</div>
|
||||
${c.image ? `<img src="${c.image}" style="width:120px;height:120px;object-fit:cover;border-radius:8px;margin-bottom:12px" onerror="this.style.display='none'">` : ''}
|
||||
<div style="margin-bottom:8px">
|
||||
<span style="color:#56d364;font-size:18px;font-weight:600">${c.price} ₽</span>
|
||||
${c.old_price && c.old_price > c.price ? `<span style="color:#8b949e;text-decoration:line-through;margin-left:8px">${c.old_price} ₽</span>` : ''}
|
||||
<span style="color:#8b949e;margin-left:12px;font-size:13px">${feedNames[c.feed] || c.feed}</span>
|
||||
</div>
|
||||
<div class="analysis-text">${c.analysis || ''}</div>
|
||||
${tags ? `<div class="analysis-tags">${tags}</div>` : ''}
|
||||
${c.emailCopy ? `
|
||||
<div style="margin-top:20px;border-top:1px solid #30363d;padding-top:16px">
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">
|
||||
<h4 style="color:#f0883e;margin:0;font-size:14px">Текст для рассылки</h4>
|
||||
<button class="btn" style="font-size:11px;padding:3px 10px" onclick="navigator.clipboard.writeText(this.closest('.modal').querySelector('.email-copy-text').innerText);this.textContent='Скопировано!';setTimeout(()=>this.textContent='Копировать',1500)">Копировать</button>
|
||||
</div>
|
||||
<div class="email-copy-text" style="background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:14px;font-size:13px;color:#e1e4e8;line-height:1.7;white-space:pre-line">${c.emailCopy}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
<div style="margin-top:16px">
|
||||
<a href="${c.url}" target="_blank" class="btn btn-primary" style="text-decoration:none;display:inline-block">Открыть на сайте</a>
|
||||
</div>
|
||||
</div>`
|
||||
document.body.appendChild(overlay)
|
||||
}
|
||||
|
||||
// Tabs
|
||||
document.querySelectorAll('.tab').forEach(t => {
|
||||
t.addEventListener('click', () => {
|
||||
document.querySelectorAll('.tab').forEach(x => x.classList.remove('active'))
|
||||
t.classList.add('active')
|
||||
currentTab = t.dataset.tab
|
||||
const noToolbar = ['settings', 'method', 'dashboard', 'compare']
|
||||
document.getElementById('toolbar').style.display = noToolbar.includes(currentTab) ? 'none' : 'flex'
|
||||
if (currentTab === 'settings') loadSettings()
|
||||
else if (currentTab === 'method') loadMethodology()
|
||||
else if (currentTab === 'dashboard') loadDashboard()
|
||||
else if (currentTab === 'compare') loadCompare()
|
||||
else if (currentTab === 'new') loadNew()
|
||||
else if (currentTab === 'all') loadAll()
|
||||
else loadCoins()
|
||||
})
|
||||
})
|
||||
|
||||
// Format helpers
|
||||
const feedNames = { AT: 'numizm.at', KB: 'coinsbolhov.ru', RU: 'numizmat.ru' }
|
||||
function materialClass(m) {
|
||||
if (!m) return ''
|
||||
const l = m.toLowerCase()
|
||||
if (/золото|gold/.test(l)) return 'gold'
|
||||
if (/серебро|silver|биллон/.test(l)) return 'silver'
|
||||
return ''
|
||||
}
|
||||
function gradeClass(g) {
|
||||
if (!g) return ''
|
||||
const gs = ['G','AG','VG','F','VF','XF','EF','AU','UNC','BU','Proof']
|
||||
const upper = g.toUpperCase()
|
||||
for (let i = gs.length - 1; i >= 0; i--) {
|
||||
if (upper.includes(gs[i])) return i >= 6 ? 'grade-high' : i >= 4 ? 'grade-mid' : ''
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function coinCard(c, rank) {
|
||||
const isTop = rank < 3
|
||||
return `
|
||||
<div class="coin-card ${isTop ? 'top' : ''}">
|
||||
${c.image ? `<img class="coin-img" src="${c.image}" loading="lazy" onerror="this.style.display='none'">` : ''}
|
||||
<div class="coin-info">
|
||||
<div class="coin-name"><a href="${c.url}" target="_blank">${c.name}</a></div>
|
||||
<div class="coin-meta">
|
||||
${c.grade ? `<span class="tag ${gradeClass(c.grade)}">${c.grade}</span>` : '<span class="tag">? грейд</span>'}
|
||||
${c.material ? `<span class="tag ${materialClass(c.material)}">${c.material}</span>` : ''}
|
||||
${c.year_from ? `<span class="tag">${c.year_from}${c.year_to && c.year_to !== c.year_from ? '–' + c.year_to : ''}</span>` : ''}
|
||||
${c.in_stock === 0 ? '<span class="tag no-stock">Нет в наличии</span>' : ''}
|
||||
</div>
|
||||
<div class="coin-price">${c.price} ₽${c.old_price && c.old_price > c.price ? `<span class="old">${c.old_price} ₽</span>` : ''}</div>
|
||||
${c.score ? `<div class="coin-score">Score: ${c.score}</div>` : ''}
|
||||
${c.summary ? `<div style="font-size:11px;color:#c9d1d9;margin-top:3px;line-height:1.4">${c.summary}</div>` : ''}
|
||||
${c.analysis ? `<button class="coin-detail-btn" data-coin-id="${c.id}">Подробный анализ</button>` : ''}
|
||||
<div class="coin-feed">${feedNames[c.feed] || c.feed}</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
async function loadCoins() {
|
||||
const content = document.getElementById('content')
|
||||
content.innerHTML = '<div class="loading">Загрузка...</div>'
|
||||
const params = new URLSearchParams({
|
||||
max_price: document.getElementById('f-price').value,
|
||||
min_grade: document.getElementById('f-grade').value,
|
||||
material: document.getElementById('f-material').value,
|
||||
in_stock: document.getElementById('f-stock').checked ? '1' : '0',
|
||||
hide_dupes: document.getElementById('f-dupes').checked ? '1' : '0',
|
||||
feed: document.getElementById('f-feed').value,
|
||||
})
|
||||
try {
|
||||
const res = await fetch('/api/coins?' + params)
|
||||
const data = await res.json()
|
||||
if (!data.coins.length) {
|
||||
content.innerHTML = '<div class="empty"><h3>Ничего не найдено</h3><p>Попробуйте изменить фильтры или запустить сканирование</p></div>'
|
||||
return
|
||||
}
|
||||
data.coins.forEach(c => coinStore.set(c.id, c))
|
||||
content.innerHTML = `<div class="scan-bar"><span>Найдено: <b>${data.total}</b> монет</span></div>
|
||||
<div class="coin-grid">${data.coins.map((c, i) => coinCard(c, i)).join('')}</div>`
|
||||
} catch (e) {
|
||||
content.innerHTML = `<div class="empty"><h3>Ошибка</h3><p>${e.message}</p></div>`
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNew() {
|
||||
const content = document.getElementById('content')
|
||||
content.innerHTML = '<div class="loading">Загрузка...</div>'
|
||||
try {
|
||||
const res = await fetch('/api/coins/new?days=7')
|
||||
const data = await res.json()
|
||||
if (!data.coins.length) {
|
||||
content.innerHTML = '<div class="empty"><h3>Новых поступлений нет</h3><p>Запустите сканирование или подождите</p></div>'
|
||||
return
|
||||
}
|
||||
data.coins.forEach(c => coinStore.set(c.id, c))
|
||||
content.innerHTML = `<div class="scan-bar"><span>Новых за 7 дней: <b>${data.total}</b></span></div>
|
||||
<div class="coin-grid">${data.coins.map((c, i) => coinCard(c, i)).join('')}</div>`
|
||||
} catch (e) {
|
||||
content.innerHTML = `<div class="empty"><h3>Ошибка</h3><p>${e.message}</p></div>`
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAll() {
|
||||
const content = document.getElementById('content')
|
||||
content.innerHTML = '<div class="loading">Загрузка...</div>'
|
||||
try {
|
||||
const res = await fetch('/api/coins/all?limit=200')
|
||||
const data = await res.json()
|
||||
data.coins.forEach(c => coinStore.set(c.id, c))
|
||||
content.innerHTML = `<div class="scan-bar"><span>Всего: <b>${data.total}</b></span></div>
|
||||
<div class="coin-grid">${data.coins.map((c, i) => coinCard(c, 999)).join('')}</div>`
|
||||
} catch (e) {
|
||||
content.innerHTML = `<div class="empty"><h3>Ошибка</h3><p>${e.message}</p></div>`
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
const content = document.getElementById('content')
|
||||
try {
|
||||
const res = await fetch('/api/settings')
|
||||
settings = await res.json()
|
||||
content.innerHTML = `
|
||||
<div class="settings-panel">
|
||||
<h3 style="margin-bottom: 16px;">Настройки Coin Scout</h3>
|
||||
<div class="field">
|
||||
<label>Максимальная цена по умолчанию (₽)</label>
|
||||
<input type="number" id="s-price" value="${settings.max_price || 3000}" step="100">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Минимальный грейд</label>
|
||||
<select id="s-grade">
|
||||
${['G','VG','F','VF','XF','AU','UNC','Proof'].map(g =>
|
||||
`<option value="${g}" ${g === settings.min_grade ? 'selected' : ''}>${g}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Предпочтительный материал</label>
|
||||
<input type="text" id="s-material" value="${settings.preferred_material || 'серебро,золото'}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Час автосканирования (0-23)</label>
|
||||
<input type="number" id="s-hour" value="${settings.auto_scan_hour || 8}" min="0" max="23">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label><input type="checkbox" id="s-enabled" ${settings.scan_enabled === '1' ? 'checked' : ''}> Автосканирование включено</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label><input type="checkbox" id="s-dupes" ${settings.hide_dupes === '1' ? 'checked' : ''}> Скрывать дубликаты по умолчанию</label>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="saveSettings()">Сохранить</button>
|
||||
<span id="s-saved" style="color: #56d364; margin-left: 12px; display: none;">Сохранено!</span>
|
||||
</div>`
|
||||
} catch (e) {
|
||||
content.innerHTML = `<div class="empty"><p>Ошибка: ${e.message}</p></div>`
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
await fetch('/api/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
max_price: document.getElementById('s-price').value,
|
||||
min_grade: document.getElementById('s-grade').value,
|
||||
preferred_material: document.getElementById('s-material').value,
|
||||
auto_scan_hour: document.getElementById('s-hour').value,
|
||||
scan_enabled: document.getElementById('s-enabled').checked ? '1' : '0',
|
||||
hide_dupes: document.getElementById('s-dupes').checked ? '1' : '0',
|
||||
}),
|
||||
})
|
||||
const el = document.getElementById('s-saved')
|
||||
el.style.display = 'inline'
|
||||
setTimeout(() => el.style.display = 'none', 2000)
|
||||
}
|
||||
|
||||
async function startScan() {
|
||||
const btn = document.getElementById('btn-scan')
|
||||
btn.disabled = true
|
||||
btn.textContent = 'Сканирование...'
|
||||
|
||||
const panel = document.getElementById('progress-panel')
|
||||
const bar = document.getElementById('progress-bar')
|
||||
const log = document.getElementById('progress-log')
|
||||
const title = document.getElementById('progress-title')
|
||||
panel.classList.add('active')
|
||||
log.innerHTML = ''
|
||||
bar.style.width = '0%'
|
||||
|
||||
function addLog(text, cls = '') {
|
||||
const d = document.createElement('div')
|
||||
d.className = cls
|
||||
d.textContent = text
|
||||
log.appendChild(d)
|
||||
log.scrollTop = log.scrollHeight
|
||||
}
|
||||
|
||||
// SSE
|
||||
const es = new EventSource('/api/scan/progress')
|
||||
es.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data)
|
||||
title.textContent = data.message || 'Сканирование...'
|
||||
|
||||
if (data.stage === 'start') {
|
||||
addLog(data.message, 'info')
|
||||
} else if (data.stage === 'feed') {
|
||||
addLog(data.message, 'info')
|
||||
bar.style.width = ((data.feedIndex + 1) / (data.feedTotal * 2) * 100) + '%'
|
||||
} else if (data.stage === 'feed_done') {
|
||||
addLog(data.message, 'ok')
|
||||
} else if (data.stage === 'feed_error') {
|
||||
addLog(data.message, 'err')
|
||||
} else if (data.stage === 'saving' || data.stage === 'details') {
|
||||
if (data.total) bar.style.width = (50 + (data.current / data.total * 50)) + '%'
|
||||
} else if (data.stage === 'done') {
|
||||
addLog(data.message, 'ok')
|
||||
bar.style.width = '100%'
|
||||
es.close()
|
||||
btn.disabled = false
|
||||
btn.textContent = 'Сканировать'
|
||||
setTimeout(() => { panel.classList.remove('active') }, 5000)
|
||||
loadCoins()
|
||||
loadStats()
|
||||
} else if (data.stage === 'error') {
|
||||
addLog(data.message, 'err')
|
||||
es.close()
|
||||
btn.disabled = false
|
||||
btn.textContent = 'Сканировать'
|
||||
}
|
||||
}
|
||||
es.onerror = () => {
|
||||
es.close()
|
||||
}
|
||||
|
||||
// Trigger scan
|
||||
try {
|
||||
await fetch('/api/scan', { method: 'POST' })
|
||||
} catch (e) {
|
||||
btn.disabled = false
|
||||
btn.textContent = 'Сканировать'
|
||||
addLog('Ошибка: ' + e.message, 'err')
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const res = await fetch('/api/stats')
|
||||
const data = await res.json()
|
||||
document.getElementById('stats').textContent =
|
||||
`${data.total} монет | ${data.withDetails} с деталями | ${data.feeds.map(f => `${feedNames[f.feed] || f.feed}: ${f.cnt}`).join(', ')}`
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ─── Dashboard ───
|
||||
async function loadDashboard() {
|
||||
const content = document.getElementById('content')
|
||||
content.innerHTML = '<div class="loading">Загрузка дашборда...</div>'
|
||||
try {
|
||||
const res = await fetch('/api/dashboard')
|
||||
const d = await res.json()
|
||||
const feedNames = { AT: 'numizm.at', KB: 'coinsbolhov.ru', RU: 'numizmat.ru' }
|
||||
content.innerHTML = `
|
||||
<div style="max-width:900px">
|
||||
<h2 style="color:#e1e4e8;margin-bottom:16px">Дашборд</h2>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px">
|
||||
<div style="background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;text-align:center">
|
||||
<div style="font-size:28px;font-weight:700;color:#56d364">${d.total.toLocaleString()}</div>
|
||||
<div style="color:#8b949e;font-size:13px">Всего монет</div>
|
||||
</div>
|
||||
<div style="background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;text-align:center">
|
||||
<div style="font-size:28px;font-weight:700;color:#58a6ff">${d.newThisWeek}</div>
|
||||
<div style="color:#8b949e;font-size:13px">Новых за 7 дней</div>
|
||||
</div>
|
||||
<div style="background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;text-align:center">
|
||||
<div style="font-size:28px;font-weight:700;color:#f0883e">${d.priceDrops.length}</div>
|
||||
<div style="color:#8b949e;font-size:13px">Снижения цен</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-bottom:20px">
|
||||
${d.feeds.map(f => `
|
||||
<div style="background:#161b22;border:1px solid #30363d;border-radius:8px;padding:12px">
|
||||
<div style="color:#e1e4e8;font-weight:600">${feedNames[f.feed] || f.feed}</div>
|
||||
<div style="color:#8b949e;font-size:13px">${f.cnt.toLocaleString()} монет · средняя ${f.avg_price}₽</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
${d.priceDrops.length ? `
|
||||
<h3 style="color:#f0883e;margin:20px 0 10px">Снижения цен</h3>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<tr style="border-bottom:1px solid #30363d;color:#8b949e"><th style="text-align:left;padding:6px">Монета</th><th>Было</th><th>Стало</th><th>Δ</th><th>Магазин</th></tr>
|
||||
${d.priceDrops.map(c => `
|
||||
<tr style="border-bottom:1px solid #21262d">
|
||||
<td style="padding:6px"><a href="${c.url}" target="_blank">${c.name.substring(0,50)}</a></td>
|
||||
<td style="text-align:center;color:#8b949e">${c.prev_price}₽</td>
|
||||
<td style="text-align:center;color:#56d364">${c.price}₽</td>
|
||||
<td style="text-align:center;color:#f0883e">−${c.drop_pct}%</td>
|
||||
<td style="text-align:center;color:#8b949e">${feedNames[c.feed] || c.feed}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</table>
|
||||
` : ''}
|
||||
|
||||
${d.priceRises.length ? `
|
||||
<h3 style="color:#f85149;margin:20px 0 10px">Повышения цен</h3>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<tr style="border-bottom:1px solid #30363d;color:#8b949e"><th style="text-align:left;padding:6px">Монета</th><th>Было</th><th>Стало</th><th>Δ</th><th>Магазин</th></tr>
|
||||
${d.priceRises.slice(0,10).map(c => `
|
||||
<tr style="border-bottom:1px solid #21262d">
|
||||
<td style="padding:6px"><a href="${c.url}" target="_blank">${c.name.substring(0,50)}</a></td>
|
||||
<td style="text-align:center;color:#8b949e">${c.prev_price}₽</td>
|
||||
<td style="text-align:center;color:#f85149">${c.price}₽</td>
|
||||
<td style="text-align:center;color:#f85149">+${c.rise_pct}%</td>
|
||||
<td style="text-align:center;color:#8b949e">${feedNames[c.feed] || c.feed}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</table>
|
||||
` : ''}
|
||||
|
||||
${d.disappeared.length ? `
|
||||
<h3 style="color:#8b949e;margin:20px 0 10px">Исчезли за 7 дней (проданы?)</h3>
|
||||
<div style="font-size:13px;color:#8b949e">
|
||||
${d.disappeared.map(c => `<div style="padding:3px 0">${c.name.substring(0,60)} · ${c.price}₽ · ${feedNames[c.feed] || c.feed}</div>`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:20px">
|
||||
<div>
|
||||
<h3 style="color:#58a6ff;margin-bottom:8px">По материалу</h3>
|
||||
<table style="width:100%;font-size:12px;border-collapse:collapse">
|
||||
${d.materials.map(m => `<tr style="border-bottom:1px solid #21262d"><td style="padding:3px">${m.material}</td><td style="text-align:right">${m.cnt}</td><td style="text-align:right;color:#8b949e">~${m.avg_price}₽</td></tr>`).join('')}
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="color:#58a6ff;margin-bottom:8px">По грейду</h3>
|
||||
<table style="width:100%;font-size:12px;border-collapse:collapse">
|
||||
${d.grades.map(g => `<tr style="border-bottom:1px solid #21262d"><td style="padding:3px">${g.grade}</td><td style="text-align:right">${g.cnt}</td><td style="text-align:right;color:#8b949e">~${g.avg_price}₽</td></tr>`).join('')}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
} catch (e) {
|
||||
content.innerHTML = `<div class="empty"><p>Ошибка: ${e.message}</p></div>`
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Cross-store comparison ───
|
||||
async function loadCompare() {
|
||||
const content = document.getElementById('content')
|
||||
content.innerHTML = '<div class="loading">Сравнение магазинов...</div>'
|
||||
try {
|
||||
const res = await fetch('/api/compare')
|
||||
const d = await res.json()
|
||||
const feedNames = { AT: 'numizm.at', KB: 'coinsbolhov.ru', RU: 'numizmat.ru' }
|
||||
if (!d.comparisons.length) {
|
||||
content.innerHTML = '<div class="empty"><h3>Совпадений не найдено</h3><p>Нужно больше сканов чтобы найти одинаковые монеты в разных магазинах</p></div>'
|
||||
return
|
||||
}
|
||||
content.innerHTML = `
|
||||
<div style="max-width:900px">
|
||||
<h2 style="color:#e1e4e8;margin-bottom:16px">Сравнение магазинов</h2>
|
||||
<p style="color:#8b949e;margin-bottom:16px;font-size:13px">Одинаковые монеты в разных магазинах, отсортированные по разнице в цене.</p>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<tr style="border-bottom:1px solid #30363d;color:#8b949e">
|
||||
<th style="text-align:left;padding:8px">Монета</th>
|
||||
<th>Магазин 1</th><th>Цена 1</th>
|
||||
<th>Магазин 2</th><th>Цена 2</th>
|
||||
<th>Разница</th>
|
||||
</tr>
|
||||
${d.comparisons.map(c => {
|
||||
const cheaper = c.price1 <= c.price2 ? 1 : 2
|
||||
return `<tr style="border-bottom:1px solid #21262d">
|
||||
<td style="padding:8px;max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${c.name.substring(0,50)}</td>
|
||||
<td style="text-align:center"><a href="${c.url1}" target="_blank">${feedNames[c.feed1] || c.feed1}</a></td>
|
||||
<td style="text-align:center;${cheaper===1?'color:#56d364;font-weight:600':''}">${c.price1}₽</td>
|
||||
<td style="text-align:center"><a href="${c.url2}" target="_blank">${feedNames[c.feed2] || c.feed2}</a></td>
|
||||
<td style="text-align:center;${cheaper===2?'color:#56d364;font-weight:600':''}">${c.price2}₽</td>
|
||||
<td style="text-align:center;color:#f0883e">${c.diff_pct}%</td>
|
||||
</tr>`
|
||||
}).join('')}
|
||||
</table>
|
||||
</div>`
|
||||
} catch (e) {
|
||||
content.innerHTML = `<div class="empty"><p>Ошибка: ${e.message}</p></div>`
|
||||
}
|
||||
}
|
||||
|
||||
function loadMethodology() {
|
||||
document.getElementById('content').innerHTML = `
|
||||
<div style="max-width:800px;line-height:1.7;font-size:14px">
|
||||
<div style="display:flex;align-items:center;gap:16px;margin-bottom:16px">
|
||||
<h2 style="margin:0;color:#e1e4e8">Методология оценки инвестиционной привлекательности монет</h2>
|
||||
<a href="/methodology.html?print" target="_blank" class="btn btn-primary" style="text-decoration:none;white-space:nowrap">Скачать PDF</a>
|
||||
</div>
|
||||
<p style="color:#8b949e;margin-bottom:20px">Скоринг основан на анализе <b>40+ профессиональных источников</b> по нумизматике: PCGS, NGC, Forbes.ru, numizmatik.ru, coinweek.com, zolotoy-zapas.ru, numisdon.com, raritetus.ru и др. Цена серебра: ~200₽/грамм (ЦБ РФ, апрель 2026).</p>
|
||||
|
||||
<h3 style="color:#f0883e;margin:24px 0 12px">1. Сохранность / Грейд (до 30 очков)</h3>
|
||||
<p style="color:#8b949e;margin-bottom:8px">Каждый шаг грейда может увеличить цену в 2-50 раз. Переход AU→UNC — самый большой скачок.</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Proof</td><td style="padding:4px 8px;color:#56d364">+28</td><td style="padding:4px 8px;color:#8b949e">Зеркальная поверхность, высшее качество</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">UNC</td><td style="padding:4px 8px;color:#56d364">+25</td><td style="padding:4px 8px;color:#8b949e">Не была в обращении</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">AU</td><td style="padding:4px 8px;color:#56d364">+20</td><td style="padding:4px 8px;color:#8b949e">Почти без следов обращения</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">XF</td><td style="padding:4px 8px;color:#56d364">+15</td><td style="padding:4px 8px;color:#8b949e">Лёгкий износ на выступающих частях</td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">VF</td><td style="padding:4px 8px;color:#56d364">+8</td><td style="padding:4px 8px;color:#8b949e">Умеренный износ, все детали читаемы</td></tr>
|
||||
</table>
|
||||
<p style="color:#8b949e">Бонус: VF+ для монет до 1800 г. (+8), XF для XIX века (+5) — исключительная сохранность для возраста.</p>
|
||||
|
||||
<h3 style="color:#f0883e;margin:24px 0 12px">2. Материал и стоимость металла (до 25 очков)</h3>
|
||||
<p style="color:#8b949e;margin-bottom:8px">Драгоценный металл создаёт «пол» стоимости — монета не может стоить дешевле металла.</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Золото</td><td style="padding:4px 8px;color:#56d364">+22</td><td style="padding:4px 8px;color:#8b949e">Надёжный актив, всегда ликвиден</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Серебро</td><td style="padding:4px 8px;color:#56d364">+14</td><td style="padding:4px 8px;color:#8b949e">+ бонус если цена близка к стоимости металла</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Серебро < melt</td><td style="padding:4px 8px;color:#56d364">+10 доп.</td><td style="padding:4px 8px;color:#8b949e">Дешевле стоимости серебра — безрисковая покупка</td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">Биллон</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">Сплав с серебром</td></tr>
|
||||
</table>
|
||||
|
||||
<h3 style="color:#f0883e;margin:24px 0 12px">3. Возраст и историческая значимость (до 20 очков)</h3>
|
||||
<p style="color:#8b949e;margin-bottom:8px">Античные монеты показывают 8-15% годового роста (numisdon.com). Возраст + сохранность = экспоненциальный рост ценности.</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">До н.э.</td><td style="padding:4px 8px;color:#56d364">+20</td><td style="padding:4px 8px;color:#8b949e">Античная монета</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">500+ лет</td><td style="padding:4px 8px;color:#56d364">+18</td><td style="padding:4px 8px;color:#8b949e">Средневековье</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">300+ лет</td><td style="padding:4px 8px;color:#56d364">+14</td><td style="padding:4px 8px;color:#8b949e">Раннее Новое время</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">200+ лет</td><td style="padding:4px 8px;color:#56d364">+10</td><td style="padding:4px 8px;color:#8b949e"></td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">100+ лет</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e"></td></tr>
|
||||
</table>
|
||||
|
||||
<h3 style="color:#f0883e;margin:24px 0 12px">4. Российские премиум-периоды (до 15 очков)</h3>
|
||||
<p style="color:#8b949e;margin-bottom:8px">Царские монеты растут 10-15% в год (Forbes.ru). Особые периоды и правители:</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">СССР 1947, 1958</td><td style="padding:4px 8px;color:#56d364">+15</td><td style="padding:4px 8px;color:#8b949e">Не выпущены в обращение, раритет</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Пробные монеты</td><td style="padding:4px 8px;color:#56d364">+12</td><td style="padding:4px 8px;color:#8b949e">Коллекционная элита</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Смутное время (1610-1612)</td><td style="padding:4px 8px;color:#56d364">+10</td><td style="padding:4px 8px;color:#8b949e">Редчайший период русской нумизматики</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Перепутки</td><td style="padding:4px 8px;color:#56d364">+10</td><td style="padding:4px 8px;color:#8b949e">Монета на чужой заготовке</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Раннее СССР серебро (1921-31)</td><td style="padding:4px 8px;color:#56d364">+7</td><td style="padding:4px 8px;color:#8b949e">Малые тиражи серебра</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Николай II (1894-1917)</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">Культовый период, всегда в спросе</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Пётр I (1682-1725)</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">Реформатор, высокий спрос</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Медный бунт (1654-1656)</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">Историческое событие</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Сибирские монеты</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">Медь с примесью серебра и золота</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Георгий Победоносец</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">Максимальная ликвидность</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Александр II, III</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e">Растущий спрос / короткое правление</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Павел I (1796-1801)</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e">Короткое правление — мало монет</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">1 рубль 1924 серебро</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e">Недооценён относительно редкости</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Серебряный рубль Империи</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e">Топ коллекционного спроса</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Екатерина II, Анна, Елизавета</td><td style="padding:4px 8px;color:#56d364">+4</td><td style="padding:4px 8px;color:#8b949e">Популярные периоды</td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">Русская Финляндия серебро</td><td style="padding:4px 8px;color:#56d364">+4</td><td style="padding:4px 8px;color:#8b949e">Узкая серия</td></tr>
|
||||
</table>
|
||||
|
||||
<h3 style="color:#f0883e;margin:24px 0 12px">5. Мировые монеты (до 10 очков)</h3>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Древняя Греция</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">~15% годового роста</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Византия</td><td style="padding:4px 8px;color:#56d364">+5 (+4 золото)</td><td style="padding:4px 8px;color:#8b949e">Доступная античная империя</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Римская империя</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e">Растущий глобальный рынок</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Боспорское царство</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e">Крымская античность</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Сефевиды, Талеры, Панды</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e">Недооценённые сегменты</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Османская империя</td><td style="padding:4px 8px;color:#56d364">+4</td><td style="padding:4px 8px;color:#8b949e">Недооценённый сегмент</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Соверены, Лунные серии</td><td style="padding:4px 8px;color:#56d364">+4</td><td style="padding:4px 8px;color:#8b949e">Стабильный глобальный спрос</td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">Японские монеты 100+ лет</td><td style="padding:4px 8px;color:#56d364">+4</td><td style="padding:4px 8px;color:#8b949e">Бум азиатской нумизматики</td></tr>
|
||||
</table>
|
||||
|
||||
<h3 style="color:#f0883e;margin:24px 0 12px">6. Ошибки чеканки и разновидности (до 15 очков)</h3>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Мул / двойной аверс</td><td style="padding:4px 8px;color:#56d364">+15</td><td style="padding:4px 8px;color:#8b949e">Крайне редкий брак</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Брак чеканки (смещение, раскол, перечекан...)</td><td style="padding:4px 8px;color:#56d364">+12</td><td style="padding:4px 8px;color:#8b949e">Коллекционная редкость</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Ефимок / надчеканка</td><td style="padding:4px 8px;color:#56d364">+10</td><td style="padding:4px 8px;color:#8b949e">Нумизматическая элита</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Серия ЧЯП (10₽ биметалл)</td><td style="padding:4px 8px;color:#56d364">+10</td><td style="padding:4px 8px;color:#8b949e">Малотиражная, высокий спрос</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Без знака монетного двора</td><td style="padding:4px 8px;color:#56d364">+8</td><td style="padding:4px 8px;color:#8b949e">Редкий вариант</td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">Широкий кант 1997/1998 ММД</td><td style="padding:4px 8px;color:#56d364">+8</td><td style="padding:4px 8px;color:#8b949e">Редкий производственный вариант</td></tr>
|
||||
</table>
|
||||
|
||||
<h3 style="color:#f0883e;margin:24px 0 12px">7. Ценовая эффективность (до 12 очков)</h3>
|
||||
<p style="color:#8b949e;margin-bottom:8px">Главное правило: покупай самую редкую монету в лучшем состоянии за доступную цену.</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Скидка 30%+</td><td style="padding:4px 8px;color:#56d364">+8</td><td style="padding:4px 8px;color:#8b949e">Сильное снижение цены</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">AU+ дешевле 500₽</td><td style="padding:4px 8px;color:#56d364">+6</td><td style="padding:4px 8px;color:#8b949e">Ниже рыночной стоимости</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Скидка 15%+</td><td style="padding:4px 8px;color:#56d364">+5</td><td style="padding:4px 8px;color:#8b949e"></td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">UNC до 1000₽</td><td style="padding:4px 8px;color:#56d364">+4</td><td style="padding:4px 8px;color:#8b949e">Выгодная цена для грейда</td></tr>
|
||||
</table>
|
||||
|
||||
<h3 style="color:#f85149;margin:24px 0 12px">8. Штрафы (негативные факторы)</h3>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:12px">
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Копия / жетон / сувенир</td><td style="padding:4px 8px;color:#f85149">-20</td><td style="padding:4px 8px;color:#8b949e">Не оригинальная монета</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Массовые юбилейные СССР</td><td style="padding:4px 8px;color:#f85149">-12</td><td style="padding:4px 8px;color:#8b949e">Не Proof, не драгмет — нет вторичного рынка</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Чищеная монета</td><td style="padding:4px 8px;color:#f85149">-10</td><td style="padding:4px 8px;color:#8b949e">Потеря 50-90% коллекционной ценности (PCGS)</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Современные памятные ЦБ РФ</td><td style="padding:4px 8px;color:#f85149">-8</td><td style="padding:4px 8px;color:#8b949e">Слабый вторичный рынок</td></tr>
|
||||
<tr style="border-bottom:1px solid #30363d"><td style="padding:4px 8px;color:#8b949e">Медно-никель после 1950</td><td style="padding:4px 8px;color:#f85149">-4</td><td style="padding:4px 8px;color:#8b949e">Массовый чекан, нет Proof</td></tr>
|
||||
<tr><td style="padding:4px 8px;color:#8b949e">Проволочные монеты (чешуя)</td><td style="padding:4px 8px;color:#f85149">-2</td><td style="padding:4px 8px;color:#8b949e">Высокий риск подделки</td></tr>
|
||||
</table>
|
||||
|
||||
<h3 style="color:#58a6ff;margin:24px 0 12px">Источники (40+)</h3>
|
||||
<div style="color:#8b949e;font-size:12px;columns:2;column-gap:24px">
|
||||
<p><a href="https://www.pcgs.com/prices/us" target="_blank">PCGS Price Guide</a></p>
|
||||
<p><a href="https://www.ngccoin.com/price-guide/" target="_blank">NGC Price Guide</a></p>
|
||||
<p><a href="https://www.forbes.ru/finansy-i-investicii/356677" target="_blank">Forbes.ru — Ценная мелочь</a></p>
|
||||
<p><a href="https://www.numizmatik.ru/biblio/top-redkikh-monet-2025-2026" target="_blank">numizmatik.ru — Редкие монеты 2025-2026</a></p>
|
||||
<p><a href="https://www.zolotoy-zapas.ru/why-gold-coins/useful/" target="_blank">Золотой Запас — гайды</a></p>
|
||||
<p><a href="https://rarecoins.ru/stati/kriterii-ocenki-stoimosti-monet-v-numizmatike.html" target="_blank">Аукционный дом Редкие Монеты</a></p>
|
||||
<p><a href="https://www.numisdon.com/ancient-coins-good-investment-2025/" target="_blank">NumisDon — Ancient coins 2025</a></p>
|
||||
<p><a href="https://thebullionbank.com/blog/undervalued-silver-coins-2025" target="_blank">BullionBank — Undervalued Silver</a></p>
|
||||
<p><a href="https://thecoinsexplorer.com/best-rare-coins-investment-2025/" target="_blank">CoinsExplorer — Best Rare Coins</a></p>
|
||||
<p><a href="https://coinweek.com/patina-on-ancient-bronze-coins/" target="_blank">CoinWeek — Patina Guide</a></p>
|
||||
<p><a href="https://www.raritetus.ru/stoimost-monet/" target="_blank">Raritetus — каталог цен</a></p>
|
||||
<p><a href="https://www.cbr.ru/hd_base/metall/metall_base_new/" target="_blank">ЦБ РФ — цена серебра</a></p>
|
||||
<p><a href="https://www.pcgs.com/news/how-to-determine-the-value-of-a-mint-error-coin" target="_blank">PCGS — Error Coin Guide</a></p>
|
||||
<p><a href="https://www.numizmatik.ru/biblio/brak-na-monetakh" target="_blank">numizmatik.ru — Брак монет</a></p>
|
||||
<p><a href="https://www.russian-money.ru/articles/1rub-1997-shirokii-kant" target="_blank">russian-money.ru — Широкий кант</a></p>
|
||||
<p><a href="https://coins.tsbnk.ru/" target="_blank">ТрансСтройБанк — каталог</a></p>
|
||||
<p><a href="https://www.usagold.com/gold-silver-ratio-guide/" target="_blank">USAGOLD — Gold/Silver Ratio</a></p>
|
||||
<p><a href="https://www.rarecoins101.com/coin-investments.html" target="_blank">RareCoins101 — Returns</a></p>
|
||||
<p><a href="https://lermontovgallery.ru/spravochnik-antikvariata/" target="_blank">Лермонтов — царские монеты</a></p>
|
||||
<p><a href="https://www.numisdon.com/byzantine-coins-value/" target="_blank">NumisDon — Byzantine Value</a></p>
|
||||
<p><a href="https://stonexbullion.com/en/blog/rise-of-asian-bullion-coins/" target="_blank">StoneX — Asian Bullion Coins</a></p>
|
||||
<p><a href="https://www.greysheet.com/news/story/five-coin-collecting-investment-mistakes-to-avoid" target="_blank">Greysheet — 5 Mistakes to Avoid</a></p>
|
||||
<p><a href="https://www.marketresearchfuture.com/reports/coin-collecting-market-22623" target="_blank">Market Research — 8% CAGR</a></p>
|
||||
<p><a href="https://www.coingraderai.com/blog/natural-vs-artificial-coin-toning-grading" target="_blank">CoinGraderAI — Toning</a></p>
|
||||
<p><a href="https://deigoldandsilvercoins.com/top-underappreciated-world-coins-with-hidden-long-term-value/" target="_blank">DEI — Underappreciated World Coins</a></p>
|
||||
</div>
|
||||
|
||||
<h3 style="color:#58a6ff;margin:24px 0 12px">Статистика рынка</h3>
|
||||
<ul style="color:#8b949e">
|
||||
<li>Средний рост рынка монет: <b>8% в год</b> (CAGR, прогноз до 2035)</li>
|
||||
<li>PCGS Key Dates Index: <b>4.8% годовых</b> за 25 лет (2000-2025)</li>
|
||||
<li>Топовые монеты: <b>12-14% годовых</b> (PCGS3000 Index с 1971)</li>
|
||||
<li>Царские монеты: <b>10-15% годовых</b> (Forbes.ru)</li>
|
||||
<li>Античные монеты: <b>8-15% годовых</b> (numisdon.com)</li>
|
||||
<li>Рекомендуемый горизонт инвестиций: <b>7-10 лет минимум</b></li>
|
||||
<li>Дилерская наценка: 10-30% — монета должна вырасти на столько же для безубытка</li>
|
||||
</ul>
|
||||
|
||||
<h3 style="color:#58a6ff;margin:24px 0 12px">Фильтрация</h3>
|
||||
<p style="color:#8b949e">Из фидов автоматически исключаются: открытки, альбомы, капсулы, листы, подставки, футляры, лупы, пинцеты, рамки, планшеты, холдеры, книги.</p>
|
||||
|
||||
<p style="color:#8b949e;margin-top:24px;font-size:12px"><i>Disclaimer: скоринг является аналитическим инструментом, не финансовой рекомендацией. Всегда проверяйте монету лично перед покупкой.</i></p>
|
||||
</div>`
|
||||
}
|
||||
|
||||
// Init
|
||||
loadStats()
|
||||
loadCoins()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
225
coin-scout/public/methodology.html
Normal file
225
coin-scout/public/methodology.html
Normal file
@@ -0,0 +1,225 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Coin Scout — Методология</title>
|
||||
<style>
|
||||
@media print {
|
||||
body { font-size: 11px; }
|
||||
h1 { font-size: 20px; }
|
||||
h2 { font-size: 15px; }
|
||||
h3 { font-size: 13px; }
|
||||
.no-break { page-break-inside: avoid; }
|
||||
a { color: #333; }
|
||||
}
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; max-width: 750px; margin: 40px auto; padding: 0 20px; color: #1a1a1a; line-height: 1.6; }
|
||||
h1 { text-align: center; margin-bottom: 4px; }
|
||||
.subtitle { text-align: center; color: #666; margin-bottom: 30px; font-size: 13px; }
|
||||
h2 { color: #c45500; border-bottom: 2px solid #c45500; padding-bottom: 4px; margin-top: 28px; }
|
||||
h3 { color: #333; margin-top: 18px; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 8px 0 16px; font-size: 12px; }
|
||||
th { background: #f5f5f5; text-align: left; padding: 6px 8px; border: 1px solid #ddd; font-weight: 600; }
|
||||
td { padding: 5px 8px; border: 1px solid #ddd; }
|
||||
.plus { color: #1a7f37; font-weight: 600; }
|
||||
.minus { color: #cf222e; font-weight: 600; }
|
||||
.note { color: #666; font-size: 12px; font-style: italic; }
|
||||
.sources { font-size: 11px; columns: 2; column-gap: 20px; }
|
||||
.sources a { color: #0969da; }
|
||||
ul { margin: 4px 0; padding-left: 20px; }
|
||||
li { margin-bottom: 3px; }
|
||||
.stat-box { background: #f8f9fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 12px 16px; margin: 12px 0; }
|
||||
.stat-box b { color: #c45500; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Coin Scout — Методология оценки</h1>
|
||||
<p class="subtitle">Инвестиционный скоринг монет на основе 40+ профессиональных источников<br>
|
||||
PCGS · NGC · Forbes.ru · numizmatik.ru · coinweek.com · zolotoy-zapas.ru · numisdon.com · raritetus.ru<br>
|
||||
Версия: апрель 2026 · Цена серебра: ~200₽/грамм (ЦБ РФ)</p>
|
||||
|
||||
<h2>1. Сохранность / Грейд (до 30 очков)</h2>
|
||||
<p>Каждый шаг грейда может увеличить цену монеты в 2–50 раз. Переход AU→UNC — самый значительный скачок стоимости.</p>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Грейд</th><th>Баллы</th><th>Описание</th></tr>
|
||||
<tr><td>Proof</td><td class="plus">+28</td><td>Зеркальная поверхность, высшее качество чеканки</td></tr>
|
||||
<tr><td>UNC (MS)</td><td class="plus">+25</td><td>Не была в обращении, без следов износа</td></tr>
|
||||
<tr><td>AU</td><td class="plus">+20</td><td>Почти без следов обращения, минимальный износ</td></tr>
|
||||
<tr><td>XF (EF)</td><td class="plus">+15</td><td>Лёгкий износ на выступающих частях рельефа</td></tr>
|
||||
<tr><td>VF</td><td class="plus">+8</td><td>Умеренный износ, все основные детали читаемы</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<p class="note">Бонусы: VF+ для монет до 1800 г. (+8) — исключительная сохранность для возраста. XF для монет XIX века (+5).</p>
|
||||
|
||||
<h2>2. Материал и стоимость металла (до 25 очков)</h2>
|
||||
<p>Драгоценный металл создаёт «пол» стоимости — монета не может стоить дешевле содержащегося в ней металла.</p>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Материал</th><th>Баллы</th><th>Описание</th></tr>
|
||||
<tr><td>Золото</td><td class="plus">+22</td><td>Надёжный актив, всегда ликвиден</td></tr>
|
||||
<tr><td>Серебро</td><td class="plus">+14</td><td>Базовый бонус за драгоценный металл</td></tr>
|
||||
<tr><td>Серебро: цена < melt value</td><td class="plus">+10 доп.</td><td>Монета дешевле стоимости содержащегося серебра — безрисковая покупка</td></tr>
|
||||
<tr><td>Серебро: цена ≈ 70-100% melt</td><td class="plus">+5 доп.</td><td>Цена близка к стоимости металла</td></tr>
|
||||
<tr><td>Биллон</td><td class="plus">+6</td><td>Сплав с содержанием серебра</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<p class="note">Расчёт melt value: вес монеты (г) × 200₽ (цена серебра за грамм по ЦБ РФ, апрель 2026).</p>
|
||||
|
||||
<h2>3. Возраст и историческая значимость (до 20 очков)</h2>
|
||||
<p>Античные монеты показывают 8–15% годового роста (numisdon.com). Комбинация возраста и хорошей сохранности создаёт экспоненциальный рост ценности.</p>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Возраст</th><th>Баллы</th><th>Пример</th></tr>
|
||||
<tr><td>До нашей эры</td><td class="plus">+20</td><td>Греческие, римские, боспорские</td></tr>
|
||||
<tr><td>500+ лет</td><td class="plus">+18</td><td>Средневековье, ранний Ренессанс</td></tr>
|
||||
<tr><td>300+ лет</td><td class="plus">+14</td><td>XVII–XVIII век</td></tr>
|
||||
<tr><td>200+ лет</td><td class="plus">+10</td><td>Начало XIX века</td></tr>
|
||||
<tr><td>100+ лет</td><td class="plus">+6</td><td>Начало XX века</td></tr>
|
||||
<tr><td>50+ лет</td><td class="plus">+2</td><td>Середина XX века</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>4. Российские премиум-периоды (до 15 очков)</h2>
|
||||
<p>Царские монеты растут на 10–15% в год (Forbes.ru). Отдельные периоды и типы монет обладают повышенным инвестиционным потенциалом.</p>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Период / Тип</th><th>Баллы</th><th>Почему</th></tr>
|
||||
<tr><td>СССР 1947, 1958 годов</td><td class="plus">+15</td><td>Не выпущены в обращение, крайне редки</td></tr>
|
||||
<tr><td>Пробные монеты</td><td class="plus">+12</td><td>Коллекционная элита, единичные экземпляры</td></tr>
|
||||
<tr><td>Смутное время (1610–1612)</td><td class="plus">+10</td><td>Редчайший период русской нумизматики</td></tr>
|
||||
<tr><td>Перепутки (чужие заготовки)</td><td class="plus">+10</td><td>Монета отчеканена на заготовке другого номинала</td></tr>
|
||||
<tr><td>Раннее СССР серебро (1921–1931)</td><td class="plus">+7</td><td>Малые тиражи, серебро в обращении</td></tr>
|
||||
<tr><td>Николай II (1894–1917)</td><td class="plus">+6</td><td>Культовый период, всегда востребован</td></tr>
|
||||
<tr><td>Пётр I (1682–1725)</td><td class="plus">+6</td><td>Реформатор, высокий коллекционный спрос</td></tr>
|
||||
<tr><td>Медный бунт (1654–1656)</td><td class="plus">+6</td><td>Историческое событие, Алексей Михайлович</td></tr>
|
||||
<tr><td>Сибирские монеты (Сузун)</td><td class="plus">+6</td><td>Медь с примесью серебра и золота</td></tr>
|
||||
<tr><td>Георгий Победоносец</td><td class="plus">+6</td><td>Максимальная ликвидность среди инвестиционных</td></tr>
|
||||
<tr><td>Александр II (1855–1881)</td><td class="plus">+5</td><td>Растущий спрос коллекционеров</td></tr>
|
||||
<tr><td>Александр III (1881–1894)</td><td class="plus">+5</td><td>Короткое правление — мало монет</td></tr>
|
||||
<tr><td>Павел I (1796–1801)</td><td class="plus">+5</td><td>5 лет правления — ограниченная чеканка</td></tr>
|
||||
<tr><td>1 рубль 1924, серебро</td><td class="plus">+5</td><td>Недооценён относительно редкости</td></tr>
|
||||
<tr><td>Серебряный рубль Империи</td><td class="plus">+5</td><td>Топ коллекционного спроса</td></tr>
|
||||
<tr><td>Полтина серебро</td><td class="plus">+4</td><td>Ликвидный крупный номинал</td></tr>
|
||||
<tr><td>Екатерина II (1762–1796)</td><td class="plus">+4</td><td>Популярный период, много коллекционеров</td></tr>
|
||||
<tr><td>Анна Иоанновна (1730–1740)</td><td class="plus">+4</td><td>Высокий инвестиционный потенциал</td></tr>
|
||||
<tr><td>Елизавета Петровна (1741–1762)</td><td class="plus">+4</td><td>Редкие тиражи, дорогие монеты</td></tr>
|
||||
<tr><td>Русская Финляндия, серебро</td><td class="plus">+4</td><td>Узкая серия, отдельный коллекционный спрос</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>5. Мировые монеты (до 10 очков)</h2>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Регион / Тип</th><th>Баллы</th><th>Описание</th></tr>
|
||||
<tr><td>Древняя Греция</td><td class="plus">+6</td><td>~15% годового роста, Афины, Коринф, Сиракузы</td></tr>
|
||||
<tr><td>Византия</td><td class="plus">+5 (+4 золото)</td><td>Доступная античная империя, солиды</td></tr>
|
||||
<tr><td>Римская империя</td><td class="plus">+5</td><td>Растущий глобальный рынок, денарии</td></tr>
|
||||
<tr><td>Боспорское царство</td><td class="plus">+5</td><td>Крымская античность, Пантикапей</td></tr>
|
||||
<tr><td>Сефевиды (Персия)</td><td class="plus">+5</td><td>Редкий исламский регион</td></tr>
|
||||
<tr><td>Талеры</td><td class="plus">+5</td><td>Крупное серебро, всегда ликвидны</td></tr>
|
||||
<tr><td>Китайские Панды</td><td class="plus">+5</td><td>Растущий азиатский рынок</td></tr>
|
||||
<tr><td>Османская империя</td><td class="plus">+4</td><td>Недооценённый сегмент</td></tr>
|
||||
<tr><td>Британские соверены</td><td class="plus">+4</td><td>Стабильный глобальный спрос</td></tr>
|
||||
<tr><td>Лунные серии</td><td class="plus">+4</td><td>Австралия, Великобритания — коллекционный рост</td></tr>
|
||||
<tr><td>Японские монеты 100+ лет</td><td class="plus">+4</td><td>Бум азиатской нумизматики, CAGR 7%</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>6. Ошибки чеканки и разновидности (до 15 очков)</h2>
|
||||
<p>Монеты с ошибками чеканки образуют отдельную и высоко ценимую категорию коллекционирования (PCGS).</p>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Тип</th><th>Баллы</th><th>Описание</th></tr>
|
||||
<tr><td>Мул / двойной аверс / без реверса</td><td class="plus">+15</td><td>Крайне редкий брак, до 7–12 тыс. ₽</td></tr>
|
||||
<tr><td>Брак чеканки</td><td class="plus">+12</td><td>Смещение, раскол, перечекан, двойной удар, выкус, залипуха, непрочекан, гурт на канте, двойной кант</td></tr>
|
||||
<tr><td>Ефимок / надчеканка</td><td class="plus">+10</td><td>Нумизматическая элита, всегда высокий спрос</td></tr>
|
||||
<tr><td>Серия ЧЯП (10₽ биметалл)</td><td class="plus">+10</td><td>Чечня, ЯНАО, Пермский край — малотиражные</td></tr>
|
||||
<tr><td>Без знака монетного двора</td><td class="plus">+8</td><td>Редкий вариант, ценится выше обычного</td></tr>
|
||||
<tr><td>Широкий кант 1997/1998 ММД</td><td class="plus">+8</td><td>Редкий производственный вариант</td></tr>
|
||||
<tr><td>Ключевая дата серии</td><td class="plus">+6</td><td>Давление коллекционеров на завершение серии</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>7. Ценовая эффективность (до 12 очков)</h2>
|
||||
<p><b>Главное правило:</b> покупай самую редкую монету в лучшем состоянии за доступную цену.</p>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Фактор</th><th>Баллы</th><th>Описание</th></tr>
|
||||
<tr><td>Скидка ≥30%</td><td class="plus">+8</td><td>Сильное снижение цены от старой</td></tr>
|
||||
<tr><td>AU+ дешевле 500₽</td><td class="plus">+6</td><td>Ниже рыночной стоимости для грейда</td></tr>
|
||||
<tr><td>Скидка ≥15%</td><td class="plus">+5</td><td>Заметное снижение цены</td></tr>
|
||||
<tr><td>UNC до 1000₽</td><td class="plus">+4</td><td>Выгодная цена для не бывшей в обращении монеты</td></tr>
|
||||
<tr><td>Общий бонус за дешевизну</td><td class="plus">до +7</td><td>Чем дешевле при VF+ — тем больше бонус</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>8. Штрафы — негативные факторы</h2>
|
||||
<div class="no-break">
|
||||
<table>
|
||||
<tr><th>Фактор</th><th>Баллы</th><th>Почему</th></tr>
|
||||
<tr><td>Копия / жетон / сувенир / новодел</td><td class="minus">−20</td><td>Не оригинальная монета, нет инвестиционной ценности</td></tr>
|
||||
<tr><td>Массовые юбилейные СССР (не Proof, не драгмет)</td><td class="minus">−12</td><td>Нет вторичного рынка, не растут в цене</td></tr>
|
||||
<tr><td>Чищеная монета</td><td class="minus">−10</td><td>Потеря 50–90% коллекционной ценности (данные PCGS)</td></tr>
|
||||
<tr><td>Современные памятные ЦБ РФ (не драгмет)</td><td class="minus">−8</td><td>Слабый вторичный рынок</td></tr>
|
||||
<tr><td>Медно-никель после 1950 (массовый чекан)</td><td class="minus">−4</td><td>Не Proof — нет коллекционной ценности</td></tr>
|
||||
<tr><td>Проволочные монеты (чешуя)</td><td class="minus">−2</td><td>Высокий риск подделки</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Статистика рынка</h2>
|
||||
<div class="stat-box">
|
||||
<ul>
|
||||
<li>Глобальный рынок монет: <b>CAGR 8%</b> до 2035 года (Market Research Future)</li>
|
||||
<li>PCGS Key Dates Index: <b>4.8% годовых</b> за 25 лет (2000–2025)</li>
|
||||
<li>PCGS3000 Index: <b>12–14% годовых</b> с 1971 года (Penn State study)</li>
|
||||
<li>Царские монеты: <b>10–15% годовых</b> (Forbes.ru)</li>
|
||||
<li>Античные монеты: <b>8–15% годовых</b> (numisdon.com, 2021–2025)</li>
|
||||
<li>Азиатский рынок (Китай, Япония): <b>CAGR 6–7%</b></li>
|
||||
<li>Рекомендуемый горизонт: <b>7–10 лет минимум</b> (ANA рекомендует 10+)</li>
|
||||
<li>Дилерская наценка: <b>10–30%</b> — учитывать при расчёте точки безубыточности</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Источники (40+)</h2>
|
||||
<div class="sources">
|
||||
<p><a href="https://www.pcgs.com/prices/us">PCGS Price Guide</a></p>
|
||||
<p><a href="https://www.ngccoin.com/price-guide/">NGC Price Guide</a></p>
|
||||
<p><a href="https://www.forbes.ru/finansy-i-investicii/356677">Forbes.ru — Ценная мелочь</a></p>
|
||||
<p><a href="https://www.numizmatik.ru/biblio/top-redkikh-monet-2025-2026">numizmatik.ru — Редкие монеты 2025–2026</a></p>
|
||||
<p><a href="https://www.zolotoy-zapas.ru/why-gold-coins/useful/">Золотой Запас — гайды по монетам</a></p>
|
||||
<p><a href="https://rarecoins.ru/stati/kriterii-ocenki-stoimosti-monet-v-numizmatike.html">Аукционный дом «Редкие Монеты»</a></p>
|
||||
<p><a href="https://www.numisdon.com/ancient-coins-good-investment-2025/">NumisDon — Ancient Coins 2025</a></p>
|
||||
<p><a href="https://thebullionbank.com/blog/undervalued-silver-coins-2025">BullionBank — Undervalued Silver</a></p>
|
||||
<p><a href="https://thecoinsexplorer.com/best-rare-coins-investment-2025/">CoinsExplorer — Best Rare Coins 2025</a></p>
|
||||
<p><a href="https://coinweek.com/patina-on-ancient-bronze-coins/">CoinWeek — Patina Guide</a></p>
|
||||
<p><a href="https://www.raritetus.ru/stoimost-monet/">Raritetus — каталог цен</a></p>
|
||||
<p><a href="https://www.cbr.ru/hd_base/metall/metall_base_new/">ЦБ РФ — цена драгметаллов</a></p>
|
||||
<p><a href="https://www.pcgs.com/news/how-to-determine-the-value-of-a-mint-error-coin">PCGS — Error Coin Guide</a></p>
|
||||
<p><a href="https://www.numizmatik.ru/biblio/brak-na-monetakh">numizmatik.ru — Брак на монетах</a></p>
|
||||
<p><a href="https://www.russian-money.ru/articles/1rub-1997-shirokii-kant">russian-money.ru — Широкий кант</a></p>
|
||||
<p><a href="https://coins.tsbnk.ru/">ТрансСтройБанк — каталог монет</a></p>
|
||||
<p><a href="https://www.usagold.com/gold-silver-ratio-guide/">USAGOLD — Gold/Silver Ratio</a></p>
|
||||
<p><a href="https://www.rarecoins101.com/coin-investments.html">RareCoins101 — Investment Returns</a></p>
|
||||
<p><a href="https://lermontovgallery.ru/spravochnik-antikvariata/">Лермонтов — царские монеты</a></p>
|
||||
<p><a href="https://www.numisdon.com/byzantine-coins-value/">NumisDon — Byzantine Coins</a></p>
|
||||
<p><a href="https://stonexbullion.com/en/blog/rise-of-asian-bullion-coins/">StoneX — Asian Bullion Coins</a></p>
|
||||
<p><a href="https://www.greysheet.com/news/story/five-coin-collecting-investment-mistakes-to-avoid">Greysheet — 5 Mistakes to Avoid</a></p>
|
||||
<p><a href="https://www.marketresearchfuture.com/reports/coin-collecting-market-22623">Market Research Future — CAGR 8%</a></p>
|
||||
<p><a href="https://www.coingraderai.com/blog/natural-vs-artificial-coin-toning-grading">CoinGraderAI — Toning Guide</a></p>
|
||||
<p><a href="https://deigoldandsilvercoins.com/top-underappreciated-world-coins-with-hidden-long-term-value/">DEI — Underappreciated World Coins</a></p>
|
||||
<p><a href="https://www.pcgs.com/news/toning-does-it-help/">PCGS — Toning Impact on Value</a></p>
|
||||
<p><a href="https://www.monetnik.ru/obuchenie/numizmatika/monety-sibirskie/">Monetnik — Сибирские монеты</a></p>
|
||||
<p><a href="https://www.blanchardgold.com/wp-content/uploads/2017/02/blanchard_lombra_report_2016.pdf">Blanchard — Lombra Report (Penn State)</a></p>
|
||||
</div>
|
||||
|
||||
<p class="note" style="margin-top:30px;text-align:center">Coin Scout · Версия апрель 2026 · Скоринг является аналитическим инструментом, не финансовой рекомендацией.<br>Всегда проверяйте монету лично перед покупкой.</p>
|
||||
|
||||
<script>
|
||||
// Auto-print for PDF generation
|
||||
if (location.search.includes('print')) {
|
||||
window.onload = () => window.print()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1303
coin-scout/server.js
Normal file
1303
coin-scout/server.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user