feat(room): замена фона видео на картинку — только на десктопе
Кнопка «Фон» в тулбаре комнаты и выбор фона в превью на входе: три готовые
сцены и свои картинки из профиля. Фон применяется процессором к самому
публикуемому треку (`LocalVideoTrack.setProcessor`), а НЕ пересозданием
`RoomOptions` — ссылка на них обязана оставаться стабильной, иначе
`LiveKitRoom` переподключается к комнате.
Фича только для ДЕСКТОПА, и «десктоп» определяется по возможностям устройства
(`pointer: fine` + `hover: hover` + `maxTouchPoints`), а НЕ по ширине окна:
узкое окно на десктопе — всё ещё десктоп, а широкий планшет — всё ещё планшет,
который сегментация греет. На мобильном кнопки нет вовсе, а не задизейбленной.
Ассеты сегментации отдаются СО СВОЕГО домена: библиотека по умолчанию тянет
wasm с jsdelivr, а модель с storage.googleapis.com, и в закрытом контуре фича
молча не работала бы. Модель (Apache 2.0, см. NOTICE.txt) лежит в репозитории,
wasm-рантайм (~19 МБ) копируется из node_modules плагином сборки. Сама
библиотека и модель грузятся ЛЕНИВО — только когда фон реально включают, вход
в конференцию не стал медленнее.
Три дефолтные сцены — собственные векторные рисунки (`design/backgrounds/`),
а не фотографии из интернета: у нарисованной сцены нет чужой лицензии, а
продукт расходится по инсталляциям, и проверять права на каждую копию некому.
Свои картинки — в профиле, до 10 штук, с уменьшением до 1280px и переводом в
WebP прямо в браузере перед отправкой. Удаление применённого сейчас фона
сбрасывает выбор на «без фона»: хранится ключ записи, а не URL картинки.
Смена камеры фон не теряет (`restartTrack` перезапускает процессор сам),
выключение и включение камеры — навешивает его на новый трек заново.
⚠️ Прокси dev-сервера для `/media/` — обязательно со слэшем: ключ `/media`
Vite матчит префиксом и перехватывает заодно `/mediapipe/...`, из-за чего
модель получала 404 и фон молча не включался.
This commit is contained in:
33
design/backgrounds/README.md
Normal file
33
design/backgrounds/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Дефолтные фоны замены фона видео
|
||||
|
||||
Три сцены, которые предлагаются участнику «из коробки» при включённом модуле
|
||||
«Замена фона» (`instance_settings.virtual_background`):
|
||||
|
||||
| файл | сцена |
|
||||
|---|---|
|
||||
| `office.svg` | офис: окно с городом, картина, лампа, стеллаж, растение |
|
||||
| `beach.svg` | пляж: море, песок, пальмы по краям кадра |
|
||||
| `space-station.svg` | космическая станция: панели, иллюминатор с Землёй |
|
||||
|
||||
## Откуда взялись картинки
|
||||
|
||||
**Это собственные векторные рисунки VidConf, а не фотографии из интернета.**
|
||||
Так решено сознательно (решение оператора от 09.08.2026): чужая фотография
|
||||
тянет за собой чужую лицензию, а продукт самоуправляемый и расходится по
|
||||
инсталляциям — проверять права на каждую копию некому. У нарисованной сцены
|
||||
правовых вопросов нет вовсе.
|
||||
|
||||
Сцены намеренно расфокусированы (`feGaussianBlur`) и оставляют спокойным центр
|
||||
кадра: там находится человек, а резкая графика за спиной сильнее всего выдаёт
|
||||
подмену фона.
|
||||
|
||||
## Как пересобрать
|
||||
|
||||
```bash
|
||||
python3 design/backgrounds/render.py
|
||||
```
|
||||
|
||||
Скрипт растеризует каждый `*.svg` в 1280×720 через headless Chrome и кладёт
|
||||
результат в `frontend/public/backgrounds/*.webp` (именно эти файлы и
|
||||
раздаются фронтендом). Нужны установленный Google Chrome и Pillow.
|
||||
Правьте SVG — WebP пересобирается из них, вручную растровые файлы не трогаем.
|
||||
106
design/backgrounds/beach.svg
Normal file
106
design/backgrounds/beach.svg
Normal file
@@ -0,0 +1,106 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720">
|
||||
<title>Пляж с пальмами — дефолтный фон замены фона видео</title>
|
||||
<desc>Собственная векторная сцена VidConf. Море, песок и пальмы по краям кадра;
|
||||
центр оставлен спокойным — там находится человек.</desc>
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#7fc4e8"/>
|
||||
<stop offset="0.55" stop-color="#bfe4f2"/>
|
||||
<stop offset="1" stop-color="#f2e7c9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#2a8fb5"/>
|
||||
<stop offset="0.5" stop-color="#3aa8c4"/>
|
||||
<stop offset="1" stop-color="#63c8cf"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sand" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#f0dfb8"/>
|
||||
<stop offset="1" stop-color="#d8bf90"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="trunk" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#8c6239"/>
|
||||
<stop offset="1" stop-color="#5f4126"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="sun" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0" stop-color="#fffbe8" stop-opacity="0.95"/>
|
||||
<stop offset="1" stop-color="#fff3c4" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="beachVignette" cx="0.5" cy="0.45" r="0.8">
|
||||
<stop offset="0.55" stop-color="#000000" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#1f4a5c" stop-opacity="0.26"/>
|
||||
</radialGradient>
|
||||
<filter id="seaSoft" x="-5%" y="-5%" width="110%" height="110%">
|
||||
<feGaussianBlur stdDeviation="4"/>
|
||||
</filter>
|
||||
<filter id="palmSoft" x="-8%" y="-8%" width="116%" height="116%">
|
||||
<feGaussianBlur stdDeviation="7"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect width="1280" height="720" fill="url(#sky)"/>
|
||||
<ellipse cx="900" cy="150" rx="300" ry="230" fill="url(#sun)"/>
|
||||
|
||||
<g filter="url(#seaSoft)">
|
||||
<!-- Облака -->
|
||||
<ellipse cx="270" cy="128" rx="130" ry="34" fill="#ffffff" opacity="0.72"/>
|
||||
<ellipse cx="350" cy="112" rx="88" ry="28" fill="#ffffff" opacity="0.6"/>
|
||||
<ellipse cx="1010" cy="96" rx="112" ry="28" fill="#ffffff" opacity="0.5"/>
|
||||
|
||||
<!-- Море -->
|
||||
<rect x="0" y="352" width="1280" height="176" fill="url(#sea)"/>
|
||||
<!-- Блики на воде -->
|
||||
<g fill="#ffffff" opacity="0.4">
|
||||
<rect x="120" y="392" width="150" height="6" rx="3"/>
|
||||
<rect x="360" y="420" width="210" height="6" rx="3"/>
|
||||
<rect x="700" y="400" width="170" height="6" rx="3"/>
|
||||
<rect x="980" y="440" width="190" height="6" rx="3"/>
|
||||
<rect x="240" y="466" width="130" height="5" rx="2.5"/>
|
||||
<rect x="820" y="474" width="150" height="5" rx="2.5"/>
|
||||
</g>
|
||||
<!-- Пена у берега -->
|
||||
<path d="M0 512c180 26 360-12 540 8s360 34 740-8v36H0z" fill="#ffffff" opacity="0.75"/>
|
||||
</g>
|
||||
|
||||
<!-- Песок -->
|
||||
<rect x="0" y="530" width="1280" height="190" fill="url(#sand)"/>
|
||||
<g fill="#c9ac7c" opacity="0.45">
|
||||
<ellipse cx="300" cy="640" rx="180" ry="18"/>
|
||||
<ellipse cx="960" cy="682" rx="220" ry="20"/>
|
||||
</g>
|
||||
|
||||
<!-- Пальмы по краям кадра -->
|
||||
<g filter="url(#palmSoft)">
|
||||
<g>
|
||||
<path d="M168 704c-6-190 6-320 40-420l26 8c-30 100-40 226-34 412z" fill="url(#trunk)"/>
|
||||
<g fill="#2f7a4f">
|
||||
<path d="M234 292c-84-30-150-16-196 40 66-16 128-14 186 12z"/>
|
||||
<path d="M234 292c-58-72-124-96-196-76 62 22 116 58 164 102z"/>
|
||||
<path d="M240 288c50-72 116-98 190-82-62 26-114 62-158 108z"/>
|
||||
<path d="M242 296c86-24 154-4 198 56-66-22-128-24-186 4z"/>
|
||||
<path d="M238 282c14-78-8-140-70-176 26 66 38 128 40 184z"/>
|
||||
</g>
|
||||
<g fill="#245f3e">
|
||||
<path d="M236 300c-46 16-88 46-124 90 48-32 94-52 138-60z"/>
|
||||
<path d="M240 300c50 14 94 46 130 92-48-34-96-56-142-64z"/>
|
||||
</g>
|
||||
<circle cx="228" cy="306" r="12" fill="#7a5a30"/>
|
||||
<circle cx="252" cy="312" r="11" fill="#7a5a30"/>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<path d="M1136 720c10-166 2-278-26-360l-24 8c26 84 34 190 26 352z" fill="url(#trunk)"/>
|
||||
<g fill="#2f7a4f">
|
||||
<path d="M1084 352c72-30 134-20 178 30-60-16-118-14-172 8z"/>
|
||||
<path d="M1084 352c50-66 110-90 176-72-56 20-106 54-150 96z"/>
|
||||
<path d="M1080 348c-46-64-104-88-170-74 56 24 102 58 142 100z"/>
|
||||
<path d="M1078 356c-78-22-140-4-180 50 60-20 116-22 168 4z"/>
|
||||
</g>
|
||||
<g fill="#245f3e">
|
||||
<path d="M1082 360c42 14 80 42 112 82-42-30-84-48-124-56z"/>
|
||||
</g>
|
||||
<circle cx="1090" cy="364" r="11" fill="#7a5a30"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<rect width="1280" height="720" fill="url(#beachVignette)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
130
design/backgrounds/office.svg
Normal file
130
design/backgrounds/office.svg
Normal file
@@ -0,0 +1,130 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720">
|
||||
<title>Офис — дефолтный фон замены фона видео</title>
|
||||
<desc>Собственная векторная сцена VidConf. Светлый переговорный угол: окно с
|
||||
тёплым светом, стеллаж с книгами и растениями. Центр кадра намеренно спокойный —
|
||||
там будет человек, детали вынесены к краям.</desc>
|
||||
<defs>
|
||||
<linearGradient id="wall" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#f3ede4"/>
|
||||
<stop offset="0.62" stop-color="#e7ded1"/>
|
||||
<stop offset="1" stop-color="#d9cebe"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="floor" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#c9b49a"/>
|
||||
<stop offset="1" stop-color="#a98e70"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="daylight" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#fdfaf2"/>
|
||||
<stop offset="1" stop-color="#e8eef1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wood" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#b58a5f"/>
|
||||
<stop offset="1" stop-color="#8d6540"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0" stop-color="#fff6de" stop-opacity="0.85"/>
|
||||
<stop offset="1" stop-color="#fff6de" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="vignette" cx="0.5" cy="0.45" r="0.78">
|
||||
<stop offset="0.55" stop-color="#000000" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#3b2d1e" stop-opacity="0.3"/>
|
||||
</radialGradient>
|
||||
<!-- Мягкое расфокусирование: фон за спиной человека в кадре камеры никогда
|
||||
не бывает резким, и резкая графика выдаёт подмену сильнее всего. -->
|
||||
<filter id="soft" x="-6%" y="-6%" width="112%" height="112%">
|
||||
<feGaussianBlur stdDeviation="5"/>
|
||||
</filter>
|
||||
<filter id="softer" x="-8%" y="-8%" width="116%" height="116%">
|
||||
<feGaussianBlur stdDeviation="10"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect width="1280" height="720" fill="url(#wall)"/>
|
||||
|
||||
<g filter="url(#soft)">
|
||||
<!-- Окно слева -->
|
||||
<rect x="52" y="86" width="330" height="392" rx="10" fill="#cbb9a3"/>
|
||||
<rect x="66" y="100" width="302" height="364" rx="6" fill="url(#daylight)"/>
|
||||
<rect x="212" y="100" width="10" height="364" fill="#cbb9a3"/>
|
||||
<rect x="66" y="276" width="302" height="10" fill="#cbb9a3"/>
|
||||
<!-- Городская дымка за стеклом -->
|
||||
<rect x="66" y="352" width="302" height="112" fill="#dfe6e6" opacity="0.85"/>
|
||||
<rect x="96" y="300" width="46" height="164" fill="#d3dcde" opacity="0.7"/>
|
||||
<rect x="158" y="330" width="34" height="134" fill="#cfd8db" opacity="0.6"/>
|
||||
<rect x="248" y="316" width="52" height="148" fill="#d3dcde" opacity="0.65"/>
|
||||
<rect x="314" y="344" width="30" height="120" fill="#cfd8db" opacity="0.55"/>
|
||||
|
||||
<!-- Стеллаж справа -->
|
||||
<rect x="876" y="120" width="352" height="392" rx="8" fill="url(#wood)"/>
|
||||
<rect x="892" y="136" width="320" height="112" fill="#e9dcc9" opacity="0.55"/>
|
||||
<rect x="892" y="264" width="320" height="112" fill="#e9dcc9" opacity="0.5"/>
|
||||
<rect x="892" y="392" width="320" height="104" fill="#e9dcc9" opacity="0.45"/>
|
||||
<!-- Книги -->
|
||||
<g>
|
||||
<rect x="906" y="156" width="18" height="92" fill="#8a5a4a"/>
|
||||
<rect x="928" y="168" width="14" height="80" fill="#6c7f6a"/>
|
||||
<rect x="946" y="150" width="20" height="98" fill="#c08a4a"/>
|
||||
<rect x="970" y="172" width="16" height="76" fill="#4f6478"/>
|
||||
<rect x="990" y="160" width="12" height="88" fill="#9d5f5f"/>
|
||||
<rect x="1010" y="176" width="22" height="72" fill="#7a6a55"/>
|
||||
|
||||
<rect x="906" y="292" width="16" height="84" fill="#5f7382"/>
|
||||
<rect x="926" y="284" width="20" height="92" fill="#a86f4e"/>
|
||||
<rect x="950" y="300" width="14" height="76" fill="#7f8f76"/>
|
||||
<rect x="968" y="288" width="18" height="88" fill="#8d5a63"/>
|
||||
</g>
|
||||
<!-- Растение на полке -->
|
||||
<g>
|
||||
<path d="M1140 392c-26-14-40-42-34-72 26 6 44 30 44 60z" fill="#4f7a52"/>
|
||||
<path d="M1150 392c26-16 38-46 30-76-26 8-42 34-40 64z" fill="#5f8f5f"/>
|
||||
<rect x="1128" y="386" width="40" height="30" rx="5" fill="#b98a63"/>
|
||||
</g>
|
||||
|
||||
<!-- Картина на стене между окном и стеллажом -->
|
||||
<g>
|
||||
<rect x="486" y="150" width="196" height="146" rx="6" fill="#c2a882"/>
|
||||
<rect x="498" y="162" width="172" height="122" fill="#eae3d3"/>
|
||||
<path d="M498 284l52-58 40 34 44-52 36 76z" fill="#9fb5a2"/>
|
||||
<circle cx="622" cy="196" r="16" fill="#e8c579"/>
|
||||
</g>
|
||||
|
||||
<!-- Настольная лампа и край стола справа от картины -->
|
||||
<g>
|
||||
<rect x="700" y="452" width="220" height="14" rx="4" fill="#c8a878"/>
|
||||
<rect x="712" y="466" width="12" height="46" fill="#b2915f"/>
|
||||
<rect x="896" y="466" width="12" height="46" fill="#b2915f"/>
|
||||
<rect x="792" y="392" width="8" height="60" fill="#6f7b82"/>
|
||||
<path d="M760 392l36-52 36 52z" fill="#7f8d95"/>
|
||||
<ellipse cx="796" cy="424" rx="54" ry="18" fill="#ffe9b8" opacity="0.5"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Пол -->
|
||||
<rect x="0" y="512" width="1280" height="208" fill="url(#floor)"/>
|
||||
<rect x="0" y="506" width="1280" height="14" fill="#e9e0d3" opacity="0.7"/>
|
||||
|
||||
<!-- Крупное растение в левом углу — передний план, размыто сильнее -->
|
||||
<g filter="url(#softer)" opacity="0.95">
|
||||
<g fill="#3f6b45">
|
||||
<ellipse cx="88" cy="508" rx="52" ry="34" transform="rotate(-24 88 508)"/>
|
||||
<ellipse cx="206" cy="556" rx="56" ry="36" transform="rotate(18 206 556)"/>
|
||||
<ellipse cx="70" cy="596" rx="48" ry="32" transform="rotate(-8 70 596)"/>
|
||||
</g>
|
||||
<g fill="#4c7f52">
|
||||
<ellipse cx="158" cy="486" rx="50" ry="32" transform="rotate(6 158 486)"/>
|
||||
<ellipse cx="248" cy="626" rx="46" ry="30" transform="rotate(32 248 626)"/>
|
||||
<ellipse cx="124" cy="574" rx="54" ry="34" transform="rotate(-14 124 574)"/>
|
||||
</g>
|
||||
<g stroke="#35603c" stroke-width="7" fill="none" stroke-linecap="round">
|
||||
<path d="M120 668c-16-56-24-108-32-160"/>
|
||||
<path d="M132 668c14-52 34-96 62-136"/>
|
||||
<path d="M126 668c2-44 20-76 46-96"/>
|
||||
</g>
|
||||
<rect x="52" y="656" width="150" height="86" rx="14" fill="#9d7550"/>
|
||||
<rect x="52" y="656" width="150" height="16" rx="8" fill="#b08a63"/>
|
||||
</g>
|
||||
|
||||
<!-- Свет из окна и общая виньетка -->
|
||||
<ellipse cx="300" cy="250" rx="460" ry="330" fill="url(#glow)"/>
|
||||
<rect width="1280" height="720" fill="url(#vignette)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
73
design/backgrounds/render.py
Normal file
73
design/backgrounds/render.py
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Запекает векторные сцены дефолтных фонов (`*.svg`) в WebP для фронтенда.
|
||||
|
||||
Исходники сцен — собственные рисунки VidConf (см. README рядом), поэтому
|
||||
лежат в репозитории вместе с результатом: чужих фотографий с их лицензиями в
|
||||
продукте нет.
|
||||
|
||||
Запуск (нужен установленный Google Chrome и Pillow):
|
||||
|
||||
python3 design/backgrounds/render.py
|
||||
|
||||
Результат — `frontend/public/backgrounds/*.webp`, 1280×720. Chrome нужен
|
||||
только для растеризации SVG (rsvg/ImageMagick на машине может не быть),
|
||||
Pillow — для перевода PNG в WebP: браузерный скриншот WebP не отдаёт.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
WIDTH, HEIGHT = 1280, 720
|
||||
# 82 — визуально неотличимо от исходника на плавных градиентах сцены, а вес
|
||||
# втрое меньше, чем при 95. Фон грузится по сети при каждом первом показе.
|
||||
WEBP_QUALITY = 82
|
||||
|
||||
SOURCE_DIR = Path(__file__).parent
|
||||
TARGET_DIR = SOURCE_DIR.parents[1] / "frontend" / "public" / "backgrounds"
|
||||
|
||||
|
||||
def render(svg_path: Path, out_path: Path, workdir: Path) -> None:
|
||||
"""Растеризовать одну сцену и сохранить её в WebP."""
|
||||
png_path = workdir / f"{svg_path.stem}.png"
|
||||
subprocess.run(
|
||||
[
|
||||
CHROME,
|
||||
"--headless",
|
||||
"--disable-gpu",
|
||||
"--hide-scrollbars",
|
||||
"--default-background-color=00000000",
|
||||
f"--screenshot={png_path}",
|
||||
f"--window-size={WIDTH},{HEIGHT}",
|
||||
svg_path.resolve().as_uri(),
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
with Image.open(png_path) as image:
|
||||
image.convert("RGB").save(out_path, "WEBP", quality=WEBP_QUALITY, method=6)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not Path(CHROME).exists():
|
||||
print(f"не найден Chrome: {CHROME}", file=sys.stderr)
|
||||
return 1
|
||||
TARGET_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
workdir = Path(tmp)
|
||||
for svg_path in sorted(SOURCE_DIR.glob("*.svg")):
|
||||
out_path = TARGET_DIR / f"{svg_path.stem}.webp"
|
||||
render(svg_path, out_path, workdir)
|
||||
print(
|
||||
f"{svg_path.name} → {out_path.relative_to(SOURCE_DIR.parents[1])}"
|
||||
f" ({out_path.stat().st_size // 1024} КБ)"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
150
design/backgrounds/space-station.svg
Normal file
150
design/backgrounds/space-station.svg
Normal file
@@ -0,0 +1,150 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720">
|
||||
<title>Космическая станция — дефолтный фон замены фона видео</title>
|
||||
<desc>Собственная векторная сцена VidConf. Интерьер модуля станции: панели,
|
||||
подсветка, иллюминатор с Землёй. Центр кадра спокойный — там человек.</desc>
|
||||
<defs>
|
||||
<linearGradient id="hull" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#2b3446"/>
|
||||
<stop offset="0.5" stop-color="#222a3a"/>
|
||||
<stop offset="1" stop-color="#161c28"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="panel" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#39445a"/>
|
||||
<stop offset="1" stop-color="#28304180"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="strip" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#4fd7ff" stop-opacity="0.1"/>
|
||||
<stop offset="0.5" stop-color="#7fe8ff" stop-opacity="0.9"/>
|
||||
<stop offset="1" stop-color="#4fd7ff" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="earth" cx="0.36" cy="0.32" r="0.78">
|
||||
<stop offset="0" stop-color="#7fd1f5"/>
|
||||
<stop offset="0.55" stop-color="#2f7fc4"/>
|
||||
<stop offset="1" stop-color="#0c2c56"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="atmo" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0.78" stop-color="#8fd8ff" stop-opacity="0"/>
|
||||
<stop offset="0.9" stop-color="#8fd8ff" stop-opacity="0.5"/>
|
||||
<stop offset="1" stop-color="#8fd8ff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="portGlow" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0" stop-color="#8fd8ff" stop-opacity="0.4"/>
|
||||
<stop offset="1" stop-color="#8fd8ff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="spaceVignette" cx="0.5" cy="0.45" r="0.78">
|
||||
<stop offset="0.5" stop-color="#000000" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#05080f" stop-opacity="0.65"/>
|
||||
</radialGradient>
|
||||
<filter id="stationSoft" x="-6%" y="-6%" width="112%" height="112%">
|
||||
<feGaussianBlur stdDeviation="4"/>
|
||||
</filter>
|
||||
<filter id="stationSofter" x="-8%" y="-8%" width="116%" height="116%">
|
||||
<feGaussianBlur stdDeviation="9"/>
|
||||
</filter>
|
||||
<!-- Всё, что «за стеклом», обрезается по стеклу: без этого Земля вылезала
|
||||
за переплёт иллюминатора и сцена читалась как наклейка на стене. -->
|
||||
<clipPath id="portholeGlass">
|
||||
<circle cx="640" cy="318" r="192"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<rect width="1280" height="720" fill="url(#hull)"/>
|
||||
|
||||
<g filter="url(#stationSoft)">
|
||||
<!-- Рёбра модуля -->
|
||||
<g fill="#39445a" opacity="0.55">
|
||||
<rect x="0" y="0" width="1280" height="52"/>
|
||||
<rect x="0" y="668" width="1280" height="52"/>
|
||||
<rect x="150" y="52" width="26" height="616"/>
|
||||
<rect x="1104" y="52" width="26" height="616"/>
|
||||
</g>
|
||||
|
||||
<!-- Приборные панели слева -->
|
||||
<g>
|
||||
<rect x="46" y="150" width="220" height="300" rx="14" fill="url(#panel)"/>
|
||||
<rect x="66" y="172" width="180" height="86" rx="6" fill="#101826"/>
|
||||
<g fill="#5ee0a8" opacity="0.85">
|
||||
<rect x="78" y="230" width="120" height="4" rx="2"/>
|
||||
<rect x="78" y="216" width="82" height="4" rx="2"/>
|
||||
<rect x="78" y="202" width="146" height="4" rx="2"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle cx="92" cy="292" r="11" fill="#ff8a5c"/>
|
||||
<circle cx="126" cy="292" r="11" fill="#ffd166"/>
|
||||
<circle cx="160" cy="292" r="11" fill="#5ee0a8"/>
|
||||
</g>
|
||||
<g fill="#4a5670">
|
||||
<rect x="66" y="322" width="180" height="16" rx="8"/>
|
||||
<rect x="66" y="352" width="180" height="16" rx="8"/>
|
||||
<rect x="66" y="382" width="180" height="16" rx="8"/>
|
||||
</g>
|
||||
<rect x="66" y="322" width="104" height="16" rx="8" fill="#7fe8ff" opacity="0.8"/>
|
||||
<rect x="66" y="352" width="62" height="16" rx="8" fill="#7fe8ff" opacity="0.6"/>
|
||||
</g>
|
||||
|
||||
<!-- Стеллаж оборудования справа -->
|
||||
<g>
|
||||
<rect x="1014" y="128" width="230" height="344" rx="14" fill="url(#panel)"/>
|
||||
<g fill="#101826">
|
||||
<rect x="1034" y="150" width="190" height="70" rx="6"/>
|
||||
<rect x="1034" y="238" width="190" height="70" rx="6"/>
|
||||
<rect x="1034" y="326" width="190" height="70" rx="6"/>
|
||||
</g>
|
||||
<g fill="#7fe8ff" opacity="0.6">
|
||||
<rect x="1050" y="176" width="86" height="5" rx="2.5"/>
|
||||
<rect x="1050" y="264" width="126" height="5" rx="2.5"/>
|
||||
<rect x="1050" y="352" width="64" height="5" rx="2.5"/>
|
||||
</g>
|
||||
<circle cx="1206" cy="424" r="14" fill="#ff8a5c" opacity="0.8"/>
|
||||
</g>
|
||||
|
||||
<!-- Иллюминатор -->
|
||||
<g>
|
||||
<circle cx="640" cy="318" r="212" fill="#39445a"/>
|
||||
<circle cx="640" cy="318" r="192" fill="#0b1120"/>
|
||||
<g clip-path="url(#portholeGlass)">
|
||||
<!-- Звёзды -->
|
||||
<g fill="#ffffff">
|
||||
<circle cx="530" cy="200" r="2.5" opacity="0.9"/>
|
||||
<circle cx="596" cy="168" r="1.8" opacity="0.7"/>
|
||||
<circle cx="712" cy="188" r="2.2" opacity="0.8"/>
|
||||
<circle cx="768" cy="252" r="1.6" opacity="0.6"/>
|
||||
<circle cx="500" cy="300" r="1.8" opacity="0.65"/>
|
||||
<circle cx="742" cy="356" r="2" opacity="0.7"/>
|
||||
<circle cx="556" cy="424" r="1.7" opacity="0.6"/>
|
||||
<circle cx="676" cy="456" r="2.3" opacity="0.75"/>
|
||||
</g>
|
||||
<!-- Земля -->
|
||||
<circle cx="596" cy="386" r="150" fill="url(#earth)"/>
|
||||
<g fill="#3f8f5f" opacity="0.75">
|
||||
<path d="M520 330c40-16 74-10 96 14-34 8-64 12-96 6z"/>
|
||||
<path d="M596 430c46-8 84 6 108 40-42 4-80-8-108-30z"/>
|
||||
<path d="M498 404c26 6 46 22 58 46-28-4-50-18-66-38z"/>
|
||||
</g>
|
||||
<g fill="#ffffff" opacity="0.35">
|
||||
<ellipse cx="560" cy="352" rx="70" ry="16"/>
|
||||
<ellipse cx="656" cy="416" rx="86" ry="18"/>
|
||||
</g>
|
||||
<circle cx="596" cy="386" r="150" fill="url(#atmo)"/>
|
||||
</g>
|
||||
<!-- Переплёт иллюминатора -->
|
||||
<circle cx="640" cy="318" r="192" fill="none" stroke="#4a5670" stroke-width="14"/>
|
||||
<circle cx="640" cy="318" r="206" fill="none" stroke="#2b3446" stroke-width="16"/>
|
||||
<g fill="#4a5670">
|
||||
<rect x="622" y="98" width="36" height="34" rx="8"/>
|
||||
<rect x="622" y="504" width="36" height="34" rx="8"/>
|
||||
<rect x="420" y="300" width="34" height="36" rx="8"/>
|
||||
<rect x="826" y="300" width="34" height="36" rx="8"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Свечение из иллюминатора и световые полосы модуля -->
|
||||
<ellipse cx="640" cy="318" rx="420" ry="330" fill="url(#portGlow)"/>
|
||||
<g filter="url(#stationSofter)">
|
||||
<rect x="176" y="60" width="928" height="10" rx="5" fill="url(#strip)"/>
|
||||
<rect x="176" y="650" width="928" height="10" rx="5" fill="url(#strip)"/>
|
||||
</g>
|
||||
|
||||
<rect width="1280" height="720" fill="url(#spaceVignette)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
Reference in New Issue
Block a user