/* ================================================================ Components A - Header, Hero, Form, Quiz ================================================================ */ const { useState, useEffect, useRef, useCallback } = React; /* ---------- Nav data ---------- */ const NAV = [ { href: '#categories', label: 'Что шьём' }, { href: '#why', label: 'Преимущества' }, { href: '#how', label: 'Как мы работаем' }, { href: '#faq', label: 'FAQ' }, { href: '#contact', label: 'Контакты' }, ]; /* ---------- Counter ---------- */ function Counter({ value, suffix = '', prefix = '', noSep = false, duration = 1700 }) { const ref = useRef(null); const [n, setN] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; let started = false; let rafId; const run = () => { const start = performance.now(); const step = (now) => { const t = Math.min(1, (now - start) / duration); const eased = 1 - Math.pow(1 - t, 3); setN(Math.round(value * eased)); if (t < 1) rafId = requestAnimationFrame(step); }; rafId = requestAnimationFrame(step); }; if (typeof IntersectionObserver === 'undefined') { run(); return; } const obs = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting && !started) { started = true; run(); obs.disconnect(); } }); }, { threshold: 0.3 }); obs.observe(el); return () => { obs.disconnect(); if (rafId) cancelAnimationFrame(rafId); }; }, [value, duration]); const formatted = noSep ? String(n) : n.toLocaleString('ru-RU').replace(/,/g, ' ').replace(/\u00a0/g, ' '); return {prefix}{formatted}{suffix}; } /* ---------- Reusable: response badge ---------- */ function RespBadge({ dark }) { return ( Ответ за 30 минут ); } /* ---------- Reusable: brand logo ---------- */ function BrandLogo({ inFooter }) { return ( DanaBaby ); } /* ---------- Header ---------- */ function Header({ scrolled, onOpenMenu, onOpenQuiz }) { return (
+996 779 766 666
); } /* ---------- MobileMenu ---------- */ function MobileMenu({ open, onClose, onOpenQuiz }) { return (
e.stopPropagation()}>
WhatsApp Telegram
); } /* ---------- Hero ---------- */ function Hero({ onOpenQuiz }) { return (

Пошив детской одежды под вашим брендом

Полный цикл OEM-производства: лекала, образец, отшив партии, ваши лейблы и упаковка. Работаем с готовыми ТЗ и помогаем создать модель с нуля. Организуем доставку по России и СНГ.

Минимальная партия от 150 единиц
Посмотреть, что мы шьём
Своя фабрика в Бишкеке
MOQ от 150 ед.
Ваш бренд, лейблы, упаковка
Образец до запуска партии
OEM-пошив детской одежды DanaBaby Production { e.currentTarget.style.display = 'none'; }} />
); } /* ================================================================ FormSection (form + "use quiz" promo) ================================================================ */ function FormSection({ onOpenQuiz }) { const [data, setData] = useState({ name: '', phone: '', brief: '', link: '' }); const [files, setFiles] = useState([]); const [sent, setSent] = useState(false); const [showMore, setShowMore] = useState(false); const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(''); const upd = (k) => (e) => setData((d) => ({ ...d, [k]: e.target.value })); const submit = async (e) => { e.preventDefault(); if (!data.name || !data.phone || submitting) return; setSubmitting(true); setSubmitError(''); try { const commentParts = [ data.brief ? `Что отшить: ${data.brief}` : '', data.link ? `Ссылка на ТЗ: ${data.link}` : '', files.length ? `Файлы: ${files.map((f) => f.name).join(', ')}` : '', ].filter(Boolean); const formData = new FormData(); formData.append('name', data.name); formData.append('phone', data.phone); formData.append('city', ''); formData.append('comment', commentParts.join('\n') || 'Заявка из формы расчёта пошива'); files.forEach((file) => formData.append('files[]', file)); const resp = await fetch('/mail.php', { method: 'POST', headers: { Accept: 'application/json' }, body: formData, }); if (!resp.ok) throw new Error('send_failed'); const result = await resp.json().catch(() => ({})); if (!result || result.ok !== true) throw new Error('send_failed'); setSent(true); } catch (err) { setSubmitError('Не удалось отправить заявку. Попробуйте ещё раз или напишите в WhatsApp.'); } finally { setSubmitting(false); } }; const onFiles = (e) => { const list = Array.from(e.target.files || []); if (list.length) setFiles((f) => [...f, ...list].slice(0, 5)); e.target.value = ''; }; const removeFile = (i) => setFiles((f) => f.filter((_, idx) => idx !== i)); return (
Расчёт пошива

Получите расчёт пошива под ваш бренд - за 30 минут

Пришлите ТЗ, эскиз или фото референса. Менеджер рассчитает стоимость единицы, сроки и пакет услуг под вашу задачу.

  • Расчёт цены за единицу
  • Сроки производства партии
  • Рекомендации по тканям и фурнитуре
  • Варианты лейблов, бирок и упаковки
  • Стоимость и сроки тестового образца
  • Условия предоплаты и оплаты партии
  • Сертификация ТР ТС 007/2011
  • Помощь с маркировкой «Честный знак»
Менеджер на связи
{!sent ? (
{showMore && ( <>
{files.length > 0 && (
{files.map((f, i) => ( {f.name.length > 22 ? f.name.slice(0, 20) + '…' : f.name} ))}
)}
)} {!showMore && (
)}
{submitError && (

{submitError}

)}

Нажимая кнопку, вы соглашаетесь с политикой конфиденциальности.

) : (

Заявка принята

Менеджер свяжется с вами в течение 30 минут и пришлёт расчёт по вашему ТЗ.

)}
); } /* ================================================================ QuizModal - 5-step OEM calculator ================================================================ */ const QUIZ_STEPS = [ { key: 'item', title: 'Что хотите отшить?', tag: '1 / 5 · Категория', multi: false, options: [ { v: 'bodi', label: 'Боди, распашонки, ползунки', iconPng: 'assets/1.png' }, { v: 'slip', label: 'Слипы и комбинезоны', iconPng: 'assets/2.png' }, { v: 'konvert', label: 'Конверты и одеяла на выписку', iconPng: 'assets/3.png' }, { v: 'dress', label: 'Платья и сарафаны', iconPng: 'assets/4.png' }, { v: 'kostum', label: 'Сумки, матрасы', iconPng: 'assets/5.png' }, { v: 'other', label: 'Другое / несколько категорий', iconPng: 'assets/6.png' }, ], }, { key: 'qty', title: 'Какой объём планируете?', tag: '2 / 5 · Тираж', multi: false, options: [ { v: '150-300', label: '150-300 ед. (тест модели)', ico: Ico.QuizQty }, { v: '300-500', label: '300-500 ед.', ico: Ico.QuizQty }, { v: '500-1000', label: '500-1 000 ед.', ico: Ico.QuizQty }, { v: '1000+', label: '1 000+ ед. (серия)', ico: Ico.QuizQty }, ], }, { key: 'patterns', title: 'Есть ли у вас лекала или эскиз?', tag: '3 / 5 · Лекала', multi: false, options: [ { v: 'ready', label: 'Есть готовые лекала', ico: Ico.StepPattern }, { v: 'sketch', label: 'Есть эскиз или фото референса', ico: Ico.AudPattern }, { v: 'idea', label: 'Только идея - нужна разработка с нуля', ico: Ico.Sparkles }, ], }, { key: 'when', title: 'Когда нужна партия?', tag: '4 / 5 · Сроки', multi: false, options: [ { v: 'asap', label: 'Чем быстрее, тем лучше', ico: Ico.Clock }, { v: '1-2m', label: 'Через 1-2 месяца', ico: Ico.QuizDate }, { v: '2-3m', label: 'Через 2-3 месяца', ico: Ico.QuizDate }, { v: 'flex', label: 'Сроки гибкие, важна цена', ico: Ico.QuizDate }, ], }, ]; const QUIZ_LABELS = { item: { bodi: 'Боди, распашонки', slip: 'Слипы и комбинезоны', konvert: 'Конверты и одеяла', dress: 'Платья и сарафаны', kostum: 'Сумки, матрасы', other: 'Другое', }, qty: { '150-300': '150-300 ед.', '300-500': '300-500 ед.', '500-1000': '500-1 000 ед.', '1000+': '1 000+ ед.', }, patterns: { ready: 'Лекала готовы', sketch: 'Есть эскиз/референс', idea: 'Разработка с нуля', }, when: { asap: 'Срочно', '1-2m': 'Через 1-2 мес.', '2-3m': 'Через 2-3 мес.', flex: 'Гибкие сроки', }, }; function QuizModal({ open, onClose }) { const [step, setStep] = useState(0); const [answers, setAnswers] = useState({}); const [contact, setContact] = useState({ name: '', phone: '', link: '' }); const [files, setFiles] = useState([]); const [done, setDone] = useState(false); const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(''); // Reset when reopened useEffect(() => { if (open) { setStep(0); setAnswers({}); setContact({ name: '', phone: '', link: '' }); setFiles([]); setDone(false); setSubmitting(false); setSubmitError(''); } }, [open]); // ESC to close useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', onKey); return () => document.removeEventListener('keydown', onKey); }, [open, onClose]); // Lock scroll useEffect(() => { if (!open) return; const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, [open]); const totalSteps = QUIZ_STEPS.length + 1; // +1 contact step const stepNum = Math.min(step + 1, totalSteps); const progress = (stepNum / totalSteps) * 100; const pick = (key, v) => { setAnswers((a) => ({ ...a, [key]: v })); // Auto-advance unless on last quiz step setTimeout(() => { if (step < QUIZ_STEPS.length) setStep((s) => s + 1); }, 220); }; const onFiles = (e) => { const list = Array.from(e.target.files || []); if (list.length) setFiles((f) => [...f, ...list].slice(0, 5)); e.target.value = ''; }; const removeFile = (i) => setFiles((f) => f.filter((_, idx) => idx !== i)); const submit = async (e) => { e.preventDefault(); if (!contact.name || !contact.phone || submitting) return; setSubmitting(true); setSubmitError(''); try { const commentParts = [ answers.item ? `Категория: ${QUIZ_LABELS.item[answers.item]}` : '', answers.qty ? `Тираж: ${QUIZ_LABELS.qty[answers.qty]}` : '', answers.patterns ? `Лекала: ${QUIZ_LABELS.patterns[answers.patterns]}` : '', answers.when ? `Сроки: ${QUIZ_LABELS.when[answers.when]}` : '', contact.link ? `Ссылка на ТЗ: ${contact.link}` : '', files.length ? `Файлы: ${files.map((f) => f.name).join(', ')}` : '', ].filter(Boolean); const formData = new FormData(); formData.append('name', contact.name); formData.append('phone', contact.phone); formData.append('city', ''); formData.append('comment', commentParts.join('\n')); files.forEach((file) => formData.append('files[]', file)); const resp = await fetch('/mail.php', { method: 'POST', headers: { Accept: 'application/json' }, body: formData, }); if (!resp.ok) throw new Error('send_failed'); const data = await resp.json().catch(() => ({})); if (!data || data.ok !== true) throw new Error('send_failed'); setDone(true); } catch (err) { setSubmitError('Не удалось отправить заявку. Попробуйте ещё раз или напишите в WhatsApp.'); } finally { setSubmitting(false); } }; const back = () => setStep((s) => Math.max(0, s - 1)); return (
e.stopPropagation()}>
Расчёт пошива под ваш бренд
5 шагов · ~2 минуты · ответ за 30 мин
{done ? (

Заявка принята

Менеджер свяжется с вами в течение 30 минут и пришлёт расчёт по вашему ТЗ + рекомендации по тканям, фурнитуре и срокам.

Написать сразу Telegram
) : step < QUIZ_STEPS.length ? (
{QUIZ_STEPS[step].tag}

{QUIZ_STEPS[step].title}

{QUIZ_STEPS[step].options.map((opt) => { const Icon = opt.ico; const selected = answers[QUIZ_STEPS[step].key] === opt.v; return ( ); })}
) : (
5 / 5 · Контакт

Куда отправить расчёт?

setContact((c) => ({ ...c, name: e.target.value }))} required />
setContact((c) => ({ ...c, phone: e.target.value }))} required />
{files.length > 0 && (
{files.map((f, i) => ( {f.name.length > 22 ? f.name.slice(0, 20) + '…' : f.name} ))}
)} setContact((c) => ({ ...c, link: e.target.value }))} style={{ marginTop: 8 }} />
Ваш запрос: {answers.item && <>{QUIZ_LABELS.item[answers.item]} · } {answers.qty && <>{QUIZ_LABELS.qty[answers.qty]} · } {answers.patterns && <>{QUIZ_LABELS.patterns[answers.patterns]} · } {answers.when && <>{QUIZ_LABELS.when[answers.when]}}
{submitError && (

{submitError}

)}
)}
{!done && (
{step >= QUIZ_STEPS.length ? ( ) : ( )}
)}
); } /* Export to window for use across files */ Object.assign(window, { Counter, RespBadge, BrandLogo, Header, MobileMenu, Hero, FormSection, QuizModal, NAV, });