2 Commits

Author SHA1 Message Date
Claude c4e5a532a1 fix(docker): собирать bcrypt из исходников
Готовый бинарник с github внутри сборки не скачивается — запрос к
release-assets обрывается, и node-pre-gyp уходит в компиляцию, а в
node:20-alpine нет python3/make/g++. Ставим их временно в обеих
стадиях и сносим вместе с кэшем node-gyp, образ остаётся ~260 МБ.
2026-08-30 11:46:09 +03:00
Claude 913b66a9cb feat(fonts): локальные шрифты вместо fonts.googleapis.com
Inter (лендинг), Unbounded и Onest (страница визитки) — подмножества
cyrillic+latin. Под ограничениями доступа внешние шрифты не приезжали:
у визиток, выданных клиентам, съезжала вёрстка.
2026-08-30 11:34:13 +03:00
26 changed files with 213 additions and 5 deletions
+11 -1
View File
@@ -15,6 +15,11 @@ RUN npm run build
# ===================================================== # =====================================================
FROM node:20-alpine AS backend-build FROM node:20-alpine AS backend-build
# bcrypt — нативный модуль. Готовый бинарник с github внутри сборки не
# скачивается (запрос к release-assets обрывается), поэтому собираем из
# исходников: для этого нужны python3/make/g++.
RUN apk add --no-cache python3 make g++
WORKDIR /app/backend WORKDIR /app/backend
COPY backend/package.json backend/package-lock.json* ./ COPY backend/package.json backend/package-lock.json* ./
RUN npm install RUN npm install
@@ -33,7 +38,12 @@ WORKDIR /app
# Backend production deps # Backend production deps
COPY backend/package.json backend/package-lock.json* ./backend/ COPY backend/package.json backend/package-lock.json* ./backend/
RUN cd backend && npm install --omit=dev # То же самое для боевых зависимостей: компилятор ставим временно и сносим
# сразу после установки, чтобы не тащить его в итоговый образ.
RUN apk add --no-cache --virtual .build-deps python3 make g++ \
&& cd backend && npm install --omit=dev \
&& apk del .build-deps \
&& rm -rf /root/.cache
# Built backend # Built backend
COPY --from=backend-build /app/backend/dist ./backend/dist COPY --from=backend-build /app/backend/dist ./backend/dist
+3 -3
View File
@@ -10,9 +10,9 @@
<meta property="og:description" content="Технологии, которые решают реальные задачи" /> <meta property="og:description" content="Технологии, которые решают реальные задачи" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://umbyte.ru" /> <meta property="og:url" content="https://umbyte.ru" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <!-- ШРИФТЫ ЛОКАЛЬНЫЕ: раньше тянулись с fonts.googleapis.com и при
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> ограничениях доступа не приезжали. Подмножества cyrillic+latin. -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" /> <link href="/fonts/fonts.css" rel="stylesheet" />
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+198
View File
@@ -0,0 +1,198 @@
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/Inter-400-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/Inter-400-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/Inter-500-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/Inter-500-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/Inter-600-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/Inter-600-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/fonts/Inter-700-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/fonts/Inter-700-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Onest';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/Onest-400-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Onest';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/Onest-400-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Onest';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/Onest-500-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Onest';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/Onest-500-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Onest';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/Onest-600-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Onest';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/Onest-600-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/Unbounded-400-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/fonts/Unbounded-400-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/Unbounded-500-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/fonts/Unbounded-500-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/Unbounded-600-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(/fonts/Unbounded-600-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/fonts/Unbounded-700-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin */
@font-face {
font-family: 'Unbounded';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/fonts/Unbounded-700-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
+1 -1
View File
@@ -176,7 +176,7 @@ export function CardPage() {
} }
const cardCss = ` const cardCss = `
@import url('https://fonts.googleapis.com/css2?family=Unbounded:wght@400;500;600;700&family=Onest:wght@400;500;600&display=swap'); @import url('/fonts/fonts.css');
.cardpage{--bg:#E9EDF7;--bg2:#DDE3F2;--card:#FFFFFF;--card2:#FBFCFF;--ink:#15132E;--ink2:#5C5984;--line:#E7E9F4;--indigo:#312E81;--indigo2:#4F4BC4;--cyan:#06B6D4;--cyan-glow:#22D3EE;--violet:#7C5CFF;--shadow:0 30px 70px -24px rgba(49,46,129,.32),0 10px 30px -18px rgba(6,182,212,.28); .cardpage{--bg:#E9EDF7;--bg2:#DDE3F2;--card:#FFFFFF;--card2:#FBFCFF;--ink:#15132E;--ink2:#5C5984;--line:#E7E9F4;--indigo:#312E81;--indigo2:#4F4BC4;--cyan:#06B6D4;--cyan-glow:#22D3EE;--violet:#7C5CFF;--shadow:0 30px 70px -24px rgba(49,46,129,.32),0 10px 30px -18px rgba(6,182,212,.28);
font-family:'Onest',sans-serif;color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:30px 18px;position:relative;overflow:hidden;perspective:1200px;background:linear-gradient(165deg,var(--bg),var(--bg2))} font-family:'Onest',sans-serif;color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:30px 18px;position:relative;overflow:hidden;perspective:1200px;background:linear-gradient(165deg,var(--bg),var(--bg2))}
@media (prefers-color-scheme:dark){.cardpage{--bg:#0C0B22;--bg2:#141233;--card:#17163a;--card2:#1c1a44;--ink:#F4F4FF;--ink2:#A6A3D9;--line:rgba(255,255,255,.12);--indigo:#4F4BC4;--indigo2:#6A66E0;--cyan:#22D3EE;--cyan-glow:#67E8F9;--shadow:0 30px 80px -24px rgba(0,0,0,.7),0 0 40px -10px rgba(34,211,238,.25)}} @media (prefers-color-scheme:dark){.cardpage{--bg:#0C0B22;--bg2:#141233;--card:#17163a;--card2:#1c1a44;--ink:#F4F4FF;--ink2:#A6A3D9;--line:rgba(255,255,255,.12);--indigo:#4F4BC4;--indigo2:#6A66E0;--cyan:#22D3EE;--cyan-glow:#67E8F9;--shadow:0 30px 80px -24px rgba(0,0,0,.7),0 0 40px -10px rgba(34,211,238,.25)}}