/* eslint-disable */ /* global React, ReactDOM, TweaksPanel, useTweaks, TweakSection, TweakRadio */ const { useEffect, useRef } = React; // ============== WATER (hero background, v2) ============== const WATER_VERT = `attribute vec2 aPos; varying vec2 vUv; void main(){ vUv = aPos*0.5+0.5; gl_Position = vec4(aPos,0.0,1.0); }`; const WATER_FRAG = `precision highp float; varying vec2 vUv; uniform vec2 uRes; uniform float uTime; uniform vec3 uRipples[10]; float hash(vec2 p){ p=fract(p*vec2(123.34,345.45)); p+=dot(p,p+34.345); return fract(p.x*p.y); } float noise(vec2 p){ vec2 i=floor(p),f=fract(p); float a=hash(i),b=hash(i+vec2(1.,0.)),c=hash(i+vec2(0.,1.)),d=hash(i+vec2(1.,1.)); vec2 u=f*f*(3.-2.*f); return mix(mix(a,b,u.x),mix(c,d,u.x),u.y); } float fbm(vec2 p){ float v=0.,a=0.5; for(int i=0;i<3;i++){ v+=a*noise(p); p*=2.0; a*=0.5; } return v; } void main(){ vec2 uv=vUv; float aspect=uRes.x/uRes.y; vec2 p=vec2(uv.x*aspect,uv.y); vec2 disp=vec2(0.0); float glow=0.0; for(int i=0;i<10;i++){ vec3 r=uRipples[i]; if(r.z<0.0) continue; float age=uTime-r.z; if(age>1.6) continue; vec2 rc=vec2(r.x*aspect,r.y); float d=distance(p,rc); float front=0.03+age*0.40; float band=exp(-pow((d-front)*14.0,2.0)); float amp=smoothstep(0.0,0.12,age)*exp(-age*2.4); disp+=normalize(p-rc+1e-4)*band*amp*0.014; glow+=band*amp; } float t=uTime*0.06; vec2 q=vec2(fbm(p*3.0+t),fbm(p*3.0+vec2(5.2,1.3)-t)); vec2 warp=p*3.0+3.0*q+disp*6.0; float n=fbm(warp+t*0.7); float caustic=pow(max(0.0,1.0-abs(0.5-n)*2.3),3.0); float depth=clamp((1.0-uv.y)*0.55,0.0,1.0); // очень тёмный приглушённый неон-градиент: пинк -> лиловый -> циан vec3 cPink=vec3(0.19,0.03,0.11), cLilac=vec3(0.09,0.04,0.18), cCyan=vec3(0.02,0.10,0.16); vec3 grad=mix(mix(cPink,cLilac,smoothstep(0.0,0.5,uv.x)),cCyan,smoothstep(0.45,1.0,uv.x)); vec3 col=grad*mix(1.0,0.16,depth); float causticAmt=(1.0-depth)*0.7+0.10; col+=caustic*causticAmt*vec3(0.30,0.85,1.0)*0.30; float rays=pow(max(0.0,0.5+0.5*sin(uv.x*7.0+t*5.0)+0.32*sin(uv.x*13.0-t*3.0)),4.0)*(1.0-uv.y)*0.6; col+=rays*vec3(0.75,0.55,1.0)*0.10; // курсор: только искажение воды (disp), без свечения float vig=smoothstep(1.30,0.30,length(uv-0.5)); col*=mix(0.78,1.0,vig); gl_FragColor=vec4(col,1.0); }`; function WaterCanvas() { const ref = useRef(null); useEffect(() => { const cv = ref.current; if (!cv) return; const gl = cv.getContext("webgl") || cv.getContext("experimental-webgl"); if (!gl) return; const coarse = matchMedia("(pointer:coarse)").matches || window.innerWidth < 700; const SCALE = coarse ? 0.5 : 0.7; const FT = coarse ? 42 : 33; const compile = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s; }; const prog = gl.createProgram(); gl.attachShader(prog, compile(gl.VERTEX_SHADER, WATER_VERT)); gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, WATER_FRAG)); gl.linkProgram(prog); gl.useProgram(prog); const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW); const aPos = gl.getAttribLocation(prog, "aPos"); gl.enableVertexAttribArray(aPos); gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0); const uRes = gl.getUniformLocation(prog, "uRes"); const uTime = gl.getUniformLocation(prog, "uTime"); const uRip = gl.getUniformLocation(prog, "uRipples"); const ripples = new Float32Array(30); for (let i = 0; i < 10; i++) ripples[i*3+2] = -1; let ri = 0; const addRipple = (x, y) => { ripples[ri*3]=x; ripples[ri*3+1]=y; ripples[ri*3+2]=performance.now()/1000; ri=(ri+1)%10; }; const resize = () => { const w = Math.max(2, Math.floor(cv.clientWidth * SCALE)); const h = Math.max(2, Math.floor(cv.clientHeight * SCALE)); if (cv.width !== w || cv.height !== h) { cv.width = w; cv.height = h; gl.viewport(0,0,w,h); } }; const ro = new ResizeObserver(resize); ro.observe(cv); resize(); let lx = -1, ly = -1; const STEP = 0.03; const onMove = (e) => { const r = cv.getBoundingClientRect(); const nx = (e.clientX - r.left) / r.width; const ny = 1 - (e.clientY - r.top) / r.height; if (nx < 0 || nx > 1 || ny < 0 || ny > 1) return; if (lx < 0) { lx = nx; ly = ny; return; } const dx = nx - lx, dy = ny - ly, dist = Math.sqrt(dx*dx + dy*dy); if (dist < STEP) return; const n = Math.min(4, Math.floor(dist / STEP)); for (let k = 1; k <= n; k++) { const f = (k*STEP)/dist; addRipple(lx + dx*f, ly + dy*f); } lx += (dx*(n*STEP))/dist; ly += (dy*(n*STEP))/dist; }; const onDown = (e) => { const r = cv.getBoundingClientRect(); const nx = (e.clientX - r.left) / r.width; const ny = 1 - (e.clientY - r.top) / r.height; if (nx < 0 || nx > 1 || ny < 0 || ny > 1) return; lx = nx; ly = ny; addRipple(nx, ny); }; window.addEventListener("pointermove", onMove, { passive: true }); window.addEventListener("pointerdown", onDown, { passive: true }); let visible = true; const io = new IntersectionObserver((es) => { visible = es[0].isIntersecting; }, { threshold: 0 }); io.observe(cv); let raf = 0, lastDraw = 0; const frame = (ts) => { raf = requestAnimationFrame(frame); if (document.hidden || !visible) return; if (ts - lastDraw < FT) return; lastDraw = ts; gl.uniform2f(uRes, cv.width, cv.height); gl.uniform1f(uTime, performance.now()/1000); gl.uniform3fv(uRip, ripples); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }; raf = requestAnimationFrame(frame); return () => { cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); window.removeEventListener("pointermove", onMove); window.removeEventListener("pointerdown", onDown); const lose = gl.getExtension("WEBGL_lose_context"); if (lose) lose.loseContext(); }; }, []); return ; } // ============== DATA ============== const BENEFITS = [ { t: "Общаться без барьеров", b: "С любым человеком на планете — на английском, французском или китайском. Без «э-э-э» и без Google Translate.", span: 2 }, { t: "Уверенность везде", b: "В путешествиях, на работе, в учёбе. От заказа кофе до презентации на иностранном.", span: 2 }, { t: "Сдать экзамен", b: "Cambridge (KET/PET/FCE/CAE до C2), DELF до B2, HSK 1–3 и ЕГЭ по английскому. Разбираем формат и тренируем каждую часть.", span: 2 }, { t: "Игровой формат", b: "Интерактивные квесты и мини-викторины под твои интересы — не скучные диалоги из учебника.", span: 3 }, { t: "Свой темп", b: "Гибкий график, занятия 45 или 60 минут, удобная платформа ProgressMe.", span: 3 }, ]; const APPROACH = [ { n: "01", h: "Персональная программа", p: "Уроки под твои цели, уровень и интересы — сериалы, работа, путешествия, экзамен. Один учебник на всех — не мой подход." }, { n: "02", h: "Платформа ProgressMe", p: "Личный кабинет, история уроков, домашние задания и доступ к коллекции интерактивных игр — всё в одном месте." }, { n: "03", h: "Авторские квесты", p: "Создаю уроки-игры под конкретные темы: от детектива на английском до виртуальной поездки в Париж. Учиться так — правда весело." }, { n: "04", h: "Честный прогресс", p: "Раз в несколько недель сверяемся: что получается, что пока нет. Корректируем план, празднуем победы." }, ]; const REVIEWS = [ { q: "За пару месяцев с Таней разговорилась настолько, что обсуждаю сериалы и работу без подсказок. Английский стал частью кофе-брейка — жду урок, как новую серию любимого шоу.", n: "Мария Мамонова", r: "Консультант, блогер", img: "img/review1.jpeg", init: "ММ" }, { q: "Всегда хотела болтать по-английски без запинок — и наконец нашла своего учителя. Уроки пролетают, будто дружеский созвон, а знания оседают крепко.", n: "Виолетта Кондратюк", r: "Студентка", img: "img/review2.jpeg", init: "ВК" }, { q: "Думал, в аэропорту потеряюсь в трёх вывесках. За пару месяцев Таня превратила страх в «Hello, can you help me…?». Прошёл контроль, заказал такси, поболтал с баристой — всё без переводчика.", n: "Александр Воронцов", r: "Юрист", img: "img/review3.jpg", init: "АВ" }, { q: "Учимся год. Перешла от «э-э-э» к свободным диалогам на митингах. Атмосфера дружеская, задания чёткие, прогресс измеримый. Нужно быстро вывести язык в поле — Татьяна тот самый вариант.", n: "Марианна Никитушина", r: "Предприниматель", img: "img/review4.jpeg", init: "МН" }, ]; const TIERS = [ { kicker: "Стандарт", title: "45 минут", price: "1 900", unit: "₽", note: "за урок", feat: false, inc: ["Персональная программа под уровень и цели", "Личный кабинет в ProgressMe", "Доступ к коллекции интерактивных игр", "Интерактивная художественная книга"] }, { kicker: "Полный формат", title: "60 минут", price: "2 400", unit: "₽", note: "за урок", feat: true, badge: "Рекомендую", inc: ["Всё из стандартного тарифа", "Расширенная практика разговора", "Модули на Quizlet", "Больше времени на разбор материала"] }, ]; // ============== UI ============== function Arrow({ s = 14 }) { return ; } function Nav() { return (
Татьяна Смагло Trial · free
); } function Hero() { return (
System online · индивидуальные онлайн-занятия EN · FR · ZH · v2026

Английский, французский, китайский — под твою цель.

Привет! Меня зовут Татьяна. Помогаю взрослым и подросткам заговорить на английском, французском или китайском — от нулевого уровня до Cambridge C2, DELF B2 и HSK3.

Татьяна Смагло
LIVE · ONLINE
Tatiana Smaglo
репетитор · переводчик
ID / 001
15+
Лет с языками
C2
Cambridge CAE
3
Языка · EN·FR·ZH
{[0,1].map(k => ( Hello English Bonjour Français 你好 中文 Cambridge · DELF · HSK ))}
); } function SectionHead({ id, title, aside }) { return (
SEC / {id}

{aside}

); } function About() { return (

Моя цель — чтобы ты не «учил язык», а начал на нём жить.

На занятиях мы говорим, играем, спорим, разбираем тексты, которые тебе правда интересны. Грамматика — только та, что нужна здесь и сейчас; лексика — под твой контекст, а не «темы про хобби и погоду из учебника».

Готовлю с нуля и до продвинутого уровня, вывожу в разговор, а если нужно — доводим до сертификата. Делать это в своём темпе и в удобное время — нормально.

CambridgeC2 · CAEСертификат продвинутого уровня английского
DELFB2Сертификат по французскому языку
ОбразованиеУниверситет Бордо-Монтень (Франция), «Филология»
Готовлю кCambridge (KET, PET, FCE, CAE), DELF, HSK 1–3, ЕГЭ по английскому
ФорматИндивидуальные онлайн-занятия на ProgressMe
); } function Benefits() { return (
— 01

Общаться без барьеров.

С любым человеком на планете — на английском, французском или китайском. Без «э-э-э» и без Google Translate.

progressme · live-session
T: How was your weekend?
S: I went hiking with friends!
T: Nice! Tell me more...
· live · 45 min
— 02

Уверенность везде

В путешествиях, работе, учёбе. От заказа кофе до презентации на иностранном.

— 03

Сдать экзамен

Cambridge (KET/PET/FCE/CAE до C2), DELF до B2, HSK 1–3 и ЕГЭ по английскому.

— 04

Игровой формат

Интерактивные квесты и мини-викторины под твои интересы. Не диалоги из учебника.

— 05

Свой темп.

Гибкий график, занятия 45 или 60 минут, удобная платформа ProgressMe.

— 06

Измеримый прогресс.

Понятные цели, честная обратная связь. Никакой воды и зубрёжки «на всякий случай».

); } function Approach() { return (
{APPROACH.map(a => (
{a.n}
{a.h}
{a.p}
))}
); } function Quiz() { return (

Проверь свой словарный запас за 2 минуты.

Мини-викторина на английском, французском и китайском. Выбери язык и уровень — и узнай, где ты сейчас. Бесплатно, без регистрации.

Пройти викторину
LEVEL · A203 / 10
umbrella
чемодан
зонт
облако
гора
); } function Reviews() { return (
{REVIEWS.map((r, i) => (
{r.q}
{r.init}
{r.n}
{r.r}
))}
); } function Pricing() { return (
{TIERS.map((t, i) => (
{t.badge &&
{t.badge}
}
{t.kicker}

Занятие · {t.title}

{t.price}{t.unit}
{t.note}
    {t.inc.map((x, j) =>
  • {x}
  • )}
Записаться
))}
); } function Final() { return (

Заговорим вместе?

Первый урок — бесплатный. Напиши мне в Телеграм, и мы договоримся о времени, которое подойдёт именно тебе.

Написать в Телеграм
); } function Foot() { return ( ); } function Tweaks() { const [t, set] = useTweaks({ palette: "neon", mode: "dark" }); useEffect(() => { if (t.palette && t.palette !== "neon") document.documentElement.setAttribute("data-palette", t.palette); else document.documentElement.removeAttribute("data-palette"); if (t.mode === "light") document.documentElement.setAttribute("data-mode", "light"); else document.documentElement.removeAttribute("data-mode"); }, [t.palette, t.mode]); return ( set({ palette: v })} options={[ { value: "neon", label: "Neon (пинк/циан)" }, { value: "cyber", label: "Cyber (зелёный/пинк)" }, { value: "sunset", label: "Sunset (оранж/жёлтый)" }, { value: "ice", label: "Ice (бирюза/лаванда)" }, { value: "mono", label: "Mono (ч/б)" }, ]} /> set({ mode: v })} options={[ { value: "dark", label: "Тёмный" }, { value: "light", label: "Светлый" }, ]} /> ); } function App() { useEffect(() => { const els = document.querySelectorAll(".section, .hero, .quiz, .review, .tier, .card, .step"); const io = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.05 }); els.forEach(el => { el.classList.add("reveal"); io.observe(el); }); }, []); return ( <>