{"id":486938,"date":"2026-07-10T16:28:05","date_gmt":"2026-07-10T16:28:05","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=486938"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=486938","title":{"rendered":"\u0423\u043c\u043d\u044b\u0439 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u043f\u0438\u0442\u0430\u043d\u0438\u044f \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0441 drag-and-drop, \u0430\u0432\u0442\u043e\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u0439 \u0438 \u0432\u043a\u0443\u0441\u043d\u044b\u043c \u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432\u044b\u043c \u043c\u0435\u043d\u044e"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0425\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0438\u0442\u0430\u043d\u0438\u044f \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e. \u0417\u0430\u0434\u0430\u0447\u0430 \u043a\u0430\u0437\u0430\u043b\u0430\u0441\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439: \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044c, \u0431\u043b\u044e\u0434\u0430, \u0438\u043d\u0433\u0440\u0435\u0434\u0438\u0435\u043d\u0442\u044b. \u041d\u043e \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0432\u0441\u043f\u043b\u044b\u043b\u043e \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u043d\u044e\u0430\u043d\u0441\u043e\u0432 \u2014 \u043e\u0442 UX \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0434\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u044f\u043c\u0438 \u043d\u0430 shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435, \u2014 \u0447\u0442\u043e \u0440\u0435\u0448\u0438\u043b \u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c \u044d\u0442\u043e \u0432 \u0441\u0442\u0430\u0442\u044c\u044e.<\/p>\n<h3>\u0427\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c<\/h3>\n<p>PWA \u0434\u043b\u044f \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0438\u0442\u0430\u043d\u0438\u044f \u0441 \u0444\u0438\u0447\u0430\u043c\u0438:<\/p>\n<ul>\n<li>\n<p>Drag-and-drop \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0431\u043b\u044e\u0434 \u043c\u0435\u0436\u0434\u0443 \u0434\u043d\u044f\u043c\u0438<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0440\u0430\u0441\u0447\u0435\u0442 \u041a\u0411\u0416\u0423 \u0438 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u043e\u043a\u0443\u043f\u043e\u043a<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430 \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 (\u043a\u0440\u0443\u043f\u043d\u044b\u0439 UI, \u043e\u0444\u043b\u0430\u0439\u043d-\u0440\u0435\u0436\u0438\u043c)<\/p>\n<\/li>\n<li>\n<p>Remember Me \u043d\u0430 30 \u0434\u043d\u0435\u0439 \u0434\u0430\u0436\u0435 \u043d\u0430 shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435<\/p>\n<\/li>\n<li>\n<p>\u0424\u043e\u0442\u043e \u0431\u043b\u044e\u0434 \u0441 \u043f\u0440\u0435\u0432\u044c\u044e<\/p>\n<\/li>\n<li>\n<p>\u0421\u043a\u0440\u0435\u043c\u0431\u043b-\u0440\u0435\u0436\u0438\u043c (\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0435 \u0431\u043b\u044e\u0434\u043e, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0437\u043d\u0430\u0435\u0448\u044c \u0447\u0442\u043e \u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c)<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ff7\/fdd\/c86\/ff7fddc86f621dca3747e16f8b90928e.jpeg\" alt=\"\u0423\u043c\u043d\u044b\u0439 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a \u2013 \u0414\u0438\u0441\u043f\u043b\u0435\u0439 \u043d\u0430 \u043c\u0430\u0433\u043d\u0438\u0442\u0430\u0445 \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0441 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u043c \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c\" title=\"\u0423\u043c\u043d\u044b\u0439 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a \u2013 \u0414\u0438\u0441\u043f\u043b\u0435\u0439 \u043d\u0430 \u043c\u0430\u0433\u043d\u0438\u0442\u0430\u0445 \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0441 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u043c \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c\" width=\"5712\" height=\"4284\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/ff7\/fdd\/c86\/ff7fddc86f621dca3747e16f8b90928e.jpeg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ff7\/fdd\/c86\/ff7fddc86f621dca3747e16f8b90928e.jpeg 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0423\u043c\u043d\u044b\u0439 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a \u2013 \u0414\u0438\u0441\u043f\u043b\u0435\u0439 \u043d\u0430 \u043c\u0430\u0433\u043d\u0438\u0442\u0430\u0445 \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0441 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u043c \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0442\u0435\u043a: <strong>PHP 8.5 + PDO + MySQL + Vanilla JS (\u0431\u0435\u0437 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432)<\/strong><\/p>\n<p><strong>\u0427\u0442\u043e \u044d\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u0442\u0430\u043a\u043e\u0435<\/strong><\/p>\n<p>\u0420\u0430\u0441\u043f\u0431\u0435\u0440\u0438 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u0447 \u043d\u0435 \u043f\u043e\u0434\u043e\u0448\u0435\u043b, \u0442\u0430\u043a \u043a\u0430\u043a \u043f\u043e\u0434 \u0440\u0443\u043a\u043e\u0439 \u0431\u044b\u043b\u0430 3 \u0432\u0435\u0440\u0441\u0438\u044f, 5-\u044f \u0441\u0442\u043e\u0438\u0442 \u0443\u0436\u0435 20000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0438\u0435 \u0430\u043d\u0430\u043b\u043e\u0433\u0438 \u0432\u0438\u0434\u0430 Babana Pi \u0441\u0442\u043e\u044f\u0442 \u043e\u0442 9000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0430 Mac Mini \u0437\u0430 54000 \u0440\u0443\u0431\u043b\u0435\u0439 \u0434\u043b\u044f \u044d\u0442\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u044c \u0443\u0436\u0435 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c.  \u0412 \u0438\u0442\u043e\u0433\u0435 \u044f \u0432\u0437\u044f\u043b \u0441\u0442\u0430\u0440\u044b\u0439 \u043d\u043e\u0443\u0442\u0431\u0443\u043a \u0437\u0430 2000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0433\u0434\u0435 \u0441\u0442\u043e\u0438\u0442 \u043d\u043e\u0432\u044b\u0439 SSD 256 \u0413\u0431 \u0438 8 \u0413\u0431 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u043b\u0430\u043d\u043a\u0430 DDR3. \u0421 \u0420\u0430\u0441\u043f\u0431\u0435\u0440\u0438 \u041f\u0438 3 \u044f \u0443\u0441\u0442\u0430\u043b \u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f \u0443\u0436\u0435 \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0434\u0435\u043d\u044c \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u043b \u0435\u0435 \u043d\u0430\u0437\u0430\u0434 \u0432 \u043a\u043e\u0440\u043e\u0431\u043e\u0447\u043a\u0443 \u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u0438\u043a\u043e\u0439.<\/p>\n<p> \u0412 \u0438\u0442\u043e\u0433\u0435 \u0432\u0441\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u2013 \u044d\u0442\u043e \u043d\u043e\u0443\u0442\u0431\u0443\u043a, \u0441\u0435\u043d\u0441\u043e\u0440\u043d\u044b\u0439 \u043c\u0430\u0442\u043e\u0432\u044b\u0439 \u0434\u0438\u0441\u043f\u043b\u0435\u0439 \u043d\u0430 15 \u0434\u044e\u0439\u043c\u043e\u0432, \u043e\u0431\u0432\u044f\u0437\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0436\u0435\u043b\u0435\u0437\u0430, \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d (\u043a\u043d\u043e\u043f\u043a\u0430 \u0432 \u043c\u0435\u043d\u044e).<\/p>\n<p>\u041f\u0440\u043e\u0432\u043e\u0434\u0430 \u043d\u0435 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432 \u0433\u043e\u0444\u0440\u0443, \u043d\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0430 \u0441 \u043f\u0440\u043e\u0432\u043e\u0434\u0430\u043c\u0438 \u043d\u0430 \u0441\u0442\u044f\u0436\u043a\u0430\u0445 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435\u0442 \u043f\u0440\u0438 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0430 \u043d\u0438\u0436\u0435.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/406\/17e\/cc8\/40617ecc84fb1235718f1d3c2cc12df4.jpg\" alt=\"\u041d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0432\u044b\u0440\u043e\u0441\u043b\u0438 \u043d\u043e\u0443\u0442\u0431\u0443\u043a \u0438 \u0434\u0438\u0441\u043f\u043b\u0435\u0439, \u043d\u043e \u0432\u0440\u043e\u0434\u0435 \u0441\u0442\u0430\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0436\u0438\u0442\u044c\" title=\"\u041d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0432\u044b\u0440\u043e\u0441\u043b\u0438 \u043d\u043e\u0443\u0442\u0431\u0443\u043a \u0438 \u0434\u0438\u0441\u043f\u043b\u0435\u0439, \u043d\u043e \u0432\u0440\u043e\u0434\u0435 \u0441\u0442\u0430\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0436\u0438\u0442\u044c\" width=\"5712\" height=\"4284\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/406\/17e\/cc8\/40617ecc84fb1235718f1d3c2cc12df4.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/406\/17e\/cc8\/40617ecc84fb1235718f1d3c2cc12df4.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0432\u044b\u0440\u043e\u0441\u043b\u0438 \u043d\u043e\u0443\u0442\u0431\u0443\u043a \u0438 \u0434\u0438\u0441\u043f\u043b\u0435\u0439, \u043d\u043e \u0432\u0440\u043e\u0434\u0435 \u0441\u0442\u0430\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0436\u0438\u0442\u044c<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21161: Shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0438 \u0441\u0435\u0441\u0441\u0438\u0438<\/h3>\n<h4>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442<\/h4>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u043e\u0438\u0442 \u043d\u0430 \u043e\u0431\u044b\u0447\u043d\u043e\u043c shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c (\u043c\u043e\u044f \u043f\u043e\u0434\u0440\u0443\u0433\u0430) \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u0441 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0430, \u0430 \u043d\u0435 \u0441\u0438\u0434\u0438\u0442 \u0432 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0435, \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u043f\u0438\u0442\u0430\u043d\u0438\u0435 \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e \u0438&#8230; \u0447\u0435\u0440\u0435\u0437 24 \u043c\u0438\u043d\u0443\u0442\u044b \u0441\u0435\u0441\u0441\u0438\u044f \u0441\u043b\u0435\u0442\u0430\u0435\u0442. \u041a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u2014 \u044d\u0442\u043e UX \u0443\u0440\u043e\u0432\u043d\u044f &#171;\u0432\u044b\u043a\u043b\u044e\u0447\u0438 \u0438 \u0432\u043a\u043b\u044e\u0447\u0438&#187;.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cc8\/893\/e78\/cc8893e781cf098d6ce05735cf122030.png\" alt=\"\u0424\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438\" title=\"\u0424\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438\" width=\"333\" height=\"389\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/cc8\/893\/e78\/cc8893e781cf098d6ce05735cf122030.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cc8\/893\/e78\/cc8893e781cf098d6ce05735cf122030.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0424\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438<\/figcaption><\/div>\n<\/figure>\n<h4>\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442<\/h4>\n<p>\u041d\u0430 shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435:<\/p>\n<ol>\n<li>\n<p><code>session.gc_maxlifetime<\/code> = 1440 \u0441\u0435\u043a\u0443\u043d\u0434 (24 \u043c\u0438\u043d\u0443\u0442\u044b) \u2014 \u0434\u0435\u0444\u043e\u043b\u0442 PHP<\/p>\n<\/li>\n<li>\n<p>\u041f\u0430\u043f\u043a\u0430 <code>\/tmp<\/code> \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u0447\u0438\u0441\u0442\u0438\u0442\u0441\u044f \u0445\u043e\u0441\u0442\u0435\u0440\u043e\u043c<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0435\u0441\u0441\u0438\u0438 \u0442\u0435\u0440\u044f\u044e\u0442\u0441\u044f<\/p>\n<\/li>\n<\/ol>\n<h4>\u0420\u0435\u0448\u0435\u043d\u0438\u0435: Persistent Tokens<\/h4>\n<p>\u041f\u0440\u043e\u0441\u0442\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c <code>session.gc_maxlifetime<\/code> \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u2014 \u0445\u043e\u0441\u0442\u0435\u0440 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043f\u043e\u0447\u0438\u0441\u0442\u0438\u0442 <code>\/tmp<\/code>. \u041d\u0443\u0436\u0435\u043d \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c <strong>Remember Me<\/strong> \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u043a\u0435\u043d\u044b \u0432 \u0411\u0414<\/p>\n<pre><code class=\"php\">\/\/ === \u0410\u0412\u0422\u041e\u041b\u041e\u0413\u0418\u041d \u041f\u041e REMEMBER_ME COOKIE ===if (empty($_SESSION['user_id']) &amp;&amp; !empty($_COOKIE['remember_me'])) {    $parts = explode(':', $_COOKIE['remember_me']);    if (count($parts) === 2) {        $selector = $parts[0];        $validator = $parts[1];    \/\/ \u0418\u0449\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0432 \u0411\u0414    $stmt = $pdo-&gt;prepare(\"        SELECT rt.id, rt.user_id, rt.hashed_validator, rt.expires_at,               u.family_id, u.name, u.login, f.name as family_name        FROM remember_tokens rt        JOIN users u ON rt.user_id = u.id        JOIN families f ON u.family_id = f.id        WHERE rt.selector = ? AND rt.expires_at &gt; NOW()        LIMIT 1    \");    $stmt-&gt;execute([$selector]);    $token = $stmt-&gt;fetch();        if ($token &amp;&amp; hash_equals($token['hashed_validator'], hash('sha256', $validator))) {        \/\/ \u0422\u043e\u043a\u0435\u043d \u0432\u0430\u043b\u0438\u0434\u0435\u043d \u2014 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e        $_SESSION['user_id'] = $token['user_id'];        $_SESSION['family_id'] = $token['family_id'];        $_SESSION['user_name'] = $token['name'];        $_SESSION['family_name'] = $token['family_name'];                \/\/ Sliding expiration: \u043f\u0440\u043e\u0434\u043b\u0435\u0432\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u043d\u0430 30 \u0434\u043d\u0435\u0439        $newSelector = bin2hex(random_bytes(16));        $newValidator = bin2hex(random_bytes(32));        $newHashedValidator = hash('sha256', $newValidator);        $newExpires = date('Y-m-d H:i:s', strtotime('+30 days'));                $updateStmt = $pdo-&gt;prepare(\"            UPDATE remember_tokens             SET selector = ?, hashed_validator = ?, expires_at = ?            WHERE id = ?        \");        $updateStmt-&gt;execute([$newSelector, $newHashedValidator, $newExpires, $token['id']]);                \/\/ \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c cookie        setcookie('remember_me', $newSelector . ':' . $newValidator, [            'expires' =&gt; time() + (30 * 24 * 60 * 60),            'path' =&gt; '\/',            'httponly' =&gt; true,            'samesite' =&gt; 'Lax',            'secure' =&gt; false  \/\/ \u2190 false \u0434\u043b\u044f HTTP        ]);    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u044b<\/h4>\n<pre><code>CREATE TABLE remember_tokens (    id INT AUTO_INCREMENT PRIMARY KEY,    user_id INT NOT NULL,    selector VARCHAR(64) NOT NULL UNIQUE,    hashed_validator VARCHAR(255) NOT NULL,    expires_at DATETIME NOT NULL,    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,    INDEX idx_selector (selector),    INDEX idx_user (user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c<\/h4>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0423\u0433\u0440\u043e\u0437\u0430<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0417\u0430\u0449\u0438\u0442\u0430<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041f\u0435\u0440\u0435\u0445\u0432\u0430\u0442 cookie<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>httponly=true<\/code> (JS \u043d\u0435 \u0447\u0438\u0442\u0430\u0435\u0442)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">CSRF<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>samesite=Lax<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041a\u0440\u0430\u0436\u0430 \u0442\u043e\u043a\u0435\u043d\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">Validator \u0445\u0435\u0448\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u0411\u0414<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0411\u0440\u0443\u0442\u0444\u043e\u0440\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\">Selector + Validator (64+64 \u0431\u0438\u0442\u0430 \u044d\u043d\u0442\u0440\u043e\u043f\u0438\u0438)<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p><strong>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/strong> \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u0440\u0430\u0437 \u0432 \u043c\u0435\u0441\u044f\u0446, \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0431\u0435\u0437 \u043f\u0430\u0440\u043e\u043b\u044f.<\/p>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21162: Drag-and-drop \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435<\/h3>\n<h4>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442<\/h4>\n<p>\u041d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a \u043c\u0430\u0433\u043d\u0438\u0442\u0430\u043c\u0438 \u0437\u0430\u043a\u0440\u0435\u043f\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0438\u0441\u043f\u043b\u0435\u0439 \u0441 \u0442\u0430\u0447\u0441\u043a\u0440\u0438\u043d\u043e\u043c. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0442\u044c \u0431\u043b\u044e\u0434\u0430 \u043c\u0435\u0436\u0434\u0443 \u0434\u043d\u044f\u043c\u0438. \u041d\u043e:<\/p>\n<ul>\n<li>\n<p>\u0422\u0430\u0447-\u0441\u043e\u0431\u044b\u0442\u0438\u044f \u2260 \u043c\u044b\u0448\u044c<\/p>\n<\/li>\n<li>\n<p>\u041d\u0443\u0436\u043d\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0442\u044c \u043a\u043b\u0438\u043a \u043e\u0442 drag<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430 \u0442\u0430\u0447-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u0441\u043a\u0440\u043e\u043b\u043b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u0443\u0435\u0442 \u0441 drag<\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/27b\/d5d\/b6d\/27bd5db6dc7ca25585c78a39cecc9bea.png\" alt=\"\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0431\u043b\u044e\u0434\u0430 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u0435\u043d\u044c \u0438 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0440\u0438\u0435\u043c \u043f\u0438\u0449\u0438\" title=\"\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0431\u043b\u044e\u0434\u0430 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u0435\u043d\u044c \u0438 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0440\u0438\u0435\u043c \u043f\u0438\u0449\u0438\" width=\"1914\" height=\"940\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/27b\/d5d\/b6d\/27bd5db6dc7ca25585c78a39cecc9bea.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/27b\/d5d\/b6d\/27bd5db6dc7ca25585c78a39cecc9bea.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0431\u043b\u044e\u0434\u0430 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u0435\u043d\u044c \u0438 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0440\u0438\u0435\u043c \u043f\u0438\u0449\u0438<\/figcaption><\/div>\n<\/figure>\n<h4>\u0420\u0435\u0448\u0435\u043d\u0438\u0435: \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a<\/h4>\n<pre><code class=\"javascript\">const initDnD = () =&gt; {    const board = $('#board');    const THRESHOLD = 5; \/\/ \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u043e\u0442\u043b\u0438\u0447\u0438\u044f \u043a\u043b\u0438\u043a\u0430 \u043e\u0442 drag    let dragTarget = null;    let startX = 0, startY = 0;    let moved = false;        const onStart = e =&gt; {        const dish = e.target.closest('.dish');        if (!dish) return;                dragTarget = {            el: dish,            id: parseInt(dish.dataset.id),            dishId: parseInt(dish.dataset.dish)        };                const pt = e.touches ? e.touches[0] : e;        startX = pt.clientX;        startY = pt.clientY;        moved = false;                dish.style.opacity = '0.6';        dish.style.transition = 'none';                document.addEventListener('mousemove', onMove);        document.addEventListener('touchmove', onMove, { passive: false });        document.addEventListener('mouseup', onEnd);        document.addEventListener('touchend', onEnd);    };        const onMove = e =&gt; {        if (!dragTarget) return;                const pt = e.touches ? e.touches[0] : e;        const dx = pt.clientX - startX;        const dy = pt.clientY - startY;                \/\/ \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c: \u044d\u0442\u043e drag \u0438\u043b\u0438 \u043a\u043b\u0438\u043a?        if (!moved &amp;&amp; Math.hypot(dx, dy) &gt; THRESHOLD) {            moved = true;        }                if (moved) {            if (e.cancelable) e.preventDefault();                        dragTarget.el.style.transform = `translate(${dx}px, ${dy}px) scale(1.03)`;                        \/\/ \u041f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0430 drop-\u0437\u043e\u043d\u044b            $$('.drop-zone').forEach(zone =&gt; {                const rect = zone.getBoundingClientRect();                const over = (                    pt.clientX &gt;= rect.left &amp;&amp;                    pt.clientX &lt;= rect.right &amp;&amp;                    pt.clientY &gt;= rect.top &amp;&amp;                    pt.clientY &lt;= rect.bottom                );                zone.classList.toggle('over', over);            });        }    };        const onEnd = () =&gt; {        document.removeEventListener('mousemove', onMove);        document.removeEventListener('touchmove', onMove);        document.removeEventListener('mouseup', onEnd);        document.removeEventListener('touchend', onEnd);                if (!dragTarget) return;                dragTarget.el.style.opacity = '';        dragTarget.el.style.transform = '';        dragTarget.el.style.transition = '';                if (moved) {            \/\/ \u042d\u0442\u043e \u0431\u044b\u043b drag \u2192 \u0438\u0449\u0435\u043c drop-\u0437\u043e\u043d\u0443            const dropZone = $('.drop-zone.over');            if (dropZone) {                const toType = parseInt(dropZone.dataset.t);                const toDay = parseInt(dropZone.dataset.d);                                api('move_item', {                    item_id: dragTarget.id,                    to_type: toType,                    to_day: toDay                }).then(() =&gt; {                    loadWeek().then(render);                    toast('\u2713 \u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e', 'ok');                });            }        } else {            \/\/ \u042d\u0442\u043e \u0431\u044b\u043b \u043a\u043b\u0438\u043a \u2192 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0443 \u0431\u043b\u044e\u0434\u0430            openDishDetail(dragTarget.dishId);        }                $$('.drop-zone').forEach(z =&gt; z.classList.remove('over'));        dragTarget = null;    };        board.addEventListener('mousedown', onStart);    board.addEventListener('touchstart', onStart, { passive: true });};<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>UX-\u0444\u0438\u0448\u043a\u0438<\/h4>\n<p><strong>1. \u041f\u043e\u0440\u043e\u0433 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u044f (THRESHOLD = 5px)<\/strong><\/p>\n<ul>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 &lt; 5px \u2192 \u044d\u0442\u043e \u043a\u043b\u0438\u043a<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u2265 5px \u2192 \u044d\u0442\u043e drag<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430 \u0442\u0430\u0447-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u044d\u0442\u043e \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e: \u043f\u0430\u043b\u0435\u0446 \u0432\u0441\u0435\u0433\u0434\u0430 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0440\u043e\u0436\u0438\u0442<\/p>\n<\/li>\n<\/ul>\n<p><strong>2. \u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0442\u043d\u0430\u044f \u0441\u0432\u044f\u0437\u044c<\/strong><\/p>\n<pre><code class=\"css\">.dish {    transition: transform 0.15s, opacity 0.15s;}.dish.dragging {    opacity: 0.6;    transform: scale(1.03);    z-index: 100;    box-shadow: 0 8px 24px rgba(0,0,0,0.3);}.drop-zone.over {    height: 40px;    background: rgba(0, 184, 148, 0.2);    border: 2px dashed var(--g);    border-radius: 6px;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>3. \u041e\u0442\u043c\u0435\u043d\u0430 \u0441\u043a\u0440\u043e\u043b\u043b\u0430 \u043f\u0440\u0438 drag<\/strong><\/p>\n<pre><code class=\"javascript\">if (e.cancelable) e.preventDefault();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043d\u0430 \u0442\u0430\u0447-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441\u043a\u0440\u043e\u043b\u043b\u0438\u0442\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u0435\u043c \u0431\u043b\u044e\u0434\u0430.<\/p>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21163: COLLATE \u0438 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430<\/h3>\n<h4>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442<\/h4>\n<p>\u041f\u0440\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0448\u0438\u0431\u043a\u0430:<\/p>\n<pre><code>SQLSTATE[42000]: COLLATION 'utf8mb4_unicode_ci' is not valid for CHARACTER SET 'utf8mb3'<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041f\u0440\u0438\u0447\u0438\u043d\u0430<\/h4>\n<p>\u0412 \u0411\u0414 \u043f\u043e\u043b\u044f <code>login<\/code> \u0438 <code>email<\/code> \u0438\u043c\u0435\u044e\u0442 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0443 <code>utf8mb3<\/code>, \u0430 \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u043b <code>COLLATE utf8mb4_unicode_ci<\/code>. MySQL \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c utf8mb4 collation \u043a utf8mb3 \u043f\u043e\u043b\u044e.<\/p>\n<h4>\u0420\u0435\u0448\u0435\u043d\u0438\u0435<\/h4>\n<p>\u041c\u043e\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438  COLLATE, \u044d\u0442\u043e \u0442\u043e\u0447\u043d\u043e \u043f\u043e\u043a\u0430\u0436\u0435\u0442, \u0432 \u0447\u0435\u043c \u0435\u0449\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430, \u0435\u0441\u043b\u0438 \u0443\u0436\u0435 \u043d\u0430\u043f\u0440\u043e\u0447\u044c \u0432\u044b\u043c\u0443\u0447\u0438\u043b \u0441\u0442\u0440\u043e\u0447\u043a\u0443 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0448\u044c \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0438\u0441\u043a\u043e\u043c\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430.<\/p>\n<pre><code>$u = db_row($pdo, \"    SELECT u.*, f.name fn     FROM users u     JOIN families f ON u.family_id = f.id     WHERE COALESCE(u.login, '') = ? COLLATE utf8mb4_unicode_ci        OR u.email = ? COLLATE utf8mb4_unicode_ci\", [$identifier, $identifier]);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0418\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b<\/h4>\n<pre><code>ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;ALTER TABLE remember_tokens CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21164: \u0424\u043e\u0442\u043e \u0431\u043b\u044e\u0434 \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430<\/h3>\n<h4>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442<\/h4>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0445\u043e\u0447\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u0431\u043b\u044e\u0434\u0430. \u041d\u043e:<\/p>\n<ul>\n<li>\n<p>\u0424\u043e\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c\u0441\u044f \u0432 \u043f\u0430\u043f\u043a\u0443 <code>\/dishes_photos\/<\/code><\/p>\n<\/li>\n<li>\n<p>\u041d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440 (\u043c\u0430\u043a\u0441 5 \u041c\u0411) \u0438 \u0442\u0438\u043f (\u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f)<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u0432 \u0441\u0435\u0442\u043a\u0435 \u043d\u0435\u0434\u0435\u043b\u0438<\/p>\n<\/li>\n<\/ul>\n<h4>\u0420\u0435\u0448\u0435\u043d\u0438\u0435<\/h4>\n<p><strong>Backend (api.php):<\/strong><\/p>\n<pre><code class=\"php\">if ($act == 'upload_dish_photo') {    $dishId = (int)($_POST['dish_id'] ?? 0);    if (!$dishId) send(['error' =&gt; '\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d ID \u0431\u043b\u044e\u0434\u0430'], 400);        if (!isset($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) {        send(['error' =&gt; '\u0424\u0430\u0439\u043b \u043d\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d'], 400);    }        $file = $_FILES['photo'];        \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0437\u043c\u0435\u0440\u0430    if ($file['size'] &gt; 5 * 1024 * 1024) {        send(['error' =&gt; '\u0420\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u0435\u0432\u044b\u0448\u0430\u0435\u0442 5 \u041c\u0411'], 400);    }        \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 MIME-\u0442\u0438\u043f\u0430    $finfo = finfo_open(FILEINFO_MIME_TYPE);    $mimeType = finfo_file($finfo, $file['tmp_name']);    finfo_close($finfo);        $allowedTypes = ['image\/jpeg', 'image\/png', 'image\/gif', 'image\/webp'];    if (!in_array($mimeType, $allowedTypes)) {        send(['error' =&gt; '\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0442\u0438\u043f \u0444\u0430\u0439\u043b\u0430'], 400);    }        \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435    $extMap = [        'image\/jpeg' =&gt; 'jpg',        'image\/png' =&gt; 'png',        'image\/gif' =&gt; 'gif',        'image\/webp' =&gt; 'webp'    ];    $ext = $extMap[$mimeType];    $filename = 'dish_' . $dishId . '_' . time() . '.' . $ext;    $uploadDir = __DIR__ . '\/dishes_photos\/';        if (!is_dir($uploadDir)) {        mkdir($uploadDir, 0755, true);    }        $targetPath = $uploadDir . $filename;        if (!move_uploaded_file($file['tmp_name'], $targetPath)) {        send(['error' =&gt; '\u041e\u0448\u0438\u0431\u043a\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430'], 500);    }        \/\/ \u0423\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u0442\u0430\u0440\u043e\u0435 \u0444\u043e\u0442\u043e    $oldPhoto = db_val($pdo, \"SELECT photo FROM dishes WHERE id=?\", [$dishId]);    if ($oldPhoto &amp;&amp; file_exists(__DIR__ . '\/dishes_photos\/' . $oldPhoto)) {        unlink(__DIR__ . '\/dishes_photos\/' . $oldPhoto);    }        \/\/ \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0432 \u0411\u0414    db_exec($pdo, \"UPDATE dishes SET photo=? WHERE id=?\", [$filename, $dishId]);        send(['success' =&gt; 1, 'photo' =&gt; $filename]);}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>Frontend (\u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0431\u043e\u0440\u0430):<\/strong><\/p>\n<pre><code class=\"javascript\">window.uploadDishPhoto = dishId =&gt; {    const input = document.getElementById('e-photo-' + dishId);    if (!input || !input.files[0]) return;        const file = input.files[0];    const formData = new FormData();    formData.append('photo', file);    formData.append('dish_id', dishId);        fetch(API + '?action=upload_dish_photo', {        method: 'POST',        body: formData    })    .then(r =&gt; r.json())    .then(data =&gt; {        if (data.error) throw new Error(data.error);        toast('\u2713 \u0424\u043e\u0442\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e', 'ok');                \/\/ \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u0438 \u043f\u0435\u0440\u0435\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0435\u0442\u043a\u0443        loadDishes().then(() =&gt; {            render();            openDishDetail(dishId);        });    })    .catch(e =&gt; toast('\u041e\u0448\u0438\u0431\u043a\u0430: ' + e.message, 'err'));};<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043e \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u044f \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 &#8212; \u0441\u0442\u0430\u043b\u043e \u044f\u0432\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0435\u0435.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/efc\/0eb\/80b\/efc0eb80b467db87a330c35ad677bf8c.jpg\" alt=\"\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0431\u043b\u044e\u0434\" title=\"\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0431\u043b\u044e\u0434\" width=\"5712\" height=\"4284\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/efc\/0eb\/80b\/efc0eb80b467db87a330c35ad677bf8c.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/efc\/0eb\/80b\/efc0eb80b467db87a330c35ad677bf8c.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0431\u043b\u044e\u0434<\/figcaption><\/div>\n<\/figure>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21165: \u041a\u0442\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u0431\u043b\u044e\u0434\u0430 \u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u0442 \u043c\u0435\u043d\u044e<\/h3>\n<h4>\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442<\/h4>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0441\u043c\u043e\u0442\u0440\u0438\u0442 \u043d\u0430 \u043f\u0443\u0441\u0442\u0443\u044e \u043d\u0435\u0434\u0435\u043b\u044e \u0438 \u0434\u0443\u043c\u0430\u0435\u0442: &#171;\u0427\u0442\u043e \u043f\u0440\u0438\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c?&#187;. \u041d\u0443\u0436\u0435\u043d \u0440\u0435\u0436\u0438\u043c &#171;\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0435 \u0431\u043b\u044e\u0434\u043e&#187;. \u042d\u0442\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u0441\u043b\u043e\u0436\u043d\u043e, \u043d\u043e \u043f\u043e\u043a\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0431\u044b\u043b\u0430 \u0431\u043e\u043b\u0435\u0435 \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u043e\u0439: \u043a\u0430\u043a \u043d\u0435 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u0443\u0447\u0438 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0445 \u043e\u0434\u043d\u043e\u0442\u0438\u043f\u043d\u044b\u0445 \u0431\u043b\u044e\u0434 \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0442 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439?<\/p>\n<h4>\u0420\u0435\u0448\u0435\u043d\u0438\u0435<\/h4>\n<p>\u041c\u0435\u043d\u044e \u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u044b \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u0443 \u0441\u0435\u043c\u044c\u0438 \u21161, \u0442\u043e \u0435\u0441\u0442\u044c \u043f\u043e \u0444\u0430\u043a\u0442\u0443 \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041e\u043d\u0438 \u043d\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u044b \u043a \u0441\u0435\u043c\u044c\u0435 \u043f\u043e \u0444\u0430\u043a\u0442\u0443, \u043e\u0434\u043d\u0430\u043a\u043e \u0441\u0435\u043c\u044c\u044f \u0438\u0433\u0440\u0430\u0435\u0442 \u0440\u043e\u043b\u044c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430. \u0414\u0440\u0443\u0433\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0431\u043b\u044e\u0434\u0430, \u043d\u043e \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u043b\u0438 \u0438\u0441\u043f\u043e\u0440\u0442\u0438\u0442\u044c \u0443\u0436\u0435 \u0438\u043c\u0435\u044e\u0449\u0438\u0435\u0441\u044f.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fbb\/2ce\/34e\/fbb2ce34e308a8aa3a0f484632e77fc3.png\" alt=\"\u0411\u043b\u044e\u0434\u0430 \u0432 \u043c\u0435\u043d\u044e, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a \u0441\u0435\u0431\u0435 \u0432 \u0440\u0430\u0446\u0438\u043e\u043d\" title=\"\u0411\u043b\u044e\u0434\u0430 \u0432 \u043c\u0435\u043d\u044e, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a \u0441\u0435\u0431\u0435 \u0432 \u0440\u0430\u0446\u0438\u043e\u043d\" width=\"1782\" height=\"1005\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/fbb\/2ce\/34e\/fbb2ce34e308a8aa3a0f484632e77fc3.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fbb\/2ce\/34e\/fbb2ce34e308a8aa3a0f484632e77fc3.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0411\u043b\u044e\u0434\u0430 \u0432 \u043c\u0435\u043d\u044e, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a \u0441\u0435\u0431\u0435 \u0432 \u0440\u0430\u0446\u0438\u043e\u043d<\/figcaption><\/div>\n<\/figure>\n<h4>\u0427\u0442\u043e \u0435\u0449\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438<\/h4>\n<p>\u041c\u043e\u0436\u043d\u043e, \u0440\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f, \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0431\u043b\u044e\u0434\u043e:<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3a3\/f0f\/3a6\/3a3f0f3a6686cbf3ada1eeeeb75fd435.png\" width=\"422\" height=\"704\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/3a3\/f0f\/3a6\/3a3f0f3a6686cbf3ada1eeeeb75fd435.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3a3\/f0f\/3a6\/3a3f0f3a6686cbf3ada1eeeeb75fd435.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041c\u043e\u0436\u043d\u043e \u043e\u0442\u043c\u0435\u0447\u0430\u0442\u044c \u0441\u044a\u0435\u0434\u0435\u043d\u043d\u044b\u0435 \u0431\u043b\u044e\u0434\u0430, \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u043d\u0443\u0442\u0440\u044c \u0442\u0435\u0431\u044f \u043f\u043e\u043f\u0430\u043b\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u0436\u0438\u0440\u043e\u0432, \u0431\u0435\u043b\u043a\u043e\u0432, \u0443\u0433\u043b\u0435\u0432\u043e\u0434\u043e\u0432, \u0432\u0438\u0442\u0430\u043c\u0438\u043d\u043e\u0432 \u0438 \u043c\u0438\u043d\u0435\u0440\u0430\u043b\u043e\u0432:<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/1a0\/18c\/a49\/1a018ca49dd83006f43dc058f3b38b77.png\" alt=\"\u041d\u0435\u0445\u0432\u0430\u0442\u043a\u0430 \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0432 \u043f\u0440\u0438\u043d\u044f\u0442\u043e\u0439 \u043f\u0438\u0449\u0435\" title=\"\u041d\u0435\u0445\u0432\u0430\u0442\u043a\u0430 \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0432 \u043f\u0440\u0438\u043d\u044f\u0442\u043e\u0439 \u043f\u0438\u0449\u0435\" width=\"459\" height=\"823\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/1a0\/18c\/a49\/1a018ca49dd83006f43dc058f3b38b77.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/1a0\/18c\/a49\/1a018ca49dd83006f43dc058f3b38b77.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041d\u0435\u0445\u0432\u0430\u0442\u043a\u0430 \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0432 \u043f\u0440\u0438\u043d\u044f\u0442\u043e\u0439 \u043f\u0438\u0449\u0435<\/figcaption><\/div>\n<\/figure>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043d\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0430\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438\u0435\u043c\u044b \u043f\u0438\u0449\u0438, \u043d\u043e \u0438 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0438\u0435\u043c\u0430 \u0442\u0430\u0431\u043b\u0435\u0442\u043e\u043a, \u043f\u0440\u043e\u0442\u0435\u0438\u043d\u043e\u0432 \u0438\u043b\u0438 \u0441\u0442\u0435\u0440\u043e\u0438\u0434\u043e\u0432, \u043d\u043e \u044f \u044d\u0442\u0438\u043c \u043d\u0435 \u0443\u0432\u043b\u0435\u043a\u0430\u044e\u0441\u044c.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/49c\/4c3\/e61\/49c4c3e611ffe678859e00cf3e528cdc.png\" alt=\"\u041f\u0440\u0438\u0435\u043c\u044b \u043f\u0438\u0449\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0438\u0440\u0443\u0435\u043c\u044b\u0435\" title=\"\u041f\u0440\u0438\u0435\u043c\u044b \u043f\u0438\u0449\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0438\u0440\u0443\u0435\u043c\u044b\u0435\" width=\"480\" height=\"768\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/49c\/4c3\/e61\/49c4c3e611ffe678859e00cf3e528cdc.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/49c\/4c3\/e61\/49c4c3e611ffe678859e00cf3e528cdc.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041f\u0440\u0438\u0435\u043c\u044b \u043f\u0438\u0449\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0438\u0440\u0443\u0435\u043c\u044b\u0435<\/figcaption><\/div>\n<\/figure>\n<p>\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043c\u0435\u043d\u044e \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u043a\u0443\u043f\u043e\u043a. \u0412\u043e\u0442 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0448\u044c \u0442\u044b \u0441 \u0443\u0442\u0440\u0430 \u043a \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0443, \u0441\u043c\u043e\u0442\u0440\u0438\u0448\u044c, \u0447\u0442\u043e \u043d\u0430 \u0437\u0430\u0432\u0442\u0440\u0430\u0445 \u043e\u043c\u043b\u0435\u0442 \u0441 \u043f\u043e\u043c\u0438\u0434\u043e\u0440\u0430\u043c\u0438, \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0448\u044c \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a&#8230; \u0410 \u0442\u0430\u043c \u2013 \u0447\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e, \u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0435 \u0438\u0441\u043a\u043e\u043c\u043e\u0435. \u041c\u043e\u0436\u043d\u043e \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0441 \u0445\u043e\u0434\u0443 \u0436\u0435\u043b\u0430\u043d\u043d\u043e\u0435 \u043d\u0430 \u043d\u0435\u0436\u0435\u043b\u0430\u043d\u043d\u043e\u0435, \u0441\u0432\u0430\u0440\u0438\u0442\u044c \u0440\u0438\u0441, \u043d\u043e \u0435\u0441\u043b\u0438 \u043f\u043e\u0439\u0442\u0438 \u0432 \u043c\u0430\u0433\u0430\u0437\u0438\u043d, \u0442\u043e \u0442\u043e\u0447\u043d\u043e \u043a\u0443\u043f\u0438\u0448\u044c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u044b.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8a8\/1fa\/db7\/8a81fadb7ee4532c51ada387a142a124.png\" alt=\"\u041f\u0440\u043e\u0434\u0443\u043a\u0442\u044b \u043d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f, \u0437\u0430\u0432\u0442\u0440\u0430 \u0438\u043b\u0438 \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e\" title=\"\u041f\u0440\u043e\u0434\u0443\u043a\u0442\u044b \u043d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f, \u0437\u0430\u0432\u0442\u0440\u0430 \u0438\u043b\u0438 \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e\" width=\"447\" height=\"815\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/8a8\/1fa\/db7\/8a81fadb7ee4532c51ada387a142a124.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8a8\/1fa\/db7\/8a81fadb7ee4532c51ada387a142a124.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041f\u0440\u043e\u0434\u0443\u043a\u0442\u044b \u043d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f, \u0437\u0430\u0432\u0442\u0440\u0430 \u0438\u043b\u0438 \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e<\/figcaption><\/div>\n<\/figure>\n<hr\/>\n<h3>\u0418\u0442\u043e\u0433\u0438<\/h3>\n<h4>\u0427\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c<\/h4>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/493\/2cc\/6fe\/4932cc6fed3c0233f9e1a57e469f9b86.png\" alt=\"\u041c\u0435\u043d\u044e \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e \u0441 \u043f\u043e\u0434\u0441\u0447\u0435\u0442\u043e\u043c \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u0438\" title=\"\u041c\u0435\u043d\u044e \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e \u0441 \u043f\u043e\u0434\u0441\u0447\u0435\u0442\u043e\u043c \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u0438\" width=\"1913\" height=\"925\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/493\/2cc\/6fe\/4932cc6fed3c0233f9e1a57e469f9b86.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/493\/2cc\/6fe\/4932cc6fed3c0233f9e1a57e469f9b86.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041c\u0435\u043d\u044e \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e \u0441 \u043f\u043e\u0434\u0441\u0447\u0435\u0442\u043e\u043c \u043f\u0438\u0449\u0435\u0432\u043e\u0439 \u0446\u0435\u043d\u043d\u043e\u0441\u0442\u0438<\/figcaption><\/div>\n<\/figure>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041c\u0435\u0442\u0440\u0438\u043a\u0430<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0421\u0442\u0440\u043e\u043a \u043a\u043e\u0434\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">~2500 (PHP + JS + CSS)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0412\u0440\u0435\u043c\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">3 \u043d\u0435\u0434\u0435\u043b\u0438 (\u0432\u0435\u0447\u0435\u0440\u0430\u043c\u0438)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0420\u0430\u0437\u043c\u0435\u0440 \u0411\u0414<\/p>\n<\/td>\n<td>\n<p align=\"left\">17 \u0442\u0430\u0431\u043b\u0438\u0446, ~1000 \u0441\u0442\u0440\u043e\u043a<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0435\u0440\u0432\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 &lt; 1\u0441, subsequent &lt; 200\u043c\u0441<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h4>\u0427\u0435\u043c\u0443 \u043d\u0430\u0443\u0447\u0438\u043b\u0441\u044f<\/h4>\n<ol>\n<li>\n<p><strong>Remember Me \u043d\u0430 shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435<\/strong> \u2014 \u0442\u043e\u043a\u0435\u043d\u044b \u0432 \u0411\u0414 + sliding expiration<\/p>\n<\/li>\n<li>\n<p><strong>\u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 drag-and-drop<\/strong> \u2014 \u043c\u044b\u0448\u044c + \u0442\u0430\u0447, \u043f\u043e\u0440\u043e\u0433 5px<\/p>\n<\/li>\n<li>\n<p><strong>COLLATE<\/strong> \u2014 \u043d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u044f\u0432\u043d\u043e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0444\u043e\u043b\u0442 \u043f\u043e\u043b\u044f<\/p>\n<\/li>\n<li>\n<p><strong>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u043e\u0442\u043e<\/strong> \u2014 FormData + \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 MIME<\/p>\n<\/li>\n<li>\n<p><strong>UX \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435<\/strong> \u2014 \u043a\u0440\u0443\u043f\u043d\u044b\u0435 \u043a\u043d\u043e\u043f\u043a\u0438, \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439<\/p>\n<\/li>\n<\/ol>\n<h4>\u0427\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c<\/h4>\n<ul>\n<li>\n<p>Service Worker \u0434\u043b\u044f \u043e\u0444\u043b\u0430\u0439\u043d-\u0440\u0435\u0436\u0438\u043c\u0430<\/p>\n<\/li>\n<li>\n<p>Push-\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f &#171;\u041f\u043e\u0440\u0430 \u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043e\u0431\u0435\u0434&#187;<\/p>\n<\/li>\n<li>\n<p>\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u042f\u043d\u0434\u0435\u043a\u0441.\u041b\u0430\u0432\u043a\u043e\u0439 (\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0437\u0430\u043a\u0430\u0437 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432)<\/p>\n<\/li>\n<li>\n<p>\u0413\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0432\u0432\u043e\u0434 \u0440\u0435\u0446\u0435\u043f\u0442\u043e\u0432<\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<h3>\u0421\u0441\u044b\u043b\u043a\u0438<\/h3>\n<ul>\n<li>\n<p><strong>\u0414\u0435\u043c\u043e:<\/strong> \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044e<\/p>\n<\/li>\n<li>\n<p><strong>GitHub:<\/strong> \u043e\u0442\u043a\u0440\u043e\u044e, \u0435\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0435\u0441<\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<p><strong>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/strong> \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043f\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u2014 \u0437\u0430\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445. \u0415\u0441\u043b\u0438 \u0441\u0442\u0430\u0442\u044c\u044f \u043d\u0430\u0431\u0435\u0440\u0451\u0442 \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u2014 \u0432\u044b\u043b\u043e\u0436\u0443 \u043f\u043e\u043b\u043d\u044b\u0439 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434.<\/p>\n<p><em>P.S. \u0416\u0435\u043d\u0430 \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u043f\u0438\u0442\u0430\u043d\u0438\u0435 \u043d\u0430 \u043c\u0435\u0441\u044f\u0446 \u0432\u043f\u0435\u0440\u0451\u0434. \u0421\u0447\u0438\u0442\u0430\u0439\u0442\u0435, \u043c\u0438\u0441\u0441\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.<\/em> <\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1058020\/\">https:\/\/habr.com\/ru\/articles\/1058020\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0425\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0438\u0442\u0430\u043d\u0438\u044f \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e. \u0417\u0430\u0434\u0430\u0447\u0430 \u043a\u0430\u0437\u0430\u043b\u0430\u0441\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439: \u043a\u0430\u043b\u0435\u043d\u0434\u0430\u0440\u044c, \u0431\u043b\u044e\u0434\u0430, \u0438\u043d\u0433\u0440\u0435\u0434\u0438\u0435\u043d\u0442\u044b. \u041d\u043e \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0432\u0441\u043f\u043b\u044b\u043b\u043e \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u043d\u044e\u0430\u043d\u0441\u043e\u0432 \u2014 \u043e\u0442 UX \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0434\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u044f\u043c\u0438 \u043d\u0430 shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435, \u2014 \u0447\u0442\u043e \u0440\u0435\u0448\u0438\u043b \u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c \u044d\u0442\u043e \u0432 \u0441\u0442\u0430\u0442\u044c\u044e.\u0427\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044cPWA \u0434\u043b\u044f \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0438\u0442\u0430\u043d\u0438\u044f \u0441 \u0444\u0438\u0447\u0430\u043c\u0438:Drag-and-drop \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0431\u043b\u044e\u0434 \u043c\u0435\u0436\u0434\u0443 \u0434\u043d\u044f\u043c\u0438\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0440\u0430\u0441\u0447\u0435\u0442 \u041a\u0411\u0416\u0423 \u0438 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u043e\u043a\u0443\u043f\u043e\u043a\u0420\u0430\u0431\u043e\u0442\u0430 \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 (\u043a\u0440\u0443\u043f\u043d\u044b\u0439 UI, \u043e\u0444\u043b\u0430\u0439\u043d-\u0440\u0435\u0436\u0438\u043c)Remember Me \u043d\u0430 30 \u0434\u043d\u0435\u0439 \u0434\u0430\u0436\u0435 \u043d\u0430 shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435\u0424\u043e\u0442\u043e \u0431\u043b\u044e\u0434 \u0441 \u043f\u0440\u0435\u0432\u044c\u044e\u0421\u043a\u0440\u0435\u043c\u0431\u043b-\u0440\u0435\u0436\u0438\u043c (\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e\u0435 \u0431\u043b\u044e\u0434\u043e, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0437\u043d\u0430\u0435\u0448\u044c \u0447\u0442\u043e \u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c)\u0423\u043c\u043d\u044b\u0439 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a \u2013 \u0414\u0438\u0441\u043f\u043b\u0435\u0439 \u043d\u0430 \u043c\u0430\u0433\u043d\u0438\u0442\u0430\u0445 \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0441 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u043c \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c\u0421\u0442\u0435\u043a: PHP 8.5 + PDO + MySQL + Vanilla JS (\u0431\u0435\u0437 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432)\u0427\u0442\u043e \u044d\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u0442\u0430\u043a\u043e\u0435\u0420\u0430\u0441\u043f\u0431\u0435\u0440\u0438 \u0434\u043b\u044f \u0437\u0430\u0434\u0430\u0447 \u043d\u0435 \u043f\u043e\u0434\u043e\u0448\u0435\u043b, \u0442\u0430\u043a \u043a\u0430\u043a \u043f\u043e\u0434 \u0440\u0443\u043a\u043e\u0439 \u0431\u044b\u043b\u0430 3 \u0432\u0435\u0440\u0441\u0438\u044f, 5-\u044f \u0441\u0442\u043e\u0438\u0442 \u0443\u0436\u0435 20000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0438\u0435 \u0430\u043d\u0430\u043b\u043e\u0433\u0438 \u0432\u0438\u0434\u0430 Babana Pi \u0441\u0442\u043e\u044f\u0442 \u043e\u0442 9000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0430 Mac Mini \u0437\u0430 54000 \u0440\u0443\u0431\u043b\u0435\u0439 \u0434\u043b\u044f \u044d\u0442\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u043f\u043e\u043a\u0443\u043f\u0430\u0442\u044c \u0443\u0436\u0435 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c.  \u0412 \u0438\u0442\u043e\u0433\u0435 \u044f \u0432\u0437\u044f\u043b \u0441\u0442\u0430\u0440\u044b\u0439 \u043d\u043e\u0443\u0442\u0431\u0443\u043a \u0437\u0430 2000 \u0440\u0443\u0431\u043b\u0435\u0439, \u0433\u0434\u0435 \u0441\u0442\u043e\u0438\u0442 \u043d\u043e\u0432\u044b\u0439 SSD 256 \u0413\u0431 \u0438 8 \u0413\u0431 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u043f\u043b\u0430\u043d\u043a\u0430 DDR3. \u0421 \u0420\u0430\u0441\u043f\u0431\u0435\u0440\u0438 \u041f\u0438 3 \u044f \u0443\u0441\u0442\u0430\u043b \u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f \u0443\u0436\u0435 \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0434\u0435\u043d\u044c \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u043b \u0435\u0435 \u043d\u0430\u0437\u0430\u0434 \u0432 \u043a\u043e\u0440\u043e\u0431\u043e\u0447\u043a\u0443 \u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u0438\u043a\u043e\u0439. \u0412 \u0438\u0442\u043e\u0433\u0435 \u0432\u0441\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u2013 \u044d\u0442\u043e \u043d\u043e\u0443\u0442\u0431\u0443\u043a, \u0441\u0435\u043d\u0441\u043e\u0440\u043d\u044b\u0439 \u043c\u0430\u0442\u043e\u0432\u044b\u0439 \u0434\u0438\u0441\u043f\u043b\u0435\u0439 \u043d\u0430 15 \u0434\u044e\u0439\u043c\u043e\u0432, \u043e\u0431\u0432\u044f\u0437\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0436\u0435\u043b\u0435\u0437\u0430, \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0439 \u044d\u043a\u0440\u0430\u043d (\u043a\u043d\u043e\u043f\u043a\u0430 \u0432 \u043c\u0435\u043d\u044e).\u041f\u0440\u043e\u0432\u043e\u0434\u0430 \u043d\u0435 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432 \u0433\u043e\u0444\u0440\u0443, \u043d\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0430 \u0441 \u043f\u0440\u043e\u0432\u043e\u0434\u0430\u043c\u0438 \u043d\u0430 \u0441\u0442\u044f\u0436\u043a\u0430\u0445 \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435\u0442 \u043f\u0440\u0438 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0430 \u043d\u0438\u0436\u0435.\u041d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435 \u0432\u044b\u0440\u043e\u0441\u043b\u0438 \u043d\u043e\u0443\u0442\u0431\u0443\u043a \u0438 \u0434\u0438\u0441\u043f\u043b\u0435\u0439, \u043d\u043e \u0432\u0440\u043e\u0434\u0435 \u0441\u0442\u0430\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0436\u0438\u0442\u044c\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21161: Shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0438 \u0441\u0435\u0441\u0441\u0438\u0438\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u0442\u043e\u0438\u0442 \u043d\u0430 \u043e\u0431\u044b\u0447\u043d\u043e\u043c shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c (\u043c\u043e\u044f \u043f\u043e\u0434\u0440\u0443\u0433\u0430) \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u0441 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0430, \u0430 \u043d\u0435 \u0441\u0438\u0434\u0438\u0442 \u0432 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0435, \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u043f\u0438\u0442\u0430\u043d\u0438\u0435 \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e \u0438&#8230; \u0447\u0435\u0440\u0435\u0437 24 \u043c\u0438\u043d\u0443\u0442\u044b \u0441\u0435\u0441\u0441\u0438\u044f \u0441\u043b\u0435\u0442\u0430\u0435\u0442. \u041a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c \u2014 \u044d\u0442\u043e UX \u0443\u0440\u043e\u0432\u043d\u044f &#171;\u0432\u044b\u043a\u043b\u044e\u0447\u0438 \u0438 \u0432\u043a\u043b\u044e\u0447\u0438&#187;.\u0424\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438\u041f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442\u041d\u0430 shared-\u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435:session.gc_maxlifetime = 1440 \u0441\u0435\u043a\u0443\u043d\u0434 (24 \u043c\u0438\u043d\u0443\u0442\u044b) \u2014 \u0434\u0435\u0444\u043e\u043b\u0442 PHP\u041f\u0430\u043f\u043a\u0430 \/tmp \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u0447\u0438\u0441\u0442\u0438\u0442\u0441\u044f \u0445\u043e\u0441\u0442\u0435\u0440\u043e\u043c\u041f\u0440\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441\u0435\u0441\u0441\u0438\u0438 \u0442\u0435\u0440\u044f\u044e\u0442\u0441\u044f\u0420\u0435\u0448\u0435\u043d\u0438\u0435: Persistent Tokens\u041f\u0440\u043e\u0441\u0442\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c session.gc_maxlifetime \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u2014 \u0445\u043e\u0441\u0442\u0435\u0440 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043f\u043e\u0447\u0438\u0441\u0442\u0438\u0442 \/tmp. \u041d\u0443\u0436\u0435\u043d \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c Remember Me \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u043a\u0435\u043d\u044b \u0432 \u0411\u0414\/\/ === \u0410\u0412\u0422\u041e\u041b\u041e\u0413\u0418\u041d \u041f\u041e REMEMBER_ME COOKIE ===if (empty($_SESSION[&#8216;user_id&#8217;]) &amp;&amp; !empty($_COOKIE[&#8216;remember_me&#8217;])) {    $parts = explode(&#8216;:&#8217;, $_COOKIE[&#8216;remember_me&#8217;]);    if (count($parts) === 2) {        $selector = $parts[0];        $validator = $parts[1];    \/\/ \u0418\u0449\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0432 \u0411\u0414    $stmt = $pdo-&gt;prepare(&#187;        SELECT rt.id, rt.user_id, rt.hashed_validator, rt.expires_at,               u.family_id, u.name, u.login, f.name as family_name        FROM remember_tokens rt        JOIN users u ON rt.user_id = u.id        JOIN families f ON u.family_id = f.id        WHERE rt.selector = ? AND rt.expires_at &gt; NOW()        LIMIT 1    &#171;);    $stmt-&gt;execute([$selector]);    $token = $stmt-&gt;fetch();        if ($token &amp;&amp; hash_equals($token[&#8216;hashed_validator&#8217;], hash(&#8216;sha256&#8217;, $validator))) {        \/\/ \u0422\u043e\u043a\u0435\u043d \u0432\u0430\u043b\u0438\u0434\u0435\u043d \u2014 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u0435\u0441\u0441\u0438\u044e        $_SESSION[&#8216;user_id&#8217;] = $token[&#8216;user_id&#8217;];        $_SESSION[&#8216;family_id&#8217;] = $token[&#8216;family_id&#8217;];        $_SESSION[&#8216;user_name&#8217;] = $token[&#8216;name&#8217;];        $_SESSION[&#8216;family_name&#8217;] = $token[&#8216;family_name&#8217;];                \/\/ Sliding expiration: \u043f\u0440\u043e\u0434\u043b\u0435\u0432\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u043d\u0430 30 \u0434\u043d\u0435\u0439        $newSelector = bin2hex(random_bytes(16));        $newValidator = bin2hex(random_bytes(32));        $newHashedValidator = hash(&#8216;sha256&#8217;, $newValidator);        $newExpires = date(&#8216;Y-m-d H:i:s&#8217;, strtotime(&#8216;+30 days&#8217;));                $updateStmt = $pdo-&gt;prepare(&#187;            UPDATE remember_tokens             SET selector = ?, hashed_validator = ?, expires_at = ?            WHERE id = ?        &#171;);        $updateStmt-&gt;execute([$newSelector, $newHashedValidator, $newExpires, $token[&#8216;id&#8217;]]);                \/\/ \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c cookie        setcookie(&#8216;remember_me&#8217;, $newSelector . &#8216;:&#8217; . $newValidator, [            &#8216;expires&#8217; =&gt; time() + (30 * 24 * 60 * 60),            &#8216;path&#8217; =&gt; &#8216;\/&#8217;,            &#8216;httponly&#8217; =&gt; true,            &#8216;samesite&#8217; =&gt; &#8216;Lax&#8217;,            &#8216;secure&#8217; =&gt; false  \/\/ \u2190 false \u0434\u043b\u044f HTTP        ]);    }}\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u044bCREATE TABLE remember_tokens (    id INT AUTO_INCREMENT PRIMARY KEY,    user_id INT NOT NULL,    selector VARCHAR(64) NOT NULL UNIQUE,    hashed_validator VARCHAR(255) NOT NULL,    expires_at DATETIME NOT NULL,    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,    INDEX idx_selector (selector),    INDEX idx_user (user_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c\u0423\u0433\u0440\u043e\u0437\u0430\u0417\u0430\u0449\u0438\u0442\u0430\u041f\u0435\u0440\u0435\u0445\u0432\u0430\u0442 cookiehttponly=true (JS \u043d\u0435 \u0447\u0438\u0442\u0430\u0435\u0442)CSRFsamesite=Lax\u041a\u0440\u0430\u0436\u0430 \u0442\u043e\u043a\u0435\u043d\u0430Validator \u0445\u0435\u0448\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u0411\u0414\u0411\u0440\u0443\u0442\u0444\u043e\u0440\u0441Selector + Validator (64+64 \u0431\u0438\u0442\u0430 \u044d\u043d\u0442\u0440\u043e\u043f\u0438\u0438)\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442: \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u0440\u0430\u0437 \u0432 \u043c\u0435\u0441\u044f\u0446, \u0432\u0441\u0451 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0431\u0435\u0437 \u043f\u0430\u0440\u043e\u043b\u044f.\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21162: Drag-and-drop \u043d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a\u0435\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u041d\u0430 \u0445\u043e\u043b\u043e\u0434\u0438\u043b\u044c\u043d\u0438\u043a \u043c\u0430\u0433\u043d\u0438\u0442\u0430\u043c\u0438 \u0437\u0430\u043a\u0440\u0435\u043f\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0438\u0441\u043f\u043b\u0435\u0439 \u0441 \u0442\u0430\u0447\u0441\u043a\u0440\u0438\u043d\u043e\u043c. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0442\u044c \u0431\u043b\u044e\u0434\u0430 \u043c\u0435\u0436\u0434\u0443 \u0434\u043d\u044f\u043c\u0438. \u041d\u043e:\u0422\u0430\u0447-\u0441\u043e\u0431\u044b\u0442\u0438\u044f \u2260 \u043c\u044b\u0448\u044c\u041d\u0443\u0436\u043d\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u0442\u044c \u043a\u043b\u0438\u043a \u043e\u0442 drag\u041d\u0430 \u0442\u0430\u0447-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u0441\u043a\u0440\u043e\u043b\u043b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u0443\u0435\u0442 \u0441 drag\u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0431\u043b\u044e\u0434\u0430 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u0435\u043d\u044c \u0438 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u043f\u0440\u0438\u0435\u043c \u043f\u0438\u0449\u0438\u0420\u0435\u0448\u0435\u043d\u0438\u0435: \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043aconst initDnD = () =&gt; {    const board = $(&#8216;#board&#8217;);    const THRESHOLD = 5; \/\/ \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439 \u0434\u043b\u044f \u043e\u0442\u043b\u0438\u0447\u0438\u044f \u043a\u043b\u0438\u043a\u0430 \u043e\u0442 drag    let dragTarget = null;    let startX = 0, startY = 0;    let moved = false;        const onStart = e =&gt; {        const dish = e.target.closest(&#8216;.dish&#8217;);        if (!dish) return;                dragTarget = {            el: dish,            id: parseInt(dish.dataset.id),            dishId: parseInt(dish.dataset.dish)        };                const pt = e.touches ? e.touches[0] : e;        startX = pt.clientX;        startY = pt.clientY;        moved = false;                dish.style.opacity = &#8216;0.6&#8217;;        dish.style.transition = &#8216;none&#8217;;                document.addEventListener(&#8216;mousemove&#8217;, onMove);        document.addEventListener(&#8216;touchmove&#8217;, onMove, { passive: false });        document.addEventListener(&#8216;mouseup&#8217;, onEnd);        document.addEventListener(&#8216;touchend&#8217;, onEnd);    };        const onMove = e =&gt; {        if (!dragTarget) return;                const pt = e.touches ? e.touches[0] : e;        const dx = pt.clientX &#8212; startX;        const dy = pt.clientY &#8212; startY;                \/\/ \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c: \u044d\u0442\u043e drag \u0438\u043b\u0438 \u043a\u043b\u0438\u043a?        if (!moved &amp;&amp; Math.hypot(dx, dy) &gt; THRESHOLD) {            moved = true;        }                if (moved) {            if (e.cancelable) e.preventDefault();                        dragTarget.el.style.transform = `translate(${dx}px, ${dy}px) scale(1.03)`;                        \/\/ \u041f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0430 drop-\u0437\u043e\u043d\u044b            $$(&#8216;.drop-zone&#8217;).forEach(zone =&gt; {                const rect = zone.getBoundingClientRect();                const over = (                    pt.clientX &gt;= rect.left &amp;&amp;                    pt.clientX &lt;= rect.right &amp;&amp;                    pt.clientY &gt;= rect.top &amp;&amp;                    pt.clientY &lt;= rect.bottom                );                zone.classList.toggle(&#8216;over&#8217;, over);            });        }    };        const onEnd = () =&gt; {        document.removeEventListener(&#8216;mousemove&#8217;, onMove);        document.removeEventListener(&#8216;touchmove&#8217;, onMove);        document.removeEventListener(&#8216;mouseup&#8217;, onEnd);        document.removeEventListener(&#8216;touchend&#8217;, onEnd);                if (!dragTarget) return;                dragTarget.el.style.opacity = &#187;;        dragTarget.el.style.transform = &#187;;        dragTarget.el.style.transition = &#187;;                if (moved) {            \/\/ \u042d\u0442\u043e \u0431\u044b\u043b drag \u2192 \u0438\u0449\u0435\u043c drop-\u0437\u043e\u043d\u0443            const dropZone = $(&#8216;.drop-zone.over&#8217;);            if (dropZone) {                const toType = parseInt(dropZone.dataset.t);                const toDay = parseInt(dropZone.dataset.d);                                api(&#8216;move_item&#8217;, {                    item_id: dragTarget.id,                    to_type: toType,                    to_day: toDay                }).then(() =&gt; {                    loadWeek().then(render);                    toast(&#8216;\u2713 \u041f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u043e&#8217;, &#8216;ok&#8217;);                });            }        } else {            \/\/ \u042d\u0442\u043e \u0431\u044b\u043b \u043a\u043b\u0438\u043a \u2192 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0443 \u0431\u043b\u044e\u0434\u0430            openDishDetail(dragTarget.dishId);        }                $$(&#8216;.drop-zone&#8217;).forEach(z =&gt; z.classList.remove(&#8216;over&#8217;));        dragTarget = null;    };        board.addEventListener(&#8216;mousedown&#8217;, onStart);    board.addEventListener(&#8216;touchstart&#8217;, onStart, { passive: true });};UX-\u0444\u0438\u0448\u043a\u04381. \u041f\u043e\u0440\u043e\u0433 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u044f (THRESHOLD = 5px)\u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 &lt; 5px \u2192 \u044d\u0442\u043e \u043a\u043b\u0438\u043a\u0415\u0441\u043b\u0438 \u2265 5px \u2192 \u044d\u0442\u043e drag\u041d\u0430 \u0442\u0430\u0447-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u044d\u0442\u043e \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e: \u043f\u0430\u043b\u0435\u0446 \u0432\u0441\u0435\u0433\u0434\u0430 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0434\u0440\u043e\u0436\u0438\u04422. \u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0442\u043d\u0430\u044f \u0441\u0432\u044f\u0437\u044c.dish {    transition: transform 0.15s, opacity 0.15s;}.dish.dragging {    opacity: 0.6;    transform: scale(1.03);    z-index: 100;    box-shadow: 0 8px 24px rgba(0,0,0,0.3);}.drop-zone.over {    height: 40px;    background: rgba(0, 184, 148, 0.2);    border: 2px dashed var(&#8212;g);    border-radius: 6px;}3. \u041e\u0442\u043c\u0435\u043d\u0430 \u0441\u043a\u0440\u043e\u043b\u043b\u0430 \u043f\u0440\u0438 dragif (e.cancelable) e.preventDefault();\u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043d\u0430 \u0442\u0430\u0447-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441\u043a\u0440\u043e\u043b\u043b\u0438\u0442\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u0435\u043c \u0431\u043b\u044e\u0434\u0430.\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21163: COLLATE \u0438 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u041f\u0440\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0448\u0438\u0431\u043a\u0430:SQLSTATE[42000]: COLLATION &#8216;utf8mb4_unicode_ci&#8217; is not valid for CHARACTER SET &#8216;utf8mb3&#8217;\u041f\u0440\u0438\u0447\u0438\u043d\u0430\u0412 \u0411\u0414 \u043f\u043e\u043b\u044f login \u0438 email \u0438\u043c\u0435\u044e\u0442 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0443 utf8mb3, \u0430 \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u043b COLLATE utf8mb4_unicode_ci. MySQL \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c utf8mb4 collation \u043a utf8mb3 \u043f\u043e\u043b\u044e.\u0420\u0435\u0448\u0435\u043d\u0438\u0435\u041c\u043e\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438  COLLATE, \u044d\u0442\u043e \u0442\u043e\u0447\u043d\u043e \u043f\u043e\u043a\u0430\u0436\u0435\u0442, \u0432 \u0447\u0435\u043c \u0435\u0449\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430, \u0435\u0441\u043b\u0438 \u0443\u0436\u0435 \u043d\u0430\u043f\u0440\u043e\u0447\u044c \u0432\u044b\u043c\u0443\u0447\u0438\u043b \u0441\u0442\u0440\u043e\u0447\u043a\u0443 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0448\u044c \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0438\u0441\u043a\u043e\u043c\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430.$u = db_row($pdo, &#187;    SELECT u.*, f.name fn     FROM users u     JOIN families f ON u.family_id = f.id     WHERE COALESCE(u.login, &#187;) = ? COLLATE utf8mb4_unicode_ci        OR u.email = ? COLLATE&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-486938","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/486938","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=486938"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/486938\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=486938"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=486938"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=486938"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}