{"id":480537,"date":"2026-05-21T13:34:31","date_gmt":"2026-05-21T13:34:31","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=480537"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=480537","title":{"rendered":"\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u043e \u043a\u0430\u043a \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u00bb: \u043a\u0430\u043a \u044f \u0432 \u043e\u0434\u0438\u043d\u043e\u0447\u043a\u0443 \u0441\u0434\u0435\u043b\u0430\u043b \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043a\u0438\u043d\u043e-\u0441\u0435\u0440\u0432\u0438\u0441\u0430 (\u0441 \u043a\u043e\u0434\u043e\u043c)"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u043b, \u043a\u0430\u043a\u043e\u0432\u043e \u0432 \u043e\u0434\u0438\u043d\u043e\u0447\u043a\u0443 \u0442\u0430\u0449\u0438\u0442\u044c \u0444\u0443\u043b\u043b\u0441\u0442\u0435\u043a-\u043f\u0440\u043e\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0437\u0440\u043e\u0441\u0441\u044f \u0434\u043e \u043a\u0438\u043d\u043e-\u0441\u043e\u0446\u0441\u0435\u0442\u0438. \u0412 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u0441\u043f\u0440\u043e\u0441\u0438\u043b\u0438 \u043f\u0440\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u0438\u043a\u0443 \u2014 \u00ab\u043f\u043e\u043a\u0430\u0436\u0438 \u043a\u043e\u0434\u00bb, \u00ab\u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u043b, \u0447\u0442\u043e \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u00bb. \u041b\u043e\u0433\u0438\u0447\u043d\u043e: \u0434\u0438\u0437\u0430\u0439\u043d \u2014 \u044d\u0442\u043e \u0442\u043e, \u043f\u043e \u0447\u0435\u043c\u0443 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0435\u0440\u0436\u0438\u0442\u0435 \u0432\u0442\u043e\u0440\u0443\u044e \u0447\u0430\u0441\u0442\u044c, \u0443\u0436\u0435 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0438 \u0441 \u043a\u043e\u0434\u043e\u043c. \u0411\u0435\u0437 \u043c\u0430\u0440\u043a\u0435\u0442\u0438\u043d\u0433\u0430, \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u00ab\u0434\u043e\u0440\u043e\u0433\u0438\u043c\u00bb, \u0438 \u043f\u0430\u0440\u0430 \u0431\u044d\u043a\u0435\u043d\u0434-\u0445\u0438\u0442\u0440\u043e\u0441\u0442\u0435\u0439 \u0432 \u0434\u043e\u0432\u0435\u0441\u043e\u043a.<\/p>\n<p>\u0421\u0440\u0430\u0437\u0443 \u0434\u0438\u0441\u043a\u043b\u0435\u0439\u043c\u0435\u0440: \u044f \u043d\u0435 \u0434\u0438\u0437\u0430\u0439\u043d\u0435\u0440. \u0412\u0441\u0451 \u043d\u0430\u0436\u0438\u0442\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u00ab\u0441\u043c\u043e\u0442\u0440\u044e \u043d\u0430 \u0440\u0435\u0444\u0435\u0440\u0435\u043d\u0441\u044b (Letterboxd, Mubi, KinoPoisk HD) \u0438 \u043f\u044b\u0442\u0430\u044e\u0441\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435\u00bb. \u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u2014 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e \u0434\u043e\u0440\u043e\u0433\u0438\u0435 \u0448\u0440\u0438\u0444\u0442\u044b, \u0430 \u043f\u0440\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0445\u0441\u044f \u043f\u0440\u0438\u0451\u043c\u043e\u0432. \u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u044f\u0442\u044c.<\/p>\n<p><strong>1. \u0410\u043a\u0446\u0435\u043d\u0442\u043d\u044b\u0439 \u0446\u0432\u0435\u0442 \u0438\u0437 \u043f\u043e\u0441\u0442\u0435\u0440\u0430 \u0444\u0438\u043b\u044c\u043c\u0430 \u2014 \u0444\u0438\u0447\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u0435 \u0432\u0441\u0435\u0433\u043e \u00ab\u043f\u0440\u043e\u0434\u0430\u0451\u0442\u00bb<\/strong><\/p>\n<p>\u0421\u0430\u043c\u043e\u0435 \u0437\u0430\u043c\u0435\u0442\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u0420\u0430\u043d\u044c\u0448\u0435 \u0443 \u043c\u0435\u043d\u044f \u043d\u0430 \u0432\u0441\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u0431\u044b\u043b \u043e\u0434\u0438\u043d \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0439 \u0444\u0438\u043e\u043b\u0435\u0442\u043e\u0432\u044b\u0439 \u0430\u043a\u0446\u0435\u043d\u0442 \u2014 \u0438 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u043e \u0434\u0451\u0448\u0435\u0432\u043e \u0438 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u043e. \u0418\u0434\u0435\u044f: \u043f\u0443\u0441\u0442\u044c \u043a\u0430\u0436\u0434\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0444\u0438\u043b\u044c\u043c\u0430 \u043f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u043e\u043c\u0438\u043d\u0430\u043d\u0442\u043d\u044b\u043c \u0446\u0432\u0435\u0442\u043e\u043c \u0435\u0433\u043e \u043f\u043e\u0441\u0442\u0435\u0440\u0430. \u0417\u0430\u0445\u043e\u0434\u0438\u0448\u044c \u043d\u0430 \u043c\u0440\u0430\u0447\u043d\u044b\u0439 \u043d\u0443\u0430\u0440 \u2014 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432 \u0445\u043e\u043b\u043e\u0434\u043d\u044b\u0439 \u0441\u0438\u043d\u0438\u0439, \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0448\u044c \u043a\u043e\u043c\u0435\u0434\u0438\u044e \u2014 \u0442\u0451\u043f\u043b\u044b\u0439 \u044f\u043d\u0442\u0430\u0440\u044c. \u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0431\u0443\u0434\u0442\u043e \u00ab\u0441\u0434\u0435\u043b\u0430\u043d\u0430 \u043f\u043e\u0434 \u044d\u0442\u043e\u0442 \u0444\u0438\u043b\u044c\u043c\u00bb.<\/p>\n<p>\u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u0432\u0441\u044f\u043a\u0438\u0445 ML, \u043f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0447\u0435\u0440\u0435\u0437 canvas: \u0440\u0438\u0441\u0443\u0435\u043c \u043f\u043e\u0441\u0442\u0435\u0440 \u0432 \u043a\u0440\u043e\u0448\u0435\u0447\u043d\u044b\u0439 \u0431\u0443\u0444\u0435\u0440 32\u00d748, \u0443\u0441\u0440\u0435\u0434\u043d\u044f\u0435\u043c \u0446\u0432\u0435\u0442\u0430 (\u0432\u044b\u043a\u0438\u0434\u044b\u0432\u0430\u044f \u0447\u0451\u0440\u043d\u044b\u0435 \u0440\u0430\u043c\u043a\u0438 \u0438 \u0441\u0435\u0440\u043e\u0441\u0442\u044c), \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u0432 HSL \u0438 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u00ab\u043d\u0430\u0441\u044b\u0449\u0430\u0435\u043c\u00bb, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u043e\u0441\u0442\u0435\u0440\u044b \u0447\u0430\u0441\u0442\u043e \u0442\u0443\u0441\u043a\u043b\u044b\u0435. \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043a\u043b\u0430\u0434\u0451\u043c \u0432 CSS-\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u2014 \u0438 \u0432\u0435\u0441\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0435\u0451.<\/p>\n<pre><code>function applyPosterAccent(posterUrl: string) {  const img = new Image();  img.crossOrigin = \"anonymous\";  img.onload = () =&gt; {    const canvas = document.createElement(\"canvas\");    const w = (canvas.width = 32), h = (canvas.height = 48);    const ctx = canvas.getContext(\"2d\");    if (!ctx) return;    ctx.drawImage(img, 0, 0, w, h);    const { data } = ctx.getImageData(0, 0, w, h);    let r = 0, g = 0, b = 0, n = 0;    for (let i = 0; i &lt; data.length; i += 4) {      const R = data[i], G = data[i + 1], B = data[i + 2];      const max = Math.max(R, G, B), min = Math.min(R, G, B);      if (max &lt; 30 || min &gt; 230) continue; \/\/ \u0447\u0451\u0440\u043d\u044b\u0435 \u0440\u0430\u043c\u043a\u0438 \/ \u0432\u044b\u0431\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438      if (max - min &lt; 25) continue;          \/\/ \u0441\u0435\u0440\u043e\u0435 \u2014 \u0432 \u0430\u043a\u0446\u0435\u043d\u0442 \u043d\u0435 \u0433\u043e\u0434\u0438\u0442\u0441\u044f      r += R; g += G; b += B; n++;    }    if (!n) return;    const { h: hue, s, l } = rgbToHsl(r \/ n, g \/ n, b \/ n);    \/\/ \u043f\u043e\u0441\u0442\u0435\u0440\u044b \u0447\u0430\u0441\u0442\u043e \u0442\u0443\u0441\u043a\u043b\u044b\u0435 \u2014 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u043c \u0446\u0432\u0435\u0442 \u00ab\u0441\u043e\u0447\u043d\u044b\u043c\u00bb    const sat = Math.min(0.85, Math.max(0.45, s * 1.6 + 0.15));    const lit = Math.min(0.62, Math.max(0.52, l));    document.documentElement.style.setProperty(      \"--movie-accent\", `hsl(${hue | 0} ${(sat * 100) | 0}% ${(lit * 100) | 0}%)`    );    document.documentElement.style.setProperty(      \"--movie-accent-soft\", `hsl(${hue | 0} ${(sat * 100) | 0}% ${(lit * 100) | 0}% \/ .18)`    );  };  img.src = posterUrl;}<\/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<p><code>  const img = new Image();<\/code><\/p>\n<p><code>  img.crossOrigin = \"anonymous\";<\/code><\/p>\n<p><code>  img.onload = () =&gt; {<\/code><\/p>\n<p><code>    const canvas = document.createElement(\"canvas\");<\/code><\/p>\n<p><code>    const w = (canvas.width = 32), h = (canvas.height = 48);<\/code><\/p>\n<p><code>    const ctx = canvas.getContext(\"2d\");<\/code><\/p>\n<p><code>    if (!ctx) return;<\/code><\/p>\n<p><code>    ctx.drawImage(img, 0, 0, w, h);<\/code><\/p>\n<p><code>    const { data } = ctx.getImageData(0, 0, w, h);<\/code><\/p>\n<p><code>    let r = 0, g = 0, b = 0, n = 0;<\/code><\/p>\n<p><code>    for (let i = 0; i &lt; data.length; i += 4) {<\/code><\/p>\n<p><code>      const R = data[i], G = data[i + 1], B = data[i + 2];<\/code><\/p>\n<p><code>      const max = Math.max(R, G, B), min = Math.min(R, G, B);<\/code><\/p>\n<p><code>      if (max &lt; 30 || min &gt; 230) continue;<em> \/\/ \u0447\u0451\u0440\u043d\u044b\u0435 \u0440\u0430\u043c\u043a\u0438 \/ \u0432\u044b\u0431\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438<\/em><\/code><\/p>\n<p><code>      if (max - min &lt; 25) continue;<em>          \/\/ \u0441\u0435\u0440\u043e\u0435 \u2014 \u0432 \u0430\u043a\u0446\u0435\u043d\u0442 \u043d\u0435 \u0433\u043e\u0434\u0438\u0442\u0441\u044f<\/em><\/code><\/p>\n<p><code>      r += R; g += G; b += B; n++;<\/code><\/p>\n<p><code>    }<\/code><\/p>\n<p><code>    if (!n) return;<\/code><\/p>\n<p><code>    const { h: hue, s, l } = rgbToHsl(r \/ n, g \/ n, b \/ n);<\/code><\/p>\n<p><code><em>    \/\/ \u043f\u043e\u0441\u0442\u0435\u0440\u044b \u0447\u0430\u0441\u0442\u043e \u0442\u0443\u0441\u043a\u043b\u044b\u0435 \u2014 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u043c \u0446\u0432\u0435\u0442 \u00ab\u0441\u043e\u0447\u043d\u044b\u043c\u00bb<\/em><\/code><\/p>\n<p><code>    const sat = Math.min(0.85, Math.max(0.45, s * 1.6 + 0.15));<\/code><\/p>\n<p><code>    const lit = Math.min(0.62, Math.max(0.52, l));<\/code><\/p>\n<p><code>    <\/code><a href=\"http:\/\/document.documentElement.style\" rel=\"noopener noreferrer nofollow\"><code>document.documentElement.style<\/code><\/a><code>.setProperty(<\/code><\/p>\n<p><code>      \"--movie-accent\", hsl(${hue | 0} ${(sat <em> 100) | 0}% ${(lit <\/em> 100) | 0}%)<\/code><\/p>\n<p><code>    );<\/code><\/p>\n<p><code>    <\/code><a href=\"http:\/\/document.documentElement.style\" rel=\"noopener noreferrer nofollow\"><code>document.documentElement.style<\/code><\/a><code>.setProperty(<\/code><\/p>\n<p><code>      \"--movie-accent-soft\", hsl(${hue | 0} ${(sat <em> 100) | 0}% ${(lit <\/em> 100) | 0}% \/ .18)<\/code><\/p>\n<p><code>    );<\/code><\/p>\n<p><code>  };<\/code><\/p>\n<p><code>  img.src = posterUrl;<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p><code>rgbToHsl<\/code>\u00a0\u2014 \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u043a\u043e\u043d\u0432\u0435\u0440\u0441\u0438\u044f RGB\u2192HSL, \u043e\u043d\u0430 \u0432 \u043b\u044e\u0431\u043e\u043c \u0441\u043d\u0438\u043f\u043f\u0435\u0442\u0435 \u0432 \u0441\u0435\u0442\u0438. \u0412\u0430\u0436\u043d\u044b \u0434\u0432\u0430 \u043c\u043e\u043c\u0435\u043d\u0442\u0430: \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u00ab\u043c\u0443\u0441\u043e\u0440\u043d\u044b\u0445\u00bb \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439 (\u0431\u0435\u0437 \u043d\u0435\u0451 \u043d\u0430 \u0447\u0451\u0440\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u0435\u0440\u0430\u0445 \u0430\u043a\u0446\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0433\u0440\u044f\u0437\u043d\u043e-\u0441\u0435\u0440\u044b\u043c) \u0438 \u0431\u0443\u0441\u0442 \u043d\u0430\u0441\u044b\u0449\u0435\u043d\u043d\u043e\u0441\u0442\u0438 (<code>s * 1.6 + 0.15<\/code>) \u2014 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0444\u0438\u043b\u044c\u043c\u043e\u0432 \u0434\u0430\u0432\u0430\u043b\u0430 \u0431\u044b \u0431\u043b\u0451\u043a\u043b\u044b\u0439 \u0430\u043a\u0446\u0435\u043d\u0442.<\/p>\n<p>\u0421\u0442\u043e\u0438\u0442 \u044d\u0442\u043e \u043a\u043e\u043f\u0435\u0439\u043a\u0438 \u043f\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 (\u0434\u0430\u0443\u043d\u0441\u043a\u0435\u0439\u043b \u0434\u043e 32\u00d748 \u2014 \u044d\u0442\u043e \u043c\u0435\u043d\u044c\u0448\u0435 1500 \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439), \u0430 \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435 \u0434\u0430\u0451\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u00ab\u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0435\u00bb: \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0435\u0442 \u043d\u0430 \u043a\u043e\u043d\u0442\u0435\u043d\u0442.<\/p>\n<p><strong>2. Glassmorphism \u0447\u0435\u0440\u0435\u0437 \u043e\u0434\u043d\u0443 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e, \u0430 \u043d\u0435 100 \u0437\u0430\u0445\u0430\u0440\u0434\u043a\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0446\u0432\u0435\u0442\u043e\u0432<\/strong><\/p>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u0438\u0451\u043c \u2014 \u0441\u0442\u0435\u043a\u043b\u043e. \u041d\u043e \u043d\u0435 \u00ab\u043f\u0440\u0438\u043b\u0435\u043f\u0438\u043b blur \u0438 \u0437\u0430\u0431\u044b\u043b\u00bb, \u0430 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0432\u0441\u0451 \u0431\u044b\u043b\u043e \u0437\u0430\u0432\u044f\u0437\u0430\u043d\u043e \u043d\u0430 \u0442\u043e\u0442 \u0436\u0435\u00a0<code>--movie-accent<\/code>. \u0422\u043e\u0433\u0434\u0430 \u0441\u0442\u0435\u043a\u043b\u044f\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438, \u0441\u0432\u0435\u0447\u0435\u043d\u0438\u044f \u0438 \u043e\u0431\u0432\u043e\u0434\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0434 \u0444\u0438\u043b\u044c\u043c, \u0438 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0446\u0435\u043b\u044c\u043d\u043e.<\/p>\n<pre><code>.glass-card {  background: linear-gradient(180deg,      var(--movie-accent-soft, rgba(255,255,255,.06)),      rgba(255,255,255,.02));  backdrop-filter: blur(18px) saturate(1.2);  border: 1px solid rgba(255,255,255,.08);  border-radius: 18px;  box-shadow: 0 24px 60px -20px var(--movie-accent-soft);}<\/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><code>  background: linear-gradient(180deg,<\/code><\/p>\n<p><code>      var(--movie-accent-soft, rgba(255,255,255,.06)),<\/code><\/p>\n<p><code>      rgba(255,255,255,.02));<\/code><\/p>\n<p><code>  backdrop-filter: blur(18px) saturate(1.2);<\/code><\/p>\n<p><code>  border: 1px solid rgba(255,255,255,.08);<\/code><\/p>\n<p><code>  border-radius: 18px;<\/code><\/p>\n<p><code>  box-shadow: 0 24px 60px -20px var(--movie-accent-soft);<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0443\u0440\u043e\u043a: \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c = \u0435\u0434\u0438\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043f\u0440\u0430\u0432\u0434\u044b \u0434\u043b\u044f \u0442\u0435\u043c\u044b. \u041a\u043e\u0433\u0434\u0430 \u0446\u0432\u0435\u0442\u0430 \u0440\u0430\u0437\u0431\u0440\u043e\u0441\u0430\u043d\u044b \u043f\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c, \u0432\u0441\u0451 \u0440\u0430\u0441\u0441\u044b\u043f\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u00ab\u043f\u043e\u0447\u0442\u0438 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435\u00bb \u043e\u0442\u0442\u0435\u043d\u043a\u0438 \u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u0430\u043c\u043e\u0434\u0435\u043b\u044c\u043d\u043e. \u041a\u043e\u0433\u0434\u0430 \u0435\u0441\u0442\u044c 5\u20136 CSS-\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 (<code>--bg<\/code>,\u00a0<code>--glass<\/code>,\u00a0<code>--accent<\/code>,\u00a0<code>--accent-soft<\/code>,\u00a0<code>--text<\/code>,\u00a0<code>--dim<\/code>) \u2014 \u0432\u0441\u0451 \u0434\u044b\u0448\u0438\u0442 \u0432 \u0443\u043d\u0438\u0441\u043e\u043d, \u0438 \u0442\u0435\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443.<\/p>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0431\u043e\u043b\u044c \u0441 SSR: \u0441\u0442\u0435\u043a\u043b\u044f\u043d\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 \u043d\u0430 \u0442\u0451\u043c\u043d\u043e\u043c \u0444\u043e\u043d\u0435 \u0434\u0430\u044e\u0442 \u043c\u0435\u0440\u0437\u043a\u0438\u0439 \u00ab\u043c\u0438\u0433\u0430\u044e\u0449\u0438\u0439\u00bb FOUC, \u0435\u0441\u043b\u0438 \u0441\u0442\u0438\u043b\u0438 \u043f\u0440\u0438\u0435\u0434\u0443\u0442 \u043f\u043e\u0437\u0436\u0435 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438. \u041b\u0435\u0447\u0438\u0442\u0441\u044f \u0438\u043d\u043b\u0430\u0439\u043d\u043e\u043c \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043f\u0440\u044f\u043c\u043e \u0432\u00a0<code>&lt;head&gt;<\/code>\u00a0\u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u2014 \u0442\u043e\u0433\u0434\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u0430\u0434\u0440 \u0443\u0436\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439.<\/p>\n<p><strong>3. \u0410\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u00ab\u043a\u0438\u043d\u043e\u043b\u0435\u043d\u0442\u0430\u00bb \u0441\u0432\u0435\u0440\u0445\u0443 \u2014 \u043c\u0435\u043b\u043e\u0447\u044c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u00ab\u0434\u043e\u0440\u043e\u0433\u043e\u00bb<\/strong><\/p>\n<p>\u041f\u043e\u0434 \u0448\u0430\u043f\u043a\u043e\u0439 \u0443 \u043c\u0435\u043d\u044f \u0435\u0434\u0435\u0442 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043b\u0435\u043d\u0442\u0430 \u043f\u043e\u0441\u0442\u0435\u0440\u043e\u0432 \u2014 \u0442\u043e, \u0447\u0442\u043e \u043f\u043e\u0434\u0441\u043e\u0437\u043d\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0430\u0441\u0441\u043e\u0446\u0438\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u043e \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433-\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438. \u0421\u0430\u043c\u0430 \u043f\u043e \u0441\u0435\u0431\u0435 \u043e\u043d\u0430 \u043f\u0440\u043e\u0441\u0442\u0430\u044f (\u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043a\u0440\u043e\u043b\u043b + \u0441\u0442\u0440\u0435\u043b\u043a\u0438), \u043d\u043e \u0435\u0441\u0442\u044c \u043d\u044e\u0430\u043d\u0441 UX, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442 \u00ab\u043b\u0435\u043d\u0442\u0443\u00bb \u043e\u0442 \u00ab\u0434\u0451\u0448\u0435\u0432\u043e \u043f\u0440\u0438\u0431\u0438\u0442\u043e\u0433\u043e \u0440\u044f\u0434\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a\u00bb: \u0441\u0442\u0440\u0435\u043b\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0433\u0430\u0441\u043d\u0443\u0442\u044c \u0432 \u043a\u0440\u0430\u0439\u043d\u0438\u0445 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445.<\/p>\n<pre><code>const updateArrows = () =&gt; {  const el = stripRef.current;  if (!el) return;  setCanLeft(el.scrollLeft &gt; 4);  setCanRight(el.scrollLeft + el.clientWidth &lt; el.scrollWidth - 4);};\/\/ \u0432\u0435\u0448\u0430\u0435\u043c \u043d\u0430 el.addEventListener(\"scroll\", updateArrows, { passive: true })\/\/ + ResizeObserver, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u0440\u0435\u0441\u0430\u0439\u0437\u0435<\/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><code>  const el = stripRef.current;<\/code><\/p>\n<p><code>  if (!el) return;<\/code><\/p>\n<p><code>  setCanLeft(el.scrollLeft &gt; 4);<\/code><\/p>\n<p><code>  setCanRight(el.scrollLeft + el.clientWidth &lt; el.scrollWidth - 4);<\/code><\/p>\n<p><code>};<\/code><\/p>\n<p><code><em>\/\/ \u0432\u0435\u0448\u0430\u0435\u043c \u043d\u0430 el.addEventListener(\"scroll\", updateArrows, { passive: true })<\/em><\/code><\/p>\n<p><code><em>\/\/ + ResizeObserver, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u0440\u0435\u0441\u0430\u0439\u0437\u0435<\/em><\/code><\/p>\n<p>\u041f\u043b\u044e\u0441 \u043f\u0435\u0440\u0432\u044b\u0435 4 \u043f\u043e\u0441\u0442\u0435\u0440\u0430 \u0433\u0440\u0443\u0437\u0438\u043c\u00a0<code>loading=\"eager\"<\/code>\u00a0(\u043e\u043d\u0438 \u0432\u044b\u0448\u0435 fold \u0438 \u0432\u043b\u0438\u044f\u044e\u0442 \u043d\u0430 LCP), \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u2014\u00a0<code>lazy<\/code>. \u0417\u0432\u0443\u0447\u0438\u0442 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e, \u043d\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0438\u0437 \u0442\u0430\u043a\u0438\u0445 \u043c\u0435\u043b\u043e\u0447\u0435\u0439 \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435, \u0447\u0442\u043e \u00ab\u0432\u0441\u0451 \u043f\u043b\u0430\u0432\u043d\u043e \u0438 \u043f\u0440\u043e\u0434\u0443\u043c\u0430\u043d\u043e\u00bb.<\/p>\n<p><strong>4. Web Push \u0431\u0435\u0437 Firebase \u2014 \u043d\u0430 \u0447\u0438\u0441\u0442\u043e\u043c VAPID<\/strong><\/p>\n<p>\u0425\u0432\u0430\u0442\u0438\u0442 \u043f\u0440\u043e \u0444\u0440\u043e\u043d\u0442. \u041f\u0443\u0448-\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043d\u0430 \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u043c Web Push, \u0431\u0435\u0437 FCM \u0438 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u2014 \u043d\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0432\u0435\u043d\u0434\u043e\u0440-\u043b\u043e\u043a\u0430 \u0440\u0430\u0434\u0438 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c service worker \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f:<\/p>\n<pre><code>const reg = await navigator.serviceWorker.register(\"\/sw.js\");const sub = await reg.pushManager.subscribe({  userVisibleOnly: true,  applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY),});await fetch(\"\/api\/push\/subscribe\", {  method: \"POST\",  headers: { \"Content-Type\": \"application\/json\" },  body: JSON.stringify(sub),});<\/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><code>const sub = await reg.pushManager.subscribe({<\/code><\/p>\n<p><code>  userVisibleOnly: true,<\/code><\/p>\n<p><code>  applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY),<\/code><\/p>\n<p><code>});<\/code><\/p>\n<p><code>await fetch(\"\/api\/push\/subscribe\", {<\/code><\/p>\n<p><code>  method: \"POST\",<\/code><\/p>\n<p><code>  headers: { \"Content-Type\": \"application\/json\" },<\/code><\/p>\n<p><code>  body: JSON.stringify(sub),<\/code><\/p>\n<p><code>});<\/code><\/p>\n<p>\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0443 (<code>endpoint<\/code>\u00a0+ \u043a\u043b\u044e\u0447\u0438\u00a0<code>p256dh<\/code>\/<code>auth<\/code>) \u0445\u0440\u0430\u043d\u044e \u0432 PostgreSQL, \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e. \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043d\u0430 \u0431\u044d\u043a\u0435 (FastAPI) \u2014 \u0447\u0435\u0440\u0435\u0437\u00a0<code>pywebpush<\/code>:<\/p>\n<pre><code>from pywebpush import webpush, WebPushExceptiondef send_push(sub, title, body, url=\"\/\"):    try:        webpush(            subscription_info={                \"endpoint\": sub.endpoint,                \"keys\": {\"p256dh\": sub.p256dh, \"auth\": sub.auth},            },            data=json.dumps({\"title\": title, \"body\": body, \"url\": url}),            vapid_private_key=VAPID_PRIVATE,            vapid_claims={\"sub\": \"mailto:admin@example.com\"},            ttl=3600,        )    except WebPushException as e:        # 404\/410 = \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u043e\u0442\u0443\u0445\u043b\u0430 \u2192 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0438\u0437 \u0431\u0430\u0437\u044b, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043a\u043e\u043f\u0438\u043b\u0441\u044f \u043c\u0443\u0441\u043e\u0440        if e.response is not None and e.response.status_code in (404, 410):            delete_subscription(sub.id)<\/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><code>def send_push(sub, title, body, url=\"\/\"):<\/code><\/p>\n<p><code>    try:<\/code><\/p>\n<p><code>        webpush(<\/code><\/p>\n<p><code>            subscription_info={<\/code><\/p>\n<p><code>                \"endpoint\": sub.endpoint,<\/code><\/p>\n<p><code>                \"keys\": {\"p256dh\": sub.p256dh, \"auth\": sub.auth},<\/code><\/p>\n<p><code>            },<\/code><\/p>\n<p><code>            data=json.dumps({\"title\": title, \"body\": body, \"url\": url}),<\/code><\/p>\n<p><code>            vapid_private_key=VAPID_PRIVATE,<\/code><\/p>\n<p><code>            vapid_claims={\"sub\": \"<\/code><a href=\"mailto:admin@example.com\" rel=\"noopener noreferrer nofollow\"><code>mailto:admin@example.com<\/code><\/a><code>\"},<\/code><\/p>\n<p><code>            ttl=3600,<\/code><\/p>\n<p><code>        )<\/code><\/p>\n<p><code>    except WebPushException as e:<\/code><\/p>\n<p><code><em>        # 404\/410 = \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u043e\u0442\u0443\u0445\u043b\u0430 \u2192 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0438\u0437 \u0431\u0430\u0437\u044b, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043a\u043e\u043f\u0438\u043b\u0441\u044f \u043c\u0443\u0441\u043e\u0440<\/em><\/code><\/p>\n<p><code>        if e.response is not None and e.response.status_code in (404, 410):<\/code><\/p>\n<p><code>            delete_subscription(<\/code><a href=\"http:\/\/sub.id\" rel=\"noopener noreferrer nofollow\"><code>sub.id<\/code><\/a><code>)<\/code><\/p>\n<p>\u0413\u0440\u0430\u0431\u043b\u0438, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0442\u043e\u0438\u0442 \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u044c \u0432\u0430\u043c \u043d\u0435\u0440\u0432\u044b: \u043d\u0430 iOS \u043f\u0443\u0448\u0438 \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u0441\u0430\u0439\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043a\u0430\u043a PWA (Safari 16.4+) \u2014 \u044d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 Apple, \u043d\u0435 \u0432\u0430\u0448\u0435. \u0418 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0438\u0441\u0442\u0438\u0442\u0435 \u043c\u0451\u0440\u0442\u0432\u044b\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043f\u043e 404\/410, \u0438\u043d\u0430\u0447\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0440\u0430\u0441\u043f\u0443\u0445\u0430\u0435\u0442 \u00ab\u0444\u0430\u043d\u0442\u043e\u043c\u043d\u044b\u043c\u0438\u00bb \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438.<\/p>\n<p><strong>5. \u041e\u0434\u0438\u043d \u0447\u0435\u0441\u0442\u043d\u044b\u0439 \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u0438\u0437 \u0433\u043e\u043b\u043e\u0441\u043e\u0432 \u0438 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0439 (\u0438 \u0431\u0430\u0433, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u044d\u0442\u0438\u043c \u0437\u0430\u043a\u0440\u044b\u043b)<\/strong><\/p>\n<p>\u0411\u044d\u043a\u0435\u043d\u0434-\u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0425\u0430\u0431\u0440 \u043b\u044e\u0431\u0438\u0442: \u00ab\u0431\u044b\u043b\u043e \u043a\u0440\u0438\u0432\u043e \u2014 \u0441\u0442\u0430\u043b\u043e \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u00bb. \u0423 \u043c\u0435\u043d\u044f \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u043e\u0446\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u043c \u2014 \u0437\u0432\u0451\u0437\u0434\u043d\u044b\u0439 \u0433\u043e\u043b\u043e\u0441 (\u00ab\u043e\u0446\u0435\u043d\u0438\u0442\u044c\u00bb) \u0438 \u043e\u0446\u0435\u043d\u043a\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0438. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043d\u0438 \u0436\u0438\u043b\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u044c\u043d\u043e: \u043e\u0434\u0438\u043d \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u0447\u0438\u0442\u0430\u043b \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u0438\u0437 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0433\u043e\u043b\u043e\u0441\u043e\u0432, \u0434\u0440\u0443\u0433\u043e\u0439 \u2014 \u0438\u0437 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0439, \u0438 \u043e\u0431\u0430 \u043f\u0438\u0441\u0430\u043b\u0438 \u0432 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e \u0436\u0435 \u043f\u043e\u043b\u0435. \u0418\u0442\u043e\u0433 \u2014 \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u0441\u043a\u0430\u043a\u0430\u043b \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u043c. \u041a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u00ab\u043d\u0435\u0440\u0430\u0437\u0431\u0435\u0440\u0438\u0445\u0430\u00bb.<\/p>\n<p>\u041f\u043e\u0447\u0438\u043d\u0438\u043b \u043e\u0434\u043d\u0438\u043c SQL \u2014 \u0435\u0434\u0438\u043d\u044b\u0439 \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u043a\u0430\u043a \u0441\u0440\u0435\u0434\u043d\u0435\u0435 \u043f\u043e \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c, \u0433\u0434\u0435 \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u0435\u0433\u043e \u0437\u0432\u0451\u0437\u0434\u043d\u044b\u0439 \u0433\u043e\u043b\u043e\u0441, \u0430 \u0435\u0441\u043b\u0438 \u0433\u043e\u043b\u043e\u0441\u0430 \u043d\u0435\u0442 \u2014 \u043e\u0446\u0435\u043d\u043a\u0430 \u0438\u0437 \u043e\u0434\u043e\u0431\u0440\u0435\u043d\u043d\u043e\u0439 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0438:<\/p>\n<pre><code>WITH per_user AS (    SELECT user_id, rating FROM movie_votes WHERE movie_id = :mid    UNION ALL    SELECT user_id, rating FROM reviews      WHERE movie_id = :mid        AND status = 'approved'        AND user_id NOT IN (SELECT user_id FROM movie_votes WHERE movie_id = :mid))SELECT COALESCE(AVG(rating), 0), COUNT(*) FROM per_user;<\/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><code>    SELECT user_id, rating FROM movie_votes WHERE movie_id = :mid<\/code><\/p>\n<p><code>    UNION ALL<\/code><\/p>\n<p><code>    SELECT user_id, rating FROM reviews<\/code><\/p>\n<p><code>      WHERE movie_id = :mid<\/code><\/p>\n<p><code>        AND status = 'approved'<\/code><\/p>\n<p><code>        AND user_id NOT IN (SELECT user_id FROM movie_votes WHERE movie_id = :mid)<\/code><\/p>\n<p><code>)<\/code><\/p>\n<p><code>SELECT COALESCE(AVG(rating), 0), COUNT(*) FROM per_user;<\/code><\/p>\n<p><code>NOT IN (...\u0433\u043e\u043b\u043e\u0441\u0430...)<\/code>\u00a0\u2014 \u044d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u0434\u0435\u0434\u0443\u043f\u043b\u0438\u043a\u0430\u0446\u0438\u044f: \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u044e \u2014 \u0443\u0447\u0442\u0451\u043c \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u0433\u043e\u043b\u043e\u0441 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u043d\u0435\u0435. \u042d\u0442\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u044f \u0437\u043e\u0432\u0443 \u0438\u0437 \u0432\u0441\u0435\u0445 \u043c\u0435\u0441\u0442, \u0433\u0434\u0435 \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u043f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f (\u0433\u043e\u043b\u043e\u0441, \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\/\u043f\u0440\u0430\u0432\u043a\u0430\/\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0438). \u041e\u0434\u0438\u043d \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043f\u0440\u0430\u0432\u0434\u044b \u2014 \u0438 \u043f\u043e\u043b\u0435 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b\u043e \u00ab\u0434\u044b\u0448\u0430\u0442\u044c\u00bb.<\/p>\n<p><strong>\u0411\u043e\u043d\u0443\u0441: \u0432\u043e\u0437\u0440\u0430\u0441\u0442\u043d\u043e\u0439 \u0433\u0435\u0439\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u0443\u0431\u0438\u0432\u0430\u0435\u0442 SEO<\/strong><\/p>\n<p>\u0420\u0430\u0437 \u0443\u0436 \u043f\u0440\u043e \u043a\u0438\u043d\u043e \u2014 \u043d\u0443\u0436\u0435\u043d \u0431\u044b\u043b \u0431\u043b\u043e\u043a 18+ \u043d\u0430 \u00ab\u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445\u00bb \u0444\u0438\u043b\u044c\u043c\u0430\u0445. \u0422\u0443\u0442 \u043b\u0435\u0433\u043a\u043e \u0432\u044b\u0441\u0442\u0440\u0435\u043b\u0438\u0442\u044c \u0441\u0435\u0431\u0435 \u0432 \u043d\u043e\u0433\u0443: \u0435\u0441\u043b\u0438 \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0433\u0435\u0439\u0442 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0438 \u043f\u0440\u044f\u0442\u0430\u0442\u044c \u043f\u043e\u0434 \u043d\u0438\u043c \u043a\u043e\u043d\u0442\u0435\u043d\u0442, \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u0438\u043a \u0443\u0432\u0438\u0434\u0438\u0442 \u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0443 \u0432\u043c\u0435\u0441\u0442\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u0432\u044b\u043a\u0438\u043d\u0435\u0442 \u0435\u0451 \u0438\u0437 \u0438\u043d\u0434\u0435\u043a\u0441\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0433\u0435\u0439\u0442 \u0443 \u043c\u0435\u043d\u044f \u2014 \u0441\u0442\u0440\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043e\u0432\u0435\u0440\u043b\u0435\u0439 \u043f\u043e\u0432\u0435\u0440\u0445 \u0443\u0436\u0435 \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430:<\/p>\n<pre><code>\"use client\";export default function AgeGate({ ageRating, mpaa }: Props) {  const [confirmed, setConfirmed] = useState(false);  useEffect(() =&gt; {    setConfirmed(localStorage.getItem(\"vm_age18_ok\") === \"1\");  }, []);  if (!is18Plus(ageRating, mpaa) || confirmed) return null;  return &lt;div className=\"age-overlay\"&gt;\/* \u2026 *\/&lt;\/div&gt;;}<\/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><code>export default function AgeGate({ ageRating, mpaa }: Props) {<\/code><\/p>\n<p><code>  const [confirmed, setConfirmed] = useState(false);<\/code><\/p>\n<p><code>  useEffect(() =&gt; {<\/code><\/p>\n<p><code>    setConfirmed(localStorage.getItem(\"vm_age18_ok\") === \"1\");<\/code><\/p>\n<p><code>  }, []);<\/code><\/p>\n<p><code>  if (!is18Plus(ageRating, mpaa) || confirmed) return null;<\/code><\/p>\n<p><code>  return &lt;div className=\"age-overlay\"&gt;\/* \u2026 *\/&lt;\/div&gt;;<\/code><\/p>\n<p><code>}<\/code><\/p>\n<p>\u041f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0440\u043e\u0431\u043e\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u043e\u043b\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 HTML \u0441\u043e \u0432\u0441\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u043e\u043c \u0444\u0438\u043b\u044c\u043c\u0430; \u0433\u0435\u0439\u0442\u0430 \u0432 \u044d\u0442\u043e\u0439 HTML \u043d\u0435\u0442 (\u043e\u043d \u0434\u043e\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u043f\u043e\u00a0<code>localStorage<\/code>), \u0440\u0435\u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0432 \u0438\u00a0<code>noindex<\/code>\u00a0\u0442\u043e\u0436\u0435 \u043d\u0435\u0442. \u0414\u043b\u044f \u0436\u0438\u0432\u043e\u0433\u043e \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0442, \u0434\u043b\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u0430\u0446\u0438\u0438 \u2014 \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u0433\u0435\u0439\u0442\u0430 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442. \u0412\u043e\u0437\u0440\u0430\u0441\u0442\u043d\u044b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u043a \u0441\u043b\u043e\u0432\u0443, \u043f\u0440\u044f\u043c\u043e \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0438\u0437 \u00ab\u0448\u0442\u0440\u0430\u0444\u0430 \u0437\u0430 \u043d\u0430\u0432\u044f\u0437\u0447\u0438\u0432\u044b\u0435 \u0431\u0430\u043d\u043d\u0435\u0440\u044b\u00bb \u0443 Google.<\/p>\n<p><strong>\u0427\u0442\u043e \u0432 \u0438\u0442\u043e\u0433\u0435<\/strong><\/p>\n<p>\u041f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0432 \u043e\u0434\u0438\u043d\u043e\u0447\u043a\u0443 \u2014 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e \u0431\u044e\u0434\u0436\u0435\u0442, \u0430 \u043f\u0440\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0441\u0446\u0438\u043f\u043b\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043f\u0440\u0438\u0451\u043c\u043e\u0432: \u0440\u0435\u0430\u043a\u0446\u0438\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043d\u0430 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 (\u0446\u0432\u0435\u0442 \u0438\u0437 \u043f\u043e\u0441\u0442\u0435\u0440\u0430), \u0435\u0434\u0438\u043d\u0430\u044f \u0442\u0435\u043c\u0430 \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043a \u043c\u0438\u043a\u0440\u043e-UX (\u0433\u0430\u0441\u043d\u0443\u0449\u0438\u0435 \u0441\u0442\u0440\u0435\u043b\u043a\u0438, \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438), \u0438 \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u044b\u0435 \u00ab\u0441\u043a\u0443\u0447\u043d\u044b\u0435\u00bb \u0431\u044d\u043a\u0435\u043d\u0434-\u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u043f\u043e\u0434\u0432\u043e\u0434\u044f\u0442. \u041d\u0438\u0447\u0435\u0433\u043e \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0438 \u0433\u043e\u0442\u043e\u0432\u043d\u043e\u0441\u0442\u0438 \u043f\u0435\u0440\u0435\u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0430\u0437 \u043f\u044f\u0442\u044c.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u043f\u043e\u0442\u044b\u043a\u0430\u0442\u044c \u0436\u0438\u0432\u044c\u0451\u043c, \u043d\u0430 \u0447\u0451\u043c \u0432\u0441\u0451 \u044d\u0442\u043e \u043a\u0440\u0443\u0442\u0438\u0442\u0441\u044f \u0432 \u0431\u043e\u044e \u2014 \u043f\u0440\u043e\u0435\u043a\u0442 \u043e\u0442\u043a\u0440\u044b\u0442: <a href=\"http:\/\/vibemuvik.ru\" rel=\"noopener noreferrer nofollow\">vibemuvik.ru<\/a>. \u041d\u0435 \u0437\u043e\u0432\u0443 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u043f\u0440\u043e\u0441\u0442\u043e \u0435\u0441\u043b\u0438 \u043f\u043e \u0445\u043e\u0434\u0443 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441\u0442\u0430\u043b\u043e \u043b\u044e\u0431\u043e\u043f\u044b\u0442\u043d\u043e, \u043a\u0430\u043a \u00ab\u0446\u0432\u0435\u0442 \u0438\u0437 \u043f\u043e\u0441\u0442\u0435\u0440\u0430\u00bb \u0438 \u0441\u0442\u0435\u043a\u043b\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u2014 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0439\u0442\u0438 \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043b\u044e\u0431\u043e\u0433\u043e \u0444\u0438\u043b\u044c\u043c\u0430.<\/p>\n<p>\u0410 \u0432\u0430\u043c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e: \u043a\u0430\u043a\u0438\u043c\u0438 \u043f\u0440\u0438\u0451\u043c\u0430\u043c\u0438 \u0432\u044b \u00ab\u0443\u0434\u0435\u0448\u0435\u0432\u043b\u044f\u0435\u0442\u0435\u00bb \u0438\u043b\u0438, \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u00ab\u0443\u0434\u043e\u0440\u043e\u0436\u0430\u0435\u0442\u0435\u00bb \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043c\u0430\u043b\u043e\u0439 \u043a\u0440\u043e\u0432\u044c\u044e? \u0418 \u0433\u0434\u0435, \u043f\u043e-\u0432\u0430\u0448\u0435\u043c\u0443, \u0433\u0440\u0430\u043d\u0438\u0446\u0430 \u043c\u0435\u0436\u0434\u0443 \u00ab\u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u00bb \u0438 \u00ab\u043f\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0430\u043c\u0438\u00bb? \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u043b\u044e\u0431\u043e\u043f\u044b\u0442\u043d\u043e \u043e\u0442 \u0442\u0435\u0445, \u043a\u0442\u043e \u0442\u043e\u0436\u0435 \u0442\u0430\u0449\u0438\u0442 \u0444\u0440\u043e\u043d\u0442 \u0432 \u043e\u0434\u0438\u043d\u043e\u0447\u043a\u0443 \u2014 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u0435\u0441\u044c \u0441\u0432\u043e\u0438\u043c\u0438 \u043d\u0430\u0445\u043e\u0434\u043a\u0430\u043c\u0438 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9d0\/b7e\/f51\/9d0b7ef51fddc04b2df5de60966b5726.png\" width=\"2160\" height=\"1350\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/9d0\/b7e\/f51\/9d0b7ef51fddc04b2df5de60966b5726.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9d0\/b7e\/f51\/9d0b7ef51fddc04b2df5de60966b5726.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0438 \u043a\u0430\u043a\u043e\u0439 \u0441\u0442\u0430\u043b<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0fd\/eac\/7b6\/0fdeac7b60dbb98a25fb6a4dec0ac937.png\" width=\"780\" height=\"440\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/0fd\/eac\/7b6\/0fdeac7b60dbb98a25fb6a4dec0ac937.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0fd\/eac\/7b6\/0fdeac7b60dbb98a25fb6a4dec0ac937.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0431\u044b\u043b\u043e<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9ef\/46f\/1d6\/9ef46f1d6dfba2994c38e1338cfbc0a1.png\" width=\"2160\" height=\"1350\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/9ef\/46f\/1d6\/9ef46f1d6dfba2994c38e1338cfbc0a1.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9ef\/46f\/1d6\/9ef46f1d6dfba2994c38e1338cfbc0a1.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0438 \u0441\u0442\u0430\u043b\u043e<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/50a\/acd\/9c6\/50aacd9c64e2e27aeec6449930d0c628.png\" width=\"1879\" height=\"874\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/50a\/acd\/9c6\/50aacd9c64e2e27aeec6449930d0c628.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/50a\/acd\/9c6\/50aacd9c64e2e27aeec6449930d0c628.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\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\/1037818\/\">https:\/\/habr.com\/ru\/articles\/1037818\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u043b, \u043a\u0430\u043a\u043e\u0432\u043e \u0432 \u043e\u0434\u0438\u043d\u043e\u0447\u043a\u0443 \u0442\u0430\u0449\u0438\u0442\u044c \u0444\u0443\u043b\u043b\u0441\u0442\u0435\u043a-\u043f\u0440\u043e\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0437\u0440\u043e\u0441\u0441\u044f \u0434\u043e \u043a\u0438\u043d\u043e-\u0441\u043e\u0446\u0441\u0435\u0442\u0438. \u0412 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u0441\u043f\u0440\u043e\u0441\u0438\u043b\u0438 \u043f\u0440\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u0438\u043a\u0443 \u2014 \u00ab\u043f\u043e\u043a\u0430\u0436\u0438 \u043a\u043e\u0434\u00bb, \u00ab\u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u043b, \u0447\u0442\u043e \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043a\u0430\u043a \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u00bb. \u041b\u043e\u0433\u0438\u0447\u043d\u043e: \u0434\u0438\u0437\u0430\u0439\u043d \u2014 \u044d\u0442\u043e \u0442\u043e, \u043f\u043e \u0447\u0435\u043c\u0443 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0435\u0440\u0436\u0438\u0442\u0435 \u0432\u0442\u043e\u0440\u0443\u044e \u0447\u0430\u0441\u0442\u044c, \u0443\u0436\u0435 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0438 \u0441 \u043a\u043e\u0434\u043e\u043c. \u0411\u0435\u0437 \u043c\u0430\u0440\u043a\u0435\u0442\u0438\u043d\u0433\u0430, \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u00ab\u0434\u043e\u0440\u043e\u0433\u0438\u043c\u00bb, \u0438 \u043f\u0430\u0440\u0430 \u0431\u044d\u043a\u0435\u043d\u0434-\u0445\u0438\u0442\u0440\u043e\u0441\u0442\u0435\u0439 \u0432 \u0434\u043e\u0432\u0435\u0441\u043e\u043a.\u0421\u0440\u0430\u0437\u0443 \u0434\u0438\u0441\u043a\u043b\u0435\u0439\u043c\u0435\u0440: \u044f \u043d\u0435 \u0434\u0438\u0437\u0430\u0439\u043d\u0435\u0440. \u0412\u0441\u0451 \u043d\u0430\u0436\u0438\u0442\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u00ab\u0441\u043c\u043e\u0442\u0440\u044e \u043d\u0430 \u0440\u0435\u0444\u0435\u0440\u0435\u043d\u0441\u044b (Letterboxd, Mubi, KinoPoisk HD) \u0438 \u043f\u044b\u0442\u0430\u044e\u0441\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435\u00bb. \u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u2014 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e \u0434\u043e\u0440\u043e\u0433\u0438\u0435 \u0448\u0440\u0438\u0444\u0442\u044b, \u0430 \u043f\u0440\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0449\u0438\u0445\u0441\u044f \u043f\u0440\u0438\u0451\u043c\u043e\u0432. \u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u044f\u0442\u044c.1. \u0410\u043a\u0446\u0435\u043d\u0442\u043d\u044b\u0439 \u0446\u0432\u0435\u0442 \u0438\u0437 \u043f\u043e\u0441\u0442\u0435\u0440\u0430 \u0444\u0438\u043b\u044c\u043c\u0430 \u2014 \u0444\u0438\u0447\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u043e\u0440\u043e\u0436\u0435 \u0432\u0441\u0435\u0433\u043e \u00ab\u043f\u0440\u043e\u0434\u0430\u0451\u0442\u00bb\u0421\u0430\u043c\u043e\u0435 \u0437\u0430\u043c\u0435\u0442\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u0420\u0430\u043d\u044c\u0448\u0435 \u0443 \u043c\u0435\u043d\u044f \u043d\u0430 \u0432\u0441\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u0431\u044b\u043b \u043e\u0434\u0438\u043d \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0439 \u0444\u0438\u043e\u043b\u0435\u0442\u043e\u0432\u044b\u0439 \u0430\u043a\u0446\u0435\u043d\u0442 \u2014 \u0438 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u043e \u0434\u0451\u0448\u0435\u0432\u043e \u0438 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u043e. \u0418\u0434\u0435\u044f: \u043f\u0443\u0441\u0442\u044c \u043a\u0430\u0436\u0434\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0444\u0438\u043b\u044c\u043c\u0430 \u043f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u043e\u043c\u0438\u043d\u0430\u043d\u0442\u043d\u044b\u043c \u0446\u0432\u0435\u0442\u043e\u043c \u0435\u0433\u043e \u043f\u043e\u0441\u0442\u0435\u0440\u0430. \u0417\u0430\u0445\u043e\u0434\u0438\u0448\u044c \u043d\u0430 \u043c\u0440\u0430\u0447\u043d\u044b\u0439 \u043d\u0443\u0430\u0440 \u2014 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432 \u0445\u043e\u043b\u043e\u0434\u043d\u044b\u0439 \u0441\u0438\u043d\u0438\u0439, \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0448\u044c \u043a\u043e\u043c\u0435\u0434\u0438\u044e \u2014 \u0442\u0451\u043f\u043b\u044b\u0439 \u044f\u043d\u0442\u0430\u0440\u044c. \u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0431\u0443\u0434\u0442\u043e \u00ab\u0441\u0434\u0435\u043b\u0430\u043d\u0430 \u043f\u043e\u0434 \u044d\u0442\u043e\u0442 \u0444\u0438\u043b\u044c\u043c\u00bb.\u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437 \u0432\u0441\u044f\u043a\u0438\u0445 ML, \u043f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0447\u0435\u0440\u0435\u0437 canvas: \u0440\u0438\u0441\u0443\u0435\u043c \u043f\u043e\u0441\u0442\u0435\u0440 \u0432 \u043a\u0440\u043e\u0448\u0435\u0447\u043d\u044b\u0439 \u0431\u0443\u0444\u0435\u0440 32\u00d748, \u0443\u0441\u0440\u0435\u0434\u043d\u044f\u0435\u043c \u0446\u0432\u0435\u0442\u0430 (\u0432\u044b\u043a\u0438\u0434\u044b\u0432\u0430\u044f \u0447\u0451\u0440\u043d\u044b\u0435 \u0440\u0430\u043c\u043a\u0438 \u0438 \u0441\u0435\u0440\u043e\u0441\u0442\u044c), \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u0432 HSL \u0438 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u00ab\u043d\u0430\u0441\u044b\u0449\u0430\u0435\u043c\u00bb, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u043e\u0441\u0442\u0435\u0440\u044b \u0447\u0430\u0441\u0442\u043e \u0442\u0443\u0441\u043a\u043b\u044b\u0435. \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043a\u043b\u0430\u0434\u0451\u043c \u0432 CSS-\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u2014 \u0438 \u0432\u0435\u0441\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0435\u0451.function applyPosterAccent(posterUrl: string) {  const img = new Image();  img.crossOrigin = &#171;anonymous&#187;;  img.onload = () =&gt; {    const canvas = document.createElement(&#171;canvas&#187;);    const w = (canvas.width = 32), h = (canvas.height = 48);    const ctx = canvas.getContext(&#171;2d&#187;);    if (!ctx) return;    ctx.drawImage(img, 0, 0, w, h);    const { data } = ctx.getImageData(0, 0, w, h);    let r = 0, g = 0, b = 0, n = 0;    for (let i = 0; i &lt; data.length; i += 4) {      const R = data[i], G = data[i + 1], B = data[i + 2];      const max = Math.max(R, G, B), min = Math.min(R, G, B);      if (max &lt; 30 || min &gt; 230) continue; \/\/ \u0447\u0451\u0440\u043d\u044b\u0435 \u0440\u0430\u043c\u043a\u0438 \/ \u0432\u044b\u0431\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438      if (max &#8212; min &lt; 25) continue;          \/\/ \u0441\u0435\u0440\u043e\u0435 \u2014 \u0432 \u0430\u043a\u0446\u0435\u043d\u0442 \u043d\u0435 \u0433\u043e\u0434\u0438\u0442\u0441\u044f      r += R; g += G; b += B; n++;    }    if (!n) return;    const { h: hue, s, l } = rgbToHsl(r \/ n, g \/ n, b \/ n);    \/\/ \u043f\u043e\u0441\u0442\u0435\u0440\u044b \u0447\u0430\u0441\u0442\u043e \u0442\u0443\u0441\u043a\u043b\u044b\u0435 \u2014 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u043c \u0446\u0432\u0435\u0442 \u00ab\u0441\u043e\u0447\u043d\u044b\u043c\u00bb    const sat = Math.min(0.85, Math.max(0.45, s * 1.6 + 0.15));    const lit = Math.min(0.62, Math.max(0.52, l));    document.documentElement.style.setProperty(      &#171;&#8212;movie-accent&#187;, `hsl(${hue | 0} ${(sat * 100) | 0}% ${(lit * 100) | 0}%)`    );    document.documentElement.style.setProperty(      &#171;&#8212;movie-accent-soft&#187;, `hsl(${hue | 0} ${(sat * 100) | 0}% ${(lit * 100) | 0}% \/ .18)`    );  };  img.src = posterUrl;}  const img = new Image();  img.crossOrigin = &#171;anonymous&#187;;  img.onload = () =&gt; {    const canvas = document.createElement(&#171;canvas&#187;);    const w = (canvas.width = 32), h = (canvas.height = 48);    const ctx = canvas.getContext(&#171;2d&#187;);    if (!ctx) return;    ctx.drawImage(img, 0, 0, w, h);    const { data } = ctx.getImageData(0, 0, w, h);    let r = 0, g = 0, b = 0, n = 0;    for (let i = 0; i &lt; data.length; i += 4) {      const R = data[i], G = data[i + 1], B = data[i + 2];      const max = Math.max(R, G, B), min = Math.min(R, G, B);      if (max &lt; 30 || min &gt; 230) continue; \/\/ \u0447\u0451\u0440\u043d\u044b\u0435 \u0440\u0430\u043c\u043a\u0438 \/ \u0432\u044b\u0431\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0438\u043a\u0441\u0435\u043b\u0438      if (max &#8212; min &lt; 25) continue;          \/\/ \u0441\u0435\u0440\u043e\u0435 \u2014 \u0432 \u0430\u043a\u0446\u0435\u043d\u0442 \u043d\u0435 \u0433\u043e\u0434\u0438\u0442\u0441\u044f      r += R; g += G; b += B; n++;    }    if (!n) return;    const { h: hue, s, l } = rgbToHsl(r \/ n, g \/ n, b \/ n);    \/\/ \u043f\u043e\u0441\u0442\u0435\u0440\u044b \u0447\u0430\u0441\u0442\u043e \u0442\u0443\u0441\u043a\u043b\u044b\u0435 \u2014 \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u043c \u0446\u0432\u0435\u0442 \u00ab\u0441\u043e\u0447\u043d\u044b\u043c\u00bb    const sat = Math.min(0.85, Math.max(0.45, s * 1.6 + 0.15));    const lit = Math.min(0.62, Math.max(0.52, l));    document.documentElement.style.setProperty(      &#171;&#8212;movie-accent&#187;, hsl(${hue | 0} ${(sat  100) | 0}% ${(lit  100) | 0}%)    );    document.documentElement.style.setProperty(      &#171;&#8212;movie-accent-soft&#187;, hsl(${hue | 0} ${(sat  100) | 0}% ${(lit  100) | 0}% \/ .18)    );  };  img.src = posterUrl;}rgbToHsl\u00a0\u2014 \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u043a\u043e\u043d\u0432\u0435\u0440\u0441\u0438\u044f RGB\u2192HSL, \u043e\u043d\u0430 \u0432 \u043b\u044e\u0431\u043e\u043c \u0441\u043d\u0438\u043f\u043f\u0435\u0442\u0435 \u0432 \u0441\u0435\u0442\u0438. \u0412\u0430\u0436\u043d\u044b \u0434\u0432\u0430 \u043c\u043e\u043c\u0435\u043d\u0442\u0430: \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u00ab\u043c\u0443\u0441\u043e\u0440\u043d\u044b\u0445\u00bb \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439 (\u0431\u0435\u0437 \u043d\u0435\u0451 \u043d\u0430 \u0447\u0451\u0440\u043d\u044b\u0445 \u043f\u043e\u0441\u0442\u0435\u0440\u0430\u0445 \u0430\u043a\u0446\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0433\u0440\u044f\u0437\u043d\u043e-\u0441\u0435\u0440\u044b\u043c) \u0438 \u0431\u0443\u0441\u0442 \u043d\u0430\u0441\u044b\u0449\u0435\u043d\u043d\u043e\u0441\u0442\u0438 (s * 1.6 + 0.15) \u2014 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0444\u0438\u043b\u044c\u043c\u043e\u0432 \u0434\u0430\u0432\u0430\u043b\u0430 \u0431\u044b \u0431\u043b\u0451\u043a\u043b\u044b\u0439 \u0430\u043a\u0446\u0435\u043d\u0442.\u0421\u0442\u043e\u0438\u0442 \u044d\u0442\u043e \u043a\u043e\u043f\u0435\u0439\u043a\u0438 \u043f\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 (\u0434\u0430\u0443\u043d\u0441\u043a\u0435\u0439\u043b \u0434\u043e 32\u00d748 \u2014 \u044d\u0442\u043e \u043c\u0435\u043d\u044c\u0448\u0435 1500 \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439), \u0430 \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435 \u0434\u0430\u0451\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u00ab\u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0435\u00bb: \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u0435\u0442 \u043d\u0430 \u043a\u043e\u043d\u0442\u0435\u043d\u0442.2. Glassmorphism \u0447\u0435\u0440\u0435\u0437 \u043e\u0434\u043d\u0443 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e, \u0430 \u043d\u0435 100 \u0437\u0430\u0445\u0430\u0440\u0434\u043a\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0446\u0432\u0435\u0442\u043e\u0432\u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u0438\u0451\u043c \u2014 \u0441\u0442\u0435\u043a\u043b\u043e. \u041d\u043e \u043d\u0435 \u00ab\u043f\u0440\u0438\u043b\u0435\u043f\u0438\u043b blur \u0438 \u0437\u0430\u0431\u044b\u043b\u00bb, \u0430 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0432\u0441\u0451 \u0431\u044b\u043b\u043e \u0437\u0430\u0432\u044f\u0437\u0430\u043d\u043e \u043d\u0430 \u0442\u043e\u0442 \u0436\u0435\u00a0&#8212;movie-accent. \u0422\u043e\u0433\u0434\u0430 \u0441\u0442\u0435\u043a\u043b\u044f\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438, \u0441\u0432\u0435\u0447\u0435\u043d\u0438\u044f \u0438 \u043e\u0431\u0432\u043e\u0434\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u043a\u0440\u0430\u0448\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0434 \u0444\u0438\u043b\u044c\u043c, \u0438 \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0446\u0435\u043b\u044c\u043d\u043e..glass-card {  background: linear-gradient(180deg,      var(&#8212;movie-accent-soft, rgba(255,255,255,.06)),      rgba(255,255,255,.02));  backdrop-filter: blur(18px) saturate(1.2);  border: 1px solid rgba(255,255,255,.08);  border-radius: 18px;  box-shadow: 0 24px 60px -20px var(&#8212;movie-accent-soft);}  background: linear-gradient(180deg,      var(&#8212;movie-accent-soft, rgba(255,255,255,.06)),      rgba(255,255,255,.02));  backdrop-filter: blur(18px) saturate(1.2);  border: 1px solid rgba(255,255,255,.08);  border-radius: 18px;  box-shadow: 0 24px 60px -20px var(&#8212;movie-accent-soft);}\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0443\u0440\u043e\u043a: \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c = \u0435\u0434\u0438\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043f\u0440\u0430\u0432\u0434\u044b \u0434\u043b\u044f \u0442\u0435\u043c\u044b. \u041a\u043e\u0433\u0434\u0430 \u0446\u0432\u0435\u0442\u0430 \u0440\u0430\u0437\u0431\u0440\u043e\u0441\u0430\u043d\u044b \u043f\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c, \u0432\u0441\u0451 \u0440\u0430\u0441\u0441\u044b\u043f\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u00ab\u043f\u043e\u0447\u0442\u0438 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435\u00bb \u043e\u0442\u0442\u0435\u043d\u043a\u0438 \u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u0430\u043c\u043e\u0434\u0435\u043b\u044c\u043d\u043e. \u041a\u043e\u0433\u0434\u0430 \u0435\u0441\u0442\u044c 5\u20136 CSS-\u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 (&#8212;bg,\u00a0&#8212;glass,\u00a0&#8212;accent,\u00a0&#8212;accent-soft,\u00a0&#8212;text,\u00a0&#8212;dim) \u2014 \u0432\u0441\u0451 \u0434\u044b\u0448\u0438\u0442 \u0432 \u0443\u043d\u0438\u0441\u043e\u043d, \u0438 \u0442\u0435\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043a\u0440\u0443\u0442\u0438\u0442\u044c \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443.\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0431\u043e\u043b\u044c \u0441 SSR: \u0441\u0442\u0435\u043a\u043b\u044f\u043d\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 \u043d\u0430 \u0442\u0451\u043c\u043d\u043e\u043c \u0444\u043e\u043d\u0435 \u0434\u0430\u044e\u0442 \u043c\u0435\u0440\u0437\u043a\u0438\u0439 \u00ab\u043c\u0438\u0433\u0430\u044e\u0449\u0438\u0439\u00bb FOUC, \u0435\u0441\u043b\u0438 \u0441\u0442\u0438\u043b\u0438 \u043f\u0440\u0438\u0435\u0434\u0443\u0442 \u043f\u043e\u0437\u0436\u0435 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438. \u041b\u0435\u0447\u0438\u0442\u0441\u044f \u0438\u043d\u043b\u0430\u0439\u043d\u043e\u043c \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043f\u0440\u044f\u043c\u043e \u0432\u00a0&lt;head&gt;\u00a0\u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u2014 \u0442\u043e\u0433\u0434\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u0430\u0434\u0440 \u0443\u0436\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439.3. \u0410\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u00ab\u043a\u0438\u043d\u043e\u043b\u0435\u043d\u0442\u0430\u00bb \u0441\u0432\u0435\u0440\u0445\u0443 \u2014 \u043c\u0435\u043b\u043e\u0447\u044c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u00ab\u0434\u043e\u0440\u043e\u0433\u043e\u00bb\u041f\u043e\u0434 \u0448\u0430\u043f\u043a\u043e\u0439 \u0443 \u043c\u0435\u043d\u044f \u0435\u0434\u0435\u0442 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043b\u0435\u043d\u0442\u0430 \u043f\u043e\u0441\u0442\u0435\u0440\u043e\u0432 \u2014 \u0442\u043e, \u0447\u0442\u043e \u043f\u043e\u0434\u0441\u043e\u0437\u043d\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0430\u0441\u0441\u043e\u0446\u0438\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u043e \u0441\u0442\u0440\u0438\u043c\u0438\u043d\u0433-\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c\u0438. \u0421\u0430\u043c\u0430 \u043f\u043e \u0441\u0435\u0431\u0435 \u043e\u043d\u0430 \u043f\u0440\u043e\u0441\u0442\u0430\u044f (\u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043a\u0440\u043e\u043b\u043b + \u0441\u0442\u0440\u0435\u043b\u043a\u0438), \u043d\u043e \u0435\u0441\u0442\u044c \u043d\u044e\u0430\u043d\u0441 UX, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442 \u00ab\u043b\u0435\u043d\u0442\u0443\u00bb \u043e\u0442 \u00ab\u0434\u0451\u0448\u0435\u0432\u043e \u043f\u0440\u0438\u0431\u0438\u0442\u043e\u0433\u043e \u0440\u044f\u0434\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a\u00bb: \u0441\u0442\u0440\u0435\u043b\u043a\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0433\u0430\u0441\u043d\u0443\u0442\u044c \u0432 \u043a\u0440\u0430\u0439\u043d\u0438\u0445 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445.const updateArrows = () =&gt; {  const el = stripRef.current;  if (!el) return;  setCanLeft(el.scrollLeft &gt; 4);  setCanRight(el.scrollLeft + el.clientWidth &lt; el.scrollWidth &#8212; 4);};\/\/ \u0432\u0435\u0448\u0430\u0435\u043c \u043d\u0430 el.addEventListener(&#171;scroll&#187;, updateArrows, { passive: true })\/\/ + ResizeObserver, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u0440\u0435\u0441\u0430\u0439\u0437\u0435  const el = stripRef.current;  if (!el) return;  setCanLeft(el.scrollLeft &gt; 4);  setCanRight(el.scrollLeft + el.clientWidth &lt; el.scrollWidth &#8212; 4);};\/\/ \u0432\u0435\u0448\u0430\u0435\u043c \u043d\u0430 el.addEventListener(&#171;scroll&#187;, updateArrows, { passive: true })\/\/ + ResizeObserver, \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u0440\u0435\u0441\u0430\u0439\u0437\u0435\u041f\u043b\u044e\u0441 \u043f\u0435\u0440\u0432\u044b\u0435 4 \u043f\u043e\u0441\u0442\u0435\u0440\u0430 \u0433\u0440\u0443\u0437\u0438\u043c\u00a0loading=&#187;eager&#187;\u00a0(\u043e\u043d\u0438 \u0432\u044b\u0448\u0435 fold \u0438 \u0432\u043b\u0438\u044f\u044e\u0442 \u043d\u0430 LCP), \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u2014\u00a0lazy. \u0417\u0432\u0443\u0447\u0438\u0442 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e, \u043d\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0438\u0437 \u0442\u0430\u043a\u0438\u0445 \u043c\u0435\u043b\u043e\u0447\u0435\u0439 \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435, \u0447\u0442\u043e \u00ab\u0432\u0441\u0451 \u043f\u043b\u0430\u0432\u043d\u043e \u0438 \u043f\u0440\u043e\u0434\u0443\u043c\u0430\u043d\u043e\u00bb.4. Web Push \u0431\u0435\u0437 Firebase \u2014 \u043d\u0430 \u0447\u0438\u0441\u0442\u043e\u043c VAPID\u0425\u0432\u0430\u0442\u0438\u0442 \u043f\u0440\u043e \u0444\u0440\u043e\u043d\u0442. \u041f\u0443\u0448-\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u043d\u0430 \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u043c Web Push, \u0431\u0435\u0437 FCM \u0438 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u2014 \u043d\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0432\u0435\u043d\u0434\u043e\u0440-\u043b\u043e\u043a\u0430 \u0440\u0430\u0434\u0438 \u043f\u0435\u0442-\u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c service worker \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f:const reg = await navigator.serviceWorker.register(&#171;\/sw.js&#187;);const sub = await reg.pushManager.subscribe({  userVisibleOnly: true,  applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY),});await fetch(&#171;\/api\/push\/subscribe&#187;, {  method: &#171;POST&#187;,  headers: { &#171;Content-Type&#187;: &#171;application\/json&#187; },  body: JSON.stringify(sub),});const sub = await reg.pushManager.subscribe({  userVisibleOnly: true,  applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY),});await fetch(&#171;\/api\/push\/subscribe&#187;, {  method: &#171;POST&#187;,  headers: { &#171;Content-Type&#187;: &#171;application\/json&#187; },  body: JSON.stringify(sub),});\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0443 (endpoint\u00a0+ \u043a\u043b\u044e\u0447\u0438\u00a0p256dh\/auth) \u0445\u0440\u0430\u043d\u044e \u0432 PostgreSQL, \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e. \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043d\u0430 \u0431\u044d\u043a\u0435 (FastAPI) \u2014 \u0447\u0435\u0440\u0435\u0437\u00a0pywebpush:from pywebpush import webpush, WebPushExceptiondef send_push(sub, title, body, url=&#187;\/&#187;):    try:        webpush(            subscription_info={                &#171;endpoint&#187;: sub.endpoint,                &#171;keys&#187;: {&#171;p256dh&#187;: sub.p256dh, &#171;auth&#187;: sub.auth},            },            data=json.dumps({&#171;title&#187;: title, &#171;body&#187;: body, &#171;url&#187;: url}),            vapid_private_key=VAPID_PRIVATE,            vapid_claims={&#171;sub&#187;: &#171;mailto:admin@example.com&#187;},            ttl=3600,        )    except WebPushException as e:        # 404\/410 = \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u043e\u0442\u0443\u0445\u043b\u0430 \u2192 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0438\u0437 \u0431\u0430\u0437\u044b, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043a\u043e\u043f\u0438\u043b\u0441\u044f \u043c\u0443\u0441\u043e\u0440        if e.response is not None and e.response.status_code in (404, 410):            delete_subscription(sub.id)def send_push(sub, title, body, url=&#187;\/&#187;):    try:        webpush(            subscription_info={                &#171;endpoint&#187;: sub.endpoint,                &#171;keys&#187;: {&#171;p256dh&#187;: sub.p256dh, &#171;auth&#187;: sub.auth},            },            data=json.dumps({&#171;title&#187;: title, &#171;body&#187;: body, &#171;url&#187;: url}),            vapid_private_key=VAPID_PRIVATE,            vapid_claims={&#171;sub&#187;: &#171;mailto:admin@example.com&#187;},            ttl=3600,        )    except WebPushException as e:        # 404\/410 = \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u043e\u0442\u0443\u0445\u043b\u0430 \u2192 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0438\u0437 \u0431\u0430\u0437\u044b, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043a\u043e\u043f\u0438\u043b\u0441\u044f \u043c\u0443\u0441\u043e\u0440        if e.response is not None and e.response.status_code in (404, 410):            delete_subscription(sub.id)\u0413\u0440\u0430\u0431\u043b\u0438, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0442\u043e\u0438\u0442 \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u044c \u0432\u0430\u043c \u043d\u0435\u0440\u0432\u044b: \u043d\u0430 iOS \u043f\u0443\u0448\u0438 \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u0441\u0430\u0439\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u043a\u0430\u043a PWA (Safari 16.4+) \u2014 \u044d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 Apple, \u043d\u0435 \u0432\u0430\u0448\u0435. \u0418 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0438\u0441\u0442\u0438\u0442\u0435 \u043c\u0451\u0440\u0442\u0432\u044b\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043f\u043e 404\/410, \u0438\u043d\u0430\u0447\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0440\u0430\u0441\u043f\u0443\u0445\u0430\u0435\u0442 \u00ab\u0444\u0430\u043d\u0442\u043e\u043c\u043d\u044b\u043c\u0438\u00bb \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438.5. \u041e\u0434\u0438\u043d \u0447\u0435\u0441\u0442\u043d\u044b\u0439 \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u0438\u0437 \u0433\u043e\u043b\u043e\u0441\u043e\u0432 \u0438 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0439 (\u0438 \u0431\u0430\u0433, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u044d\u0442\u0438\u043c \u0437\u0430\u043a\u0440\u044b\u043b)\u0411\u044d\u043a\u0435\u043d\u0434-\u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0425\u0430\u0431\u0440 \u043b\u044e\u0431\u0438\u0442: \u00ab\u0431\u044b\u043b\u043e \u043a\u0440\u0438\u0432\u043e \u2014 \u0441\u0442\u0430\u043b\u043e \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u00bb. \u0423 \u043c\u0435\u043d\u044f \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u043e\u0446\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u043c \u2014 \u0437\u0432\u0451\u0437\u0434\u043d\u044b\u0439 \u0433\u043e\u043b\u043e\u0441 (\u00ab\u043e\u0446\u0435\u043d\u0438\u0442\u044c\u00bb) \u0438 \u043e\u0446\u0435\u043d\u043a\u0430 \u0432\u043d\u0443\u0442\u0440\u0438 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0438. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043e\u043d\u0438 \u0436\u0438\u043b\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u044c\u043d\u043e: \u043e\u0434\u0438\u043d \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u0447\u0438\u0442\u0430\u043b \u0440\u0435\u0439\u0442\u0438\u043d\u0433 \u0438\u0437 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0433\u043e\u043b\u043e\u0441\u043e\u0432, \u0434\u0440\u0443\u0433\u043e\u0439 \u2014 \u0438\u0437 \u0440\u0435\u0446\u0435\u043d\u0437\u0438\u0439, \u0438 \u043e\u0431\u0430 \u043f\u0438\u0441\u0430\u043b\u0438 \u0432 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e \u0436\u0435 \u043f\u043e\u043b\u0435. \u0418\u0442\u043e\u0433 \u2014&#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-480537","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/480537","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=480537"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/480537\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=480537"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=480537"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=480537"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}