/* 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 (
Привет! Меня зовут Татьяна. Помогаю взрослым и подросткам заговорить на английском, французском или китайском — от нулевого уровня до Cambridge C2, DELF B2 и HSK3.
Английский,
французский,
китайский —
под твою цель.
Моя цель — чтобы ты не «учил язык», а начал на нём жить.
На занятиях мы говорим, играем, спорим, разбираем тексты, которые тебе правда интересны. Грамматика — только та, что нужна здесь и сейчас; лексика — под твой контекст, а не «темы про хобби и погоду из учебника».
Готовлю с нуля и до продвинутого уровня, вывожу в разговор, а если нужно — доводим до сертификата. Делать это в своём темпе и в удобное время — нормально.
С любым человеком на планете — на английском, французском или китайском. Без «э-э-э» и без Google Translate.
В путешествиях, работе, учёбе. От заказа кофе до презентации на иностранном.
Cambridge (KET/PET/FCE/CAE до C2), DELF до B2, HSK 1–3 и ЕГЭ по английскому.
Интерактивные квесты и мини-викторины под твои интересы. Не диалоги из учебника.
Гибкий график, занятия 45 или 60 минут, удобная платформа ProgressMe.
Понятные цели, честная обратная связь. Никакой воды и зубрёжки «на всякий случай».
Мини-викторина на английском, французском и китайском. Выбери язык и уровень — и узнай, где ты сейчас. Бесплатно, без регистрации.
Пройти викторинуПервый урок — бесплатный. Напиши мне в Телеграм, и мы договоримся о времени, которое подойдёт именно тебе.
Написать в Телеграм