diff --git a/site/index.html b/site/index.html index 18f8219..dadb3ec 100644 --- a/site/index.html +++ b/site/index.html @@ -6,6 +6,7 @@ + Читаем по правилам — 01 diff --git a/site/public/rules-audio/a.mp3 b/site/public/rules-audio/a.mp3 new file mode 100644 index 0000000..6308c81 Binary files /dev/null and b/site/public/rules-audio/a.mp3 differ diff --git a/site/public/rules-audio/autumn.mp3 b/site/public/rules-audio/autumn.mp3 new file mode 100644 index 0000000..e6eb6e7 Binary files /dev/null and b/site/public/rules-audio/autumn.mp3 differ diff --git a/site/public/rules-audio/b.mp3 b/site/public/rules-audio/b.mp3 new file mode 100644 index 0000000..75271e5 Binary files /dev/null and b/site/public/rules-audio/b.mp3 differ diff --git a/site/public/rules-audio/back.mp3 b/site/public/rules-audio/back.mp3 new file mode 100644 index 0000000..2c18c18 Binary files /dev/null and b/site/public/rules-audio/back.mp3 differ diff --git a/site/public/rules-audio/bed.mp3 b/site/public/rules-audio/bed.mp3 new file mode 100644 index 0000000..8f9e195 Binary files /dev/null and b/site/public/rules-audio/bed.mp3 differ diff --git a/site/public/rules-audio/begin.mp3 b/site/public/rules-audio/begin.mp3 new file mode 100644 index 0000000..c98143f Binary files /dev/null and b/site/public/rules-audio/begin.mp3 differ diff --git a/site/public/rules-audio/big.mp3 b/site/public/rules-audio/big.mp3 new file mode 100644 index 0000000..53e85c1 Binary files /dev/null and b/site/public/rules-audio/big.mp3 differ diff --git a/site/public/rules-audio/black.mp3 b/site/public/rules-audio/black.mp3 new file mode 100644 index 0000000..74fcd46 Binary files /dev/null and b/site/public/rules-audio/black.mp3 differ diff --git a/site/public/rules-audio/boat.mp3 b/site/public/rules-audio/boat.mp3 new file mode 100644 index 0000000..e95e41b Binary files /dev/null and b/site/public/rules-audio/boat.mp3 differ diff --git a/site/public/rules-audio/book.mp3 b/site/public/rules-audio/book.mp3 new file mode 100644 index 0000000..dd07af2 Binary files /dev/null and b/site/public/rules-audio/book.mp3 differ diff --git a/site/public/rules-audio/box.mp3 b/site/public/rules-audio/box.mp3 new file mode 100644 index 0000000..26227a4 Binary files /dev/null and b/site/public/rules-audio/box.mp3 differ diff --git a/site/public/rules-audio/boy.mp3 b/site/public/rules-audio/boy.mp3 new file mode 100644 index 0000000..27630d4 Binary files /dev/null and b/site/public/rules-audio/boy.mp3 differ diff --git a/site/public/rules-audio/bread.mp3 b/site/public/rules-audio/bread.mp3 new file mode 100644 index 0000000..ad9daba Binary files /dev/null and b/site/public/rules-audio/bread.mp3 differ diff --git a/site/public/rules-audio/c.mp3 b/site/public/rules-audio/c.mp3 new file mode 100644 index 0000000..25e9ea2 Binary files /dev/null and b/site/public/rules-audio/c.mp3 differ diff --git a/site/public/rules-audio/cap.mp3 b/site/public/rules-audio/cap.mp3 new file mode 100644 index 0000000..ac1661c Binary files /dev/null and b/site/public/rules-audio/cap.mp3 differ diff --git a/site/public/rules-audio/cape.mp3 b/site/public/rules-audio/cape.mp3 new file mode 100644 index 0000000..e68ec28 Binary files /dev/null and b/site/public/rules-audio/cape.mp3 differ diff --git a/site/public/rules-audio/cat.mp3 b/site/public/rules-audio/cat.mp3 new file mode 100644 index 0000000..8b8d054 Binary files /dev/null and b/site/public/rules-audio/cat.mp3 differ diff --git a/site/public/rules-audio/catch.mp3 b/site/public/rules-audio/catch.mp3 new file mode 100644 index 0000000..f036019 Binary files /dev/null and b/site/public/rules-audio/catch.mp3 differ diff --git a/site/public/rules-audio/chair.mp3 b/site/public/rules-audio/chair.mp3 new file mode 100644 index 0000000..01f61f5 Binary files /dev/null and b/site/public/rules-audio/chair.mp3 differ diff --git a/site/public/rules-audio/chicken.mp3 b/site/public/rules-audio/chicken.mp3 new file mode 100644 index 0000000..8d88cc9 Binary files /dev/null and b/site/public/rules-audio/chicken.mp3 differ diff --git a/site/public/rules-audio/city.mp3 b/site/public/rules-audio/city.mp3 new file mode 100644 index 0000000..634c610 Binary files /dev/null and b/site/public/rules-audio/city.mp3 differ diff --git a/site/public/rules-audio/coin.mp3 b/site/public/rules-audio/coin.mp3 new file mode 100644 index 0000000..52359eb Binary files /dev/null and b/site/public/rules-audio/coin.mp3 differ diff --git a/site/public/rules-audio/come.mp3 b/site/public/rules-audio/come.mp3 new file mode 100644 index 0000000..a161d0b Binary files /dev/null and b/site/public/rules-audio/come.mp3 differ diff --git a/site/public/rules-audio/could.mp3 b/site/public/rules-audio/could.mp3 new file mode 100644 index 0000000..953473c Binary files /dev/null and b/site/public/rules-audio/could.mp3 differ diff --git a/site/public/rules-audio/cow.mp3 b/site/public/rules-audio/cow.mp3 new file mode 100644 index 0000000..aad43e7 Binary files /dev/null and b/site/public/rules-audio/cow.mp3 differ diff --git a/site/public/rules-audio/cry.mp3 b/site/public/rules-audio/cry.mp3 new file mode 100644 index 0000000..15edda6 Binary files /dev/null and b/site/public/rules-audio/cry.mp3 differ diff --git a/site/public/rules-audio/cub.mp3 b/site/public/rules-audio/cub.mp3 new file mode 100644 index 0000000..cc78d2f Binary files /dev/null and b/site/public/rules-audio/cub.mp3 differ diff --git a/site/public/rules-audio/cube.mp3 b/site/public/rules-audio/cube.mp3 new file mode 100644 index 0000000..7f98af7 Binary files /dev/null and b/site/public/rules-audio/cube.mp3 differ diff --git a/site/public/rules-audio/cup.mp3 b/site/public/rules-audio/cup.mp3 new file mode 100644 index 0000000..4720ae0 Binary files /dev/null and b/site/public/rules-audio/cup.mp3 differ diff --git a/site/public/rules-audio/cute.mp3 b/site/public/rules-audio/cute.mp3 new file mode 100644 index 0000000..b578cf4 Binary files /dev/null and b/site/public/rules-audio/cute.mp3 differ diff --git a/site/public/rules-audio/d.mp3 b/site/public/rules-audio/d.mp3 new file mode 100644 index 0000000..a62c618 Binary files /dev/null and b/site/public/rules-audio/d.mp3 differ diff --git a/site/public/rules-audio/day.mp3 b/site/public/rules-audio/day.mp3 new file mode 100644 index 0000000..c2e2a52 Binary files /dev/null and b/site/public/rules-audio/day.mp3 differ diff --git a/site/public/rules-audio/e.mp3 b/site/public/rules-audio/e.mp3 new file mode 100644 index 0000000..bf104bd Binary files /dev/null and b/site/public/rules-audio/e.mp3 differ diff --git a/site/public/rules-audio/exact.mp3 b/site/public/rules-audio/exact.mp3 new file mode 100644 index 0000000..e0a5625 Binary files /dev/null and b/site/public/rules-audio/exact.mp3 differ diff --git a/site/public/rules-audio/exam.mp3 b/site/public/rules-audio/exam.mp3 new file mode 100644 index 0000000..6084408 Binary files /dev/null and b/site/public/rules-audio/exam.mp3 differ diff --git a/site/public/rules-audio/f.mp3 b/site/public/rules-audio/f.mp3 new file mode 100644 index 0000000..a1f9cfe Binary files /dev/null and b/site/public/rules-audio/f.mp3 differ diff --git a/site/public/rules-audio/fish.mp3 b/site/public/rules-audio/fish.mp3 new file mode 100644 index 0000000..e722211 Binary files /dev/null and b/site/public/rules-audio/fish.mp3 differ diff --git a/site/public/rules-audio/five.mp3 b/site/public/rules-audio/five.mp3 new file mode 100644 index 0000000..d0e64b3 Binary files /dev/null and b/site/public/rules-audio/five.mp3 differ diff --git a/site/public/rules-audio/g.mp3 b/site/public/rules-audio/g.mp3 new file mode 100644 index 0000000..deda7e6 Binary files /dev/null and b/site/public/rules-audio/g.mp3 differ diff --git a/site/public/rules-audio/give.mp3 b/site/public/rules-audio/give.mp3 new file mode 100644 index 0000000..f72fcdc Binary files /dev/null and b/site/public/rules-audio/give.mp3 differ diff --git a/site/public/rules-audio/green.mp3 b/site/public/rules-audio/green.mp3 new file mode 100644 index 0000000..1ce5ff9 Binary files /dev/null and b/site/public/rules-audio/green.mp3 differ diff --git a/site/public/rules-audio/grew.mp3 b/site/public/rules-audio/grew.mp3 new file mode 100644 index 0000000..0d73ec2 Binary files /dev/null and b/site/public/rules-audio/grew.mp3 differ diff --git a/site/public/rules-audio/gym.mp3 b/site/public/rules-audio/gym.mp3 new file mode 100644 index 0000000..df2f44c Binary files /dev/null and b/site/public/rules-audio/gym.mp3 differ diff --git a/site/public/rules-audio/h.mp3 b/site/public/rules-audio/h.mp3 new file mode 100644 index 0000000..4055449 Binary files /dev/null and b/site/public/rules-audio/h.mp3 differ diff --git a/site/public/rules-audio/happy.mp3 b/site/public/rules-audio/happy.mp3 new file mode 100644 index 0000000..8625834 Binary files /dev/null and b/site/public/rules-audio/happy.mp3 differ diff --git a/site/public/rules-audio/have.mp3 b/site/public/rules-audio/have.mp3 new file mode 100644 index 0000000..d0730f2 Binary files /dev/null and b/site/public/rules-audio/have.mp3 differ diff --git a/site/public/rules-audio/home.mp3 b/site/public/rules-audio/home.mp3 new file mode 100644 index 0000000..47c5828 Binary files /dev/null and b/site/public/rules-audio/home.mp3 differ diff --git a/site/public/rules-audio/hop.mp3 b/site/public/rules-audio/hop.mp3 new file mode 100644 index 0000000..47b0a5e Binary files /dev/null and b/site/public/rules-audio/hop.mp3 differ diff --git a/site/public/rules-audio/hope.mp3 b/site/public/rules-audio/hope.mp3 new file mode 100644 index 0000000..1a2d8ee Binary files /dev/null and b/site/public/rules-audio/hope.mp3 differ diff --git a/site/public/rules-audio/hot.mp3 b/site/public/rules-audio/hot.mp3 new file mode 100644 index 0000000..fca4c3f Binary files /dev/null and b/site/public/rules-audio/hot.mp3 differ diff --git a/site/public/rules-audio/house.mp3 b/site/public/rules-audio/house.mp3 new file mode 100644 index 0000000..944c3ec Binary files /dev/null and b/site/public/rules-audio/house.mp3 differ diff --git a/site/public/rules-audio/i.mp3 b/site/public/rules-audio/i.mp3 new file mode 100644 index 0000000..a3d195f Binary files /dev/null and b/site/public/rules-audio/i.mp3 differ diff --git a/site/public/rules-audio/important.mp3 b/site/public/rules-audio/important.mp3 new file mode 100644 index 0000000..584c546 Binary files /dev/null and b/site/public/rules-audio/important.mp3 differ diff --git a/site/public/rules-audio/j.mp3 b/site/public/rules-audio/j.mp3 new file mode 100644 index 0000000..a298d60 Binary files /dev/null and b/site/public/rules-audio/j.mp3 differ diff --git a/site/public/rules-audio/k.mp3 b/site/public/rules-audio/k.mp3 new file mode 100644 index 0000000..536fa6c Binary files /dev/null and b/site/public/rules-audio/k.mp3 differ diff --git a/site/public/rules-audio/kit.mp3 b/site/public/rules-audio/kit.mp3 new file mode 100644 index 0000000..b984631 Binary files /dev/null and b/site/public/rules-audio/kit.mp3 differ diff --git a/site/public/rules-audio/kite.mp3 b/site/public/rules-audio/kite.mp3 new file mode 100644 index 0000000..c469780 Binary files /dev/null and b/site/public/rules-audio/kite.mp3 differ diff --git a/site/public/rules-audio/l.mp3 b/site/public/rules-audio/l.mp3 new file mode 100644 index 0000000..de03071 Binary files /dev/null and b/site/public/rules-audio/l.mp3 differ diff --git a/site/public/rules-audio/late.mp3 b/site/public/rules-audio/late.mp3 new file mode 100644 index 0000000..ab5e386 Binary files /dev/null and b/site/public/rules-audio/late.mp3 differ diff --git a/site/public/rules-audio/light.mp3 b/site/public/rules-audio/light.mp3 new file mode 100644 index 0000000..ce9a8c5 Binary files /dev/null and b/site/public/rules-audio/light.mp3 differ diff --git a/site/public/rules-audio/long.mp3 b/site/public/rules-audio/long.mp3 new file mode 100644 index 0000000..db62f1b Binary files /dev/null and b/site/public/rules-audio/long.mp3 differ diff --git a/site/public/rules-audio/love.mp3 b/site/public/rules-audio/love.mp3 new file mode 100644 index 0000000..453b3cd Binary files /dev/null and b/site/public/rules-audio/love.mp3 differ diff --git a/site/public/rules-audio/m.mp3 b/site/public/rules-audio/m.mp3 new file mode 100644 index 0000000..6d32be9 Binary files /dev/null and b/site/public/rules-audio/m.mp3 differ diff --git a/site/public/rules-audio/map.mp3 b/site/public/rules-audio/map.mp3 new file mode 100644 index 0000000..4561534 Binary files /dev/null and b/site/public/rules-audio/map.mp3 differ diff --git a/site/public/rules-audio/match.mp3 b/site/public/rules-audio/match.mp3 new file mode 100644 index 0000000..9f1aa61 Binary files /dev/null and b/site/public/rules-audio/match.mp3 differ diff --git a/site/public/rules-audio/moon.mp3 b/site/public/rules-audio/moon.mp3 new file mode 100644 index 0000000..db793f2 Binary files /dev/null and b/site/public/rules-audio/moon.mp3 differ diff --git a/site/public/rules-audio/mother.mp3 b/site/public/rules-audio/mother.mp3 new file mode 100644 index 0000000..abb471b Binary files /dev/null and b/site/public/rules-audio/mother.mp3 differ diff --git a/site/public/rules-audio/mouse.mp3 b/site/public/rules-audio/mouse.mp3 new file mode 100644 index 0000000..66d5935 Binary files /dev/null and b/site/public/rules-audio/mouse.mp3 differ diff --git a/site/public/rules-audio/move.mp3 b/site/public/rules-audio/move.mp3 new file mode 100644 index 0000000..9df73dd Binary files /dev/null and b/site/public/rules-audio/move.mp3 differ diff --git a/site/public/rules-audio/my.mp3 b/site/public/rules-audio/my.mp3 new file mode 100644 index 0000000..3980520 Binary files /dev/null and b/site/public/rules-audio/my.mp3 differ diff --git a/site/public/rules-audio/n.mp3 b/site/public/rules-audio/n.mp3 new file mode 100644 index 0000000..f1e83fb Binary files /dev/null and b/site/public/rules-audio/n.mp3 differ diff --git a/site/public/rules-audio/name.mp3 b/site/public/rules-audio/name.mp3 new file mode 100644 index 0000000..d776bb4 Binary files /dev/null and b/site/public/rules-audio/name.mp3 differ diff --git a/site/public/rules-audio/new.mp3 b/site/public/rules-audio/new.mp3 new file mode 100644 index 0000000..521032e Binary files /dev/null and b/site/public/rules-audio/new.mp3 differ diff --git a/site/public/rules-audio/night.mp3 b/site/public/rules-audio/night.mp3 new file mode 100644 index 0000000..8c37806 Binary files /dev/null and b/site/public/rules-audio/night.mp3 differ diff --git a/site/public/rules-audio/note.mp3 b/site/public/rules-audio/note.mp3 new file mode 100644 index 0000000..3dd3346 Binary files /dev/null and b/site/public/rules-audio/note.mp3 differ diff --git a/site/public/rules-audio/o.mp3 b/site/public/rules-audio/o.mp3 new file mode 100644 index 0000000..1ae2d6c Binary files /dev/null and b/site/public/rules-audio/o.mp3 differ diff --git a/site/public/rules-audio/p.mp3 b/site/public/rules-audio/p.mp3 new file mode 100644 index 0000000..eb0d5b2 Binary files /dev/null and b/site/public/rules-audio/p.mp3 differ diff --git a/site/public/rules-audio/pen.mp3 b/site/public/rules-audio/pen.mp3 new file mode 100644 index 0000000..82a73dc Binary files /dev/null and b/site/public/rules-audio/pen.mp3 differ diff --git a/site/public/rules-audio/phone.mp3 b/site/public/rules-audio/phone.mp3 new file mode 100644 index 0000000..2999ec5 Binary files /dev/null and b/site/public/rules-audio/phone.mp3 differ diff --git a/site/public/rules-audio/photo.mp3 b/site/public/rules-audio/photo.mp3 new file mode 100644 index 0000000..2fe5c29 Binary files /dev/null and b/site/public/rules-audio/photo.mp3 differ diff --git a/site/public/rules-audio/present-noun.mp3 b/site/public/rules-audio/present-noun.mp3 new file mode 100644 index 0000000..3df0699 Binary files /dev/null and b/site/public/rules-audio/present-noun.mp3 differ diff --git a/site/public/rules-audio/present-verb.mp3 b/site/public/rules-audio/present-verb.mp3 new file mode 100644 index 0000000..32848ac Binary files /dev/null and b/site/public/rules-audio/present-verb.mp3 differ diff --git a/site/public/rules-audio/q.mp3 b/site/public/rules-audio/q.mp3 new file mode 100644 index 0000000..e5e8e44 Binary files /dev/null and b/site/public/rules-audio/q.mp3 differ diff --git a/site/public/rules-audio/queen.mp3 b/site/public/rules-audio/queen.mp3 new file mode 100644 index 0000000..b232163 Binary files /dev/null and b/site/public/rules-audio/queen.mp3 differ diff --git a/site/public/rules-audio/quick.mp3 b/site/public/rules-audio/quick.mp3 new file mode 100644 index 0000000..d601695 Binary files /dev/null and b/site/public/rules-audio/quick.mp3 differ diff --git a/site/public/rules-audio/r.mp3 b/site/public/rules-audio/r.mp3 new file mode 100644 index 0000000..9561b35 Binary files /dev/null and b/site/public/rules-audio/r.mp3 differ diff --git a/site/public/rules-audio/rain.mp3 b/site/public/rules-audio/rain.mp3 new file mode 100644 index 0000000..deab6f9 Binary files /dev/null and b/site/public/rules-audio/rain.mp3 differ diff --git a/site/public/rules-audio/read-past.mp3 b/site/public/rules-audio/read-past.mp3 new file mode 100644 index 0000000..94e805e Binary files /dev/null and b/site/public/rules-audio/read-past.mp3 differ diff --git a/site/public/rules-audio/read-present.mp3 b/site/public/rules-audio/read-present.mp3 new file mode 100644 index 0000000..ce43271 Binary files /dev/null and b/site/public/rules-audio/read-present.mp3 differ diff --git a/site/public/rules-audio/record-noun.mp3 b/site/public/rules-audio/record-noun.mp3 new file mode 100644 index 0000000..510316d Binary files /dev/null and b/site/public/rules-audio/record-noun.mp3 differ diff --git a/site/public/rules-audio/record-verb.mp3 b/site/public/rules-audio/record-verb.mp3 new file mode 100644 index 0000000..c265b90 Binary files /dev/null and b/site/public/rules-audio/record-verb.mp3 differ diff --git a/site/public/rules-audio/road.mp3 b/site/public/rules-audio/road.mp3 new file mode 100644 index 0000000..79c797c Binary files /dev/null and b/site/public/rules-audio/road.mp3 differ diff --git a/site/public/rules-audio/rule.mp3 b/site/public/rules-audio/rule.mp3 new file mode 100644 index 0000000..ae306ea Binary files /dev/null and b/site/public/rules-audio/rule.mp3 differ diff --git a/site/public/rules-audio/s.mp3 b/site/public/rules-audio/s.mp3 new file mode 100644 index 0000000..b8a99ef Binary files /dev/null and b/site/public/rules-audio/s.mp3 differ diff --git a/site/public/rules-audio/saw.mp3 b/site/public/rules-audio/saw.mp3 new file mode 100644 index 0000000..679310b Binary files /dev/null and b/site/public/rules-audio/saw.mp3 differ diff --git a/site/public/rules-audio/see.mp3 b/site/public/rules-audio/see.mp3 new file mode 100644 index 0000000..5fe8065 Binary files /dev/null and b/site/public/rules-audio/see.mp3 differ diff --git a/site/public/rules-audio/ship.mp3 b/site/public/rules-audio/ship.mp3 new file mode 100644 index 0000000..af6e7df Binary files /dev/null and b/site/public/rules-audio/ship.mp3 differ diff --git a/site/public/rules-audio/sing.mp3 b/site/public/rules-audio/sing.mp3 new file mode 100644 index 0000000..3fc956b Binary files /dev/null and b/site/public/rules-audio/sing.mp3 differ diff --git a/site/public/rules-audio/sit.mp3 b/site/public/rules-audio/sit.mp3 new file mode 100644 index 0000000..c251808 Binary files /dev/null and b/site/public/rules-audio/sit.mp3 differ diff --git a/site/public/rules-audio/six.mp3 b/site/public/rules-audio/six.mp3 new file mode 100644 index 0000000..07c163e Binary files /dev/null and b/site/public/rules-audio/six.mp3 differ diff --git a/site/public/rules-audio/snow.mp3 b/site/public/rules-audio/snow.mp3 new file mode 100644 index 0000000..571fb87 Binary files /dev/null and b/site/public/rules-audio/snow.mp3 differ diff --git a/site/public/rules-audio/sound-a.mp3 b/site/public/rules-audio/sound-a.mp3 new file mode 100644 index 0000000..8b8d054 Binary files /dev/null and b/site/public/rules-audio/sound-a.mp3 differ diff --git a/site/public/rules-audio/sound-e.mp3 b/site/public/rules-audio/sound-e.mp3 new file mode 100644 index 0000000..8f9e195 Binary files /dev/null and b/site/public/rules-audio/sound-e.mp3 differ diff --git a/site/public/rules-audio/sound-i.mp3 b/site/public/rules-audio/sound-i.mp3 new file mode 100644 index 0000000..c251808 Binary files /dev/null and b/site/public/rules-audio/sound-i.mp3 differ diff --git a/site/public/rules-audio/sound-o.mp3 b/site/public/rules-audio/sound-o.mp3 new file mode 100644 index 0000000..fca4c3f Binary files /dev/null and b/site/public/rules-audio/sound-o.mp3 differ diff --git a/site/public/rules-audio/sound-u.mp3 b/site/public/rules-audio/sound-u.mp3 new file mode 100644 index 0000000..4720ae0 Binary files /dev/null and b/site/public/rules-audio/sound-u.mp3 differ diff --git a/site/public/rules-audio/sound-y.mp3 b/site/public/rules-audio/sound-y.mp3 new file mode 100644 index 0000000..df2f44c Binary files /dev/null and b/site/public/rules-audio/sound-y.mp3 differ diff --git a/site/public/rules-audio/sun.mp3 b/site/public/rules-audio/sun.mp3 new file mode 100644 index 0000000..8ee26ee Binary files /dev/null and b/site/public/rules-audio/sun.mp3 differ diff --git a/site/public/rules-audio/system.mp3 b/site/public/rules-audio/system.mp3 new file mode 100644 index 0000000..64561a9 Binary files /dev/null and b/site/public/rules-audio/system.mp3 differ diff --git a/site/public/rules-audio/t.mp3 b/site/public/rules-audio/t.mp3 new file mode 100644 index 0000000..c338bf6 Binary files /dev/null and b/site/public/rules-audio/t.mp3 differ diff --git a/site/public/rules-audio/table.mp3 b/site/public/rules-audio/table.mp3 new file mode 100644 index 0000000..6a24269 Binary files /dev/null and b/site/public/rules-audio/table.mp3 differ diff --git a/site/public/rules-audio/team.mp3 b/site/public/rules-audio/team.mp3 new file mode 100644 index 0000000..473e07a Binary files /dev/null and b/site/public/rules-audio/team.mp3 differ diff --git a/site/public/rules-audio/theme.mp3 b/site/public/rules-audio/theme.mp3 new file mode 100644 index 0000000..7f94d1c Binary files /dev/null and b/site/public/rules-audio/theme.mp3 differ diff --git a/site/public/rules-audio/these.mp3 b/site/public/rules-audio/these.mp3 new file mode 100644 index 0000000..b03bb7d Binary files /dev/null and b/site/public/rules-audio/these.mp3 differ diff --git a/site/public/rules-audio/think.mp3 b/site/public/rules-audio/think.mp3 new file mode 100644 index 0000000..5baabdc Binary files /dev/null and b/site/public/rules-audio/think.mp3 differ diff --git a/site/public/rules-audio/this.mp3 b/site/public/rules-audio/this.mp3 new file mode 100644 index 0000000..3992287 Binary files /dev/null and b/site/public/rules-audio/this.mp3 differ diff --git a/site/public/rules-audio/three.mp3 b/site/public/rules-audio/three.mp3 new file mode 100644 index 0000000..1afb1cd Binary files /dev/null and b/site/public/rules-audio/three.mp3 differ diff --git a/site/public/rules-audio/time.mp3 b/site/public/rules-audio/time.mp3 new file mode 100644 index 0000000..f9bd502 Binary files /dev/null and b/site/public/rules-audio/time.mp3 differ diff --git a/site/public/rules-audio/u.mp3 b/site/public/rules-audio/u.mp3 new file mode 100644 index 0000000..a7b709a Binary files /dev/null and b/site/public/rules-audio/u.mp3 differ diff --git a/site/public/rules-audio/v.mp3 b/site/public/rules-audio/v.mp3 new file mode 100644 index 0000000..c2525ca Binary files /dev/null and b/site/public/rules-audio/v.mp3 differ diff --git a/site/public/rules-audio/w.mp3 b/site/public/rules-audio/w.mp3 new file mode 100644 index 0000000..6d3bf7f Binary files /dev/null and b/site/public/rules-audio/w.mp3 differ diff --git a/site/public/rules-audio/what.mp3 b/site/public/rules-audio/what.mp3 new file mode 100644 index 0000000..9de2194 Binary files /dev/null and b/site/public/rules-audio/what.mp3 differ diff --git a/site/public/rules-audio/when.mp3 b/site/public/rules-audio/when.mp3 new file mode 100644 index 0000000..d6dd7a7 Binary files /dev/null and b/site/public/rules-audio/when.mp3 differ diff --git a/site/public/rules-audio/window.mp3 b/site/public/rules-audio/window.mp3 new file mode 100644 index 0000000..eb1f76e Binary files /dev/null and b/site/public/rules-audio/window.mp3 differ diff --git a/site/public/rules-audio/x-ray.mp3 b/site/public/rules-audio/x-ray.mp3 new file mode 100644 index 0000000..c8503e8 Binary files /dev/null and b/site/public/rules-audio/x-ray.mp3 differ diff --git a/site/public/rules-audio/x.mp3 b/site/public/rules-audio/x.mp3 new file mode 100644 index 0000000..cf7ee89 Binary files /dev/null and b/site/public/rules-audio/x.mp3 differ diff --git a/site/public/rules-audio/xylophone.mp3 b/site/public/rules-audio/xylophone.mp3 new file mode 100644 index 0000000..b7b7cf1 Binary files /dev/null and b/site/public/rules-audio/xylophone.mp3 differ diff --git a/site/public/rules-audio/y.mp3 b/site/public/rules-audio/y.mp3 new file mode 100644 index 0000000..2cc67af Binary files /dev/null and b/site/public/rules-audio/y.mp3 differ diff --git a/site/public/rules-audio/yellow.mp3 b/site/public/rules-audio/yellow.mp3 new file mode 100644 index 0000000..a64a501 Binary files /dev/null and b/site/public/rules-audio/yellow.mp3 differ diff --git a/site/public/rules-audio/yes.mp3 b/site/public/rules-audio/yes.mp3 new file mode 100644 index 0000000..2123cca Binary files /dev/null and b/site/public/rules-audio/yes.mp3 differ diff --git a/site/public/rules-audio/you.mp3 b/site/public/rules-audio/you.mp3 new file mode 100644 index 0000000..4c991af Binary files /dev/null and b/site/public/rules-audio/you.mp3 differ diff --git a/site/public/rules-audio/z.mp3 b/site/public/rules-audio/z.mp3 new file mode 100644 index 0000000..b5eef33 Binary files /dev/null and b/site/public/rules-audio/z.mp3 differ diff --git a/site/src/data.rs b/site/src/data.rs index 00d39a4..375bfba 100644 --- a/site/src/data.rs +++ b/site/src/data.rs @@ -23,6 +23,36 @@ pub struct ReadingText { pub tokens: &'static [Token], } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RuleTable { + pub headers: &'static [&'static str], + pub rows: &'static [&'static [&'static str]], + pub audio_columns: &'static [RuleAudioColumn], + pub audio_rows: &'static [&'static [&'static [&'static str]]], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RuleAudioColumn { + pub after_text_column: usize, + pub header: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RuleBlock { + Paragraph(&'static str), + Subheading(&'static str), + Table(RuleTable), + Bullets(&'static [&'static str]), + Steps(&'static [&'static str]), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RuleSection { + pub number: u8, + pub title: &'static str, + pub blocks: &'static [RuleBlock], +} + macro_rules! entry { ($id:literal, $ipa:literal, $hint:literal) => { Word { @@ -53,6 +83,19 @@ macro_rules! punct { Token::Punctuation($value) }; } +macro_rules! audio_row { + ($($file:literal),+ $(,)?) => { + &[&[$(concat!("/rules-audio/", $file, ".mp3")),+]] + }; +} +macro_rules! audio_pair_row { + ($left:literal, $right:literal) => { + &[ + &[concat!("/rules-audio/", $left, ".mp3")], + &[concat!("/rules-audio/", $right, ".mp3")], + ] + }; +} pub const DICTIONARY: &[Word] = &[ entry!("sam", "sæm", "сэм"), @@ -724,6 +767,141 @@ pub const READING_TEXTS: &[ReadingText] = &[ReadingText { tokens: BASIC_RULES_TOKENS, }]; +pub const READING_RULES_CAVEAT: &str = "Английское чтение имеет много исключений. Правила ниже помогают прочитать большинство простых слов, но незнакомое слово иногда нужно проверить в словаре."; + +pub const READING_RULES: &[RuleSection] = &[ + RuleSection { number: 1, title: "Алфавит", blocks: &[ + RuleBlock::Table(RuleTable { headers: &["Буква", "Название", "Транскрипция"], rows: &[ + &["A", "[эй]", "[eɪ]"], &["B", "[би]", "[biː]"], &["C", "[си]", "[siː]"], &["D", "[ди]", "[diː]"], &["E", "[и]", "[iː]"], &["F", "[эф]", "[ef]"], &["G", "[джи]", "[dʒiː]"], &["H", "[эйч]", "[eɪtʃ]"], &["I", "[ай]", "[aɪ]"], &["J", "[джей]", "[dʒeɪ]"], &["K", "[кей]", "[keɪ]"], &["L", "[эл]", "[el]"], &["M", "[эм]", "[em]"], + &["N", "[эн]", "[en]"], &["O", "[оу]", "[əʊ] / [oʊ]"], &["P", "[пи]", "[piː]"], &["Q", "[кью]", "[kjuː]"], &["R", "[а(р)]", "[ɑːr]"], &["S", "[эс]", "[es]"], &["T", "[ти]", "[tiː]"], &["U", "[ю:]", "[juː]"], &["V", "[ви]", "[viː]"], &["W", "[дабл-ю]", "[ˈdʌbəljuː]"], &["X", "[экс]", "[eks]"], &["Y", "[уай]", "[waɪ]"], &["Z", "[зи] / [зэд]", "[ziː] / [zed]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 3, header: "Аудио" }], audio_rows: &[ + audio_row!("a"), audio_row!("b"), audio_row!("c"), audio_row!("d"), audio_row!("e"), audio_row!("f"), audio_row!("g"), audio_row!("h"), audio_row!("i"), audio_row!("j"), audio_row!("k"), audio_row!("l"), audio_row!("m"), + audio_row!("n"), audio_row!("o"), audio_row!("p"), audio_row!("q"), audio_row!("r"), audio_row!("s"), audio_row!("t"), audio_row!("u"), audio_row!("v"), audio_row!("w"), audio_row!("x"), audio_row!("y"), audio_row!("z"), + ] }), + ] }, + RuleSection { number: 2, title: "Краткие гласные (закрытый слог)", blocks: &[ + RuleBlock::Paragraph("В закрытом слоге гласная обычно читается кратко. Закрытый слог обычно заканчивается согласной: `cat`, `bed`."), + RuleBlock::Table(RuleTable { headers: &["Буква", "Звук", "Примеры"], rows: &[ + &["a", "[æ]", "`cat` [kæt], `map` [mæp]"], &["e", "[e]", "`bed` [bed], `pen` [pen]"], &["i", "[ɪ]", "`sit` [sɪt], `big` [bɪɡ]"], &["o", "[ɑ]", "`hot` [hɑt], `box` [bɑks]"], &["u", "[ʌ]", "`cup` [kʌp], `sun` [sʌn]"], &["y", "[ɪ]", "`gym` [dʒɪm], `system` [ˈsɪstəm]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 3, header: "Аудио примеров" }], audio_rows: &[ + audio_row!("cat", "map"), audio_row!("bed", "pen"), audio_row!("sit", "big"), audio_row!("hot", "box"), audio_row!("cup", "sun"), audio_row!("gym", "system"), + ] }), + ] }, + RuleSection { number: 3, title: "Долгие гласные (открытый слог)", blocks: &[ + RuleBlock::Paragraph("Долгий звук часто совпадает с названием буквы. В таблице использовано американское произношение."), + RuleBlock::Table(RuleTable { headers: &["Буква", "Звук", "Примеры"], rows: &[ + &["a", "[eɪ]", "`name` [neɪm], `late` [leɪt]"], &["e", "[i:]", "`these` [ðiːz], `theme` [θiːm]"], &["i", "[aɪ]", "`time` [taɪm], `five` [faɪv]"], &["o", "[oʊ]", "`home` [hoʊm], `note` [noʊt]"], &["u", "[ju:] или [u:]", "`cute` [kjuːt], `rule` [ruːl]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 3, header: "Аудио примеров" }], audio_rows: &[ + audio_row!("name", "late"), audio_row!("these", "theme"), audio_row!("time", "five"), audio_row!("home", "note"), audio_row!("cute", "rule"), + ] }), + ] }, + RuleSection { number: 4, title: "Немая `e`", blocks: &[ + RuleBlock::Paragraph("В модели `гласная + согласная + e` последняя `e` обычно не читается, а первая гласная становится долгой (слог стал открытым). В таблице использовано американское произношение."), + RuleBlock::Table(RuleTable { headers: &["Короткий вариант", "Длинный вариант"], rows: &[ + &["`cap` [kæp]", "`cape` [keɪp]"], &["`kit` [kɪt]", "`kite` [kaɪt]"], &["`hop` [hɑp]", "`hope` [hoʊp]"], &["`cub` [kʌb]", "`cube` [kjuːb]"], + ], audio_columns: &[ + RuleAudioColumn { after_text_column: 1, header: "Аудио" }, + RuleAudioColumn { after_text_column: 2, header: "Аудио" }, + ], audio_rows: &[ + audio_pair_row!("cap", "cape"), audio_pair_row!("kit", "kite"), audio_pair_row!("hop", "hope"), audio_pair_row!("cub", "cube"), + ] }), + RuleBlock::Subheading("Исключения"), + RuleBlock::Table(RuleTable { headers: &["Слово", "Транскрипция"], rows: &[ + &["`have`", "[hæv]"], &["`give`", "[ɡɪv]"], &["`love`", "[lʌv]"], &["`move`", "[muːv]"], &["`come`", "[kʌm]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 2, header: "Аудио" }], audio_rows: &[ + audio_row!("have"), audio_row!("give"), audio_row!("love"), audio_row!("move"), audio_row!("come"), + ] }), + ] }, + RuleSection { number: 5, title: "Сочетания гласных", blocks: &[ + RuleBlock::Subheading("Сочетания с предсказуемым чтением"), + RuleBlock::Paragraph("Эти сочетания обычно читаются одинаково в большинстве знакомых слов:"), + RuleBlock::Table(RuleTable { headers: &["Сочетание", "Звук", "Примеры"], rows: &[ + &["`ee`", "[i:]", "`see` [si:], `green` [ɡri:n]"], &["`ai` в середине слова", "[eɪ]", "`rain` [reɪn]"], &["`ay` в конце слова", "[eɪ]", "`day` [deɪ]"], &["`oa`", "[oʊ]", "`boat` [boʊt], `road` [roʊd]"], &["`oi` в середине слова", "[ɔɪ]", "`coin` [kɔɪn]"], &["`oy` в конце слова", "[ɔɪ]", "`boy` [bɔɪ]"], &["`igh`", "[aɪ]", "`night` [naɪt], `light` [laɪt]"], &["`au`, `aw`", "[ɔ:]", "`autumn` [ˈɔ:təm], `saw` [sɔ:]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 3, header: "Аудио" }], audio_rows: &[ + audio_row!("see", "green"), audio_row!("rain"), audio_row!("day"), audio_row!("boat", "road"), audio_row!("coin"), audio_row!("boy"), audio_row!("night", "light"), audio_row!("autumn", "saw"), + ] }), + ] }, + RuleSection { number: 6, title: "Основные согласные", blocks: &[ + RuleBlock::Paragraph("Большинство согласных читаются стабильно: `b` — [b] (`book`), `d` — [d] (`dog`), `f` — [f] (`fish`), `h` — [h] (`house`), `k` — [k] (`kite`), `l` — [l] (`leg`), `m` — [m] (`man`), `n` — [n] (`name`), `p` — [p] (`pen`), `r` — английский [ɹ] (`red`), `t` — [t] (`ten`), `v` — [v] (`van`), `z` — [z] (`zoo`). Английский [ɹ] не нужно раскатывать, как русский [р]."), + ] }, + RuleSection { number: 7, title: "Буква `c`", blocks: &[ + RuleBlock::Bullets(&["перед `e`, `i`, `y` обычно читается [s]: `city`, `cent`, `cycle`;", "перед `a`, `o`, `u` обычно читается [k]: `cat`, `cold`, `cup`."]), + RuleBlock::Paragraph("Исключения: `school`, `muscle`, `ocean`."), + ] }, + RuleSection { number: 8, title: "Буква `g`", blocks: &[ + RuleBlock::Bullets(&["перед `e`, `i`, `y` часто читается [dʒ]: `giant`, `gym`, `page`;", "перед `a`, `o`, `u` обычно читается [g]: `game`, `go`, `gum`."]), + RuleBlock::Paragraph("Исключения: `get`, `give`, `girl`, `gear`."), + ] }, + RuleSection { number: 9, title: "Сочетания согласных", blocks: &[ + RuleBlock::Table(RuleTable { headers: &["Сочетание", "Звук", "Примеры"], rows: &[ + &["`ch`", "[tʃ]", "`chair` [tʃer], `chicken` [ˈtʃɪkən]"], &["`sh`", "[ʃ]", "`ship` [ʃɪp], `fish` [fɪʃ]"], &["`ph`", "[f]", "`phone` [foʊn], `photo` [ˈfoʊtoʊ]"], &["`th`", "[θ]", "`think` [θɪŋk], `three` [θriː]"], &["`th`", "[ð]", "`this` [ðɪs], `mother` [ˈmʌðər]"], &["`wh`", "[w]", "`what` [wʌt], `when` [wen]"], &["`ck`", "[k]", "`back` [bæk], `black` [blæk]"], &["`ng`", "[ŋ]", "`sing` [sɪŋ], `long` [lɔŋ]"], &["`qu`", "[kw]", "`queen` [kwiːn], `quick` [kwɪk]"], &["`tch`", "[tʃ]", "`match` [mætʃ], `catch` [kætʃ]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 3, header: "Аудио" }], audio_rows: &[ + audio_row!("chair", "chicken"), audio_row!("ship", "fish"), audio_row!("phone", "photo"), audio_row!("think", "three"), audio_row!("this", "mother"), audio_row!("what", "when"), audio_row!("back", "black"), audio_row!("sing", "long"), audio_row!("queen", "quick"), audio_row!("match", "catch"), + ] }), + ] }, + RuleSection { number: 10, title: "Буквы `x` и `y`", blocks: &[ + RuleBlock::Subheading("`x`"), + RuleBlock::Table(RuleTable { headers: &["Положение или случай", "Звук", "Примеры"], rows: &[ + &["внутри и в конце слова", "[ks]", "`box` [bɑks], `six` [sɪks]"], &["в начале слов греческого происхождения", "[z]", "`xylophone` [ˈzaɪləfoʊn]"], &["в некоторых словах исключениях", "[gz]", "`exam` [ɪɡˈzæm], `exact` [ɪɡˈzækt]"], &["исключение `X-ray`", "[eks]", "`X-ray` [ˈɛksreɪ]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 3, header: "Аудио" }], audio_rows: &[ + audio_row!("box", "six"), audio_row!("xylophone"), audio_row!("exam", "exact"), audio_row!("x-ray"), + ] }), + RuleBlock::Subheading("`y`"), + RuleBlock::Paragraph("Буква `y` может вести себя как согласная или как гласная:"), + RuleBlock::Table(RuleTable { headers: &["Роль", "Положение", "Звук", "Примеры"], rows: &[ + &["Согласная", "в начале слова", "[j]", "`yes` [jes], `yellow` [ˈjeloʊ]"], &["Гласная", "в конце односложного слова", "[aɪ]", "`my` [maɪ], `cry` [kraɪ]"], &["Гласная", "в конце многосложного слова", "[i]", "`happy` [ˈhæpi], `city` [ˈsɪti]"], &["Гласная", "внутри слова, часто", "[ɪ]", "`gym` [dʒɪm], `system` [ˈsɪstəm]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 4, header: "Аудио" }], audio_rows: &[ + audio_row!("yes", "yellow"), audio_row!("my", "cry"), audio_row!("happy", "city"), audio_row!("gym", "system"), + ] }), + RuleBlock::Paragraph("Итак, в `yes` буква `y` — согласная, а в `my`, `happy`, `gym` и `system` — гласная."), + ] }, + RuleSection { number: 11, title: "Немые буквы: применимые шаблоны", blocks: &[ + RuleBlock::Table(RuleTable { headers: &["Шаблон", "Правило", "Примеры"], rows: &[ + &["`kn-` в начале слова", "`k` не читается", "`know` [noʊ], `knife` [naɪf]"], &["`wr-` в начале слова", "`w` не читается", "`wrap` [ræp], `wrist` [rɪst]"], &["`-mb` в конце слова", "`b` не читается", "`lamb` [læm], `thumb` [θʌm]"], &["`gn-` в начале слова", "`g` не читается", "`gnome` [noʊm], `gnat` [næt]"], &["`-ould`", "`l` не читается", "`could` [kʊd], `would` [wʊd], `should` [ʃʊd]"], &["`-sten`", "`t` не читается", "`listen` [ˈlɪsən], `fasten` [ˈfæsən]"], &["`-stle`", "`t` не читается", "`castle` [ˈkæsəl], `whistle` [ˈwɪsəl]"], + ], audio_columns: &[], audio_rows: &[] }), + ] }, + RuleSection { number: 12, title: "Окончания: применимые шаблоны", blocks: &[ + RuleBlock::Table(RuleTable { headers: &["Шаблон", "Звук", "Примеры"], rows: &[ + &["`-tion`", "[ʃən]", "`station`, `nation`, `information`"], &["`-stion`", "[tʃən]", "`question`, `suggestion`"], &["`-cian`", "[ʃən]", "`musician`, `politician`"], &["`-ssion`", "[ʃən]", "`mission`, `expression`"], + ], audio_columns: &[], audio_rows: &[] }), + RuleBlock::Paragraph("Окончание `-sion` без второго `s` не вынесено в правило: его чтение нужно проверять в словаре и запоминать со словом."), + ] }, + RuleSection { number: 13, title: "Окончания `-s` и `-es`", blocks: &[ + RuleBlock::Table(RuleTable { headers: &["Произношение", "Когда", "Примеры"], rows: &[ + &["[s]", "после глухих согласных, кроме шипящих", "`cats`, `books`, `cups`"], &["[z]", "после звонких звуков и гласных", "`dogs`, `pens`, `plays`"], &["[ɪz]", "после шипящих звуков", "`boxes`, `washes`, `buses`"], + ], audio_columns: &[], audio_rows: &[] }), + ] }, + RuleSection { number: 14, title: "Окончания `-er` и `-or`", blocks: &[ + RuleBlock::Paragraph("В безударном положении они часто читаются как [ə]:"), + RuleBlock::Bullets(&["`teacher` — [ˈti:tʃə];", "`doctor` — [ˈdɒktə];", "`actor` — [ˈæktə]."]), + RuleBlock::Paragraph("В американском английском конечный `r` обычно произносится."), + ] }, + RuleSection { number: 15, title: "Безударная гласная", blocks: &[ + RuleBlock::Paragraph("В безударном слоге гласная часто превращается в нейтральный звук [ə]:"), + RuleBlock::Bullets(&["`about` — [əˈbaʊt];", "`banana` — [bəˈnɑ:nə];", "`teacher` — [ˈti:tʃə]."]), + ] }, + RuleSection { number: 16, title: "Деление на слоги", blocks: &[ + RuleBlock::Bullets(&["один согласный между гласными часто относится к следующему слогу: `pa-per`;", "две согласные обычно разделяются: `win-ter`;", "конечное `le` образует отдельный слог: `ta-ble`, `lit-tle`."]), + ] }, + RuleSection { number: 17, title: "Ударение", blocks: &[ + RuleBlock::Paragraph("Ударный слог произносится сильнее, дольше и выше. В таблицах используется американское произношение."), + RuleBlock::Table(RuleTable { headers: &["Слово", "Транскрипция"], rows: &[ + &["`TAble`", "[ˈteɪbəl]"], &["`WINdow`", "[ˈwɪndoʊ]"], &["`beGIN`", "[bɪˈɡɪn]"], &["`imPORtant`", "[ɪmˈpɔrtənt]"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 2, header: "Аудио" }], audio_rows: &[ + audio_row!("table"), audio_row!("window"), audio_row!("begin"), audio_row!("important"), + ] }), + RuleBlock::Paragraph("Ударение может менять значение или часть речи:"), + RuleBlock::Table(RuleTable { headers: &["Значение или роль", "Слово", "Транскрипция", "Контекст в аудио"], rows: &[ + &["запись", "`REcord`", "[ˈrɛkɚd]", "`a record`"], &["записывать", "`reCORD`", "[rɪˈkɔrd]", "`record it`"], &["подарок / настоящий", "`PREsent`", "[ˈprɛzənt]", "`a present`"], &["представлять", "`preSENT`", "[prɪˈzɛnt]", "`present it`"], + ], audio_columns: &[RuleAudioColumn { after_text_column: 4, header: "Аудио" }], audio_rows: &[ + audio_row!("record-noun"), audio_row!("record-verb"), audio_row!("present-noun"), audio_row!("present-verb"), + ] }), + ] }, + RuleSection { number: 18, title: "Алгоритм чтения нового слова", blocks: &[ + RuleBlock::Steps(&["Найди гласные и согласные.", "Определи, закрытый ли это слог.", "Проверь правило немой `e`.", "Найди сочетания гласных и согласных.", "Проверь, нет ли немых букв.", "Раздели длинное слово на слоги.", "Поставь ударение.", "Проверь слово в словаре, если чтение не подходит."]), + ] }, +]; + pub fn word_by_id(id: &str) -> Option<&'static Word> { DICTIONARY.iter().find(|word| word.id == id) } @@ -828,4 +1006,193 @@ mod tests { } } } + + #[test] + fn reading_rules_cover_all_source_sections_with_source_derived_content() { + let source = include_str!("../../english/ex/theme-13-reading-rules/teoria-from-zero.md"); + let source_audio_paths = source + .split("src=\"audio/") + .skip(1) + .map(|fragment| { + let (filename, _) = fragment + .split_once('"') + .expect("source audio path must end with a quote"); + format!("/rules-audio/{filename}") + }) + .collect::>(); + let expected_titles = [ + "Алфавит", + "Краткие гласные (закрытый слог)", + "Долгие гласные (открытый слог)", + "Немая `e`", + "Сочетания гласных", + "Основные согласные", + "Буква `c`", + "Буква `g`", + "Сочетания согласных", + "Буквы `x` и `y`", + "Немые буквы: применимые шаблоны", + "Окончания: применимые шаблоны", + "Окончания `-s` и `-es`", + "Окончания `-er` и `-or`", + "Безударная гласная", + "Деление на слоги", + "Ударение", + "Алгоритм чтения нового слова", + ]; + let expected_audio_schemas = vec![ + ( + 1, + vec!["Буква", "Название", "Транскрипция"], + vec![(3, "Аудио")], + ), + ( + 2, + vec!["Буква", "Звук", "Примеры"], + vec![(3, "Аудио примеров")], + ), + ( + 3, + vec!["Буква", "Звук", "Примеры"], + vec![(3, "Аудио примеров")], + ), + ( + 4, + vec!["Короткий вариант", "Длинный вариант"], + vec![(1, "Аудио"), (2, "Аудио")], + ), + (4, vec!["Слово", "Транскрипция"], vec![(2, "Аудио")]), + (5, vec!["Сочетание", "Звук", "Примеры"], vec![(3, "Аудио")]), + (9, vec!["Сочетание", "Звук", "Примеры"], vec![(3, "Аудио")]), + ( + 10, + vec!["Положение или случай", "Звук", "Примеры"], + vec![(3, "Аудио")], + ), + ( + 10, + vec!["Роль", "Положение", "Звук", "Примеры"], + vec![(4, "Аудио")], + ), + (17, vec!["Слово", "Транскрипция"], vec![(2, "Аудио")]), + ( + 17, + vec![ + "Значение или роль", + "Слово", + "Транскрипция", + "Контекст в аудио", + ], + vec![(4, "Аудио")], + ), + ]; + + assert_eq!(READING_RULES.len(), expected_titles.len()); + assert_eq!( + READING_RULES + .iter() + .map(|section| section.number) + .collect::>(), + (1..=18).collect::>() + ); + assert_eq!( + READING_RULES + .iter() + .map(|section| section.title) + .collect::>(), + expected_titles + ); + assert_eq!( + READING_RULES_CAVEAT, + "Английское чтение имеет много исключений. Правила ниже помогают прочитать большинство простых слов, но незнакомое слово иногда нужно проверить в словаре." + ); + assert!(source.contains(READING_RULES_CAVEAT)); + + let mut table_count = 0; + let mut row_count = 0; + let mut step_count = 0; + let mut rule_audio_paths = Vec::new(); + let mut actual_audio_schemas = Vec::new(); + for section in READING_RULES { + assert!( + source.contains(section.title), + "missing title: {}", + section.title + ); + for block in section.blocks { + match block { + RuleBlock::Paragraph(text) | RuleBlock::Subheading(text) => { + assert!(source.contains(text), "missing text: {text}"); + } + RuleBlock::Bullets(items) | RuleBlock::Steps(items) => { + for item in *items { + assert!(source.contains(item), "missing list item: {item}"); + } + if let RuleBlock::Steps(items) = block { + step_count += items.len(); + } + } + RuleBlock::Table(table) => { + table_count += 1; + row_count += table.rows.len(); + for header in table.headers { + assert!(source.contains(header), "missing table header: {header}"); + } + for row in table.rows { + for cell in *row { + assert!(source.contains(cell), "missing table cell: {cell}"); + } + } + if table.audio_columns.is_empty() { + assert!(table.audio_rows.is_empty()); + } else { + actual_audio_schemas.push(( + section.number, + table.headers.to_vec(), + table + .audio_columns + .iter() + .map(|column| (column.after_text_column, column.header)) + .collect::>(), + )); + assert_eq!(table.audio_rows.len(), table.rows.len()); + for audio_row in table.audio_rows { + assert_eq!(audio_row.len(), table.audio_columns.len()); + for paths in *audio_row { + assert!(!paths.is_empty()); + for path in *paths { + let source_path = format!( + "src=\"audio/{}\"", + path.trim_start_matches("/rules-audio/") + ); + assert!( + source.contains(&source_path), + "missing source audio: {path}" + ); + let asset = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("public") + .join(path.trim_start_matches('/')); + assert!( + asset.is_file(), + "missing rules audio: {}", + asset.display() + ); + rule_audio_paths.push((*path).to_string()); + } + } + } + } + } + } + } + } + rule_audio_paths.sort_unstable(); + let mut source_audio_paths = source_audio_paths; + source_audio_paths.sort_unstable(); + assert_eq!(rule_audio_paths, source_audio_paths); + assert_eq!(actual_audio_schemas, expected_audio_schemas); + assert_eq!(table_count, 14); + assert_eq!(row_count, 94); + assert_eq!(step_count, 8); + } } diff --git a/site/src/main.rs b/site/src/main.rs index d1a0f85..10fc550 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,6 +1,9 @@ mod data; -use data::{word_by_id, ReadingText, Token, Word, DICTIONARY, READING_TEXTS}; +use data::{ + word_by_id, ReadingText, RuleBlock, RuleTable, Token, Word, DICTIONARY, READING_RULES, + READING_RULES_CAVEAT, READING_TEXTS, +}; use leptos::ev::KeyboardEvent; use leptos::prelude::*; use wasm_bindgen_futures::{spawn_local, JsFuture}; @@ -38,6 +41,13 @@ fn App() -> impl IntoView { audio_state.set(AudioState::Ready); selected_id.set(Some(id)); }; + let clear_selection = move |_| { + if let Some(audio) = audio_ref.get() { + let _ = audio.pause(); + } + audio_state.set(AudioState::Ready); + selected_id.set(None); + }; let activate_key = move |event: KeyboardEvent, id: &'static str| { if event.key() == "Enter" || event.key() == " " { event.prevent_default(); @@ -74,15 +84,27 @@ fn App() -> impl IntoView {
-
- {move || selected_id.get().and_then(word_by_id).map(|word| detail_view(word, audio_ref, audio_state, toggle_audio)).unwrap_or_else(|| view! { -
-

"Словарная карточка"

-

"Выберите слово в тексте"

-

"Карточка покажет написание, американское произношение и русскую подсказку."

+
+
@@ -144,6 +166,121 @@ fn App() -> impl IntoView { } } +fn rules_view() -> AnyView { + view! { +

{render_rule_text(READING_RULES_CAVEAT)}

+ {READING_RULES.iter().map(|section| { + let section_id = format!("rule-section-{}", section.number); + let heading_id = section_id.clone(); + view! { +
+
+

{format!("{:02}", section.number)}

+

{render_rule_text(section.title)}

+
+ {section.blocks.iter().map(render_rule_block).collect_view()} +
+ } + }).collect_view()} + } + .into_any() +} + +fn render_rule_block(block: &RuleBlock) -> AnyView { + match block { + RuleBlock::Paragraph(text) => view! { +

{render_rule_text(text)}

+ } + .into_any(), + RuleBlock::Subheading(text) => view! { +

{render_rule_text(text)}

+ } + .into_any(), + RuleBlock::Table(table) => rule_table_view(*table), + RuleBlock::Bullets(items) => view! { +
    + {items.iter().map(|item| view! {
  • {render_rule_text(item)}
  • }).collect_view()} +
+ } + .into_any(), + RuleBlock::Steps(items) => view! { +
    + {items.iter().map(|item| view! {
  1. {render_rule_text(item)}
  2. }).collect_view()} +
+ } + .into_any(), + } +} + +fn rule_table_view(table: RuleTable) -> AnyView { + view! { +
+ + + + {table.headers.iter().enumerate().map(|(text_index, header)| view! { + + {table.audio_columns.iter().filter(|column| column.after_text_column == text_index + 1).map(|column| view! { + + }).collect_view()} + }).collect_view()} + + + + {table.rows.iter().enumerate().map(|(row_index, row)| { + let audio_cells = table.audio_rows.get(row_index).copied().unwrap_or(&[]); + view! { + + {row.iter().enumerate().map(|(text_index, cell)| view! { + + {table.audio_columns.iter().enumerate().filter(|(_, column)| column.after_text_column == text_index + 1).map(|(audio_index, _)| { + let paths = audio_cells.get(audio_index).copied().unwrap_or(&[]); + rule_audio_cell_view(paths) + }).collect_view()} + }).collect_view()} + + } + }).collect_view()} + +
{render_rule_text(header)}{render_rule_text(column.header)}
{render_rule_text(cell)}
+
+ } + .into_any() +} + +fn rule_audio_cell_view(paths: &'static [&'static str]) -> AnyView { + view! { + + {paths.iter().map(|path| view! { + + }).collect_view()} + + } + .into_any() +} + +fn render_rule_text(text: &'static str) -> AnyView { + text.split('`') + .enumerate() + .map(|(index, fragment)| { + if index % 2 == 0 { + view! { {fragment} }.into_any() + } else { + view! { {fragment} }.into_any() + } + }) + .collect_view() + .into_any() +} + fn render_reading_text( reading_text: &'static ReadingText, selected_id: RwSignal>, @@ -186,6 +323,7 @@ fn detail_view( audio_ref: NodeRef, audio_state: RwSignal, toggle_audio: impl Fn(leptos::ev::MouseEvent) + Copy + 'static, + clear_selection: impl Fn(leptos::ev::MouseEvent) + Copy + 'static, ) -> AnyView { view! {
@@ -215,6 +353,9 @@ fn detail_view( {move || audio_button_text(audio_state.get())}

{move || audio_status_text(audio_state.get())}

+
}.into_any() } diff --git a/site/style.css b/site/style.css index 5546c7c..6112610 100644 --- a/site/style.css +++ b/site/style.css @@ -31,6 +31,8 @@ --type-caption: 0.85rem; --type-body: 1rem; --type-lede: 1.15rem; + --type-rule-heading: 1.15rem; + --type-rule-copy: 0.9rem; --type-word: clamp(1.25rem, 2vw, 1.65rem); --type-detail: 1.4rem; --type-heading: clamp(1.8rem, 4vw, 3.2rem); @@ -39,6 +41,7 @@ --workspace-detail-column: 1fr; --workspace-reading-column: 1.65fr; --workspace-height: min(44rem, calc(100svh - var(--space-7) - var(--space-7) - var(--space-6))); + --rule-audio-width: 11rem; } * { box-sizing: border-box; } @@ -53,8 +56,9 @@ h1 { font-family: var(--serif); font-size: var(--type-display); font-weight: 400 h1 em { color: var(--coral); display: block; } h2 { font-family: var(--serif); font-size: var(--type-heading); font-weight: 400; letter-spacing: -0.04em; margin-bottom: var(--space-2); } .lede { font-size: var(--type-lede); line-height: 1.55; max-width: 36rem; } -.reading-card, .detail-panel, .dictionary-section { border: var(--border-width) solid var(--line); border-radius: var(--radius-lg); } +.reading-card, .detail-panel, .rules-panel, .dictionary-section { border: var(--border-width) solid var(--line); border-radius: var(--radius-lg); } .reader-workspace { display: grid; gap: var(--space-6); grid-template-columns: minmax(var(--workspace-detail-min), var(--workspace-detail-column)) minmax(0, var(--workspace-reading-column)); height: var(--workspace-height); margin-bottom: var(--space-6); } +.study-sidebar { display: flex; flex-direction: column; gap: var(--space-4); min-height: 0; } .reading-card { background: var(--surface); box-shadow: var(--shadow); padding: var(--space-6); } .reader-workspace .reading-card { min-height: 0; overflow-y: auto; overscroll-behavior: contain; scroll-padding: var(--space-4); } .section-heading, .dictionary-heading { align-items: flex-start; display: flex; gap: var(--space-4); } @@ -66,7 +70,7 @@ h2 { font-family: var(--serif); font-size: var(--type-heading); font-weight: 400 .reading-word:hover, .reading-word:focus-visible, .reading-word.active { background: var(--sun); border-color: var(--coral); color: var(--ink); outline: none; } .punctuation { white-space: pre; } .detail-panel { background: var(--forest); color: var(--paper); min-height: 17rem; padding: var(--space-6); } -.reader-workspace .detail-panel { align-self: start; position: sticky; top: var(--space-5); } +.reader-workspace .detail-panel { flex: 0 0 auto; } .detail-kicker { color: var(--sun); } .detail-content h2 { color: var(--sun); } .pronunciation-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, minmax(0, 1fr)); margin: var(--space-5) 0; } @@ -77,7 +81,34 @@ dd { font-family: var(--serif); font-size: var(--type-detail); margin: 0; } .audio-button:hover, .audio-button:focus-visible { background: transparent; color: var(--sun); outline: var(--border-emphasis) solid var(--sun); outline-offset: var(--space-1); } .audio-button:disabled { background: transparent; border-color: var(--forest-light); color: var(--forest-pale); cursor: not-allowed; } .audio-note { color: var(--forest-pale); font-size: var(--type-caption); margin: var(--space-3) 0 0; } +.clear-selection { background: transparent; border: var(--border-width) solid var(--forest-light); border-radius: var(--radius-sm); color: var(--paper); font-weight: 800; margin-top: var(--space-4); padding: var(--space-2) var(--space-3); } +.clear-selection:hover, .clear-selection:focus-visible { border-color: var(--sun); color: var(--sun); outline: var(--border-emphasis) solid var(--sun); outline-offset: var(--space-1); } .detail-empty { max-width: 32rem; } +.rules-panel { background: var(--mist); display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; overflow: hidden; padding: var(--space-4); } +.rules-heading { align-items: flex-start; display: flex; flex: 0 0 auto; gap: var(--space-3); } +.rules-heading > div { flex: 1; } +.rules-heading h2 { font-size: var(--type-detail); } +.rules-heading p:not(.section-number) { color: var(--ink-muted); font-size: var(--type-caption); line-height: 1.5; } +.rules-scroll { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--space-4) var(--space-2) var(--space-2) 0; scroll-padding: var(--space-4); } +.rules-caveat { background: var(--surface); border-left: var(--border-emphasis) solid var(--coral); font-size: var(--type-rule-copy); line-height: 1.55; margin: 0; padding: var(--space-3); } +.rule-section { border-top: var(--border-width) solid var(--line); padding: var(--space-4) 0; } +.rule-section:last-child { padding-bottom: 0; } +.rule-heading { align-items: baseline; display: flex; gap: var(--space-2); } +.rule-number { color: var(--coral); font-size: var(--type-overline); font-weight: 800; letter-spacing: 0.08em; margin: 0; } +.rule-heading h3 { font-family: var(--serif); font-size: var(--type-rule-heading); font-weight: 400; letter-spacing: -0.02em; margin: 0; } +.rule-subheading { color: var(--forest); font-size: var(--type-caption); letter-spacing: 0.08em; margin: var(--space-4) 0 var(--space-2); text-transform: uppercase; } +.rule-copy, .rule-list { font-size: var(--type-rule-copy); line-height: 1.55; margin: var(--space-3) 0 0; } +.rule-list { padding-left: var(--space-5); } +.rule-list li + li { margin-top: var(--space-2); } +.rule-steps li::marker { color: var(--coral); font-weight: 800; } +.rule-table-wrap { margin-top: var(--space-3); overflow-x: auto; } +.rule-table { font-size: var(--type-caption); min-width: 0; width: 100%; } +.rule-table th, .rule-table td { overflow-wrap: anywhere; padding: var(--space-2); vertical-align: top; } +.rule-table th { font-size: var(--type-overline); } +.rule-audio-cell { min-width: var(--rule-audio-width); } +.rule-audio { display: block; max-width: 100%; } +.rule-audio + .rule-audio { margin-top: var(--space-2); } +code { background: var(--forest-pale); border-radius: var(--radius-sm); color: var(--ink); font-family: var(--sans); font-size: 0.9em; padding: 0 var(--space-1); } .dictionary-section { background: var(--mist); margin-bottom: var(--space-6); padding: var(--space-6); } .search-label { display: block; font-size: var(--type-caption); font-weight: 800; margin: var(--space-5) 0 var(--space-2); } input { background: var(--input); border: var(--border-width) solid var(--ink); border-radius: var(--radius-sm); color: var(--ink); padding: var(--space-3); width: min(100%, 34rem); } @@ -89,5 +120,5 @@ th { color: var(--forest); font-size: var(--type-overline); letter-spacing: 0.08 .table-word { background: none; border: 0; border-bottom: 1px solid var(--forest); color: var(--forest); font-weight: 800; padding: 0; } .table-word:hover, .table-word:focus-visible { color: var(--coral); outline: none; } @media (min-width: 80rem) { .lesson-shell { width: calc(100% - clamp(4rem, 5vw, 12rem)); } } -@media (max-width: 58rem) { .reader-workspace { display: flex; flex-direction: column; height: auto; } .reader-workspace .reading-card { order: -1; overflow: visible; } .reader-workspace .detail-panel { position: static; } } -@media (max-width: 38rem) { .lesson-shell { width: min(100% - var(--space-4), 76rem); padding: var(--space-5) 0; } .reading-card, .detail-panel, .dictionary-section { padding: var(--space-4); } .section-heading, .dictionary-heading { gap: var(--space-3); } .word-count { display: none; } .pronunciation-grid { grid-template-columns: 1fr; } } +@media (max-width: 58rem) { .reader-workspace { display: flex; flex-direction: column; height: auto; } .reader-workspace .reading-card { order: -1; overflow: visible; } .rules-panel { flex: initial; } .rules-scroll { overflow: visible; } } +@media (max-width: 38rem) { .lesson-shell { width: min(100% - var(--space-4), 76rem); padding: var(--space-5) 0; } .reading-card, .detail-panel, .rules-panel, .dictionary-section { padding: var(--space-4); } .section-heading, .dictionary-heading { gap: var(--space-3); } .word-count { display: none; } .pronunciation-grid { grid-template-columns: 1fr; } }