feat(shop): Phase 5 — 22 new shop items (frames + titles + theme)

Triples the catalogue from 10 to 32 active items so coins finally have
somewhere to land. Migration 033 seeds:

  • 12 new frames at 200-1200 coin tiers (морская, лесная, закат,
    минимал, винтаж, пиксельный, молния, космос, изумруд, призрак,
    кибер, золотой ободок) — each with curated CSS that renders
    correctly in the shop preview added in Phase 4

  • 9 new titles at 150-2000 coin tiers (стажёр, аналитик, геометр,
    алгебраист, физик, олимпиец, боссфайтер, магистр, профессор)
    — colored pills that pair with the new title preview UI

  • 1 new theme (тёплая бумага) using the existing active_theme slot

Effects are intentionally not extended in this migration — js/api.js
_applyEffect() only knows pulse/sparkle/snow today, and adding new
effect kinds belongs in a follow-up that updates the renderer in
tandem with the catalogue entries.

Re-runnable: each row is gated by WHERE NOT EXISTS (name, type) so
re-applying the migration on a partially-seeded environment is safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-29 20:32:08 +03:00
parent 268ea31bb8
commit dbeee44fc7
@@ -0,0 +1,149 @@
-- ═══════════════════════════════════════════════════════════════
-- 033: Phase 5 — shop catalogue expansion
--
-- Triples the shop's selection (10 → 30 items) so coins finally have
-- something interesting to buy. Adds:
-- • 12 new frames spanning price tiers 200-1200, palette covering
-- warm/cool/multi-color/animated/event ranges
-- • 9 new titles in 150-2000 coin range (some prestige-only)
-- • 1 new theme (warm paper) using the existing theme slug system
--
-- Effects are intentionally NOT extended here — the front-end
-- _applyEffect() in js/api.js only supports pulse/sparkle/snow today,
-- and adding new effect kinds belongs in a follow-up that updates the
-- renderer. Frame previews and title pills already render properly
-- thanks to the Phase 4 shop UI changes (profile.html).
--
-- Re-runnable (INSERT OR IGNORE — `name` is informational; the dedup
-- happens because the (name, type) pair would already be in shop_items
-- after the first run, but to be safe we re-check by name+type below).
-- ═══════════════════════════════════════════════════════════════
-- Helper pattern: INSERT only if no row with same name+type exists.
-- We can't use INSERT OR IGNORE without a UNIQUE index, so do it via
-- WHERE NOT EXISTS for each row.
-- ── FRAMES ───────────────────────────────────────────────────────
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Морская рамка', 'Спокойное синее свечение', 'frame', 'cosmetic', 250,
'{"css":"box-shadow:0 0 0 3px #1E88E5,0 0 14px rgba(30,136,229,0.45);border-color:#1E88E5"}',
'waves', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Морская рамка' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Лесная рамка', 'Глубокий зелёный ореол', 'frame', 'cosmetic', 250,
'{"css":"box-shadow:0 0 0 3px #2E7D32,0 0 14px rgba(46,125,50,0.45);border-color:#2E7D32"}',
'tree-pine', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Лесная рамка' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Закат', 'Розово-оранжевый градиент', 'frame', 'cosmetic', 350,
'{"css":"box-shadow:0 0 0 3px #FF6F61,0 0 16px rgba(255,111,97,0.55);border-color:#FF6F61"}',
'sunset', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Закат' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Минимал', 'Чистая тонкая линия', 'frame', 'cosmetic', 200,
'{"css":"box-shadow:0 0 0 2px #1F2937;border-color:#1F2937"}',
'square', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Минимал' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Винтаж', 'Тёплая сепия с патиной', 'frame', 'cosmetic', 400,
'{"css":"box-shadow:0 0 0 3px #8B6F47,0 0 12px rgba(139,111,71,0.4);border-color:#8B6F47;filter:sepia(0.2)"}',
'image', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Винтаж' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Пиксельный', 'Ретро-двухпиксельный шаг', 'frame', 'cosmetic', 350,
'{"css":"box-shadow:0 0 0 2px #fff,0 0 0 4px #9B5DE5,0 0 0 6px #fff,0 0 0 8px #06D6E0;border-color:transparent"}',
'grid-2x2', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Пиксельный' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Молния', 'Яркий жёлтый разряд', 'frame', 'cosmetic', 500,
'{"css":"box-shadow:0 0 0 3px #FBBF24,0 0 18px rgba(251,191,36,0.6);border-color:#FBBF24"}',
'zap', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Молния' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Космос', 'Бескрайняя тьма с фиолетовым ядром', 'frame', 'cosmetic', 600,
'{"css":"box-shadow:0 0 0 3px #1E1B4B,0 0 18px rgba(124,58,237,0.6);border-color:#1E1B4B"}',
'galaxy', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Космос' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Изумруд', 'Глубокий изумрудный градиент', 'frame', 'cosmetic', 700,
'{"css":"box-shadow:0 0 0 3px #10B981,0 0 18px rgba(16,185,129,0.6);border-color:#10B981"}',
'gem', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Изумруд' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Призрак', 'Полупрозрачный размытый ореол', 'frame', 'cosmetic', 800,
'{"css":"box-shadow:0 0 0 3px rgba(255,255,255,0.3),0 0 20px rgba(255,255,255,0.5);border-color:rgba(255,255,255,0.3)"}',
'ghost', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Призрак' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Кибер', 'Яркий неоновый ободок', 'frame', 'cosmetic', 900,
'{"css":"box-shadow:0 0 0 2px #06D6E0,0 0 0 4px #FF6B35,0 0 24px rgba(6,214,224,0.8);border-color:transparent"}',
'cpu', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Кибер' AND type='frame');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Золотой ободок', 'Толстое золото вокруг аватара', 'frame', 'cosmetic', 1200,
'{"css":"box-shadow:0 0 0 4px #FFD700,0 0 22px rgba(255,215,0,0.7);border-color:#FFD700"}',
'crown', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Золотой ободок' AND type='frame');
-- ── TITLES ───────────────────────────────────────────────────────
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Стажёр', 'Только начинаешь путь', 'title', 'cosmetic', 150,
'{"text":"Стажёр","color":"#6B7280"}', 'user', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Стажёр' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Аналитик', 'Видишь паттерны там, где другие — числа', 'title', 'cosmetic', 300,
'{"text":"Аналитик","color":"#06D6E0"}', 'pie-chart', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Аналитик' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Геометр', 'Любитель углов и треугольников', 'title', 'cosmetic', 350,
'{"text":"Геометр","color":"#10B981"}', 'triangle', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Геометр' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Алгебраист', 'Уравнения — твоя стихия', 'title', 'cosmetic', 350,
'{"text":"Алгебраист","color":"#9B5DE5"}', 'sigma', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Алгебраист' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Физик', 'F = ma наизусть', 'title', 'cosmetic', 400,
'{"text":"Физик","color":"#FF6B35"}', 'atom', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Физик' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Олимпиец', 'Готов к олимпиадам', 'title', 'cosmetic', 600,
'{"text":"Олимпиец","color":"#FBBF24"}', 'medal', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Олимпиец' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Боссфайтер', 'Уничтожает боссов глав одной формулой', 'title', 'cosmetic', 700,
'{"text":"Боссфайтер","color":"#DC2626"}', 'swords', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Боссфайтер' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Магистр', 'Освоил всё, что предлагают', 'title', 'cosmetic', 1200,
'{"text":"Магистр","color":"#7C3AED"}', 'graduation-cap', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Магистр' AND type='title');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Титул: Профессор', 'Топовый престиж', 'title', 'cosmetic', 2000,
'{"text":"Профессор","color":"#FFD700"}', 'graduation-cap', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Титул: Профессор' AND type='title');
-- ── THEMES (single addition — renderer already supports active_theme) ──
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Тёплая бумага', 'Молочная палитра вместо синего', 'theme', 'cosmetic', 300,
'{"theme":"warm-paper"}', 'sun', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Тёплая бумага' AND type='theme');