mirror of
https://github.com/Cheviiot/Nivora.git
synced 2026-08-03 15:51:12 +00:00
feat: create autonomous Nivora package repository
Maintain a validated cross-distribution package catalog. Automate upstream updates, isolated builds, diagnostics, and direct publication. Build the official GitHub Desktop sources for Linux with working OAuth.
This commit is contained in:
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
package_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
repo_root="$(cd "${package_dir}/.." && pwd)"
|
||||
exec "${repo_root}/tools/package_updates.sh" check "$(basename "$package_dir")"
|
||||
@@ -0,0 +1,12 @@
|
||||
Сведения о лицензии пакета Happ
|
||||
|
||||
Лицензия: Custom.
|
||||
Официальный сайт: https://happ.su/
|
||||
Условия лицензии: https://happ.su/license
|
||||
|
||||
Нюансы пакета:
|
||||
- Пакет перепаковывает официальный deb-артефакт Happ Desktop для Linux.
|
||||
- Happ является проприетарным приложением FlyFrog LLC.
|
||||
- В составе приложения могут использоваться проприетарные компоненты и сетевые функции, связанные с xray-core/sing-box-сценариями.
|
||||
- Использование клиента, подписок и подключений регулируется условиями Happ и провайдера сервиса.
|
||||
- Пакет помечен как несвободный через `nonfree=1`.
|
||||
@@ -0,0 +1,336 @@
|
||||
name='happ'
|
||||
version=3.3.6
|
||||
release=4
|
||||
summary='User-friendly GUI proxy client for xray-core'
|
||||
summary_ru='Удобный GUI-прокси-клиент для xray-core'
|
||||
group='Networking/Other'
|
||||
desc='Happ is a GUI client for xray-core with TUN/VPN mode via sing-box, anti-censorship tools, QR code import and subscription management.'
|
||||
desc_ru='Happ — GUI-клиент для xray-core с режимом TUN/VPN через sing-box, средствами обхода блокировок, импортом QR-кодов и управлением подписками.'
|
||||
homepage='https://happ.su/'
|
||||
maintainer='FlyFrog LLC <support@happ.su>'
|
||||
|
||||
architectures=('amd64' 'arm64')
|
||||
|
||||
license=('Custom')
|
||||
nonfree=1
|
||||
nonfree_msg='Happ является проприетарным приложением FlyFrog LLC. Использование клиента, подписок и сетевых функций регулируется условиями Happ.'
|
||||
nonfree_url='https://happ.su/license'
|
||||
|
||||
provides=()
|
||||
replaces=('happ')
|
||||
conflicts=()
|
||||
|
||||
# auto_req отключен — Qt-приложение поставляется с bundled библиотеками
|
||||
auto_reqprov_method="dirty"
|
||||
auto_req=0
|
||||
auto_prov=0
|
||||
|
||||
deps=()
|
||||
deps_debian=('libc6' 'libssl3 | libssl3t64')
|
||||
deps_ubuntu=('libc6' 'libssl3 | libssl3t64')
|
||||
deps_fedora=('glibc' 'openssl-libs')
|
||||
deps_arch=('glibc' 'openssl')
|
||||
deps_opensuse=('glibc' 'libopenssl1_1')
|
||||
deps_altlinux=('glibc' 'libssl3')
|
||||
|
||||
build_deps=('binutils')
|
||||
build_deps_debian=('binutils' 'python3')
|
||||
build_deps_ubuntu=("${build_deps_debian[@]}")
|
||||
build_deps_fedora=('binutils' 'python3')
|
||||
build_deps_arch=('binutils' 'python')
|
||||
build_deps_opensuse=('binutils' 'python3')
|
||||
build_deps_altlinux=('binutils' 'python3')
|
||||
|
||||
sources=(
|
||||
"https://github.com/Happ-proxy/happ-desktop/releases/download/${version}/Happ.linux.x64.deb?~archive=false&~name=happ.deb"
|
||||
'local:///patch-tray-icons.py'
|
||||
'local:///happ-launcher'
|
||||
'local:///tray-idle.svg'
|
||||
'local:///tray-connected.svg'
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
sources_arm64=(
|
||||
"https://github.com/Happ-proxy/happ-desktop/releases/download/${version}/Happ.linux.arm64.deb?~archive=false&~name=happ.deb"
|
||||
'local:///patch-tray-icons.py'
|
||||
'local:///happ-launcher'
|
||||
'local:///tray-idle.svg'
|
||||
'local:///tray-connected.svg'
|
||||
'local:///LICENSE'
|
||||
)
|
||||
|
||||
checksums=(
|
||||
'a7dac51277387bfe1049b1ad40f40f2e74af233a5eab020b5be1a622effc46a4'
|
||||
'5b0106dda286b039d6bdce45208f44ea3eb7a2deb4e00455190ce7a07177dde0'
|
||||
'fa4d741a4e2df82b0a19291e2c7bad4a8753a1e59237b2bfe5fef7de8c22efd2'
|
||||
'af47a069497a8063b9028549dc6db111776589818c6165c7a46cb8b25a822ead'
|
||||
'7e105f63e4535676e15e4d613426d6af843376a09fd25674bbbf367778668ae5'
|
||||
'5f0fbc1b0b0e8399c8a04425512168e3dd87960381e85a7cb62a41d55b054b0f'
|
||||
)
|
||||
|
||||
checksums_arm64=(
|
||||
'a4d3d6dcab1db61db23cdf0f86bce736014887262b570927befe48cb49f14aa6'
|
||||
'5b0106dda286b039d6bdce45208f44ea3eb7a2deb4e00455190ce7a07177dde0'
|
||||
'fa4d741a4e2df82b0a19291e2c7bad4a8753a1e59237b2bfe5fef7de8c22efd2'
|
||||
'af47a069497a8063b9028549dc6db111776589818c6165c7a46cb8b25a822ead'
|
||||
'7e105f63e4535676e15e4d613426d6af843376a09fd25674bbbf367778668ae5'
|
||||
'5f0fbc1b0b0e8399c8a04425512168e3dd87960381e85a7cb62a41d55b054b0f'
|
||||
)
|
||||
|
||||
scripts=(
|
||||
['postinstall']='postinstall.sh'
|
||||
['postremove']='postremove.sh'
|
||||
)
|
||||
|
||||
package() {
|
||||
cd "${srcdir}"
|
||||
|
||||
mkdir -p "${srcdir}/happ-extracted"
|
||||
ar x happ.deb
|
||||
tar -xf data.tar.* -C "${srcdir}/happ-extracted"
|
||||
|
||||
cp -a "${srcdir}/happ-extracted/opt" "${pkgdir}/opt"
|
||||
python3 patch-tray-icons.py \
|
||||
"${pkgdir}/opt/happ/bin/Happ" \
|
||||
tray-idle.svg \
|
||||
tray-connected.svg
|
||||
|
||||
install-systemd "${srcdir}/happ-extracted/etc/systemd/system/happd.service"
|
||||
|
||||
sed -i 's|^Exec=/opt/happ/bin/Happ|Exec=/usr/bin/happ|' \
|
||||
"${srcdir}/happ-extracted/usr/share/applications/Happ.desktop"
|
||||
install-desktop "${srcdir}/happ-extracted/usr/share/applications/Happ.desktop"
|
||||
|
||||
python3 - \
|
||||
"${srcdir}/happ-extracted/usr/share/icons/hicolor/256x256/apps/happ.png" \
|
||||
"${pkgdir}/usr/share/icons/hicolor/512x512/apps/happ.png" <<'PY'
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
PNG_SIG = b"\x89PNG\r\n\x1a\n"
|
||||
|
||||
|
||||
def read_png(path):
|
||||
data = Path(path).read_bytes()
|
||||
if not data.startswith(PNG_SIG):
|
||||
raise ValueError("not a PNG")
|
||||
|
||||
pos = len(PNG_SIG)
|
||||
width = height = bit_depth = color_type = None
|
||||
idat = bytearray()
|
||||
while pos < len(data):
|
||||
length = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
pos += 4
|
||||
chunk_type = data[pos:pos + 4]
|
||||
pos += 4
|
||||
chunk = data[pos:pos + length]
|
||||
pos += length + 4
|
||||
if chunk_type == b"IHDR":
|
||||
width, height, bit_depth, color_type, _, _, _ = struct.unpack(
|
||||
">IIBBBBB", chunk
|
||||
)
|
||||
elif chunk_type == b"IDAT":
|
||||
idat.extend(chunk)
|
||||
elif chunk_type == b"IEND":
|
||||
break
|
||||
|
||||
if bit_depth != 8 or color_type not in (2, 6):
|
||||
raise ValueError("unsupported PNG format")
|
||||
|
||||
channels = 4 if color_type == 6 else 3
|
||||
raw = zlib.decompress(bytes(idat))
|
||||
stride = width * channels
|
||||
rows = []
|
||||
prev = [0] * stride
|
||||
index = 0
|
||||
|
||||
for _ in range(height):
|
||||
filter_type = raw[index]
|
||||
index += 1
|
||||
encoded = list(raw[index:index + stride])
|
||||
index += stride
|
||||
row = [0] * stride
|
||||
|
||||
for x, value in enumerate(encoded):
|
||||
left = row[x - channels] if x >= channels else 0
|
||||
up = prev[x]
|
||||
upper_left = prev[x - channels] if x >= channels else 0
|
||||
|
||||
if filter_type == 0:
|
||||
decoded = value
|
||||
elif filter_type == 1:
|
||||
decoded = (value + left) & 255
|
||||
elif filter_type == 2:
|
||||
decoded = (value + up) & 255
|
||||
elif filter_type == 3:
|
||||
decoded = (value + ((left + up) // 2)) & 255
|
||||
elif filter_type == 4:
|
||||
predictor = left + up - upper_left
|
||||
distances = (
|
||||
abs(predictor - left),
|
||||
abs(predictor - up),
|
||||
abs(predictor - upper_left),
|
||||
)
|
||||
decoded = (value + (left, up, upper_left)[distances.index(min(distances))]) & 255
|
||||
else:
|
||||
raise ValueError("unsupported PNG filter")
|
||||
|
||||
row[x] = decoded
|
||||
|
||||
prev = row
|
||||
rows.append(row)
|
||||
|
||||
pixels = []
|
||||
for row in rows:
|
||||
out_row = []
|
||||
for x in range(width):
|
||||
offset = x * channels
|
||||
if channels == 4:
|
||||
out_row.append(tuple(row[offset:offset + 4]))
|
||||
else:
|
||||
out_row.append((row[offset], row[offset + 1], row[offset + 2], 255))
|
||||
pixels.append(out_row)
|
||||
|
||||
return width, height, pixels
|
||||
|
||||
|
||||
def write_png(path, width, height, pixels):
|
||||
def chunk(chunk_type, payload):
|
||||
crc = zlib.crc32(chunk_type + payload) & 0xFFFFFFFF
|
||||
return struct.pack(">I", len(payload)) + chunk_type + payload + struct.pack(">I", crc)
|
||||
|
||||
raw = bytearray()
|
||||
for y in range(height):
|
||||
raw.append(0)
|
||||
for x in range(width):
|
||||
raw.extend(bytes(pixels[y][x]))
|
||||
|
||||
ihdr = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0)
|
||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(path).write_bytes(
|
||||
PNG_SIG + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(bytes(raw), 9))
|
||||
+ chunk(b"IEND", b"")
|
||||
)
|
||||
|
||||
|
||||
def rounded_rect_coverage(px, py, size, margin, radius, samples=4):
|
||||
covered = 0
|
||||
left = top = margin
|
||||
right = bottom = size - margin
|
||||
|
||||
for sy in range(samples):
|
||||
for sx in range(samples):
|
||||
x = px + (sx + 0.5) / samples
|
||||
y = py + (sy + 0.5) / samples
|
||||
if x < left or x > right or y < top or y > bottom:
|
||||
continue
|
||||
cx = min(max(x, left + radius), right - radius)
|
||||
cy = min(max(y, top + radius), bottom - radius)
|
||||
if (x - cx) ** 2 + (y - cy) ** 2 <= radius ** 2:
|
||||
covered += 1
|
||||
|
||||
return covered / (samples * samples)
|
||||
|
||||
|
||||
def sample_alpha(alpha, x, y):
|
||||
height = len(alpha)
|
||||
width = len(alpha[0])
|
||||
if x < 0 or y < 0 or x > width - 1 or y > height - 1:
|
||||
return 0.0
|
||||
|
||||
x0 = int(math.floor(x))
|
||||
y0 = int(math.floor(y))
|
||||
x1 = min(x0 + 1, width - 1)
|
||||
y1 = min(y0 + 1, height - 1)
|
||||
dx = x - x0
|
||||
dy = y - y0
|
||||
|
||||
top = alpha[y0][x0] * (1 - dx) + alpha[y0][x1] * dx
|
||||
bottom = alpha[y1][x0] * (1 - dx) + alpha[y1][x1] * dx
|
||||
return top * (1 - dy) + bottom * dy
|
||||
|
||||
|
||||
src_path, dest_path = sys.argv[1], sys.argv[2]
|
||||
src_width, src_height, source = read_png(src_path)
|
||||
background = 43
|
||||
|
||||
logo_alpha = []
|
||||
for y in range(src_height):
|
||||
row = []
|
||||
for x in range(src_width):
|
||||
r, g, b, a = source[y][x]
|
||||
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
row.append(max(0.0, min(1.0, (luminance - background) / (255 - background))) * (a / 255.0))
|
||||
logo_alpha.append(row)
|
||||
|
||||
xs = [x for y in range(src_height) for x in range(src_width) if logo_alpha[y][x] > 0.04]
|
||||
ys = [y for y in range(src_height) for x in range(src_width) if logo_alpha[y][x] > 0.04]
|
||||
left, right = min(xs), max(xs)
|
||||
top, bottom = min(ys), max(ys)
|
||||
crop_width = right - left + 1
|
||||
crop_height = bottom - top + 1
|
||||
cropped = [
|
||||
[logo_alpha[y][x] for x in range(left, right + 1)]
|
||||
for y in range(top, bottom + 1)
|
||||
]
|
||||
|
||||
size = 512
|
||||
margin = 38
|
||||
radius = 116
|
||||
logo_height = 292
|
||||
logo_width = round(crop_width * logo_height / crop_height)
|
||||
logo_x = (size - logo_width) // 2
|
||||
logo_y = (size - logo_height) // 2 + 6
|
||||
|
||||
pixels = [[(0, 0, 0, 0) for _ in range(size)] for _ in range(size)]
|
||||
for y in range(size):
|
||||
for x in range(size):
|
||||
coverage = rounded_rect_coverage(x, y, size, margin, radius)
|
||||
if coverage:
|
||||
pixels[y][x] = (43, 43, 43, round(255 * coverage))
|
||||
|
||||
for y in range(logo_height):
|
||||
source_y = (y + 0.5) * crop_height / logo_height - 0.5
|
||||
target_y = logo_y + y
|
||||
if target_y < 0 or target_y >= size:
|
||||
continue
|
||||
|
||||
for x in range(logo_width):
|
||||
source_x = (x + 0.5) * crop_width / logo_width - 0.5
|
||||
target_x = logo_x + x
|
||||
if target_x < 0 or target_x >= size:
|
||||
continue
|
||||
|
||||
alpha = sample_alpha(cropped, source_x, source_y)
|
||||
if alpha <= 0:
|
||||
continue
|
||||
|
||||
dr, dg, db, da = pixels[target_y][target_x]
|
||||
da_float = da / 255.0
|
||||
out_alpha = alpha + da_float * (1 - alpha)
|
||||
out_rgb = round((255 * alpha + dr * da_float * (1 - alpha)) / out_alpha)
|
||||
pixels[target_y][target_x] = (out_rgb, out_rgb, out_rgb, round(out_alpha * 255))
|
||||
|
||||
write_png(dest_path, size, size, pixels)
|
||||
PY
|
||||
|
||||
install -Dm644 "${srcdir}/happ-extracted/usr/share/mime/packages/happ-mime.xml" \
|
||||
"${pkgdir}/usr/share/mime/packages/happ-mime.xml"
|
||||
|
||||
install -Dm755 "${srcdir}/happ-launcher" "${pkgdir}/usr/bin/happ"
|
||||
install-license "${srcdir}/LICENSE" "happ/LICENSE.nivora"
|
||||
}
|
||||
|
||||
files() {
|
||||
files-find "/opt/happ/**/*"
|
||||
files-find-binary
|
||||
files-find-desktop
|
||||
files-find-systemd
|
||||
files-find-license
|
||||
files-find \
|
||||
"/usr/share/icons/hicolor/512x512/apps/happ.png" \
|
||||
"/usr/share/mime/packages/happ-mime.xml"
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Happ's bundled Qt falls back to the generic GNOME platform theme, which
|
||||
# reports Qt::ColorScheme::Unknown. The bundled portal plugin follows the
|
||||
# desktop color-scheme setting and emits updates when it changes.
|
||||
QT_QPA_PLATFORMTHEME="${QT_QPA_PLATFORMTHEME:-xdgdesktopportal}"
|
||||
export QT_QPA_PLATFORMTHEME
|
||||
|
||||
exec /opt/happ/bin/Happ "$@"
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
UPSTREAM_RESOURCES = {
|
||||
"idle": (
|
||||
"c0939b3a81a59983f0db07cdfa08eae90d6ddc211b7b790c425beb72628355ea",
|
||||
b'<svg width="64" height="64"',
|
||||
),
|
||||
"connected": (
|
||||
"5e1703683c6263b5a2cdda83b034dec23fe31e72497f6644a3853b97c6fdef85",
|
||||
b'<svg width="15" height="15"',
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def replace_resource(
|
||||
binary: bytearray,
|
||||
kind: str,
|
||||
replacement: bytes,
|
||||
expected_start: int | None = None,
|
||||
) -> tuple[int, int]:
|
||||
expected_hash, marker = UPSTREAM_RESOURCES[kind]
|
||||
matches: list[tuple[int, int]] = []
|
||||
offset = 0
|
||||
|
||||
while True:
|
||||
start = binary.find(marker, offset)
|
||||
if start < 0:
|
||||
break
|
||||
offset = start + 1
|
||||
if start < 4:
|
||||
continue
|
||||
|
||||
length = struct.unpack_from(">I", binary, start - 4)[0]
|
||||
if length <= 0 or start + length > len(binary):
|
||||
continue
|
||||
payload = bytes(binary[start:start + length])
|
||||
if (
|
||||
hashlib.sha256(payload).hexdigest() == expected_hash
|
||||
and (expected_start is None or start == expected_start)
|
||||
):
|
||||
matches.append((start, length))
|
||||
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError(
|
||||
f"expected exactly one upstream {kind} tray resource, found {len(matches)}"
|
||||
)
|
||||
|
||||
start, capacity = matches[0]
|
||||
if len(replacement) > capacity:
|
||||
raise RuntimeError(
|
||||
f"{kind} tray replacement is too large: {len(replacement)} > {capacity}"
|
||||
)
|
||||
|
||||
struct.pack_into(">I", binary, start - 4, len(replacement))
|
||||
binary[start:start + len(replacement)] = replacement
|
||||
binary[start + len(replacement):start + capacity] = b"\0" * (
|
||||
capacity - len(replacement)
|
||||
)
|
||||
return start, capacity
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 4:
|
||||
print(
|
||||
"usage: patch-tray-icons.py <Happ binary> <idle.svg> <connected.svg>",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
binary_path = Path(sys.argv[1])
|
||||
binary = bytearray(binary_path.read_bytes())
|
||||
connected_start, connected_capacity = replace_resource(
|
||||
binary, "connected", Path(sys.argv[3]).read_bytes()
|
||||
)
|
||||
replace_resource(
|
||||
binary,
|
||||
"idle",
|
||||
Path(sys.argv[2]).read_bytes(),
|
||||
connected_start + connected_capacity + 4,
|
||||
)
|
||||
binary_path.write_bytes(binary)
|
||||
print("Patched Happ tray icons")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Обновляем MIME-базу данных
|
||||
if command -v update-mime-database &>/dev/null; then
|
||||
optional_refresh "MIME-базу" update-mime-database /usr/share/mime
|
||||
fi
|
||||
|
||||
# Обновляем desktop базу данных
|
||||
if command -v update-desktop-database &>/dev/null; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
|
||||
# Обновляем кэш иконок
|
||||
if command -v gtk-update-icon-cache &>/dev/null; then
|
||||
optional_refresh "кэш иконок" gtk-update-icon-cache -f -q /usr/share/icons/hicolor
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now happd.service
|
||||
fi
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
optional_refresh() {
|
||||
local label=$1
|
||||
shift
|
||||
|
||||
if ! "$@"; then
|
||||
echo "Предупреждение: не удалось обновить ${label}." >&2
|
||||
fi
|
||||
}
|
||||
|
||||
unit=/usr/lib/systemd/system/happd.service
|
||||
if [[ ! -e "$unit" ]] && command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then
|
||||
if systemctl is-active --quiet happd.service; then
|
||||
systemctl stop happd.service
|
||||
fi
|
||||
if systemctl is-enabled --quiet happd.service; then
|
||||
systemctl disable happd.service
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
# Обновляем MIME-базу данных
|
||||
if command -v update-mime-database &>/dev/null; then
|
||||
optional_refresh "MIME-базу" update-mime-database /usr/share/mime
|
||||
fi
|
||||
|
||||
# Обновляем desktop базу данных
|
||||
if command -v update-desktop-database &>/dev/null; then
|
||||
optional_refresh "desktop-базу" update-desktop-database -q /usr/share/applications
|
||||
fi
|
||||
|
||||
# Обновляем кэш иконок
|
||||
if command -v gtk-update-icon-cache &>/dev/null; then
|
||||
optional_refresh "кэш иконок" gtk-update-icon-cache -f -q /usr/share/icons/hicolor
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
[inherit]
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><linearGradient id="g" x1="9" y1="7" x2="55" y2="57" gradientUnits="userSpaceOnUse"><stop stop-color="#35432E"/><stop offset="1" stop-color="#1D261A"/></linearGradient></defs>
|
||||
<rect x="3" y="3" width="58" height="58" rx="16" fill="url(#g)" stroke="#A8FF5D" stroke-width="2.5"/>
|
||||
<path d="M27.7 28.3l2.2-12.8L18 27.3l-3.7 21.9 11.9-11.8.3-1.8H28l7.4-7.3h-7.7ZM46 37.3l3.7-21.8-11.9 11.8-.2 1h-.8l-7.4 7.3h7l-2.3 13.6L46 37.3ZM36.1 49.2l-.7 4.4h9.9L48 37.4 36.1 49.2ZM28.4 15.3l.8-4.9h-10l-2.8 16.7 12-11.8Z" fill="#FAFAFB"/>
|
||||
<path d="M28.1 37.4 16.2 49.2l-.8 4.4h10l2.7-16.2ZM36.2 27.1l11.9-11.8.8-4.9H39l-2.8 16.7Z" fill="#A8FF5D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 737 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><linearGradient id="g" x1="9" y1="7" x2="55" y2="57" gradientUnits="userSpaceOnUse"><stop stop-color="#3B3D44"/><stop offset="1" stop-color="#202126"/></linearGradient></defs>
|
||||
<rect x="3" y="3" width="58" height="58" rx="16" fill="url(#g)" stroke="#62656E" stroke-width="2"/>
|
||||
<path d="M28.1 37.4 16.2 49.2l-.8 4.4h10l2.7-16.2ZM27.7 28.3l2.2-12.8L18 27.3l-3.7 21.9 11.9-11.8.3-1.8H28l7.4-7.3h-7.7ZM46 37.3l3.7-21.8-11.9 11.8-.2 1h-.8l-7.4 7.3h7l-2.3 13.6L46 37.3ZM36.1 49.2l-.7 4.4h9.9L48 37.4 36.1 49.2ZM28.4 15.3l.8-4.9h-10l-2.8 16.7 12-11.8ZM36.2 27.1l11.9-11.8.8-4.9H39l-2.8 16.7Z" fill="#FAFAFB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
Reference in New Issue
Block a user