1
0
mirror of git://f0xx.org/ac/ac-be-hub synced 2026-07-29 04:59:44 +03:00
This commit is contained in:
Anton Afanasyeu
2026-06-23 12:29:37 +02:00
commit 6013b848c5
56 changed files with 6942 additions and 0 deletions

124
DEPLOY.md Normal file
View File

@@ -0,0 +1,124 @@
# Landing + androidcast — BE only, port 80
**Full topology:** [docs/INFRA.md](../../docs/INFRA.md) — read after any agent/context reset.
Public: `https://apps.f0xx.org/app/androidcast_project/`
## Topology (no :8089)
```text
Browser → apps.f0xx.org (Gentoo FE, TLS)
→ http://artc0.intra.raptor.org:80 ← this vhost only
→ /etc/nginx/conf.d/apps.conf listen 80
```
**8089 is not this installation** (Janus/other). Do not point androidcast FE locations at :8089.
On FE, hub and (if you fix it) crashes should both target **`http://artc0.intra.raptor.org:80`**, same as `location /`.
## 1. Hub entry (`index.php`) + assets
Landing uses **`index.php`** (shared footer from `examples/platform/footer.config.php`). Copy **`index.html`** only as a redirect fallback.
**Multipage landing (2026-06):** sync `hub-landing.css`, `landing-pages.inc.php`, `assets/hub-landing.js`, `assets/hub-i18n.js`, `assets/i18n/hub-en.json`, `assets/i18n/hub-ru.json` with `index.php`.
**One-shot sync (recommended):**
```bash
# sshfs mount at tmp/BE_alpine/... OR pass BE path:
./examples/app_hub/scripts/sync-landing-to-be.sh
# Or direct to Alpine BE:
REMOTE=alpine-be ./examples/app_hub/scripts/sync-landing-to-be.sh
```
**Verify deploy actually landed** (old hub title is `Project hub`; new is `Landing`):
```bash
curl -sS 'https://apps.f0xx.org/app/androidcast_project/' | grep -E '<title>|hub-landing|landing-top'
curl -sS -o /dev/null -w '%{http_code}\n' 'https://apps.f0xx.org/app/androidcast_project/hub-landing.css'
```
Expect `Android Cast — Landing`, `hub-landing.css`, and HTTP **200** (not 404).
```bash
cp .../android_cast/examples/app_hub/index.php \
.../htdocs/apps/app/androidcast_project/index.php
cp .../android_cast/examples/app_hub/index.html \
.../htdocs/apps/app/androidcast_project/index.html
cp .../android_cast/examples/app_hub/hub.css \
.../htdocs/apps/app/androidcast_project/hub.css
cp .../android_cast/examples/app_hub/assets/overtime-01-alphabet.svg \
.../htdocs/apps/app/androidcast_project/assets/
# GA4 (optional): set measurementId in analytics.config.js before copy
cp .../android_cast/examples/app_hub/assets/analytics.config.js \
.../htdocs/apps/app/androidcast_project/assets/
```
Crash console ships `analytics.js` via `deploy-to-be-mount.sh` (under `crashes/assets/js/`).
## 2. BE nginx
Edit **`/etc/nginx/conf.d/apps.conf`** on Alpine (sshfs: `tmp/BE_alpine/etc/nginx/...`).
Replace the whole **`server { listen 80; ... }`** block with:
**`examples/crash_reporter/backend/nginx.apps-port80.fragment`**
That file has hub + crashes + build on **port 80**.
Or run (from repo, with write access to the mount):
```bash
sudo ./examples/crash_reporter/backend/scripts/apply-be-port80-nginx.sh
```
**FE** (`tmp/FE_gentoo/etc/nginx/apps.conf`): build must proxy to BE **:443** like hub/crashes:
```nginx
proxy_pass https://artc0.intra.raptor.org/app/androidcast_project/build/;
```
Do **not** use `http://artc0.intra.raptor.org:80` for build until BE `listen 80` includes the build block.
Copy from **`nginx.fe-apps.conf.fragment`** or run:
```bash
sudo ./examples/crash_reporter/backend/scripts/apply-build-nginx-fix.sh
```
**BE** `conf.d/apps_builder.frag` must use **full paths** (no `...` placeholders). Same script installs the repo fragment.
Use prefix location for the hub (not `alias` to a single file):
```nginx
location /app/androidcast_project/ {
alias /var/www/localhost/htdocs/apps/app/androidcast_project/;
index index.php index.html;
}
```
## 3. Reload BE
```bash
ssh alpine-be 'sudo nginx -t && sudo rc-service nginx reload'
```
## 4. Verify
```bash
curl -sS -o /dev/null -w "%{http_code}\n" -H "Host: apps.f0xx.org" \
"http://artc0.intra.raptor.org/app/androidcast_project/"
curl -sS -o /dev/null -w "%{http_code}\n" \
"https://apps.f0xx.org/app/androidcast_project/"
```
## FE (optional, not required if `location /` already proxies to :80)
If crashes on FE still say `proxy_pass https://artc0...:8089/.../crashes/`, change to:
```nginx
proxy_pass http://artc0.intra.raptor.org/app/androidcast_project/crashes/;
```
(same upstream as the hub — port **80**, no TLS to BE from FE)

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# ac-be-hub
Landing hub. Remote: `git://f0xx.org/ac/ac-be-hub`

7
analytics.config.js Normal file
View File

@@ -0,0 +1,7 @@
/** Hub GA4 config — set measurementId to enable (see analytics.config.example.js). */
window.ANDROIDCAST_ANALYTICS = {
measurementId: 'G-8RKGWGQ52P',
platform: 'hub',
basePath: '/app/androidcast_project',
debug: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -0,0 +1,7 @@
/** Copy to analytics.config.js on deploy and set your GA4 measurement ID. */
window.ANDROIDCAST_ANALYTICS = {
measurementId: 'G-XXXXXXXXXX',
platform: 'hub',
basePath: '/app/androidcast_project',
debug: false
};

View File

@@ -0,0 +1,7 @@
/** Hub GA4 config — set measurementId to enable (see analytics.config.example.js). */
window.ANDROIDCAST_ANALYTICS = {
measurementId: 'G-8RKGWGQ52P',
platform: 'hub',
basePath: '/app/androidcast_project',
debug: false
};

View File

@@ -0,0 +1,366 @@
#!/usr/bin/env python3
"""Build full Overtime and Quartz 7-segment LCD SVG symbol sets."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
# Segment indices: 0=top, 1=UL, 2=UR, 3=mid, 4=LL, 5=LR, 6=bottom
PATTERNS: dict[str, list[int]] = {
" ": [],
"0": [0, 1, 2, 4, 5, 6],
"1": [2, 5],
"2": [0, 1, 3, 5, 6],
"3": [0, 2, 3, 5, 6],
"4": [1, 2, 3, 5],
"5": [0, 2, 3, 5, 6],
"6": [0, 2, 3, 4, 5, 6],
"7": [0, 1, 2],
"8": [0, 1, 2, 3, 4, 5, 6],
"9": [0, 1, 2, 3, 5, 6],
"A": [0, 1, 2, 3, 4, 5],
"B": [0, 1, 2, 3, 5, 6],
"C": [0, 1, 4, 6],
"D": [1, 2, 3, 5, 6],
"E": [0, 3, 4, 5, 6],
"F": [0, 3, 4, 5],
"G": [0, 2, 4, 5, 6],
"H": [1, 2, 3, 4, 5],
"I": [0, 6],
"J": [1, 2, 4, 5, 6],
"K": [1, 3, 4, 5],
"L": [4, 5, 6],
"M": [0, 1, 2, 4, 5],
"N": [1, 2, 4, 5],
"O": [0, 1, 2, 4, 5, 6],
"P": [0, 1, 2, 3, 4],
"Q": [0, 1, 2, 4, 5, 6],
"R": [0, 1, 2, 3, 4, 5],
"S": [0, 2, 3, 5, 6],
"T": [0, 3, 6],
"U": [1, 2, 4, 5, 6],
"V": [1, 4, 5],
"W": [1, 2, 4, 5, 6],
"X": [1, 2, 4, 5],
"Y": [1, 2, 3],
"Z": [0, 1, 4, 6],
}
EXTRA_RECTS: dict[str, list[tuple]] = {
"M": [
("7.1", "5", "2.6", "13.2", 'transform="rotate(28 8.4 11.6)"'),
("14.3", "5", "2.6", "13.2", 'transform="rotate(-28 15.6 11.6)"'),
],
"N": [
("6.5", "6", "2.4", "12", 'transform="rotate(32 7.7 12)"'),
("14.8", "14", "2.4", "12", 'transform="rotate(32 16 20)"'),
],
"K": [
("10", "4", "2.2", "11", 'transform="rotate(-30 11.1 9.5)"'),
("10", "17", "2.2", "11", 'transform="rotate(30 11.1 22.5)"'),
],
"V": [
("7", "17", "2.2", "12", 'transform="rotate(24 8.1 23)"'),
("12", "17", "2.2", "12", 'transform="rotate(-24 13.1 23)"'),
],
"W": [
("5", "17", "2", "11", 'transform="rotate(18 6 22.5)"'),
("9.5", "17", "2", "11", 'transform="rotate(-18 10.5 22.5)"'),
("14", "17", "2", "11", 'transform="rotate(18 15 22.5)"'),
],
"X": [
("7", "5", "2.2", "11", 'transform="rotate(32 8.1 10.5)"'),
("11", "16", "2.2", "11", 'transform="rotate(-32 12.1 21.5)"'),
],
"Q": [("14", "18", "2.2", "10", 'transform="rotate(38 15.1 23)"')],
"R": [("12", "17", "2.2", "10", 'transform="rotate(38 13.1 22)"')],
"/": [
("13", "4", "2.2", "10", 'transform="rotate(52 14.1 9)"'),
("5", "18", "2.2", "10", 'transform="rotate(52 6.1 23)"'),
],
":": [("4", "10", "4", "4"), ("4", "20", "4", "4")],
"+": [("6", "8", "4", "16"), ("2", "14", "12", "4")],
"-": [("2", "14", "12", "4")],
}
# Hand-tuned Overtime Bold shapes (from hub digit geometry).
# Hand-tuned glyphs from hub (do not regenerate numerics).
OVERTIME_VERBATIM: dict[str, tuple[int, str]] = {
"0": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>'
'<rect x="1" y="17" width="3" height="11" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>',
),
"1": (20, '<rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'),
"2": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
),
"3": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
),
"4": (
20,
'<rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/>'
'<rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>',
),
"5": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
),
"6": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/>'
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>',
),
"7": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
'<rect x="16" y="17" width="3" height="11" rx="1"/>',
),
"8": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
'<rect x="16" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/>'
'<rect x="1" y="17" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/>'
'<rect x="3" y="28" width="14" height="3" rx="1"/>',
),
"9": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
'<rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>',
),
":": (12, '<rect x="4" y="10" width="4" height="4" rx="1"/><rect x="4" y="20" width="4" height="4" rx="1"/>'),
"+": (16, '<rect x="6" y="8" width="4" height="16" rx="1"/><rect x="2" y="14" width="12" height="4" rx="1"/>'),
"-": (16, '<rect x="2" y="14" width="12" height="4" rx="1"/>'),
" ": (8, ""),
"/": (
14,
'<rect x="11" y="5" width="2.2" height="10" rx="1" transform="rotate(52 12.1 10)"/>'
'<rect x="4" y="19" width="2.2" height="10" rx="1" transform="rotate(52 5.1 24)"/>',
),
}
OVERTIME_CUSTOM: dict[str, tuple[int, str]] = {
"G": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/>'
'<rect x="1" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/>'
'<rect x="16" y="17" width="3" height="11" rx="1"/><rect x="10" y="15" width="9" height="3" rx="1"/>',
),
"M": (
24,
'<rect x="1" y="4" width="3" height="24" rx="1"/><rect x="20" y="4" width="3" height="24" rx="1"/>'
'<rect x="7.1" y="5" width="2.6" height="13.2" rx="1" transform="rotate(28 8.4 11.6)"/>'
'<rect x="14.3" y="5" width="2.6" height="13.2" rx="1" transform="rotate(-28 15.6 11.6)"/>',
),
"T": (
20,
'<rect x="3" y="1" width="14" height="3" rx="1"/><rect x="8" y="4" width="4" height="24" rx="1"/>',
),
"Y": (
20,
'<rect x="1" y="4" width="3" height="11" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/>'
'<rect x="8" y="15" width="4" height="14" rx="1"/>',
),
}
@dataclass(frozen=True)
class FontStyle:
prefix: str
outfile: str
comment: str
h_pad: float
top_y: float
mid_y: float
bot_y: float
seg_h: float
v_w: float
v_h: float
left_x: float
right_x: float
seg_w: float
FONTS: dict[str, FontStyle] = {
"overtime": FontStyle(
prefix="ot",
outfile="overtime-01-alphabet.svg",
comment="Overtime Bold style 7-segment LCD (full alphabet)",
h_pad=3.0,
top_y=1.0,
mid_y=15.0,
bot_y=28.0,
seg_h=3.0,
v_w=3.0,
v_h=11.0,
left_x=1.0,
right_x=16.0,
seg_w=14.0,
),
"quartz": FontStyle(
prefix="qz",
outfile="quartz-alphabet.svg",
comment="Quartz (TS) style 7-segment LCD (full alphabet)",
h_pad=0.0,
top_y=0.0,
mid_y=0.0,
bot_y=0.0,
seg_h=0.0,
v_w=0.0,
v_h=0.0,
left_x=0.0,
right_x=0.0,
seg_w=0.0,
),
}
def _rect(x, y, width, height, extra: str = "") -> str:
attrs = f'x="{x}" y="{y}" width="{width}" height="{height}" rx="1"'
if extra:
return f"<rect {attrs} {extra}/>"
return f"<rect {attrs}/>"
def _seg_defs(style: FontStyle, w: float, h: float) -> list[tuple[float, float, float, float]]:
if style.prefix == "qz":
h_pad = max(1.0, w * 0.12)
top_y = max(1.0, h * 0.04)
mid_y = h * 0.47
bot_y = h * 0.86
seg_w = max(6.0, w - h_pad * 2)
seg_h = max(2.0, h * 0.09)
v_w = max(2.0, w * 0.14)
v_h = max(6.0, h * 0.33)
left_x = max(1.0, h_pad * 0.45)
right_x = max(left_x + v_w + 1, w - left_x - v_w)
upper_y = top_y + seg_h
lower_y = mid_y + seg_h * 0.6
return [
(h_pad, top_y, seg_w, seg_h),
(left_x, upper_y, v_w, v_h),
(right_x, upper_y, v_w, v_h),
(h_pad, mid_y, seg_w, seg_h),
(left_x, lower_y, v_w, v_h),
(right_x, lower_y, v_w, v_h),
(h_pad, bot_y, seg_w, seg_h),
]
upper_y = style.top_y + style.seg_h
lower_y = 17.0
return [
(style.h_pad, style.top_y, style.seg_w, style.seg_h),
(style.left_x, upper_y, style.v_w, style.v_h),
(style.right_x, upper_y, style.v_w, style.v_h),
(style.h_pad, style.mid_y, style.seg_w, style.seg_h),
(style.left_x, lower_y, style.v_w, style.v_h),
(style.right_x, lower_y, style.v_w, style.v_h),
(style.h_pad, style.bot_y, style.seg_w, style.seg_h),
]
def _symbol_id(prefix: str, ch: str) -> str:
if ch == " ":
return f"{prefix}-space"
if ch == ":":
return f"{prefix}-colon"
if ch == "+":
return f"{prefix}-plus"
if ch == "-":
return f"{prefix}-minus"
if ch == "/":
return f"{prefix}-slash"
if len(ch) == 1 and "0" <= ch <= "9":
return f"{prefix}-{ch}"
if len(ch) == 1 and "A" <= ch <= "Z":
return f"{prefix}-{ch}"
return f"{prefix}-u{ord(ch):04x}"
def _symbol_body(style: FontStyle, ch: str, width: int) -> str:
if style.prefix == "ot" and ch in OVERTIME_VERBATIM:
_, body = OVERTIME_VERBATIM[ch]
return body
if style.prefix == "ot" and ch in OVERTIME_CUSTOM:
_, body = OVERTIME_CUSTOM[ch]
return body
h = 32.0
w = float(width)
segs = PATTERNS.get(ch, [])
parts: list[str] = []
defs = _seg_defs(style, w, h)
for idx in segs:
if idx < 7:
x, y, rw, rh = defs[idx]
parts.append(_rect(f"{x:.2f}", f"{y:.2f}", f"{rw:.2f}", f"{rh:.2f}"))
for item in EXTRA_RECTS.get(ch, []):
if len(item) == 4:
parts.append(_rect(*item))
else:
parts.append(_rect(item[0], item[1], item[2], item[3], item[4]))
return "".join(parts)
def _char_width(ch: str) -> int:
if ch in "MWKNQRX":
return 24
if ch == ":":
return 12
if ch in "+-":
return 16
if ch == "/":
return 14
if ch == " ":
return 8
if ch in OVERTIME_VERBATIM:
return OVERTIME_VERBATIM[ch][0]
if ch in OVERTIME_CUSTOM:
return OVERTIME_CUSTOM[ch][0]
return 20
def build_font(style: FontStyle, out_dir: Path) -> None:
chars = (
[" "] + [str(d) for d in range(10)] + [chr(c) for c in range(ord("A"), ord("Z") + 1)]
+ [":", "+", "-", "/"]
)
lines = [
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" '
'style="position:absolute;width:0;height:0;overflow:hidden">',
f" <!-- {style.comment} -->",
" <defs>",
]
for ch in chars:
width = _char_width(ch)
sid = _symbol_id(style.prefix, ch)
body = _symbol_body(style, ch, width)
lines.append(f' <symbol id="{sid}" viewBox="0 0 {width} 32">{body}</symbol>')
lines.append(" </defs>")
lines.append("</svg>")
out_path = out_dir / style.outfile
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Wrote {out_path} ({len(chars)} symbols)")
def main() -> None:
out_dir = Path(__file__).resolve().parent
for style in FONTS.values():
build_font(style, out_dir)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,195 @@
#!/usr/bin/env python3
"""Generate Quartz-style 7-segment LCD symbol set for hub clock rows."""
from __future__ import annotations
# Segment indices: 0=top, 1=upper-left, 2=upper-right, 3=mid, 4=lower-left, 5=lower-right, 6=bottom
PATTERNS: dict[str, list[int]] = {
" ": [],
"0": [0, 1, 2, 4, 5, 6],
"1": [2, 5],
"2": [0, 1, 3, 4, 6],
"3": [0, 1, 2, 3, 6],
"4": [1, 2, 3, 5],
"5": [0, 2, 3, 5, 6],
"6": [0, 2, 3, 4, 5, 6],
"7": [0, 1, 2],
"8": [0, 1, 2, 3, 4, 5, 6],
"9": [0, 1, 2, 3, 5, 6],
"A": [0, 1, 2, 3, 4, 5],
"B": [0, 1, 2, 3, 5, 6],
"C": [0, 1, 4, 6],
"D": [1, 2, 3, 5, 6],
"E": [0, 3, 4, 5, 6],
"F": [0, 3, 4, 5],
"G": [0, 2, 4, 5, 6],
"H": [1, 2, 3, 4, 5],
"I": [0, 6],
"J": [1, 2, 4, 5, 6],
"K": [1, 3, 4, 5],
"L": [4, 5, 6],
"M": [0, 1, 2, 4, 5],
"N": [1, 2, 4, 5],
"O": [0, 1, 2, 4, 5, 6],
"P": [0, 1, 2, 3, 4],
"Q": [0, 1, 2, 4, 5, 6],
"R": [0, 1, 2, 3, 4, 5],
"S": [0, 2, 3, 5, 6],
"T": [0, 3, 6],
"U": [1, 2, 4, 5, 6],
"V": [1, 4, 5],
"W": [1, 2, 4, 5, 6],
"X": [1, 2, 4, 5],
"Y": [2, 3, 5, 6],
"Z": [0, 1, 4, 6],
":": [10, 11],
"+": [12, 13],
"-": [14],
"/": [15, 16],
}
EXTRA_RECTS: dict[str, list[tuple]] = {
"M": [
("7.1", "5", "2.6", "13.2", 'transform="rotate(28 8.4 11.6)"'),
("14.3", "5", "2.6", "13.2", 'transform="rotate(-28 15.6 11.6)"'),
],
"N": [
("6.5", "6", "2.4", "12", 'transform="rotate(32 7.7 12)"'),
("14.8", "14", "2.4", "12", 'transform="rotate(32 16 20)"'),
],
"K": [
("10", "4", "2.2", "11", 'transform="rotate(-30 11.1 9.5)"'),
("10", "17", "2.2", "11", 'transform="rotate(30 11.1 22.5)"'),
],
"V": [
("7", "17", "2.2", "12", 'transform="rotate(24 8.1 23)"'),
("12", "17", "2.2", "12", 'transform="rotate(-24 13.1 23)"'),
],
"W": [
("5", "17", "2", "11", 'transform="rotate(18 6 22.5)"'),
("9.5", "17", "2", "11", 'transform="rotate(-18 10.5 22.5)"'),
("14", "17", "2", "11", 'transform="rotate(18 15 22.5)"'),
],
"X": [
("7", "5", "2.2", "11", 'transform="rotate(32 8.1 10.5)"'),
("11", "16", "2.2", "11", 'transform="rotate(-32 12.1 21.5)"'),
],
"Q": [
("14", "18", "2.2", "10", 'transform="rotate(38 15.1 23)"'),
],
"R": [
("12", "17", "2.2", "10", 'transform="rotate(38 13.1 22)"'),
],
"/": [
("13", "4", "2.2", "10", 'transform="rotate(52 14.1 9)"'),
("5", "18", "2.2", "10", 'transform="rotate(52 6.1 23)"'),
],
":": [
("4", "10", "4", "4"),
("4", "20", "4", "4"),
],
"+": [
("6", "8", "4", "16"),
("2", "14", "12", "4"),
],
"-": [
("2", "14", "12", "4"),
],
}
def _seg_defs(w: float, h: float) -> list[tuple[float, float, float, float]]:
h_pad = max(1.0, w * 0.12)
top_y = max(1.0, h * 0.04)
mid_y = h * 0.47
bot_y = h * 0.86
seg_w = max(6.0, w - h_pad * 2)
seg_h = max(2.0, h * 0.09)
v_w = max(2.0, w * 0.14)
v_h = max(6.0, h * 0.33)
left_x = max(1.0, h_pad * 0.45)
right_x = max(left_x + v_w + 1, w - left_x - v_w)
upper_y = top_y + seg_h
lower_y = mid_y + seg_h * 0.6
return [
(h_pad, top_y, seg_w, seg_h),
(left_x, upper_y, v_w, v_h),
(right_x, upper_y, v_w, v_h),
(h_pad, mid_y, seg_w, seg_h),
(left_x, lower_y, v_w, v_h),
(right_x, lower_y, v_w, v_h),
(h_pad, bot_y, seg_w, seg_h),
]
def _rect(x, y, width, height, extra: str = "") -> str:
attrs = f'x="{x}" y="{y}" width="{width}" height="{height}" rx="1"'
if extra:
return f"<rect {attrs} {extra}/>"
return f"<rect {attrs}/>"
def symbol_body(ch: str, width: int = 20) -> str:
h = 32.0
w = float(width)
segs = PATTERNS.get(ch, [])
parts: list[str] = []
defs = _seg_defs(w, h)
for idx in segs:
if idx < 7:
x, y, rw, rh = defs[idx]
parts.append(_rect(f"{x:.2f}", f"{y:.2f}", f"{rw:.2f}", f"{rh:.2f}"))
for item in EXTRA_RECTS.get(ch, []):
if len(item) == 4:
parts.append(_rect(*item))
else:
parts.append(_rect(item[0], item[1], item[2], item[3], item[4]))
return "".join(parts)
def symbol_id(ch: str) -> str:
if ch == " ":
return "qz-space"
if ch == ":":
return "qz-colon"
if ch == "+":
return "qz-plus"
if ch == "-":
return "qz-minus"
if ch == "/":
return "qz-slash"
if len(ch) == 1 and "0" <= ch <= "9":
return f"qz-{ch}"
if len(ch) == 1 and "A" <= ch <= "Z":
return f"qz-{ch}"
return f"qz-u{ord(ch):04x}"
def main() -> None:
chars = (
[" "] + [str(d) for d in range(10)] + [chr(c) for c in range(ord("A"), ord("Z") + 1)]
+ [":", "+", "-", "/"]
)
wide = set("MWKNQRX")
lines = [
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" '
'style="position:absolute;width:0;height:0;overflow:hidden">',
" <!-- Quartz (TS) style 7-segment LCD glyphs for hub timezone/date rows -->",
" <defs>",
]
for ch in chars:
width = 24 if ch in wide else (12 if ch == ":" else (16 if ch in "+-" else (14 if ch == "/" else (8 if ch == " " else 20))))
sid = symbol_id(ch)
body = symbol_body(ch, width)
lines.append(f' <symbol id="{sid}" viewBox="0 0 {width} 32">{body}</symbol>')
lines.append(" </defs>")
lines.append("</svg>")
out = "\n".join(lines) + "\n"
path = __file__.replace("build_quartz_alphabet.py", "quartz-alphabet.svg")
with open(path, "w", encoding="utf-8") as fh:
fh.write(out)
print(f"Wrote {path} ({len(chars)} symbols)")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""Vectorize LCD TTF fonts into SVG symbol sets for the hub clock widget."""
from __future__ import annotations
import argparse
import re
from dataclasses import dataclass
from pathlib import Path
from fontTools.pens.boundsPen import BoundsPen
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.ttLib import TTFont
CHARS = (
[" "]
+ [str(d) for d in range(10)]
+ [chr(c) for c in range(ord("A"), ord("Z") + 1)]
+ [":", "+", "-", "/", "."]
)
TARGET_HEIGHT = 32.0
PADDING_X = 1.5
PADDING_Y = 1.0
@dataclass(frozen=True)
class FontSpec:
ttf: str
prefix: str
outfile: str
comment: str
FONTS: dict[str, FontSpec] = {
"dseg14-classic": FontSpec(
ttf="DSEG14Classic-Regular.ttf",
prefix="d14c",
outfile="dseg14-classic-alphabet.svg",
comment="DSEG14 Classic (OFL) — vectorized for hub clock",
),
"dseg14-modern": FontSpec(
ttf="DSEG14Modern-Regular.ttf",
prefix="d14m",
outfile="dseg14-modern-alphabet.svg",
comment="DSEG14 Modern (OFL) — vectorized for hub clock",
),
"ds-digital": FontSpec(
ttf="DS-Digital.ttf",
prefix="dsd",
outfile="ds-digital-alphabet.svg",
comment="DS-Digital by Dusit Supasawat (shareware) — vectorized for hub clock",
),
"digital-7-mono-italic": FontSpec(
ttf="digital-7-mono-italic.ttf",
prefix="d7mi",
outfile="digital-7-mono-italic-alphabet.svg",
comment="Digital-7 Mono Italic by Style-7 (personal use) — vectorized for hub clock",
),
}
def symbol_id(prefix: str, ch: str) -> str:
if ch == " ":
return f"{prefix}-space"
if ch == ":":
return f"{prefix}-colon"
if ch == "+":
return f"{prefix}-plus"
if ch == "-":
return f"{prefix}-minus"
if ch == "/":
return f"{prefix}-slash"
if ch == ".":
return f"{prefix}-dot"
if len(ch) == 1 and "0" <= ch <= "9":
return f"{prefix}-{ch}"
if len(ch) == 1 and "A" <= ch <= "Z":
return f"{prefix}-{ch}"
return f"{prefix}-u{ord(ch):04x}"
def glyph_path(font: TTFont, ch: str) -> tuple[str, float, float, float, float] | None:
cmap = font.getBestCmap()
code = ord(ch)
if code not in cmap:
return None
glyph_name = cmap[code]
glyph_set = font.getGlyphSet()
if glyph_name not in glyph_set:
return None
glyph = glyph_set[glyph_name]
pen = SVGPathPen(glyph_set)
bounds_pen = BoundsPen(glyph_set)
glyph.draw(bounds_pen)
if bounds_pen.bounds is None:
return None
x_min, y_min, x_max, y_max = bounds_pen.bounds
glyph.draw(pen)
path = pen.getCommands()
if not path.strip():
return None
return path, x_min, y_min, x_max, y_max
def char_width(font: TTFont, ch: str, fallback: float) -> float:
cmap = font.getBestCmap()
code = ord(ch)
if code not in cmap:
return fallback
glyph_name = cmap[code]
if glyph_name in font["hmtx"].metrics:
adv, _ = font["hmtx"].metrics[glyph_name]
return float(adv)
return fallback
def build_alphabet(spec: FontSpec, fonts_dir: Path, out_dir: Path) -> None:
ttf_path = fonts_dir / spec.ttf
font = TTFont(ttf_path)
units_per_em = float(font["head"].unitsPerEm or 1000)
lines = [
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" '
'style="position:absolute;width:0;height:0;overflow:hidden">',
f" <!-- {spec.comment} -->",
" <defs>",
]
for ch in CHARS:
sid = symbol_id(spec.prefix, ch)
if ch == " ":
width = max(8.0, TARGET_HEIGHT * 0.35)
lines.append(
f' <symbol id="{sid}" viewBox="0 0 {width:.2f} {TARGET_HEIGHT:.2f}"></symbol>'
)
continue
parsed = glyph_path(font, ch)
if parsed is None and ch.isalpha():
parsed = glyph_path(font, ch.lower())
if parsed is None:
width = char_width(font, "0", 20.0) * (TARGET_HEIGHT / units_per_em)
lines.append(
f' <symbol id="{sid}" viewBox="0 0 {width:.2f} {TARGET_HEIGHT:.2f}"></symbol>'
)
continue
path, x_min, y_min, x_max, y_max = parsed
glyph_w = max(x_max - x_min, 1.0)
glyph_h = max(y_max - y_min, 1.0)
scale = (TARGET_HEIGHT - PADDING_Y * 2) / glyph_h
tx = PADDING_X - x_min * scale
ty = TARGET_HEIGHT - PADDING_Y - y_max * scale
width = max(glyph_w * scale + PADDING_X * 2, char_width(font, ch, glyph_w) * scale * 0.55)
# Flip Y: font coords are bottom-up; SVG is top-down.
transform = f"translate({tx:.3f} {ty:.3f}) scale({scale:.6f} {scale:.6f})"
lines.append(f' <symbol id="{sid}" viewBox="0 0 {width:.2f} {TARGET_HEIGHT:.2f}">')
lines.append(f' <path class="lcd-active" d="{path}" transform="{transform}" />')
lines.append(" </symbol>")
lines.extend([" </defs>", "</svg>", ""])
out_path = out_dir / spec.outfile
out_path.write_text("\n".join(lines), encoding="utf-8")
print(f"Wrote {out_path}")
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--fonts-dir",
type=Path,
default=Path(__file__).resolve().parent / "fonts",
)
parser.add_argument(
"--out-dir",
type=Path,
default=Path(__file__).resolve().parent,
)
parser.add_argument(
"--only",
choices=sorted(FONTS.keys()),
action="append",
help="Build one font (repeatable). Default: all.",
)
args = parser.parse_args()
selected = args.only or sorted(FONTS.keys())
for key in selected:
build_alphabet(FONTS[key], args.fonts_dir, args.out_dir)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,50 @@
{
"source": "mercator-source.jpg",
"size": [
800,
519
],
"components": 8,
"layers": {
"vertical_snapshot": {
"translate": [
0.0,
238.0
],
"scale": [
1.2408477842003853,
1.2408477842003853
]
},
"inner": {
"translate": [
223.66088631984587,
238.0
],
"scale": [
1.2408477842003853,
1.2408477842003853
]
},
"outer": {
"scale": [
1.0,
1.0
]
},
"pivot": [
720.0,
560.0
]
},
"areas_top10": [
78439,
42423,
21849,
5520,
5428,
3434,
546,
540
]
}

View File

@@ -0,0 +1,129 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<!-- Digital-7 Mono Italic by Style-7 (personal use) — vectorized for hub clock -->
<defs>
<symbol id="d7mi-space" viewBox="0 0 11.20 32.00"></symbol>
<symbol id="d7mi-0" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM18 70 45 330 67 350 121 290 101 100ZM398 640H159L82 670Q96 710 137 720H436ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-1" viewBox="0 0 11.92 32.00">
<path class="lcd-active" d="M447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(-13.458 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-2" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM391 0H61Q22 10 16 50L99 80H369ZM18 70 45 330 67 350 121 290 101 100ZM398 640H159L82 670Q96 710 137 720H436ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-3" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM398 640H159L82 670Q96 710 137 720H436ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-4" viewBox="0 0 21.71 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM159 650 136 430 69 370 52 390 82 680ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(-0.667 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-5" viewBox="0 0 21.75 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM428 640H159L82 670Q96 710 137 720H466ZM156 620 136 430 69 370 52 390 79 650Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-6" viewBox="0 0 21.75 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM18 70 45 330 67 350 121 290 101 100ZM428 640H159L82 670Q96 710 137 720H466ZM156 620 136 430 69 370 52 390 79 650Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-7" viewBox="0 0 21.71 32.00">
<path class="lcd-active" d="M447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM398 640H159L82 670Q96 710 137 720H436ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(-0.667 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-8" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM18 70 45 330 67 350 121 290 101 100ZM398 640H159L82 670Q96 710 137 720H436ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-9" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM398 640H159L82 670Q96 710 137 720H436ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-A" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM398 640H159L82 670Q96 710 137 720H436ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370ZM121 290 99 80 61 0Q22 10 16 50L45 330L67 350Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-B" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM18 70 45 330 67 350 121 290 101 100ZM398 640H158L81 670L86 720H406L442 680ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 451 670 496 620 472 390 449 370ZM447 350 465 330 441 100 386 50 359 80 381 290ZM374 40 330 0H11L16 50L99 80H338Z" transform="translate(1.042 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-C" viewBox="0 0 21.75 32.00">
<path class="lcd-active" d="M391 0H61Q22 10 16 50L99 80H369ZM18 70 45 330 67 350 121 290 101 100ZM428 640H159L82 670Q96 710 137 720H466ZM156 620 136 430 69 370 52 390 79 650Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-D" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M18 70 45 330 67 350 121 290 101 100ZM398 640H158L81 670L86 720H406L442 680ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 451 670 496 620 472 390 449 370ZM447 350 465 330 441 100 386 50 359 80 381 290ZM374 40 330 0H11L16 50L99 80H338Z" transform="translate(1.042 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-E" viewBox="0 0 21.75 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM391 0H61Q22 10 16 50L99 80H369ZM18 70 45 330 67 350 121 290 101 100ZM428 640H159L82 670Q96 710 137 720H466ZM156 620 136 430 69 370 52 390 79 650Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-F" viewBox="0 0 21.75 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM428 640H159L82 670Q96 710 137 720H466ZM156 620 136 430 69 370 52 390 79 650ZM121 290 99 80 61 0Q22 10 16 50L45 330L67 350Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-G" viewBox="0 0 21.75 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H294L258 360L303 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM18 70 45 330 67 350 121 290 101 100ZM428 640H159L82 670Q96 710 137 720H466ZM156 620 136 430 69 370 52 390 79 650Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-H" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370ZM121 290 99 80 61 0Q22 10 16 50L45 330L67 350ZM68 360 51 380 80 660Q94 700 135 710L157 630L135 420Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-I" viewBox="0 0 12.08 32.00">
<path class="lcd-active" d="M216 430 238 640 276 720Q315 710 321 670L291 390L269 370ZM268 360 286 340 257 60Q242 20 201 10L180 90L202 300Z" transform="translate(-6.106 0.577) scale(0.042254 0.042254)" />
</symbol>
<symbol id="d7mi-J" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM18 70 45 330 67 350 121 290 101 100ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-K" viewBox="0 0 23.25 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM161 480 426 720Q486 720 502 680L215 420H155ZM121 290 99 80 61 0Q22 10 16 50L45 330L67 350ZM69 370 52 390 81 670Q95 710 136 720L158 640L136 430Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-L" viewBox="0 0 20.50 32.00">
<path class="lcd-active" d="M121 290 99 80 61 0Q22 10 16 50L45 330L67 350ZM119 80H359L436 50Q422 10 381 0H81ZM69 370 52 390 81 670Q95 710 136 720L158 640L136 430Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-M" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M302 390 257 350 222 390 246 620H326ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM398 640H159L82 670Q96 710 137 720H436ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370ZM121 290 99 80 61 0Q22 10 16 50L45 330L67 350Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-N" viewBox="0 0 23.45 32.00">
<path class="lcd-active" d="M121 290 99 80 61 0Q22 10 16 50L46 330L68 350ZM375 420H305L171 570L177 630H237L380 470ZM448 350 466 330 436 50Q422 10 381 0L359 80L381 290ZM395 420 417 630 455 710Q494 700 500 660L471 380L449 360ZM69 360 51 380 80 660Q94 700 135 710L157 630L135 420Z" transform="translate(0.824 1.000) scale(0.042254 0.042254)" />
</symbol>
<symbol id="d7mi-O" viewBox="0 0 22.79 32.00">
<path class="lcd-active" d="M398 640H178L142 680L186 720H406L442 680ZM396 430 418 640 451 670 496 620 472 390 449 370ZM447 350 465 330 441 100 386 50 359 80 381 290ZM69 370 52 390 76 620 131 670 158 640 136 430ZM375 40 331 0H111L75 40L119 80H339ZM121 290 99 80 66 50 21 100 45 330 67 350Z" transform="translate(0.625 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-P" viewBox="0 0 23.00 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM398 640H158L81 670L86 720H406L442 680ZM156 620 136 430 69 370 52 390 79 650ZM396 430 418 640 451 670 496 620 472 390 449 370ZM121 290 99 80 61 0Q22 10 16 50L45 330L67 350Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-Q" viewBox="0 0 22.93 32.00">
<path class="lcd-active" d="M448 350 466 330 440 90 369 170 381 290ZM341 0H61Q22 10 16 50L99 80H269ZM18 70 46 330 68 350 121 290 101 100ZM398 640H159L82 670Q96 710 138 720H437ZM156 620 136 430 70 370 52 390 79 650ZM396 430 418 640 457 720Q495 710 501 670L472 390L450 370ZM270 180H330L446 50L440 -10H380L263 120Z" transform="translate(0.842 1.411) scale(0.041096 0.041096)" />
</symbol>
<symbol id="d7mi-R" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM142 300H202L435 40Q411 0 351 0L136 240ZM121 290 99 80 61 0Q22 10 16 50L45 330L67 350ZM398 640H159L82 670Q96 710 137 720H436ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370ZM156 620 136 430 69 370 52 390 79 650Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-S" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM156 620 136 430 69 370 52 390 79 650ZM159 640 82 670Q96 710 137 720H456Q495 710 501 670L418 640Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-T" viewBox="0 0 20.46 32.00">
<path class="lcd-active" d="M257 350 293 310 266 50Q252 10 211 0L189 80L213 310ZM224 410 246 620H326L304 410L259 370ZM159 640 82 670Q96 710 137 720H456Q495 710 501 670L418 640Z" transform="translate(-1.917 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-U" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM18 70 45 330 67 350 121 290 101 100ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370ZM68 360 51 380 80 660Q94 700 135 710L157 630L135 420Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-V" viewBox="0 0 22.52 32.00">
<path class="lcd-active" d="M69 360 51 380 80 660Q94 700 135 710L157 630L135 420ZM395 420 417 630 455 710Q494 700 500 660L471 380L449 360ZM223 120 211 0 38 260 44 320 67 340 120 280ZM380 280 447 340 464 320 458 260 231 0 243 120Z" transform="translate(-0.106 1.000) scale(0.042254 0.042254)" />
</symbol>
<symbol id="d7mi-W" viewBox="0 0 23.17 32.00">
<path class="lcd-active" d="M271 100H191L215 330L259 370L295 330ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370ZM361 0H62Q23 10 17 50L100 80H339ZM18 70 45 330 67 350 121 290 101 100ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM69 370 52 390 81 670Q95 710 136 720L158 640L136 430Z" transform="translate(0.792 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-X" viewBox="0 0 23.29 32.00">
<path class="lcd-active" d="M267 350H312L434 40Q410 0 350 0L256 242ZM282 478 426 720Q486 720 502 680L315 370H270ZM235 242 91 0Q31 0 15 40L201 350H246ZM249 370H204L83 680Q107 720 167 720L261 477Z" transform="translate(0.875 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-Y" viewBox="0 0 23.21 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400ZM447 350 465 330 436 50Q422 10 381 0L359 80L381 290ZM361 0H61Q22 10 16 50L99 80H339ZM159 650 136 430 69 370 52 390 82 680ZM396 430 418 640 456 720Q495 710 501 670L472 390L449 370Z" transform="translate(0.833 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-Z" viewBox="0 0 23.29 32.00">
<path class="lcd-active" d="M391 0H111L159 80H369ZM358 640H159L82 670Q96 710 137 720H406ZM266 430 426 720Q486 720 502 680L319 370H259ZM261 290 91 0Q31 0 15 40L207 350H267Z" transform="translate(0.875 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-colon" viewBox="0 0 21.45 32.00">
<path class="lcd-active" d="M277 160H197L206 240H286ZM311 480H231L239 560H319Z" transform="translate(-13.275 -11.000) scale(0.075000 0.075000)" />
</symbol>
<symbol id="d7mi-plus" viewBox="0 0 33.00 32.00">
<path class="lcd-active" d="M223 400 232 490 276 530 312 490 303 400H393L428 360L384 320H294L285 230L241 190L205 230L214 320H124L88 360L133 400Z" transform="translate(-6.265 -15.765) scale(0.088235 0.088235)" />
</symbol>
<symbol id="d7mi-minus" viewBox="0 0 130.50 32.00">
<path class="lcd-active" d="M393 400 428 360 384 320H124L88 360L133 400Z" transform="translate(-31.500 -119.000) scale(0.375000 0.375000)" />
</symbol>
<symbol id="d7mi-slash" viewBox="0 0 23.29 32.00">
<path class="lcd-active" d="M282 478 426 720Q486 720 502 680L315 370H270ZM235 242 91 0Q31 0 15 40L201 350H246Z" transform="translate(0.875 1.000) scale(0.041667 0.041667)" />
</symbol>
<symbol id="d7mi-dot" viewBox="0 0 107.25 32.00">
<path class="lcd-active" d="M277 0H197L206 80H286Z" transform="translate(-72.375 1.000) scale(0.375000 0.375000)" />
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,129 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<!-- DS-Digital by Dusit Supasawat (shareware) — vectorized for hub clock -->
<defs>
<symbol id="dsd-space" viewBox="0 0 11.20 32.00"></symbol>
<symbol id="dsd-0" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V611L479 686ZM465 700 391 625H148L74 700ZM135 411 88 364 60 392V686L135 611ZM479 14 405 89V289L452 336L479 307ZM465 0H74L148 75H391ZM135 89 60 14V307L88 336L135 289Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-1" viewBox="0 0 7.05 32.00">
<path class="lcd-active" d="M181 307V14L106 89V289L153 336ZM106 411V611L181 686V392L153 364Z" transform="translate(-3.232 0.375) scale(0.044643 0.044643)" />
</symbol>
<symbol id="dsd-2" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM465 0H74L148 75H391ZM135 289V89L60 14V307L88 336ZM405 411V611L479 686V392L452 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-3" viewBox="0 0 20.36 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM465 0H74L148 75H391ZM479 307V14L405 89V289L452 336ZM405 411V611L479 686V392L452 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.671 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-4" viewBox="0 0 21.71 32.00">
<path class="lcd-active" d="M60 392V686L135 611V411L88 364ZM479 307V14L405 89V289L452 336ZM405 411V611L479 686V392L452 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.179 0.375) scale(0.044643 0.044643)" />
</symbol>
<symbol id="dsd-5" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM465 0H74L148 75H391ZM60 392V686L135 611V411L88 364ZM479 307V14L405 89V289L452 336ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-6" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM465 0H74L148 75H391ZM135 289V89L60 14V307L88 336ZM60 392V686L135 611V411L88 364ZM479 307V14L405 89V289L452 336ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-7" viewBox="0 0 20.71 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM479 307V14L405 89V289L452 336ZM405 411V611L479 686V392L452 364Z" transform="translate(-1.736 0.388) scale(0.043732 0.043732)" />
</symbol>
<symbol id="dsd-8" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM465 0H74L148 75H391ZM135 289V89L60 14V307L88 336ZM60 392V686L135 611V411L88 364ZM479 307V14L405 89V289L452 336ZM405 411V611L479 686V392L452 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-9" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM465 0H74L148 75H391ZM60 392V686L135 611V411L88 364ZM479 307V14L405 89V289L452 336ZM405 411V611L479 686V392L452 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-A" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM135 289V75L60 0V307L88 336ZM60 392V686L135 611V411L88 364ZM479 307V0L405 75V289L452 336ZM405 411V611L479 686V392L452 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-B" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 89 442 51 405 89V289L452 336L479 307ZM479 392 452 364 405 411V611L442 649L479 611ZM428 663 391 625H148L74 700H391ZM428 37 391 0H74L148 75H391ZM135 411 88 364 60 392V686L135 611ZM135 89 60 14V307L88 336L135 289ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-C" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 700 405 625H148L111 663L148 700ZM135 411 88 364 60 392V611L97 649L135 611ZM479 0H148L111 37L148 75H405ZM135 89 97 51 60 89V307L88 336L135 289Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-D" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 89 442 51 405 89V289L452 336L479 307ZM479 392 452 364 405 411V611L442 649L479 611ZM428 663 391 625H148L74 700H391ZM428 37 391 0H74L148 75H391ZM135 411 88 364 60 392V686L135 611ZM135 89 60 14V307L88 336L135 289Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-E" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 700 405 625H148L74 700ZM479 0H74L148 75H405ZM135 289V89L60 14V307L88 336ZM60 392V686L135 611V411L88 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-F" viewBox="0 0 21.32 32.00">
<path class="lcd-active" d="M479 700 405 625H148L74 700ZM135 289V89L60 14V307L88 336ZM60 392V686L135 611V411L88 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.124 0.388) scale(0.043732 0.043732)" />
</symbol>
<symbol id="dsd-G" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L111 663L148 700ZM438 350 400 313H316L279 350L316 387H400ZM135 411 88 364 60 392V611L97 649L135 611ZM135 89 97 51 60 89V307L88 336L135 289ZM391 0 428 37 391 75H148L111 37L148 0ZM479 89 442 51 405 89V289L452 336L479 307Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-H" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M135 289V75L60 0V307L88 336ZM60 392V700L135 625V411L88 364ZM479 307V0L405 75V289L452 336ZM405 411V625L479 700V392L452 364ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-I" viewBox="0 0 6.77 32.00">
<path class="lcd-active" d="M181 392 153 364 106 411V663L143 700L181 663ZM181 37 143 0 106 37V289L153 336L181 307Z" transform="translate(-3.043 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-J" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 0H74L148 75H391ZM135 196V89L60 14V215L88 242ZM479 307V14L405 89V289L452 336ZM405 411V625L479 700V392L452 364Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-K" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M135 289V75L60 0V307L88 336ZM60 392V700L135 625V411L88 364ZM479 307V0L405 75V289L452 336ZM391 697H444V644L206 409H153V462ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-L" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 0H74L148 75H405ZM135 289V89L60 14V307L88 336ZM60 392V700L135 625V411L88 364Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-M" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M465 700 391 625H148L74 700ZM135 289V75L60 0V307L88 336ZM60 392V686L135 611V411L88 364ZM479 307V0L405 75V289L452 336ZM405 411V611L479 686V392L452 364ZM232 569 269 606 307 569V402L269 364L232 402Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-N" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M135 289V75L60 0V307L88 336ZM60 392V700L135 625V411L88 364ZM479 307V0L405 75V289L452 336ZM405 411V625L479 700V392L452 364ZM152 571V625H205L387 443V390H334Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-O" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V611L442 649L479 611ZM428 663 391 625H148L111 663L148 700H391ZM479 89 442 51 405 89V289L452 336L479 307ZM135 411 88 364 60 392V611L97 649L135 611ZM60 89 97 51 135 89V289L88 336L60 307ZM148 0 111 37 148 75H391L428 37L391 0Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-P" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V611L442 649L479 611ZM428 663 391 625H148L74 700H391ZM135 411 88 364 60 392V686L135 611ZM135 75 60 0V307L88 336L135 289ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-Q" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V611L442 649L479 611ZM428 663 391 625H148L111 663L148 700H391ZM479 155 442 118 405 155V289L452 336L479 307ZM135 411 88 364 60 392V611L97 649L135 611ZM60 89 97 51 135 89V289L88 336L60 307ZM148 0 111 37 148 75H324L361 37L324 0ZM338 89V142H391L479 53V0H426Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-R" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V611L442 649L479 611ZM428 663 391 625H148L74 700H391ZM135 411 88 364 60 392V686L135 611ZM135 75 60 0V307L88 336L135 289ZM186 241V294H239L479 53V0H426ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-S" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 663 442 625H148L111 663L148 700H442ZM479 89 442 51 405 89V289L452 336L479 307ZM135 411 88 364 60 392V611L97 649L135 611ZM97 0 60 37 97 75H391L428 37L391 0ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-T" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 700 405 625H135L60 700ZM307 37 269 0 232 37V252L279 299L307 271ZM307 354 279 327 232 373V574L269 611L307 574Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-U" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 89 442 51 405 89V289L452 336L479 307ZM60 89 97 51 135 89V289L88 336L60 307ZM148 0 111 37 148 75H391L428 37L391 0ZM60 392V700L135 625V411L88 364ZM405 411V625L479 700V392L452 364Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-V" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V625L479 700ZM446 282 201 37H148V90L393 336L452 337ZM135 411 88 364 60 392V700L135 625ZM60 37 97 0 135 37V289L88 336L60 307Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-W" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V625L479 700ZM135 411 88 364 60 392V700L135 625ZM479 14 405 89V289L452 336L479 307ZM307 130 269 93 232 130V298L269 336L307 298ZM465 0H74L148 75H391ZM135 89 60 14V307L88 336L135 289Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-X" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M284 364V446L436 700H479V627L322 364ZM218 364 60 627V700H103L255 446V364ZM255 336V254L103 0H60V73L218 336ZM321 336 479 73V0H436L284 254V336Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-Y" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 392 452 364 405 411V625L479 700ZM60 392 88 364 135 411V625L60 700ZM148 0 111 37 148 75H391L428 37L391 0ZM479 89 442 51 405 89V289L452 336L479 307ZM400 387 438 350 400 313H139L102 350L139 387Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-Z" viewBox="0 0 20.96 32.00">
<path class="lcd-active" d="M479 663 442 625H135L60 700H442ZM465 0H97L60 37L97 75H391ZM334 369 268 361 265 401 427 606H479V551ZM211 336 251 340 260 274 115 93H60V145Z" transform="translate(-1.071 1.000) scale(0.042857 0.042857)" />
</symbol>
<symbol id="dsd-colon" viewBox="0 0 6.86 32.00">
<path class="lcd-active" d="M83 0V75H158V0ZM83 508V583H158V508Z" transform="translate(-2.771 1.000) scale(0.051458 0.051458)" />
</symbol>
<symbol id="dsd-plus" viewBox="0 0 32.91 32.00">
<path class="lcd-active" d="M265 401 228 364 190 401V480L228 518L265 480ZM395 350 358 313H279L241 350L279 387H358ZM214 350 176 313H97L60 350L97 387H176ZM265 219 228 182 190 219V299L228 336L265 299Z" transform="translate(-3.857 -15.250) scale(0.089286 0.089286)" />
</symbol>
<symbol id="dsd-minus" viewBox="0 0 138.81 32.00">
<path class="lcd-active" d="M358 387 395 350 358 313H97L60 350L97 387Z" transform="translate(-22.824 -125.892) scale(0.405405 0.405405)" />
</symbol>
<symbol id="dsd-slash" viewBox="0 0 21.52 32.00">
<path class="lcd-active" d="M246 320 64 4 36 106 172 340 235 358ZM232 446 367 680 470 707 287 393 249 382Z" transform="translate(-0.036 0.829) scale(0.042674 0.042674)" />
</symbol>
<symbol id="dsd-dot" viewBox="0 0 42.68 32.00">
<path class="lcd-active" d="M60 0V75H135V0Z" transform="translate(-22.500 1.000) scale(0.400000 0.400000)" />
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,129 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<!-- DSEG14 Classic (OFL) — vectorized for hub clock -->
<defs>
<symbol id="d14c-space" viewBox="0 0 11.20 32.00"></symbol>
<symbol id="d14c-0" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM578 887H604V746L500 567H475V709ZM341 291 238 113H212V254L316 433H341Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-1" viewBox="0 0 14.80 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424Z" transform="translate(-19.071 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-2" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-3" viewBox="0 0 20.19 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-2.820 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-4" viewBox="0 0 23.37 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.764 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-5" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-6" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-7" viewBox="0 0 22.41 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM130 515 126 510H99V924L130 955L192 893V887V782V576Z" transform="translate(-1.610 -0.414) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-8" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-9" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-A" viewBox="0 0 22.41 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.610 -0.414) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-B" viewBox="0 0 20.19 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488ZM362 673V887H454V876V673V601L408 521L362 601ZM454 157V113H362V327V399L408 479L454 399V327Z" transform="translate(-2.820 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-C" viewBox="0 0 20.19 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-D" viewBox="0 0 20.19 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM362 673V887H454V876V673V601L408 521L362 601ZM454 157V113H362V327V399L408 479L454 399V327Z" transform="translate(-2.820 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-E" viewBox="0 0 20.19 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-F" viewBox="0 0 21.00 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.610 -0.414) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-G" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-H" viewBox="0 0 23.37 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.764 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-I" viewBox="0 0 18.84 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM362 673V887H454V876V673V601L408 521L362 601ZM454 157V113H362V327V399L408 479L454 399V327Z" transform="translate(-2.820 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-J" viewBox="0 0 22.41 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107Z" transform="translate(-1.610 1.000) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-K" viewBox="0 0 19.65 32.00">
<path class="lcd-active" d="M130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM578 887H604V746L500 567H475V709ZM475 433H500L604 254V113H578L475 291Z" transform="translate(-1.764 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-L" viewBox="0 0 21.00 32.00">
<path class="lcd-active" d="M672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576Z" transform="translate(-1.610 1.000) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-M" viewBox="0 0 23.37 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM341 567H316L212 746V887H238L341 709ZM578 887H604V746L500 567H475V709Z" transform="translate(-1.764 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-N" viewBox="0 0 23.37 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM341 567H316L212 746V887H238L341 709ZM475 433H500L604 254V113H578L475 291Z" transform="translate(-1.764 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-O" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-P" viewBox="0 0 22.41 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-1.610 -0.414) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-Q" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM475 433H500L604 254V113H578L475 291Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-R" viewBox="0 0 22.41 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488ZM475 433H500L604 254V113H578L475 291Z" transform="translate(-1.610 -0.414) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-S" viewBox="0 0 20.19 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488ZM341 567H316L212 746V887H238L341 709Z" transform="translate(-2.820 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-T" viewBox="0 0 20.86 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM362 673V887H454V876V673V601L408 521L362 601ZM454 157V113H362V327V399L408 479L454 399V327Z" transform="translate(-3.370 -2.822) scale(0.033822 0.033822)" />
</symbol>
<symbol id="d14c-U" viewBox="0 0 22.41 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576Z" transform="translate(-1.610 1.000) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14c-V" viewBox="0 0 19.65 32.00">
<path class="lcd-active" d="M130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM578 887H604V746L500 567H475V709ZM341 291 238 113H212V254L316 433H341Z" transform="translate(-1.764 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-W" viewBox="0 0 23.37 32.00">
<path class="lcd-active" d="M686 955 717 924V510H690L685 514L624 575V607V782V887V893ZM685 485 690 489H717V76L686 45L624 107V113V218V424ZM130 45 99 76V490H126L130 485L192 424V218V113V107ZM130 515 126 510H99V924L130 955L192 893V887V782V576ZM341 291 238 113H212V254L316 433H341ZM475 433H500L604 254V113H578L475 291Z" transform="translate(-1.764 -0.484) scale(0.032967 0.032967)" />
</symbol>
<symbol id="d14c-X" viewBox="0 0 18.19 32.00">
<path class="lcd-active" d="M341 567H316L212 746V887H238L341 709ZM578 887H604V746L500 567H475V709ZM341 291 238 113H212V254L316 433H341ZM475 433H500L604 254V113H578L475 291Z" transform="translate(-6.717 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14c-Y" viewBox="0 0 18.19 32.00">
<path class="lcd-active" d="M341 567H316L212 746V887H238L341 709ZM578 887H604V746L500 567H475V709ZM454 157V113H362V327V399L408 479L454 399V327Z" transform="translate(-6.717 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14c-Z" viewBox="0 0 18.84 32.00">
<path class="lcd-active" d="M144 969 175 1000H641L672 969L610 907H590H454H362H226H206ZM672 31 641 0H175L144 31L206 93H226H362H454H590H610ZM578 887H604V746L500 567H475V709ZM341 291 238 113H212V254L316 433H341Z" transform="translate(-2.820 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14c-colon" viewBox="0 0 9.95 32.00">
<path class="lcd-active" d="M43 717Q48 728 56 736Q66 746 76 750Q85 754 100.0 754.0Q115 754 124 750Q135 745 144 736Q152 728 157 717Q162 705 162.0 693.0Q162 681 157.0 669.0Q152 657 144.0 649.0Q136 641 124.0 636.0Q112 631 100.0 631.0Q88 631 76.0 636.0Q64 641 56.0 649.0Q48 657 43.0 669.0Q38 681 38.0 693.0Q38 705 43 717ZM43 305Q48 316 56 324Q66 334 76 338Q85 342 100.0 342.0Q115 342 124 338Q134 334 144 324Q152 316 157 305Q162 293 162.0 281.0Q162 269 157.0 257.0Q152 245 144.0 237.0Q136 229 124.0 224.0Q112 219 100.0 219.0Q88 219 76.0 224.0Q64 229 56.0 237.0Q48 245 43.0 257.0Q38 269 38.0 281.0Q38 293 43 305Z" transform="translate(-0.631 -11.280) scale(0.056075 0.056075)" />
</symbol>
<symbol id="d14c-plus" viewBox="0 0 23.43 32.00">
<path class="lcd-active" d="M144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488ZM362 673V887H454V876V673V601L408 521L362 601ZM454 157V113H362V327V399L408 479L454 399V327Z" transform="translate(-4.081 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14c-minus" viewBox="0 0 171.19 32.00">
<path class="lcd-active" d="M144 500 192 547V546H328H369L396 500L369 454H212H209H192V453ZM624 546 671 499 624 453V454H606H604H475H454H447L420 500L447 546H454H475H488Z" transform="translate(-44.457 -143.574) scale(0.319149 0.319149)" />
</symbol>
<symbol id="d14c-slash" viewBox="0 0 18.19 32.00">
<path class="lcd-active" d="M578 887H604V746L500 567H475V709ZM341 291 238 113H212V254L316 433H341Z" transform="translate(-6.717 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14c-dot" viewBox="0 0 33.00 32.00">
<path class="lcd-active" d="M57.0 86.0Q62 74 62.0 62.0Q62 50 57.0 38.0Q52 26 44.0 18.0Q36 10 24.0 5.0Q12 0 0.0 0.0Q-12 0 -24.0 5.0Q-36 10 -44.0 18.0Q-52 26 -57.0 38.0Q-62 50 -62.0 62.0Q-62 74 -57.0 86.0Q-52 98 -44.0 106.0Q-36 114 -24.0 119.0Q-12 124 0.0 124.0Q12 124 24.0 119.0Q36 114 44.0 106.0Q52 98 57.0 86.0Z" transform="translate(16.500 1.000) scale(0.241935 0.241935)" />
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,129 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<!-- DSEG14 Modern (OFL) — vectorized for hub clock -->
<defs>
<symbol id="d14m-space" viewBox="0 0 11.20 32.00"></symbol>
<symbol id="d14m-0" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM474 707 578 887H604V746L499 564L424 521ZM342 293 238 113H212V254L317 436L392 479Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-1" viewBox="0 0 14.10 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454Z" transform="translate(-18.102 -0.225) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14m-2" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-3" viewBox="0 0 21.36 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.650 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-4" viewBox="0 0 22.41 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.610 -0.225) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14m-5" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-6" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-7" viewBox="0 0 22.29 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507Z" transform="translate(-1.591 -0.217) scale(0.031217 0.031217)" />
</symbol>
<symbol id="d14m-8" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-9" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-A" viewBox="0 0 21.65 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.488 0.819) scale(0.030181 0.030181)" />
</symbol>
<symbol id="d14m-B" viewBox="0 0 21.36 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM624 546 647 533 601 454H509L429 500L509 546ZM362 713V887H454V713L408 540ZM454 287V113H362V287L408 460Z" transform="translate(-1.650 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-C" viewBox="0 0 21.36 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-D" viewBox="0 0 21.36 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM362 713V887H454V713L408 540ZM454 287V113H362V287L408 460Z" transform="translate(-1.650 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-E" viewBox="0 0 21.36 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-F" viewBox="0 0 20.47 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.488 0.819) scale(0.030181 0.030181)" />
</symbol>
<symbol id="d14m-G" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-H" viewBox="0 0 21.77 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.506 0.818) scale(0.030364 0.030364)" />
</symbol>
<symbol id="d14m-I" viewBox="0 0 21.18 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM362 713V887H454V713L408 540ZM454 287V113H362V287L408 460Z" transform="translate(-1.650 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-J" viewBox="0 0 21.65 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430Z" transform="translate(-1.488 1.000) scale(0.030181 0.030181)" />
</symbol>
<symbol id="d14m-K" viewBox="0 0 18.86 32.00">
<path class="lcd-active" d="M192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM474 707 578 887H604V746L499 564L424 521ZM604 254V113H578L474 293L424 479L499 436Z" transform="translate(-1.610 0.812) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14m-L" viewBox="0 0 22.11 32.00">
<path class="lcd-active" d="M583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507Z" transform="translate(-1.591 1.000) scale(0.031217 0.031217)" />
</symbol>
<symbol id="d14m-M" viewBox="0 0 21.77 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM212 746V887H238L342 707L392 521L317 564ZM474 707 578 887H604V746L499 564L424 521Z" transform="translate(-1.506 0.818) scale(0.030364 0.030364)" />
</symbol>
<symbol id="d14m-N" viewBox="0 0 21.77 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM212 746V887H238L342 707L392 521L317 564ZM604 254V113H578L474 293L424 479L499 436Z" transform="translate(-1.506 0.818) scale(0.030364 0.030364)" />
</symbol>
<symbol id="d14m-O" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-P" viewBox="0 0 21.65 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-1.488 0.819) scale(0.030181 0.030181)" />
</symbol>
<symbol id="d14m-Q" viewBox="0 0 21.54 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM604 254V113H578L474 293L424 479L499 436Z" transform="translate(-1.470 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-R" viewBox="0 0 21.65 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM698 994Q717 982 717 959V517L624 570V607V782V866ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546ZM604 254V113H578L474 293L424 479L499 436Z" transform="translate(-1.488 0.819) scale(0.030181 0.030181)" />
</symbol>
<symbol id="d14m-S" viewBox="0 0 21.36 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM624 546 647 533 601 454H509L429 500L509 546ZM212 746V887H238L342 707L392 521L317 564Z" transform="translate(-1.650 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-T" viewBox="0 0 22.38 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM362 713V887H454V713L408 540ZM454 287V113H362V287L408 460Z" transform="translate(-2.051 -2.822) scale(0.033822 0.033822)" />
</symbol>
<symbol id="d14m-U" viewBox="0 0 21.65 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507Z" transform="translate(-1.488 1.000) scale(0.030181 0.030181)" />
</symbol>
<symbol id="d14m-V" viewBox="0 0 18.86 32.00">
<path class="lcd-active" d="M192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM474 707 578 887H604V746L499 564L424 521ZM342 293 238 113H212V254L317 436L392 479Z" transform="translate(-1.610 0.812) scale(0.031414 0.031414)" />
</symbol>
<symbol id="d14m-W" viewBox="0 0 21.77 32.00">
<path class="lcd-active" d="M698 994Q717 982 717 959V517L624 570V607V782V866ZM664 523 717 493V41Q717 41 717 39L624 93V95V103V113V218V454ZM192 218V134L118 6Q99 18 99 41V483L192 430ZM99 959Q99 959 99 961L192 907V905V895V887V782V578V546L152 477L99 507ZM342 293 238 113H212V254L317 436L392 479ZM604 254V113H578L474 293L424 479L499 436Z" transform="translate(-1.506 0.818) scale(0.030364 0.030364)" />
</symbol>
<symbol id="d14m-X" viewBox="0 0 18.19 32.00">
<path class="lcd-active" d="M212 746V887H238L342 707L392 521L317 564ZM474 707 578 887H604V746L499 564L424 521ZM342 293 238 113H212V254L317 436L392 479ZM604 254V113H578L474 293L424 479L499 436Z" transform="translate(-6.717 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14m-Y" viewBox="0 0 18.19 32.00">
<path class="lcd-active" d="M212 746V887H238L342 707L392 521L317 564ZM474 707 578 887H604V746L499 564L424 521ZM454 287V113H362V287L408 460Z" transform="translate(-6.717 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14m-Z" viewBox="0 0 21.18 32.00">
<path class="lcd-active" d="M233 907 105 981Q117 1000 140 1000H676Q676 1000 678 1000L624 907H621H590H454H362ZM583 93 711 19Q699 0 676 0H140Q140 0 138 0L192 93H195H226H362H454ZM474 707 578 887H604V746L499 564L424 521ZM342 293 238 113H212V254L317 436L392 479Z" transform="translate(-1.650 1.000) scale(0.030000 0.030000)" />
</symbol>
<symbol id="d14m-colon" viewBox="0 0 9.95 32.00">
<path class="lcd-active" d="M43 717Q48 728 56 736Q66 746 76 750Q85 754 100.0 754.0Q115 754 124 750Q135 745 144 736Q152 728 157 717Q162 705 162.0 693.0Q162 681 157.0 669.0Q152 657 144.0 649.0Q136 641 124.0 636.0Q112 631 100.0 631.0Q88 631 76.0 636.0Q64 641 56.0 649.0Q48 657 43.0 669.0Q38 681 38.0 693.0Q38 705 43 717ZM43 305Q48 316 56 324Q66 334 76 338Q85 342 100.0 342.0Q115 342 124 338Q134 334 144 324Q152 316 157 305Q162 293 162.0 281.0Q162 269 157.0 257.0Q152 245 144.0 237.0Q136 229 124.0 224.0Q112 219 100.0 219.0Q88 219 76.0 224.0Q64 229 56.0 237.0Q48 245 43.0 257.0Q38 269 38.0 281.0Q38 293 43 305Z" transform="translate(-0.631 -11.280) scale(0.056075 0.056075)" />
</symbol>
<symbol id="d14m-plus" viewBox="0 0 21.53 32.00">
<path class="lcd-active" d="M387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546ZM362 713V887H454V713L408 540ZM454 287V113H362V287L408 460Z" transform="translate(-5.050 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14m-minus" viewBox="0 0 158.87 32.00">
<path class="lcd-active" d="M387 500 307 454H212H192L169 467L215 546H307ZM624 546 647 533 601 454H509L429 500L509 546Z" transform="translate(-53.609 -147.043) scale(0.326087 0.326087)" />
</symbol>
<symbol id="d14m-slash" viewBox="0 0 18.19 32.00">
<path class="lcd-active" d="M474 707 578 887H604V746L499 564L424 521ZM342 293 238 113H212V254L317 436L392 479Z" transform="translate(-6.717 -3.380) scale(0.038760 0.038760)" />
</symbol>
<symbol id="d14m-dot" viewBox="0 0 33.00 32.00">
<path class="lcd-active" d="M57.0 86.0Q62 74 62.0 62.0Q62 50 57.0 38.0Q52 26 44.0 18.0Q36 10 24.0 5.0Q12 0 0.0 0.0Q-12 0 -24.0 5.0Q-36 10 -44.0 18.0Q-52 26 -57.0 38.0Q-62 50 -62.0 62.0Q-62 74 -57.0 86.0Q-52 98 -44.0 106.0Q-36 114 -24.0 119.0Q-12 124 0.0 124.0Q12 124 24.0 119.0Q36 114 44.0 106.0Q52 98 57.0 86.0Z" transform="translate(16.500 1.000) scale(0.241935 0.241935)" />
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""Extract red land polygons from Mercator reference image -> layered SVG paths."""
from __future__ import annotations
import json
import math
from collections import deque
from pathlib import Path
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parent
SRC = ROOT / "mercator-source.jpg"
OUT_FLAT = ROOT / "hub-logo-continents-mercator-flat.svg"
OUT_GLOBE = ROOT / "hub-logo-continents-mercator-globe.svg"
OUT_GLOBE_SNAP_VERTICAL = ROOT / "hub-logo-continents-mercator-globe.snapshot-vertical.svg"
OUT_META = ROOT / "continents-meta.json"
# Globe framing in 1920x1080 logo scene.
GLOBE_CX = 720.0
GLOBE_CY = 560.0
GLOBE_R = 322.0
# Orthographic center (visible hemisphere).
CENTER_LAT = 18.0
CENTER_LON = 12.0
def red_mask(rgb: np.ndarray) -> np.ndarray:
r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]
return (r.astype(np.int16) > 150) & (g.astype(np.int16) < 120) & (b.astype(np.int16) < 120)
def dilate(mask: np.ndarray, radius: int = 3) -> np.ndarray:
h, w = mask.shape
out = mask.copy()
ys, xs = np.where(mask)
for y, x in zip(ys, xs):
y0 = max(0, y - radius)
y1 = min(h, y + radius + 1)
x0 = max(0, x - radius)
x1 = min(w, x + radius + 1)
if mask[y0:y1, x0:x1].any():
out[y0:y1, x0:x1] = True
return out
def label_components(mask: np.ndarray) -> tuple[np.ndarray, int]:
h, w = mask.shape
labels = np.zeros((h, w), dtype=np.int32)
current = 0
for y in range(h):
for x in range(w):
if not mask[y, x] or labels[y, x]:
continue
current += 1
q = deque([(x, y)])
labels[y, x] = current
while q:
cx, cy = q.popleft()
for nx, ny in ((cx + 1, cy), (cx - 1, cy), (cx, cy + 1), (cx, cy - 1)):
if 0 <= nx < w and 0 <= ny < h and mask[ny, nx] and labels[ny, nx] == 0:
labels[ny, nx] = current
q.append((nx, ny))
return labels, current
def trace_boundary(labels: np.ndarray, label_id: int) -> list[tuple[int, int]]:
h, w = labels.shape
comp = labels == label_id
if not comp.any():
return []
ys, xs = np.where(comp)
start = (int(xs[0]), int(ys[0]))
x, y = start
path = [start]
dirs = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
dir_idx = 0
def is_land(px: int, py: int) -> bool:
return 0 <= px < w and 0 <= py < h and labels[py, px] == label_id
for _ in range(w * h * 4):
found = False
for i in range(8):
nd = (dir_idx + i) % 8
dx, dy = dirs[nd]
nx, ny = x + dx, y + dy
if is_land(nx, ny):
x, y = nx, ny
dir_idx = (nd + 6) % 8
path.append((x, y))
found = True
break
if not found:
break
if (x, y) == start and len(path) > 8:
break
return path
def simplify(points: list[tuple[int, int]], min_dist: float = 2.0) -> list[tuple[int, int]]:
if len(points) < 3:
return points
out = [points[0]]
for p in points[1:]:
ox, oy = out[-1]
if math.hypot(p[0] - ox, p[1] - oy) >= min_dist:
out.append(p)
if out[0] != out[-1]:
out.append(out[0])
return out
def path_d(points: list[tuple[float, float]]) -> str:
if len(points) < 3:
return ""
x0, y0 = points[0]
parts = [f"M {x0:.1f} {y0:.1f}"]
for x, y in points[1:]:
parts.append(f"L {x:.1f} {y:.1f}")
parts.append("Z")
return " ".join(parts)
def mercator_y_to_lat(y: float, h: float) -> float:
t = 1.0 - (y / h)
t = min(max(t, 0.02), 0.98)
return math.degrees(math.atan(math.sinh(math.pi * (2 * t - 1))))
def lat_lon_to_globe(lat: float, lon: float) -> tuple[float, float]:
lat_r = math.radians(lat)
lon_r = math.radians(lon)
lon0 = math.radians(CENTER_LON)
dlon = lon_r - lon0
x = GLOBE_CX + GLOBE_R * math.cos(lat_r) * math.sin(dlon)
y = GLOBE_CY - GLOBE_R * math.sin(lat_r)
return x, y
def map_to_globe(points: list[tuple[int, int]], w: int, h: int) -> str:
mapped: list[tuple[float, float]] = []
for x, y in points:
lon = (x / w) * 360.0 - 180.0
lat = mercator_y_to_lat(float(y), float(h))
mapped.append(lat_lon_to_globe(lat, lon))
if len(mapped) < 3:
return ""
return path_d(mapped)
def main() -> None:
img = Image.open(SRC).convert("RGB")
rgb = np.array(img)
w, h = rgb.shape[1], rgb.shape[0]
mask = dilate(red_mask(rgb), radius=3)
labels, n = label_components(mask)
components: list[dict] = []
for label_id in range(1, n + 1):
area = int((labels == label_id).sum())
if area < 500:
continue
boundary = trace_boundary(labels, label_id)
if len(boundary) < 20:
continue
boundary = simplify(boundary, min_dist=3.5)
flat = path_d([(float(x), float(y)) for x, y in boundary])
globe = map_to_globe(boundary, w, h)
if not flat:
continue
ys, xs = np.where(labels == label_id)
components.append(
{
"id": label_id,
"area": area,
"bbox": [int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())],
"flat": flat,
"globe": globe,
}
)
components.sort(key=lambda c: c["area"], reverse=True)
flat_paths = "\n ".join(
f'<path id="continent-{c["id"]}" data-area="{c["area"]}" d="{c["flat"]}"/>'
for c in components
)
globe_paths = "\n ".join(
f'<path id="continent-{c["id"]}" data-area="{c["area"]}" d="{c["globe"]}"/>'
for c in components
)
OUT_FLAT.write_text(
f"""<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}" role="img" aria-label="Extracted continents (flat Mercator plate)">
<rect width="100%" height="100%" fill="#b8d4e8"/>
<g id="continents-inner-layer" fill="#9ab3cc" fill-opacity="0.82" stroke="#dce8f6" stroke-opacity="0.78" stroke-width="1.1">
{flat_paths}
</g>
</svg>
""",
encoding="utf-8",
)
# Two-step alignment:
# 1) Vertical fit: Mercator north/south poles match globe top/bottom.
# 2) Horizontal fit: layer centers/pivots match at globe center.
globe_diameter = GLOBE_R * 2
inner_sy = globe_diameter / h
inner_sx = inner_sy
inner_ty = GLOBE_CY - GLOBE_R
inner_tx_vertical = 0.0
inner_tx = GLOBE_CX - (w * inner_sx) / 2
outer_sx = 1.0
outer_sy = 1.0
def globe_svg(
inner_tx_val: float,
inner_ty_val: float,
inner_sx_val: float,
inner_sy_val: float,
outer_sx_val: float,
outer_sy_val: float,
label: str,
) -> str:
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="{label}">
<defs>
<clipPath id="globeClip"><circle cx="{GLOBE_CX:.0f}" cy="{GLOBE_CY:.0f}" r="{GLOBE_R:.0f}"/></clipPath>
<radialGradient id="continentRelief" cx="34%" cy="22%" r="78%">
<stop offset="0%" stop-color="#dbe7f6" stop-opacity="0.45"/>
<stop offset="58%" stop-color="#9ab3cc" stop-opacity="0.22"/>
<stop offset="100%" stop-color="#6a7f97" stop-opacity="0.18"/>
</radialGradient>
<filter id="continentDepth" x="-30%" y="-30%" width="160%" height="160%">
<feDropShadow dx="-4" dy="-6" stdDeviation="3" flood-color="#f4fbff" flood-opacity="0.2"/>
<feDropShadow dx="4" dy="6" stdDeviation="4" flood-color="#0a1220" flood-opacity="0.28"/>
</filter>
</defs>
<g id="continents-outer-layer" clip-path="url(#globeClip)" transform="translate({GLOBE_CX:.0f} {GLOBE_CY:.0f}) scale({outer_sx_val:.3f} {outer_sy_val:.3f}) translate(-{GLOBE_CX:.0f} -{GLOBE_CY:.0f})">
<g id="continents-inner-layer" transform="translate({inner_tx_val:.2f} {inner_ty_val:.2f}) scale({inner_sx_val:.4f} {inner_sy_val:.4f})">
<g id="continents-flat" fill="url(#continentRelief)" fill-opacity="0.9" stroke="#dce8f6" stroke-opacity="0.78" stroke-width="1.2" filter="url(#continentDepth)">
{flat_paths}
</g>
</g>
</g>
</svg>
"""
OUT_GLOBE_SNAP_VERTICAL.write_text(
globe_svg(
inner_tx_vertical,
inner_ty,
inner_sx,
inner_sy,
outer_sx,
outer_sy,
"Continents vertical-fit snapshot",
),
encoding="utf-8",
)
OUT_GLOBE.write_text(
globe_svg(
inner_tx,
inner_ty,
inner_sx,
inner_sy,
outer_sx,
outer_sy,
"Continents layered Mercator overlay",
),
encoding="utf-8",
)
OUT_META.write_text(
json.dumps(
{
"source": str(SRC.name),
"size": [w, h],
"components": len(components),
"layers": {
"vertical_snapshot": {
"translate": [inner_tx_vertical, inner_ty],
"scale": [inner_sx, inner_sy],
},
"inner": {"translate": [inner_tx, inner_ty], "scale": [inner_sx, inner_sy]},
"outer": {"scale": [outer_sx, outer_sy]},
"pivot": [GLOBE_CX, GLOBE_CY],
},
"areas_top10": [c["area"] for c in components[:10]],
},
indent=2,
),
encoding="utf-8",
)
print(f"components={len(components)}")
print(f"wrote {OUT_FLAT}")
print(f"wrote {OUT_GLOBE}")
if __name__ == "__main__":
main()

BIN
assets/fonts/Clockopia.ttf Normal file

Binary file not shown.

BIN
assets/fonts/DS-Digital.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

627
assets/hub-globe.js Normal file
View File

@@ -0,0 +1,627 @@
/**
* Hub landing globe — orthographic Canvas2D (globe3d=1).
* Grid: hub SVG ellipses (EW) + meridian paths (NS) traced on sphere, then Y-rotated.
* Land: ortho hub disk on front face; mercator-flat only on back wrap (avoids front squeeze).
*/
export const GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 322, rim: 330 };
const AUTO_SPIN_RAD_S = 0.035;
const DRAG_ROTATION_PER_PX = 0.004;
const OCEAN_FALLBACK = { r: 22, g: 38, b: 60 };
const FRONT_Z = 0.04;
const RASTER_SCALE = 2;
const AXIS_STEPS = 160;
/** hub-logo-fragment-globe-grid-overlay.svg — EW parallels. */
const HUB_EW_PARALLELS = [
{ cx: 720, cy: 560, rx: 318, ry: 11, bright: true },
{ cx: 720, cy: 392, rx: 260, ry: 39, bright: false },
{ cx: 720, cy: 728, rx: 260, ry: 39, bright: false },
];
/** Same SVG — NS meridians (line + quadratic beziers). */
const HUB_NS_MERIDIANS = [
{ prime: true, points: sampleLinePoints(720, 248, 720, 872, AXIS_STEPS) },
{ prime: false, points: sampleQuadPoints(620, 248, 358, 560, 620, 872, AXIS_STEPS) },
{ prime: false, points: sampleQuadPoints(820, 248, 1082, 560, 820, 872, AXIS_STEPS) },
];
function sampleLinePoints(x0, y0, x1, y1, steps) {
const pts = [];
for (let i = 0; i <= steps; i++) {
const t = i / steps;
pts.push({ x: x0 + (x1 - x0) * t, y: y0 + (y1 - y0) * t });
}
return pts;
}
function sampleQuadPoints(x0, y0, cx, cy, x1, y1, steps) {
const pts = [];
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const u = 1 - t;
pts.push({
x: u * u * x0 + 2 * u * t * cx + t * t * x1,
y: u * u * y0 + 2 * u * t * cy + t * t * y1,
});
}
return pts;
}
function hubAsset(rel, build) {
const link = document.querySelector('link[href*="hub.css"]');
const base = link ? (link.getAttribute('href') || '').replace(/[^/]*$/, '') : '';
const sep = rel.indexOf('?') >= 0 ? '&' : '?';
return base + rel + sep + 'v=' + build;
}
export function globeCoverRect() {
const vw = window.innerWidth;
const vh = window.innerHeight;
const scale = Math.max(vw / GLOBE_VIEW.imgW, vh / GLOBE_VIEW.imgH);
const drawW = GLOBE_VIEW.imgW * scale;
const drawH = GLOBE_VIEW.imgH * scale;
const offX = (vw - drawW) / 2;
const offY = (vh - drawH) / 2;
const left = offX + (GLOBE_VIEW.cx - GLOBE_VIEW.r) * scale;
const top = offY + (GLOBE_VIEW.cy - GLOBE_VIEW.r) * scale;
const size = GLOBE_VIEW.r * 2 * scale;
return {
left,
top,
width: size,
height: size,
scale,
cx: GLOBE_VIEW.cx * scale + offX,
cy: GLOBE_VIEW.cy * scale + offY,
r: GLOBE_VIEW.r * scale,
rim: GLOBE_VIEW.rim * scale,
};
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('image load failed: ' + url));
img.src = url;
});
}
function rasterizeImage(img, w, h, scale) {
const s = scale || 1;
const rw = Math.max(1, Math.round(w * s));
const rh = Math.max(1, Math.round(h * s));
const c = document.createElement('canvas');
c.width = rw;
c.height = rh;
const ctx = c.getContext('2d', { willReadFrequently: true });
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, rw, rh);
return { data: ctx.getImageData(0, 0, rw, rh).data, w: rw, h: rh };
}
function sampleImageDataBilinear(imgData, w, h, fx, fy) {
const x = Math.max(0, Math.min(w - 1.001, fx));
const y = Math.max(0, Math.min(h - 1.001, fy));
const x0 = x | 0;
const y0 = y | 0;
const x1 = Math.min(w - 1, x0 + 1);
const y1 = Math.min(h - 1, y0 + 1);
const tx = x - x0;
const ty = y - y0;
const i00 = (y0 * w + x0) * 4;
const i10 = (y0 * w + x1) * 4;
const i01 = (y1 * w + x0) * 4;
const i11 = (y1 * w + x1) * 4;
const out = [0, 0, 0, 0];
for (let c = 0; c < 4; c++) {
out[c] = Math.round(
imgData[i00 + c] * (1 - tx) * (1 - ty) +
imgData[i10 + c] * tx * (1 - ty) +
imgData[i01 + c] * (1 - tx) * ty +
imgData[i11 + c] * tx * ty
);
}
return out;
}
function inverseRotateY(nx, ny, nz, rotY) {
const cosR = Math.cos(rotY);
const sinR = Math.sin(rotY);
return {
x: nx * cosR - nz * sinR,
y: ny,
z: nx * sinR + nz * cosR,
};
}
function bodyToTex(body) {
return { x: body.x, y: -body.y, z: body.z };
}
function bodyToView(body, rotY) {
const cosR = Math.cos(rotY);
const sinR = Math.sin(rotY);
return {
x: body.x * cosR + body.z * sinR,
y: body.y,
z: -body.x * sinR + body.z * cosR,
};
}
/** Hub disk pixel → unit-sphere body (front hemisphere, matches globe3d=0). */
function hubPixelToBody(hpX, hpY) {
const texX = (hpX - GLOBE_VIEW.cx) / GLOBE_VIEW.r;
const texY = (GLOBE_VIEW.cy - hpY) / GLOBE_VIEW.r;
const rr = texX * texX + texY * texY;
if (rr > 1.001) {
return null;
}
return {
x: texX,
y: -texY,
z: Math.sqrt(Math.max(0, 1 - rr)),
};
}
function hubPointsToBodyRing(points) {
const out = [];
for (let i = 0; i < points.length; i++) {
const body = hubPixelToBody(points[i].x, points[i].y);
if (body) {
out.push(body);
}
}
return out;
}
function ellipseBodyRing(ellipse, steps) {
const out = [];
for (let i = 0; i <= steps; i++) {
const a = (i / steps) * Math.PI * 2;
const body = hubPixelToBody(
ellipse.cx + ellipse.rx * Math.cos(a),
ellipse.cy + ellipse.ry * Math.sin(a)
);
if (body) {
out.push(body);
}
}
return out;
}
function parallelStyle(bright, scale, z) {
const t = Math.max(0, Math.min(1, (z - FRONT_Z) / (0.95 - FRONT_Z)));
if (t <= 0) {
return null;
}
const alpha = bright ? 0.55 + 0.45 * t : 0.35 + 0.5 * t;
const rgb = bright ? '219,231,246' : '231,236,243';
return {
color: 'rgba(' + rgb + ',' + alpha.toFixed(3) + ')',
width: Math.max(bright ? 3 : 2, scale * (bright ? 0.009 : 0.006)) * (0.5 + 0.5 * t),
};
}
function meridianStyle(prime, scale, z) {
const t = Math.max(0, Math.min(1, (z - FRONT_Z) / (0.95 - FRONT_Z)));
if (t <= 0) {
return null;
}
const alpha = prime ? 0.5 + 0.48 * t : 0.3 + 0.45 * t;
return {
color: 'rgba(238,245,255,' + alpha.toFixed(3) + ')',
width: Math.max(prime ? 3 : 2.5, scale * (prime ? 0.0095 : 0.008)) * (0.45 + 0.55 * t),
};
}
function strokeBodyRing(ctx, cx, cy, r, rotY, bodies, styleAt) {
let run = [];
function flush() {
if (run.length < 2) {
run = [];
return;
}
let zSum = 0;
for (let i = 0; i < run.length; i++) {
zSum += run[i].z;
}
const style = styleAt(zSum / run.length);
if (!style) {
run = [];
return;
}
ctx.beginPath();
ctx.moveTo(run[0].sx, run[0].sy);
for (let i = 1; i < run.length; i++) {
ctx.lineTo(run[i].sx, run[i].sy);
}
ctx.strokeStyle = style.color;
ctx.lineWidth = style.width;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.stroke();
run = [];
}
for (let i = 0; i < bodies.length; i++) {
const view = bodyToView(bodies[i], rotY);
if (view.z <= FRONT_Z) {
flush();
continue;
}
run.push({
sx: cx + view.x * r,
sy: cy - view.y * r,
z: view.z,
});
}
flush();
}
/** Hub SVG grid: EW ellipses + NS meridian paths (matches globe3d=0 artwork geometry). */
function drawHubGrid(ctx, cx, cy, r, rotY, layoutScale) {
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.clip();
HUB_EW_PARALLELS.forEach(function (ellipse) {
strokeBodyRing(ctx, cx, cy, r, rotY, ellipseBodyRing(ellipse, AXIS_STEPS), function (z) {
return parallelStyle(ellipse.bright, layoutScale, z);
});
});
HUB_NS_MERIDIANS.forEach(function (mer) {
strokeBodyRing(ctx, cx, cy, r, rotY, hubPointsToBodyRing(mer.points), function (z) {
return meridianStyle(mer.prime, layoutScale, z);
});
});
ctx.restore();
}
function sampleHub(layer, tex) {
const hp = {
x: GLOBE_VIEW.cx + tex.x * GLOBE_VIEW.r,
y: GLOBE_VIEW.cy - tex.y * GLOBE_VIEW.r,
};
const sx = (hp.x / GLOBE_VIEW.imgW) * (layer.w - 1);
const sy = (hp.y / GLOBE_VIEW.imgH) * (layer.h - 1);
return sampleImageDataBilinear(layer.data, layer.w, layer.h, sx, sy);
}
function texLonLat(tex) {
return {
lon: Math.atan2(tex.x, tex.z),
lat: Math.asin(Math.max(-1, Math.min(1, tex.y))),
};
}
function sampleMercFlat(mercFlat, lon, lat) {
const u = ((lon + Math.PI) / (2 * Math.PI)) * (mercFlat.w - 1);
const v = (0.5 - lat / Math.PI) * (mercFlat.h - 1);
return sampleImageDataBilinear(mercFlat.data, mercFlat.w, mercFlat.h, u, v);
}
function isLandPixel(r, g, b, a) {
return a > 10 && r > 100 && g < 140 && b < 140;
}
/** Light hub grid strokes baked into hub-logo-fragment-globe.svg (vector grid drawn separately). */
function isGridPixel(r, g, b, a) {
if (a < 12) {
return false;
}
const lum = r * 0.299 + g * 0.587 + b * 0.114;
if (lum < 125) {
return false;
}
if (g < 155 || b < 155) {
return false;
}
return Math.max(r, g, b) - Math.min(r, g, b) < 85;
}
function sampleOceanRgba(globe, tex) {
if (tex.z <= FRONT_Z) {
return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255];
}
const s = sampleHub(globe, tex);
if (s[3] > 8 && !isGridPixel(s[0], s[1], s[2], s[3])) {
return [s[0], s[1], s[2], 255];
}
return [OCEAN_FALLBACK.r, OCEAN_FALLBACK.g, OCEAN_FALLBACK.b, 255];
}
function sampleLandRgba(continents, mercFlat, tex) {
if (tex.z > FRONT_Z) {
const ortho = sampleHub(continents, tex);
if (ortho[3] > 6) {
return ortho;
}
return null;
}
if (tex.z > -FRONT_Z) {
return null;
}
const ll = texLonLat(tex);
const flat = sampleMercFlat(mercFlat, ll.lon, ll.lat);
if (isLandPixel(flat[0], flat[1], flat[2], flat[3])) {
return [flat[0], flat[1], flat[2], Math.min(255, flat[3])];
}
return null;
}
function fillDiskRegion(ctx, cx, cy, r, rotY, fn) {
const r2 = r * r;
const x0 = Math.max(0, Math.floor(cx - r));
const y0 = Math.max(0, Math.floor(cy - r));
const x1 = Math.min(ctx.canvas.width - 1, Math.ceil(cx + r));
const y1 = Math.min(ctx.canvas.height - 1, Math.ceil(cy + r));
const w = x1 - x0 + 1;
const h = y1 - y0 + 1;
if (w <= 0 || h <= 0) {
return null;
}
const out = ctx.createImageData(w, h);
const px = out.data;
let p = 0;
for (let dy = y0; dy <= y1; dy++) {
for (let dx = x0; dx <= x1; dx++) {
const ox = dx - cx;
const oy = dy - cy;
if (ox * ox + oy * oy > r2) {
px[p++] = 0;
px[p++] = 0;
px[p++] = 0;
px[p++] = 0;
continue;
}
const nx = ox / r;
const ny = oy / r;
const nz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny));
const tex = bodyToTex(inverseRotateY(nx, ny, nz, rotY));
const rgba = fn(tex);
px[p++] = rgba[0];
px[p++] = rgba[1];
px[p++] = rgba[2];
px[p++] = rgba[3] !== undefined ? rgba[3] : 255;
}
}
return { out, x0, y0 };
}
function blitLayer(ctx, cx, cy, r, layer) {
if (!layer) {
return;
}
const { out, x0, y0 } = layer;
const w = out.width;
const h = out.height;
const src = out.data;
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.clip();
const existing = ctx.getImageData(x0, y0, w, h);
const dst = existing.data;
for (let i = 0; i < src.length; i += 4) {
const sa = src[i + 3];
if (sa === 0) {
continue;
}
if (sa === 255) {
dst[i] = src[i];
dst[i + 1] = src[i + 1];
dst[i + 2] = src[i + 2];
dst[i + 3] = 255;
continue;
}
const a = sa / 255;
dst[i] = Math.round(src[i] * a + dst[i] * (1 - a));
dst[i + 1] = Math.round(src[i + 1] * a + dst[i + 1] * (1 - a));
dst[i + 2] = Math.round(src[i + 2] * a + dst[i + 2] * (1 - a));
dst[i + 3] = 255;
}
ctx.putImageData(existing, x0, y0);
ctx.restore();
}
function drawRim(ctx, globeImg, cx, cy, r, rim, layoutScale) {
const drawW = GLOBE_VIEW.imgW * layoutScale;
const drawH = GLOBE_VIEW.imgH * layoutScale;
const dx = cx - GLOBE_VIEW.cx * layoutScale;
const dy = cy - GLOBE_VIEW.cy * layoutScale;
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, rim, 0, Math.PI * 2);
ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
ctx.clip();
ctx.drawImage(globeImg, 0, 0, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH, dx, dy, drawW, drawH);
ctx.restore();
}
export async function mountHubGlobe(stage, opts) {
const build = opts.build || 'earth3d';
const reducedMotion = !!opts.reducedMotion;
const canvas = document.createElement('canvas');
canvas.className = 'hub-globe-canvas';
canvas.setAttribute('aria-hidden', 'true');
stage.appendChild(canvas);
const urls = {
globe: hubAsset('assets/hub-logo-fragment-globe.svg', build),
continents: hubAsset('assets/hub-logo-continents-mercator-globe.svg', build),
mercatorFlat: hubAsset('assets/hub-logo-continents-mercator-flat.svg', build),
};
let globeImg;
let continentsImg;
let mercatorFlatImg;
try {
[globeImg, continentsImg, mercatorFlatImg] = await Promise.all([
loadImage(urls.globe),
loadImage(urls.continents),
loadImage(urls.mercatorFlat),
]);
} catch (e) {
console.warn('HubGlobe: asset load failed', e);
canvas.remove();
return null;
}
const globe = rasterizeImage(globeImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH, RASTER_SCALE);
const continents = rasterizeImage(continentsImg, GLOBE_VIEW.imgW, GLOBE_VIEW.imgH, RASTER_SCALE);
const mercW = (mercatorFlatImg.naturalWidth || 800) * 2;
const mercH = (mercatorFlatImg.naturalHeight || 519) * 2;
const mercFlat = rasterizeImage(mercatorFlatImg, mercW, mercH, 1);
let rotation = typeof opts.initialRotationY === 'number' ? opts.initialRotationY : 0;
let angularVelocity = reducedMotion ? 0 : AUTO_SPIN_RAD_S;
let dragging = false;
let lastPointerX = 0;
let lastPointerTime = 0;
let raf = 0;
let lastFrame = performance.now();
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const resize = () => {
const rect = globeCoverRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const px = Math.max(64, Math.floor(rect.width * dpr));
canvas.style.left = rect.left + 'px';
canvas.style.top = rect.top + 'px';
canvas.style.width = rect.width + 'px';
canvas.style.height = rect.height + 'px';
canvas.width = px;
canvas.height = px;
return { rect, dpr, px };
};
let layout = resize();
window.addEventListener('resize', () => {
layout = resize();
});
const render = () => {
const { rect, dpr, px } = layout;
const cx = (rect.cx - rect.left) * dpr;
const cy = (rect.cy - rect.top) * dpr;
const r = rect.r * dpr;
const rim = rect.rim * dpr;
const layoutScale = rect.scale * dpr;
ctx.clearRect(0, 0, px, px);
blitLayer(
ctx,
cx,
cy,
r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
return sampleOceanRgba(globe, tex);
})
);
blitLayer(
ctx,
cx,
cy,
r,
fillDiskRegion(ctx, cx, cy, r, rotation, function (tex) {
const l = sampleLandRgba(continents, mercFlat, tex);
return l || [0, 0, 0, 0];
})
);
drawHubGrid(ctx, cx, cy, r, rotation, layoutScale);
drawRim(ctx, globeImg, cx, cy, r, rim, layoutScale);
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(10,14,20,0.55)';
ctx.lineWidth = Math.max(2, layoutScale * 0.006);
ctx.stroke();
ctx.restore();
};
const onPointerDown = (ev) => {
if (ev.button !== 0) return;
dragging = true;
lastPointerX = ev.clientX;
lastPointerTime = performance.now();
canvas.setPointerCapture(ev.pointerId);
canvas.classList.add('is-dragging');
ev.preventDefault();
};
const onPointerMove = (ev) => {
if (!dragging) return;
const now = performance.now();
const dx = ev.clientX - lastPointerX;
rotation += dx * DRAG_ROTATION_PER_PX;
if (now > lastPointerTime) {
const inst = (dx * DRAG_ROTATION_PER_PX) / ((now - lastPointerTime) / 1000);
angularVelocity = Math.max(-2.5, Math.min(2.5, inst));
}
lastPointerX = ev.clientX;
lastPointerTime = now;
ev.preventDefault();
};
const endDrag = (ev) => {
if (!dragging) return;
dragging = false;
canvas.classList.remove('is-dragging');
try {
canvas.releasePointerCapture(ev.pointerId);
} catch (ignored) {}
};
canvas.addEventListener('pointerdown', onPointerDown);
canvas.addEventListener('pointermove', onPointerMove);
canvas.addEventListener('pointerup', endDrag);
canvas.addEventListener('pointercancel', endDrag);
const tick = (now) => {
raf = requestAnimationFrame(tick);
const dt = Math.min(0.05, (now - lastFrame) / 1000);
lastFrame = now;
if (!dragging) {
if (!reducedMotion) {
const target = AUTO_SPIN_RAD_S;
angularVelocity += (target - angularVelocity) * 1.2 * dt;
if (Math.abs(angularVelocity - target) < 0.004) angularVelocity = target;
} else {
angularVelocity *= Math.exp(-4 * dt);
}
rotation += angularVelocity * dt;
}
render();
};
raf = requestAnimationFrame(tick);
stage.dataset.globe3d = 'active';
stage.dataset.globeAxis = 'ew-ns-grid';
return {
destroy() {
cancelAnimationFrame(raf);
window.removeEventListener('resize', resize);
canvas.remove();
delete stage.dataset.globe3d;
delete stage.dataset.globeAxis;
},
};
}

111
assets/hub-i18n.js Normal file
View File

@@ -0,0 +1,111 @@
(function (global) {
'use strict';
var STORAGE_KEY = 'crash_console_lang';
var SUPPORTED = ['en', 'ru'];
var DEFAULT_LANG = 'en';
var LANG_DISPLAY = { en: { flag: '\uD83C\uDDEC\uD83C\uDDE7', code: 'EN' }, ru: { flag: '\uD83C\uDDF7\uD83C\uDDFA', code: 'RU' } };
var lang = DEFAULT_LANG;
var catalog = {};
var fallback = {};
function hubAsset(rel) {
var link = document.querySelector('link[href*="hub.css"]');
var base = link ? (link.getAttribute('href') || '').replace(/[^/]*$/, '') : '';
return base + rel;
}
function storedLang() {
try {
var v = localStorage.getItem(STORAGE_KEY);
return SUPPORTED.indexOf(v) >= 0 ? v : DEFAULT_LANG;
} catch (e) {
return DEFAULT_LANG;
}
}
function t(key, params) {
var s = catalog[key] || fallback[key] || key;
if (params) {
Object.keys(params).forEach(function (k) {
s = s.split('{' + k + '}').join(String(params[k]));
});
}
return s;
}
function apply(root) {
var scope = root || document;
scope.querySelectorAll('[data-i18n]').forEach(function (el) {
var key = el.getAttribute('data-i18n');
if (!key) return;
el.textContent = t(key);
});
scope.querySelectorAll('[data-i18n-placeholder]').forEach(function (el) {
var key = el.getAttribute('data-i18n-placeholder');
if (key) el.setAttribute('placeholder', t(key));
});
scope.querySelectorAll('[data-i18n-aria]').forEach(function (el) {
var key = el.getAttribute('data-i18n-aria');
if (key) el.setAttribute('aria-label', t(key));
});
scope.querySelectorAll('[data-i18n-title]').forEach(function (el) {
var key = el.getAttribute('data-i18n-title');
if (key) el.setAttribute('title', t(key));
});
var disp = LANG_DISPLAY[lang] || LANG_DISPLAY.en;
document.querySelectorAll('.locale-flag').forEach(function (el) {
el.textContent = disp.flag;
});
document.querySelectorAll('.lang-select').forEach(function (sel) {
SUPPORTED.forEach(function (code) {
var meta = LANG_DISPLAY[code] || LANG_DISPLAY.en;
var opt = sel.querySelector('option[value="' + code + '"]');
if (opt) opt.textContent = meta.flag + ' ' + meta.code;
});
sel.value = lang;
});
document.documentElement.setAttribute('lang', lang);
var footerLeft = document.querySelector('.platform-footer__left[data-i18n]');
if (footerLeft) {
var holder = footerLeft.getAttribute('data-holder') || 'Anton Afanaasyeu';
var year = footerLeft.getAttribute('data-year') || String(new Date().getFullYear());
footerLeft.textContent = t('footer.copyright', { holder: holder, year: year });
}
}
function load(code) {
lang = SUPPORTED.indexOf(code) >= 0 ? code : DEFAULT_LANG;
try { localStorage.setItem(STORAGE_KEY, lang); } catch (e) {}
return fetch(hubAsset('assets/i18n/hub-' + lang + '.json'), { credentials: 'same-origin' })
.then(function (r) { return r.json(); })
.then(function (json) {
catalog = json || {};
if (lang !== DEFAULT_LANG) {
return fetch(hubAsset('assets/i18n/hub-en.json'), { credentials: 'same-origin' })
.then(function (r) { return r.json(); })
.then(function (fb) { fallback = fb || {}; });
}
fallback = catalog;
})
.then(function () { apply(document); });
}
function init() {
var sel = document.getElementById('lang-select');
lang = storedLang();
if (sel) sel.value = lang;
load(lang).then(function () {
if (sel) {
sel.addEventListener('change', function () { load(sel.value); });
}
});
}
global.HubI18n = { t: t, apply: apply, load: load, init: init };
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})(window);

160
assets/hub-landing.js Normal file
View File

@@ -0,0 +1,160 @@
(function () {
'use strict';
var PAGE_COUNT = 10;
var scrollEl = null;
var railEl = null;
var currentPage = 1;
function pageEl(n) {
return document.getElementById('landing-page-' + n);
}
function setActivePage(n, opts) {
n = Math.max(1, Math.min(PAGE_COUNT, n | 0));
currentPage = n;
var page = pageEl(n);
if (page && scrollEl) {
if (!opts || !opts.skipScroll) {
page.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
document.querySelectorAll('.landing-rail-dot').forEach(function (dot) {
var p = Number(dot.getAttribute('data-page'));
dot.classList.toggle('is-active', p === n);
dot.setAttribute('aria-current', p === n ? 'true' : 'false');
});
document.querySelectorAll('[data-goto-page]').forEach(function (el) {
var p = Number(el.getAttribute('data-goto-page'));
el.classList.toggle('is-active', p === n);
});
if (window.history && window.history.replaceState) {
var url = new URL(window.location.href);
if (n <= 1) {
url.searchParams.delete('page');
} else {
url.searchParams.set('page', String(n));
}
window.history.replaceState({ page: n }, '', url.toString());
}
}
function pageFromScroll() {
if (!scrollEl) return 1;
var mid = scrollEl.scrollTop + scrollEl.clientHeight * 0.4;
var best = 1;
var bestDist = Infinity;
for (var i = 1; i <= PAGE_COUNT; i++) {
var el = pageEl(i);
if (!el) continue;
var dist = Math.abs(el.offsetTop - mid);
if (dist < bestDist) {
bestDist = dist;
best = i;
}
}
return best;
}
function bindScrollSync() {
if (!scrollEl) return;
var ticking = false;
scrollEl.addEventListener('scroll', function () {
if (ticking) return;
ticking = true;
requestAnimationFrame(function () {
ticking = false;
var p = pageFromScroll();
if (p !== currentPage) {
setActivePage(p, { skipScroll: true });
}
});
}, { passive: true });
}
function bindRail() {
railEl = document.getElementById('landing-rail');
var handle = document.getElementById('landing-rail-handle');
if (handle && railEl) {
handle.addEventListener('mouseenter', function () {
railEl.classList.add('is-visible');
});
handle.addEventListener('focus', function () {
railEl.classList.add('is-visible');
});
var dragY = 0;
handle.addEventListener('pointerdown', function (ev) {
dragY = ev.clientY;
handle.setPointerCapture(ev.pointerId);
railEl.classList.add('is-visible');
});
handle.addEventListener('pointermove', function (ev) {
if (!handle.hasPointerCapture(ev.pointerId)) return;
var dy = ev.clientY - dragY;
if (Math.abs(dy) > 40) {
setActivePage(currentPage + (dy > 0 ? 1 : -1));
dragY = ev.clientY;
}
});
}
document.querySelectorAll('.landing-rail-dot').forEach(function (dot) {
dot.addEventListener('click', function () {
setActivePage(Number(dot.getAttribute('data-page')));
});
});
}
function bindGotoLinks() {
document.body.addEventListener('click', function (ev) {
var t = ev.target.closest('[data-goto-page]');
if (!t) return;
if (t.tagName === 'A' && t.getAttribute('href') === '#') {
ev.preventDefault();
}
setActivePage(Number(t.getAttribute('data-goto-page')));
});
}
function bindLeftNav() {
/* nav_shell.js handles #landing-nav-pane / #landing-nav-handle */
}
function initFromUrl() {
var params = new URLSearchParams(window.location.search);
var p = parseInt(params.get('page') || '1', 10);
if (p >= 1 && p <= PAGE_COUNT) {
setTimeout(function () {
setActivePage(p);
}, 80);
} else {
setActivePage(1, { skipScroll: true });
}
}
function init() {
scrollEl = document.getElementById('landing-scroll');
bindScrollSync();
bindRail();
bindGotoLinks();
bindLeftNav();
initFromUrl();
window.addEventListener('keydown', function (ev) {
if (ev.target && /input|textarea|select/i.test(ev.target.tagName)) return;
if (ev.key === 'PageDown' || ev.key === 'ArrowDown') {
ev.preventDefault();
setActivePage(currentPage + 1);
} else if (ev.key === 'PageUp' || ev.key === 'ArrowUp') {
ev.preventDefault();
setActivePage(currentPage - 1);
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.LandingNav = { goTo: setActivePage };
})();

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Compass layer">
<g id="globe-compass" transform="translate(1050 820)">
<circle cx="0" cy="0" r="52" fill="#0a0e14" fill-opacity="0.42" stroke="#c7d3e1" stroke-opacity="0.52" stroke-width="2"/>
<circle cx="0" cy="0" r="40" fill="none" stroke="#dbe5f1" stroke-opacity="0.64" stroke-width="1.5"/>
<path d="M 0 -38 L 9 0 L 0 38 L -9 0 Z" fill="#e7ecf3" fill-opacity="0.9"/>
<path d="M -38 0 L 0 -9 L 38 0 L 0 9 Z" fill="#8ea1b9" fill-opacity="0.86"/>
<path d="M 0 -28 L 6 0 L 0 28 L -6 0 Z" fill="#223246" fill-opacity="0.9"/>
<circle cx="0" cy="0" r="4.2" fill="#e7ecf3" fill-opacity="0.92"/>
<text x="0" y="-56" text-anchor="middle" fill="#e7ecf3" fill-opacity="0.72" font-size="12" font-family="Segoe UI, sans-serif">N</text>
<text x="0" y="68" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">S</text>
<text x="-66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">W</text>
<text x="66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">E</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Globe fragment 4x4">
<defs>
<radialGradient id="globeOcean" cx="36%" cy="28%" r="74%">
<stop offset="0%" stop-color="#2a3d56"/>
<stop offset="45%" stop-color="#1a2332"/>
<stop offset="100%" stop-color="#0a0e14"/>
</radialGradient>
<clipPath id="gridClip"><circle cx="720" cy="560" r="322"/></clipPath>
</defs>
<circle id="globe-body" cx="720" cy="560" r="330" fill="url(#globeOcean)"/>
<!-- Mercator-style 4x4 grid: 3 vertical + 3 horizontal lines -->
<g id="globe-grid" clip-path="url(#gridClip)" fill="none" stroke="#e7ecf3" stroke-opacity="0.92" stroke-width="7.5" stroke-linecap="round">
<path d="M 580 248 Q 510 560 580 872"/>
<path d="M 720 238 Q 720 560 720 882"/>
<path d="M 860 248 Q 930 560 860 872"/>
<ellipse cx="720" cy="395" rx="275" ry="80"/>
<ellipse cx="720" cy="560" rx="320" ry="105"/>
<ellipse cx="720" cy="725" rx="275" ry="80"/>
</g>
<circle id="globe-border" cx="720" cy="560" r="330" fill="none" stroke="#e7ecf3" stroke-width="15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Globe grid overlay build 20260527j">
<defs>
<linearGradient id="gradNorthPole" x1="830" y1="432" x2="610" y2="358" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#e7ecf3" stop-opacity="0.02"/>
<stop offset="100%" stop-color="#e7ecf3" stop-opacity="1"/>
</linearGradient>
<linearGradient id="gradSouthPole" x1="830" y1="754" x2="610" y2="680" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#e7ecf3" stop-opacity="0.02"/>
<stop offset="100%" stop-color="#e7ecf3" stop-opacity="1"/>
</linearGradient>
<linearGradient id="gradEquator" x1="1040" y1="560" x2="400" y2="560" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#e7ecf3" stop-opacity="0.02"/>
<stop offset="50%" stop-color="#e7ecf3" stop-opacity="1"/>
<stop offset="100%" stop-color="#e7ecf3" stop-opacity="0.02"/>
</linearGradient>
<linearGradient id="meridianSun" x1="390" y1="560" x2="1050" y2="560" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f2f6fc" stop-opacity="0.95"/>
<stop offset="52%" stop-color="#d7e0ec" stop-opacity="0.78"/>
<stop offset="100%" stop-color="#4f6178" stop-opacity="0.92"/>
</linearGradient>
<clipPath id="gridClip"><circle cx="720" cy="560" r="322"/></clipPath>
</defs>
<g id="globe-grid" clip-path="url(#gridClip)" fill="none" stroke-linecap="round" stroke-linejoin="round">
<circle id="globe-pivot" cx="720" cy="560" r="7" fill="#5eead4" stroke="#0a0e14" stroke-width="1.5"/>
<path id="globe-meridian-west" d="M 620 248 Q 358 560 620 872" stroke="url(#meridianSun)" stroke-opacity="0.98" stroke-width="8"/>
<path id="globe-meridian-prime" d="M 720 248 L 720 872" stroke="url(#meridianSun)" stroke-opacity="0.98" stroke-width="7.5"/>
<path id="globe-meridian-east" d="M 820 248 Q 1082 560 820 872" stroke="url(#meridianSun)" stroke-opacity="0.98" stroke-width="8"/>
<ellipse id="globe-equator" cx="720" cy="560" rx="318" ry="11" stroke="url(#gradEquator)" stroke-width="7.5"/>
<ellipse id="globe-north-pole" cx="720" cy="392" rx="260" ry="39" stroke="url(#gradNorthPole)" stroke-width="7.5"/>
<ellipse id="globe-south-pole" cx="720" cy="728" rx="260" ry="39" stroke="url(#gradSouthPole)" stroke-width="7.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Internal snapshot globe 20260526e">
<!-- Internal snapshot pointer for build 20260526e -->
<image href="hub-logo-fragment-globe.svg?v=20260526e" x="0" y="0" width="1920" height="1080"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Internal snapshot globe 20260527g">
<!-- Internal snapshot pointer for build 20260527g -->
<image href="hub-logo-fragment-globe.svg?v=20260527g" x="0" y="0" width="1920" height="1080"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Internal snapshot globe 20260527i">
<!-- Internal snapshot pointer for build 20260527i -->
<image href="hub-logo-fragment-globe.svg?v=20260527i" x="0" y="0" width="1920" height="1080"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Internal snapshot globe 20260527j">
<!-- Internal snapshot pointer for build 20260527j -->
<image href="hub-logo-fragment-globe.svg?v=20260527j" x="0" y="0" width="1920" height="1080"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Internal snapshot globe 20260530b">
<!-- Internal snapshot pointer for build 20260530b  vertical nav dock + Clockopia clock -->
<image href="hub-logo-fragment-globe.svg?v=20260530b" x="0" y="0" width="1920" height="1080"/>
</svg>

View File

@@ -0,0 +1,58 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Globe fragment 3x3 build 20260527i">
<!-- build=20260527i: thunder title pass + darker oceans with separate continent layer -->
<defs>
<radialGradient id="globeOcean" cx="24%" cy="18%" r="86%">
<stop offset="0%" stop-color="#466287"/>
<stop offset="42%" stop-color="#16263c"/>
<stop offset="100%" stop-color="#04060b"/>
</radialGradient>
<linearGradient id="gradNorthPole" x1="830" y1="432" x2="610" y2="358" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#e7ecf3" stop-opacity="0.02"/>
<stop offset="100%" stop-color="#e7ecf3" stop-opacity="1"/>
</linearGradient>
<linearGradient id="gradSouthPole" x1="830" y1="754" x2="610" y2="680" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#e7ecf3" stop-opacity="0.02"/>
<stop offset="100%" stop-color="#e7ecf3" stop-opacity="1"/>
</linearGradient>
<linearGradient id="gradEquator" x1="1040" y1="560" x2="400" y2="560" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#e7ecf3" stop-opacity="0.02"/>
<stop offset="50%" stop-color="#e7ecf3" stop-opacity="1"/>
<stop offset="100%" stop-color="#e7ecf3" stop-opacity="0.02"/>
</linearGradient>
<linearGradient id="meridianSun" x1="390" y1="560" x2="1050" y2="560" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f2f6fc" stop-opacity="0.95"/>
<stop offset="52%" stop-color="#d7e0ec" stop-opacity="0.78"/>
<stop offset="100%" stop-color="#4f6178" stop-opacity="0.92"/>
</linearGradient>
<linearGradient id="sunRim" x1="390" y1="560" x2="1050" y2="560" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f5f8ff" stop-opacity="0.98"/>
<stop offset="38%" stop-color="#dce7f5" stop-opacity="0.82"/>
<stop offset="100%" stop-color="#4a5c72" stop-opacity="0.7"/>
</linearGradient>
<filter id="sunRimGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3.2"/>
</filter>
<clipPath id="gridClip"><circle cx="720" cy="560" r="322"/></clipPath>
</defs>
<circle id="globe-body" cx="720" cy="560" r="330" fill="url(#globeOcean)"/>
<g id="globe-grid" clip-path="url(#gridClip)" fill="none" stroke-linecap="round" stroke-linejoin="round">
<circle id="globe-pivot" cx="720" cy="560" r="7" fill="#5eead4" stroke="#0a0e14" stroke-width="1.5"/>
<path id="globe-meridian-west" d="M 620 248 Q 358 560 620 872" stroke="url(#meridianSun)" stroke-opacity="0.98" stroke-width="8"/>
<path id="globe-meridian-prime" d="M 720 248 L 720 872" stroke="url(#meridianSun)" stroke-opacity="0.98" stroke-width="7.5"/>
<path id="globe-meridian-east" d="M 820 248 Q 1082 560 820 872" stroke="url(#meridianSun)" stroke-opacity="0.98" stroke-width="8"/>
<ellipse id="globe-equator" cx="720" cy="560" rx="318" ry="11" stroke="url(#gradEquator)" stroke-width="7.5"/>
<ellipse id="globe-north-pole" cx="720" cy="392" rx="260" ry="39" stroke="url(#gradNorthPole)" stroke-width="7.5"/>
<ellipse id="globe-south-pole" cx="720" cy="728" rx="260" ry="39" stroke="url(#gradSouthPole)" stroke-width="7.5"/>
</g>
<circle id="globe-border-glow" cx="720" cy="560" r="330" fill="none" stroke="url(#sunRim)" stroke-opacity="0.75" stroke-width="20" filter="url(#sunRimGlow)"/>
<circle id="globe-border" cx="720" cy="560" r="330" fill="none" stroke="url(#sunRim)" stroke-width="14"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,110 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Rocket fragment">
<defs>
<linearGradient id="hullTop" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="40%" stop-color="#e7ecf3"/>
<stop offset="100%" stop-color="#9aadc4"/>
</linearGradient>
<linearGradient id="hullSide" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#c5d0df"/>
<stop offset="55%" stop-color="#7f92a8"/>
<stop offset="100%" stop-color="#3a4a5e"/>
</linearGradient>
<linearGradient id="hullShadow" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#1a2332" stop-opacity="0.55"/>
<stop offset="100%" stop-color="#1a2332" stop-opacity="0"/>
</linearGradient>
<linearGradient id="stripeBlue" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#5ba3ff"/>
<stop offset="100%" stop-color="#1e4a8a"/>
</linearGradient>
<linearGradient id="wingGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#4f9bff"/>
<stop offset="100%" stop-color="#1a2332"/>
</linearGradient>
<linearGradient id="glassGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#0f1a28"/>
<stop offset="100%" stop-color="#243044"/>
</linearGradient>
<radialGradient id="engineGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="25%" stop-color="#5eead4"/>
<stop offset="55%" stop-color="#3d8bfd"/>
<stop offset="100%" stop-color="#3d8bfd" stop-opacity="0"/>
</radialGradient>
<filter id="engineBloom" x="-120%" y="-120%" width="340%" height="340%">
<feGaussianBlur stdDeviation="16" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="softShadow" x="-30%" y="-30%" width="160%" height="160%">
<feDropShadow dx="4" dy="8" stdDeviation="6" flood-color="#000" flood-opacity="0.45"/>
</filter>
</defs>
<g id="rocketship" transform="translate(1180,355) rotate(-30) scale(1,-1)" filter="url(#softShadow)">
<!-- Belly shadow -->
<path d="M -455 18 L -120 28 L 40 14 L -120 -4 Z" fill="url(#hullShadow)" opacity="0.65"/>
<!-- Lower hull -->
<path d="M -455 14 L -118 26 L 42 12 L 118 2 L 42 -8 L -118 -18 L -455 -8 Z" fill="url(#hullSide)" stroke="#2d3a4f" stroke-width="1.2"/>
<!-- Upper hull -->
<path d="M -118 -18 L 42 -8 L 118 2 L 42 12 L -118 26 L -420 22 L -455 14 L -420 6 L -118 -18 Z" fill="url(#hullTop)" stroke="#8b9cb3" stroke-width="1"/>
<!-- Side blue stripe -->
<path d="M -400 4 L -150 8 L -80 6 L -150 2 L -400 -2 Z" fill="url(#stripeBlue)"/>
<!-- Panel lines -->
<g stroke="#8b9cb3" stroke-opacity="0.45" stroke-width="0.75" fill="none">
<path d="M -380 6 L -380 -6"/><path d="M -300 8 L -300 -8"/><path d="M -220 10 L -220 -10"/>
<path d="M -140 10 L -140 -10"/><path d="M -60 8 L -60 -8"/>
<path d="M -380 -6 Q -250 -4 -118 -8"/><path d="M -380 6 Q -250 4 -118 10"/>
</g>
<!-- Nose cone -->
<path d="M 42 -8 L 118 2 L 42 12 L 8 10 L 8 -4 Z" fill="#f4f7fb" stroke="#9aadc4" stroke-width="1"/>
<path d="M 42 -6 L 96 2 L 42 10 L 18 8 L 18 -2 Z" fill="#ffffff" opacity="0.45"/>
<!-- Top canopy (rocketship2) -->
<path d="M -80 -14 L 72 -8 L 96 2 L 72 6 L -80 2 L -110 -4 Z" fill="url(#glassGrad)" stroke="#3d8bfd" stroke-width="0.9"/>
<g fill="#5eead4" opacity="0.9">
<circle cx="-52" cy="-6" r="2.2"/><circle cx="-28" cy="-5" r="2"/><circle cx="-4" cy="-4" r="1.8"/>
<circle cx="20" cy="-3" r="1.6"/><circle cx="44" cy="-2" r="1.4"/>
</g>
<!-- Belly observation windows -->
<path d="M -220 16 L -120 18 L -90 24 L -120 30 L -220 28 L -250 24 Z" fill="url(#glassGrad)" stroke="#3d8bfd" stroke-width="0.8" opacity="0.95"/>
<g stroke="#5eead4" stroke-width="0.6" opacity="0.7">
<line x1="-210" y1="22" x2="-210" y2="26"/><line x1="-180" y1="22" x2="-180" y2="26"/>
<line x1="-150" y1="23" x2="-150" y2="27"/><line x1="-130" y1="24" x2="-130" y2="28"/>
</g>
<!-- Main swept wings -->
<path d="M -340 26 L -190 28 L -118 118 L -300 98 Z" fill="url(#wingGrad)" stroke="#1a2332" stroke-width="1"/>
<path d="M -340 26 L -190 28 L -150 78" fill="none" stroke="#5eead4" stroke-width="1.2" opacity="0.55"/>
<path d="M -280 48 L -200 50 L -168 88 L -258 76 Z" fill="#243044" opacity="0.5"/>
<!-- Dorsal fin -->
<path d="M -320 -26 L -210 -24 L -188 -68 L -292 -56 Z" fill="#3d8bfd" stroke="#1a2332" stroke-width="1"/>
<path d="M -320 -26 L -230 -24 L -205 -48" fill="none" stroke="#5eead4" stroke-width="1" opacity="0.5"/>
<!-- Tail V-stabilizers -->
<path d="M -430 6 L -468 -8 L -458 -32 L -422 -20 Z" fill="#4a5a6e" stroke="#2d3a4f" stroke-width="0.9"/>
<path d="M -430 22 L -468 36 L -458 58 L -422 44 Z" fill="#4a5a6e" stroke="#2d3a4f" stroke-width="0.9"/>
<!-- Engine housing -->
<path d="M -448 -16 L -478 -20 L -486 -4 L -456 0 Z" fill="#2d3a4f" stroke="#8b9cb3" stroke-width="0.8"/>
<path d="M -448 16 L -478 20 L -486 4 L -456 0 Z" fill="#2d3a4f" stroke="#8b9cb3" stroke-width="0.8"/>
<ellipse cx="-478" cy="0" rx="22" ry="18" fill="#1a2332" stroke="#8b9cb3" stroke-width="1"/>
<ellipse cx="-488" cy="0" rx="14" ry="11" fill="#0a0e14"/>
<!-- Engine glow -->
<g filter="url(#engineBloom)">
<ellipse cx="-458" cy="0" rx="28" ry="24" fill="url(#engineGlow)" opacity="0.95"/>
<ellipse cx="-472" cy="0" rx="44" ry="36" fill="url(#engineGlow)" opacity="0.5"/>
</g>
<ellipse cx="-458" cy="0" rx="11" ry="9" fill="#ffffff" opacity="0.98"/>
<circle cx="100" cy="6" r="3" fill="#5eead4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -0,0 +1,75 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Space fragment">
<!-- Static vector sky (not runtime-generated). Editable: #space-bg, #nebula-*, #stars-layer, #stars-blinks -->
<defs>
<radialGradient id="spaceBgGrad" cx="52%" cy="48%" r="78%">
<stop offset="0%" stop-color="#1a2332"/>
<stop offset="55%" stop-color="#0a0e14"/>
<stop offset="100%" stop-color="#060810"/>
</radialGradient>
<radialGradient id="nebulaAmber" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#b45309" stop-opacity="0.26"/>
<stop offset="55%" stop-color="#92400e" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#78350f" stop-opacity="0"/>
</radialGradient>
<radialGradient id="nebulaBlue" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3d8bfd" stop-opacity="0.22"/>
<stop offset="55%" stop-color="#1e3a5f" stop-opacity="0.11"/>
<stop offset="100%" stop-color="#0f172a" stop-opacity="0"/>
</radialGradient>
<radialGradient id="nebulaTeal" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#5eead4" stop-opacity="0.14"/>
<stop offset="100%" stop-color="#5eead4" stop-opacity="0"/>
</radialGradient>
<filter id="starBlink" x="-300%" y="-300%" width="700%" height="700%">
<feGaussianBlur stdDeviation="2.2" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<rect id="space-bg" width="1920" height="1080" fill="url(#spaceBgGrad)"/>
<ellipse id="nebula-amber" cx="280" cy="210" rx="420" ry="290" fill="url(#nebulaAmber)" transform="rotate(-16 280 210)"/>
<ellipse id="nebula-blue" cx="1660" cy="860" rx="460" ry="320" fill="url(#nebulaBlue)" transform="rotate(14 1660 860)"/>
<ellipse id="nebula-teal" cx="1180" cy="180" rx="300" ry="200" fill="url(#nebulaTeal)" transform="rotate(8 1180 180)"/>
<g id="stars-layer" fill="#e7ecf3">
<circle cx="48" cy="42" r="0.9" opacity="0.5"/><circle cx="132" cy="96" r="0.7" opacity="0.42"/>
<circle cx="204" cy="24" r="1.0" opacity="0.55"/><circle cx="292" cy="78" r="0.8" opacity="0.4"/>
<circle cx="388" cy="52" r="0.9" opacity="0.48"/><circle cx="472" cy="114" r="0.7" opacity="0.44"/>
<circle cx="562" cy="28" r="1.1" opacity="0.58"/><circle cx="652" cy="82" r="0.8" opacity="0.4"/>
<circle cx="748" cy="46" r="0.9" opacity="0.46"/><circle cx="836" cy="108" r="0.7" opacity="0.42"/>
<circle cx="928" cy="34" r="1.0" opacity="0.56"/><circle cx="1024" cy="88" r="0.8" opacity="0.45"/>
<circle cx="1124" cy="22" r="0.9" opacity="0.5"/><circle cx="1216" cy="94" r="1.0" opacity="0.54"/>
<circle cx="1324" cy="48" r="0.8" opacity="0.42"/><circle cx="1416" cy="116" r="0.9" opacity="0.48"/>
<circle cx="1524" cy="62" r="0.8" opacity="0.44"/><circle cx="1632" cy="30" r="1.1" opacity="0.58"/>
<circle cx="1736" cy="86" r="0.9" opacity="0.46"/><circle cx="1824" cy="148" r="0.8" opacity="0.4"/>
<circle cx="1888" cy="64" r="1.0" opacity="0.52"/>
<circle cx="72" cy="198" r="0.8" opacity="0.42"/><circle cx="156" cy="268" r="0.9" opacity="0.46"/>
<circle cx="248" cy="318" r="0.7" opacity="0.38"/><circle cx="92" cy="418" r="1.0" opacity="0.52"/>
<circle cx="168" cy="538" r="0.8" opacity="0.4"/><circle cx="58" cy="628" r="0.9" opacity="0.45"/>
<circle cx="138" cy="728" r="0.7" opacity="0.38"/><circle cx="68" cy="838" r="1.0" opacity="0.5"/>
<circle cx="212" cy="898" r="0.8" opacity="0.42"/><circle cx="324" cy="978" r="0.9" opacity="0.48"/>
<circle cx="456" cy="1024" r="0.7" opacity="0.38"/><circle cx="588" cy="986" r="1.0" opacity="0.54"/>
<circle cx="704" cy="1052" r="0.8" opacity="0.4"/><circle cx="848" cy="1014" r="0.9" opacity="0.46"/>
<circle cx="968" cy="1062" r="0.7" opacity="0.36"/><circle cx="1088" cy="1026" r="1.0" opacity="0.52"/>
<circle cx="1228" cy="996" r="0.8" opacity="0.42"/><circle cx="1368" cy="1046" r="0.9" opacity="0.48"/>
<circle cx="1508" cy="1012" r="0.7" opacity="0.38"/><circle cx="1628" cy="1058" r="1.0" opacity="0.52"/>
<circle cx="1752" cy="1024" r="0.8" opacity="0.42"/><circle cx="1868" cy="996" r="0.9" opacity="0.46"/>
<circle cx="1724" cy="208" r="0.9" opacity="0.5"/><circle cx="1848" cy="328" r="0.8" opacity="0.42"/>
<circle cx="1904" cy="458" r="1.0" opacity="0.54"/><circle cx="1796" cy="588" r="0.8" opacity="0.4"/>
<circle cx="1876" cy="712" r="0.9" opacity="0.46"/><circle cx="1768" cy="832" r="0.7" opacity="0.38"/>
<circle cx="118" cy="156" r="1.4" opacity="0.66"/><circle cx="392" cy="188" r="1.5" opacity="0.68"/>
<circle cx="528" cy="944" r="1.4" opacity="0.62"/><circle cx="1176" cy="164" r="1.6" opacity="0.7"/>
<circle cx="1476" cy="886" r="1.4" opacity="0.64"/><circle cx="1776" cy="388" r="1.5" opacity="0.66"/>
<circle cx="172" cy="312" r="2.1" opacity="0.78"/><circle cx="1692" cy="146" r="1.9" opacity="0.74"/>
<circle cx="992" cy="928" r="2.0" opacity="0.72"/>
</g>
<g id="stars-blinks" filter="url(#starBlink)" opacity="0.85">
<path d="M 228 146 L 231 134 L 234 146 L 246 149 L 234 152 L 231 164 L 228 152 L 216 149 Z" fill="#e7ecf3"/>
<path d="M 1544 218 L 1547 208 L 1550 218 L 1562 221 L 1550 224 L 1547 234 L 1544 224 L 1532 221 Z" fill="#5eead4"/>
<path d="M 1824 818 L 1827 808 L 1830 818 L 1842 821 L 1830 824 L 1827 834 L 1824 824 L 1812 821 Z" fill="#e7ecf3"/>
<path d="M 1384 78 L 1386 72 L 1388 78 L 1396 80 L 1388 82 L 1386 88 L 1384 82 L 1376 80 Z" fill="#3d8bfd"/>
<path d="M 468 842 L 470 837 L 472 842 L 478 844 L 472 846 L 470 851 L 468 846 L 462 844 Z" fill="#e7ecf3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,58 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Tail fragment">
<defs>
<linearGradient id="plumeOuter" x1="0%" y1="50%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#5eead4" stop-opacity="0"/>
<stop offset="15%" stop-color="#5eead4" stop-opacity="0.18"/>
<stop offset="45%" stop-color="#3d8bfd" stop-opacity="0.48"/>
<stop offset="75%" stop-color="#5eead4" stop-opacity="0.78"/>
<stop offset="100%" stop-color="#ffffff" stop-opacity="0.95"/>
</linearGradient>
<linearGradient id="plumeMid" x1="0%" y1="50%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#3d8bfd" stop-opacity="0"/>
<stop offset="50%" stop-color="#5eead4" stop-opacity="0.55"/>
<stop offset="100%" stop-color="#ffffff" stop-opacity="0.88"/>
</linearGradient>
<radialGradient id="nozzleBloom" cx="100%" cy="50%" r="40%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="35%" stop-color="#5eead4" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#3d8bfd" stop-opacity="0"/>
</radialGradient>
<filter id="plumeBlur" x="-50%" y="-100%" width="200%" height="300%">
<feGaussianBlur stdDeviation="12" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<g id="exhaust-tail" filter="url(#plumeBlur)">
<!-- Rounded volumetric plume: thin at Earth limb, wide at nozzle (~804,584) -->
<path d="
M 372 832
C 452 778, 538 718, 628 658
C 712 602, 772 572, 804 584
C 818 590, 826 598, 832 606
C 768 618, 688 668, 598 728
C 508 788, 428 842, 358 868
Z" fill="url(#plumeOuter)" opacity="0.7"/>
<path d="
M 388 840
C 468 788, 558 728, 648 672
C 718 624, 778 592, 802 584
C 810 586, 814 590, 818 594
C 758 602, 688 642, 618 688
C 538 738, 458 788, 392 812
Z" fill="url(#plumeMid)" opacity="0.82"/>
<!-- Inner hot core -->
<path d="
M 620 668
Q 710 612, 788 588
Q 798 584, 804 584
Q 798 588, 788 592
Q 710 618, 628 672
Z" fill="#ffffff" opacity="0.55"/>
</g>
<ellipse id="exhaust-nozzle-bloom" cx="812" cy="592" rx="48" ry="32" fill="url(#nozzleBloom)" opacity="0.9"/>
<ellipse cx="804" cy="584" rx="16" ry="12" fill="#ffffff" opacity="0.95"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Android Cast logo mark">
<image href="hub-logo-fragment-globe.svg" x="0" y="0" width="1920" height="1080"/>
</svg>

After

Width:  |  Height:  |  Size: 268 B

14
assets/hub-thunder.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="46" viewBox="0 0 340 46" role="img" aria-label="Thunder schematic underline">
<defs>
<linearGradient id="thunderFill" x1="0%" y1="50%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#8ecfff" stop-opacity="0.22"/>
<stop offset="18%" stop-color="#c9ecff" stop-opacity="0.98"/>
<stop offset="50%" stop-color="#ffffff" stop-opacity="1"/>
<stop offset="82%" stop-color="#c9ecff" stop-opacity="0.98"/>
<stop offset="100%" stop-color="#8ecfff" stop-opacity="0.22"/>
</linearGradient>
</defs>
<path d="M 10 9 L 196 9" fill="none" stroke="url(#thunderFill)" stroke-width="5.5" stroke-linecap="round"/>
<path d="M 196 9 L 248 31" fill="none" stroke="url(#thunderFill)" stroke-width="5.5" stroke-linecap="round"/>
<path d="M 248 31 L 330 31" fill="none" stroke="url(#thunderFill)" stroke-width="5.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 932 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45.279 45.279" aria-hidden="true">
<symbol id="resizeArrow" viewBox="0 0 45.279 45.279">
<path d="M44.106,42.943H3.987L44.933,1.995c0.456-0.455,0.456-1.196,0-1.651c-0.452-0.455-1.195-0.455-1.651,0L2.336,41.292V1.168
C2.336,0.524,1.81,0,1.168,0C0.523,0,0,0.524,0,1.168v44.111h44.111c0.644,0,1.168-0.522,1.168-1.168
C45.279,43.469,44.755,42.943,44.106,42.943z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 451 B

62
assets/i18n/hub-en.json Normal file
View File

@@ -0,0 +1,62 @@
{
"nav.androidcast": "AndroidCast",
"nav.about": "About",
"nav.why": "Why",
"nav.tech": "Technologies",
"nav.resources": "Resources",
"nav.people": "People",
"nav.culture": "Culture",
"nav.pricing": "Pricing",
"nav.contact": "Contact us",
"nav.console": "Console",
"nav.git": "Git",
"nav.issues": "Issues",
"nav.tickets": "Tickets",
"nav.crashes": "Crashes",
"nav.graphs": "Analytics",
"nav.live_sessions": "Live sessions",
"nav.education": "Education (demo)",
"nav.remote": "Remote access",
"nav.short_links": "Short links",
"nav.short_links_desc": "Mint bearer tokens and shorten URLs for s.f0xx.org.",
"nav.console_desc": "Issue triage home and workspace cards.",
"nav.git_desc": "Browse repos, branches, and tags.",
"nav.issues_desc": "Browse and triage issues.",
"nav.tickets_desc": "Roadmap tasks, QA items, and tags.",
"nav.graphs_desc": "Sessions, live cast stats, and device activity.",
"nav.live_sessions_desc": "Cast intents, join stats, session history.",
"nav.education_desc": "Browser screen-share trial (5 min).",
"nav.remote_desc": "WireGuard sessions and device reachability.",
"nav.builder_desc": "Docker CI for APK baking and OTA artifacts.",
"nav.builder": "Builder",
"nav.toggle": "Navigation",
"nav.security": "Security",
"nav.logout": "Logout",
"nav.sign_in": "Sign in",
"lang.label": "Language",
"theme.label": "Theme",
"rail.label": "Page navigation",
"rail.handle": "Show page navigation",
"page1.title": "Android Cast",
"page1.lead": "Screen casting, crash intelligence, and CI — one ecosystem.",
"page1.cta": "Explore AndroidCast",
"page2.title": "AndroidCast",
"page2.lead": "Mirror Android screens with low latency, native codecs, and operator-grade tooling.",
"page3.title": "About",
"page3.lead": "Android Cast connects devices, backends, and operators across your stack.",
"page4.title": "Why AndroidCast",
"page4.lead": "Built for teams who need reliability, observability, and control — not another black box.",
"page5.title": "Technologies",
"page5.lead": "WebRTC, Opus/Speex, WireGuard remote access, PHP consoles, and Docker CI pipelines.",
"page6.title": "Resources",
"page6.lead": "Documentation, deployment guides, and integration patterns for your environment.",
"page7.title": "People",
"page7.lead": "Engineers and operators shipping cast, crash, and build workflows every day.",
"page8.title": "Culture",
"page8.lead": "Open tooling, green master branches, and honest postmortems.",
"page9.title": "Pricing",
"page9.lead": "Self-hosted core; enterprise options for SLA, SSO, and dedicated builders.",
"page10.title": "Contact us",
"page10.lead": "Reach the maintainer for partnerships, support, or deployment help.",
"footer.copyright": "© {holder}, {year}"
}

62
assets/i18n/hub-ru.json Normal file
View File

@@ -0,0 +1,62 @@
{
"nav.androidcast": "AndroidCast",
"nav.about": "О проекте",
"nav.why": "Зачем",
"nav.tech": "Технологии",
"nav.resources": "Ресурсы",
"nav.people": "Люди",
"nav.culture": "Культура",
"nav.pricing": "Цены",
"nav.contact": "Контакты",
"nav.console": "Консоль",
"nav.git": "Git",
"nav.issues": "Проблемы",
"nav.tickets": "Тикеты",
"nav.crashes": "Крэши",
"nav.graphs": "Аналитика",
"nav.live_sessions": "Live-сессии",
"nav.education": "Education (demo)",
"nav.remote": "Удалённый доступ",
"nav.short_links": "Короткие ссылки",
"nav.short_links_desc": "Токены и сокращение URL для s.f0xx.org.",
"nav.console_desc": "Главная консоли и карточки разделов.",
"nav.git_desc": "Репозитории, ветки и теги.",
"nav.issues_desc": "Просмотр и разбор issues.",
"nav.tickets_desc": "Задачи roadmap, QA и теги.",
"nav.graphs_desc": "Сессии, live cast и активность устройств.",
"nav.live_sessions_desc": "Intent, join-статистика, история сессий.",
"nav.education_desc": "Пробный screen-share в браузере (5 мин).",
"nav.remote_desc": "Сессии WireGuard и доступ к устройствам.",
"nav.builder_desc": "Docker CI для сборки APK и OTA.",
"nav.builder": "Сборщик",
"nav.toggle": "Навигация",
"nav.security": "Безопасность",
"nav.logout": "Выход",
"nav.sign_in": "Вход",
"lang.label": "Язык",
"theme.label": "Тема",
"rail.label": "Навигация по страницам",
"rail.handle": "Показать навигацию",
"page1.title": "Android Cast",
"page1.lead": "Трансляция экрана, аналитика сбоев и CI — одна экосистема.",
"page1.cta": "Узнать об AndroidCast",
"page2.title": "AndroidCast",
"page2.lead": "Зеркалирование Android с низкой задержкой, нативными кодеками и инструментами оператора.",
"page3.title": "О проекте",
"page3.lead": "Android Cast связывает устройства, бэкенды и операторов в едином стеке.",
"page4.title": "Зачем AndroidCast",
"page4.lead": "Для команд, которым нужны надёжность, наблюдаемость и контроль — не чёрный ящик.",
"page5.title": "Технологии",
"page5.lead": "WebRTC, Opus/Speex, WireGuard, PHP-консоли и Docker CI.",
"page6.title": "Ресурсы",
"page6.lead": "Документация, деплой и паттерны интеграции для вашей среды.",
"page7.title": "Люди",
"page7.lead": "Инженеры и операторы, которые каждый день развивают cast, crash и сборки.",
"page8.title": "Культура",
"page8.lead": "Открытые инструменты, зелёный master и честные postmortem.",
"page9.title": "Цены",
"page9.lead": "Self-hosted ядро; enterprise — SLA, SSO и выделенные builder-ы.",
"page10.title": "Контакты",
"page10.lead": "Связь с maintainer: партнёрство, поддержка, помощь с деплоем.",
"footer.copyright": "© {holder}, {year}"
}

BIN
assets/mercator-source.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<!-- Overtime Bold style 7-segment LCD (full alphabet) -->
<defs>
<symbol id="ot-space" viewBox="0 0 8 32"></symbol>
<symbol id="ot-0" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-1" viewBox="0 0 20 32"><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-2" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-3" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-4" viewBox="0 0 20 32"><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-5" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-6" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-7" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-8" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-9" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-A" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/></symbol>
<symbol id="ot-B" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-C" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-D" viewBox="0 0 20 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-E" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-F" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/></symbol>
<symbol id="ot-G" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="10" y="15" width="9" height="3" rx="1"/></symbol>
<symbol id="ot-H" viewBox="0 0 20 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/></symbol>
<symbol id="ot-I" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-J" viewBox="0 0 20 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-K" viewBox="0 0 24 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="10" y="4" width="2.2" height="11" rx="1" transform="rotate(-30 11.1 9.5)"/><rect x="10" y="17" width="2.2" height="11" rx="1" transform="rotate(30 11.1 22.5)"/></symbol>
<symbol id="ot-L" viewBox="0 0 20 32"><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-M" viewBox="0 0 24 32"><rect x="1" y="4" width="3" height="24" rx="1"/><rect x="20" y="4" width="3" height="24" rx="1"/><rect x="7.1" y="5" width="2.6" height="13.2" rx="1" transform="rotate(28 8.4 11.6)"/><rect x="14.3" y="5" width="2.6" height="13.2" rx="1" transform="rotate(-28 15.6 11.6)"/></symbol>
<symbol id="ot-N" viewBox="0 0 24 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="6.5" y="6" width="2.4" height="12" rx="1" transform="rotate(32 7.7 12)"/><rect x="14.8" y="14" width="2.4" height="12" rx="1" transform="rotate(32 16 20)"/></symbol>
<symbol id="ot-O" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-P" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/></symbol>
<symbol id="ot-Q" viewBox="0 0 24 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/><rect x="14" y="18" width="2.2" height="10" rx="1" transform="rotate(38 15.1 23)"/></symbol>
<symbol id="ot-R" viewBox="0 0 24 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="12" y="17" width="2.2" height="10" rx="1" transform="rotate(38 13.1 22)"/></symbol>
<symbol id="ot-S" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="15.00" width="14.00" height="3.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-T" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="8" y="4" width="4" height="24" rx="1"/></symbol>
<symbol id="ot-U" viewBox="0 0 20 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-V" viewBox="0 0 20 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="7" y="17" width="2.2" height="12" rx="1" transform="rotate(24 8.1 23)"/><rect x="12" y="17" width="2.2" height="12" rx="1" transform="rotate(-24 13.1 23)"/></symbol>
<symbol id="ot-W" viewBox="0 0 24 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/><rect x="5" y="17" width="2" height="11" rx="1" transform="rotate(18 6 22.5)"/><rect x="9.5" y="17" width="2" height="11" rx="1" transform="rotate(-18 10.5 22.5)"/><rect x="14" y="17" width="2" height="11" rx="1" transform="rotate(18 15 22.5)"/></symbol>
<symbol id="ot-X" viewBox="0 0 24 32"><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="16.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="7" y="5" width="2.2" height="11" rx="1" transform="rotate(32 8.1 10.5)"/><rect x="11" y="16" width="2.2" height="11" rx="1" transform="rotate(-32 12.1 21.5)"/></symbol>
<symbol id="ot-Y" viewBox="0 0 20 32"><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="8" y="15" width="4" height="14" rx="1"/></symbol>
<symbol id="ot-Z" viewBox="0 0 20 32"><rect x="3.00" y="1.00" width="14.00" height="3.00" rx="1"/><rect x="1.00" y="4.00" width="3.00" height="11.00" rx="1"/><rect x="1.00" y="17.00" width="3.00" height="11.00" rx="1"/><rect x="3.00" y="28.00" width="14.00" height="3.00" rx="1"/></symbol>
<symbol id="ot-colon" viewBox="0 0 12 32"><rect x="4" y="10" width="4" height="4" rx="1"/><rect x="4" y="20" width="4" height="4" rx="1"/></symbol>
<symbol id="ot-plus" viewBox="0 0 16 32"><rect x="6" y="8" width="4" height="16" rx="1"/><rect x="2" y="14" width="12" height="4" rx="1"/></symbol>
<symbol id="ot-minus" viewBox="0 0 16 32"><rect x="2" y="14" width="12" height="4" rx="1"/></symbol>
<symbol id="ot-slash" viewBox="0 0 14 32"><rect x="11" y="5" width="2.2" height="10" rx="1" transform="rotate(52 12.1 10)"/><rect x="4" y="19" width="2.2" height="10" rx="1" transform="rotate(52 5.1 24)"/></symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<!-- Quartz (TS) style 7-segment LCD (full alphabet) -->
<defs>
<symbol id="qz-space" viewBox="0 0 8 32"></symbol>
<symbol id="qz-0" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-1" viewBox="0 0 20 32"><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/></symbol>
<symbol id="qz-2" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-3" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-4" viewBox="0 0 20 32"><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/></symbol>
<symbol id="qz-5" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-6" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-7" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/></symbol>
<symbol id="qz-8" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-9" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-A" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/></symbol>
<symbol id="qz-B" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-C" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-D" viewBox="0 0 20 32"><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-E" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-F" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/></symbol>
<symbol id="qz-G" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-H" viewBox="0 0 20 32"><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/></symbol>
<symbol id="qz-I" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-J" viewBox="0 0 20 32"><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-K" viewBox="0 0 24 32"><rect x="1.30" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="2.88" y="15.04" width="18.24" height="2.88" rx="1"/><rect x="1.30" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="10" y="4" width="2.2" height="11" rx="1" transform="rotate(-30 11.1 9.5)"/><rect x="10" y="17" width="2.2" height="11" rx="1" transform="rotate(30 11.1 22.5)"/></symbol>
<symbol id="qz-L" viewBox="0 0 20 32"><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-M" viewBox="0 0 24 32"><rect x="2.88" y="1.28" width="18.24" height="2.88" rx="1"/><rect x="1.30" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="1.30" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="7.1" y="5" width="2.6" height="13.2" rx="1" transform="rotate(28 8.4 11.6)"/><rect x="14.3" y="5" width="2.6" height="13.2" rx="1" transform="rotate(-28 15.6 11.6)"/></symbol>
<symbol id="qz-N" viewBox="0 0 24 32"><rect x="1.30" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="1.30" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="6.5" y="6" width="2.4" height="12" rx="1" transform="rotate(32 7.7 12)"/><rect x="14.8" y="14" width="2.4" height="12" rx="1" transform="rotate(32 16 20)"/></symbol>
<symbol id="qz-O" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-P" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/></symbol>
<symbol id="qz-Q" viewBox="0 0 24 32"><rect x="2.88" y="1.28" width="18.24" height="2.88" rx="1"/><rect x="1.30" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="1.30" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="2.88" y="27.52" width="18.24" height="2.88" rx="1"/><rect x="14" y="18" width="2.2" height="10" rx="1" transform="rotate(38 15.1 23)"/></symbol>
<symbol id="qz-R" viewBox="0 0 24 32"><rect x="2.88" y="1.28" width="18.24" height="2.88" rx="1"/><rect x="1.30" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="2.88" y="15.04" width="18.24" height="2.88" rx="1"/><rect x="1.30" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="12" y="17" width="2.2" height="10" rx="1" transform="rotate(38 13.1 22)"/></symbol>
<symbol id="qz-S" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-T" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-U" viewBox="0 0 20 32"><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-V" viewBox="0 0 20 32"><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="7" y="17" width="2.2" height="12" rx="1" transform="rotate(24 8.1 23)"/><rect x="12" y="17" width="2.2" height="12" rx="1" transform="rotate(-24 13.1 23)"/></symbol>
<symbol id="qz-W" viewBox="0 0 24 32"><rect x="1.30" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="1.30" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="2.88" y="27.52" width="18.24" height="2.88" rx="1"/><rect x="5" y="17" width="2" height="11" rx="1" transform="rotate(18 6 22.5)"/><rect x="9.5" y="17" width="2" height="11" rx="1" transform="rotate(-18 10.5 22.5)"/><rect x="14" y="17" width="2" height="11" rx="1" transform="rotate(18 15 22.5)"/></symbol>
<symbol id="qz-X" viewBox="0 0 24 32"><rect x="1.30" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="4.16" width="3.36" height="10.56" rx="1"/><rect x="1.30" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="19.34" y="16.77" width="3.36" height="10.56" rx="1"/><rect x="7" y="5" width="2.2" height="11" rx="1" transform="rotate(32 8.1 10.5)"/><rect x="11" y="16" width="2.2" height="11" rx="1" transform="rotate(-32 12.1 21.5)"/></symbol>
<symbol id="qz-Y" viewBox="0 0 20 32"><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="16.12" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="15.04" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-Z" viewBox="0 0 20 32"><rect x="2.40" y="1.28" width="15.20" height="2.88" rx="1"/><rect x="1.08" y="4.16" width="2.80" height="10.56" rx="1"/><rect x="1.08" y="16.77" width="2.80" height="10.56" rx="1"/><rect x="2.40" y="27.52" width="15.20" height="2.88" rx="1"/></symbol>
<symbol id="qz-colon" viewBox="0 0 12 32"><rect x="4" y="10" width="4" height="4" rx="1"/><rect x="4" y="20" width="4" height="4" rx="1"/></symbol>
<symbol id="qz-plus" viewBox="0 0 16 32"><rect x="6" y="8" width="4" height="16" rx="1"/><rect x="2" y="14" width="12" height="4" rx="1"/></symbol>
<symbol id="qz-minus" viewBox="0 0 16 32"><rect x="2" y="14" width="12" height="4" rx="1"/></symbol>
<symbol id="qz-slash" viewBox="0 0 14 32"><rect x="13" y="4" width="2.2" height="10" rx="1" transform="rotate(52 14.1 9)"/><rect x="5" y="18" width="2.2" height="10" rx="1" transform="rotate(52 6.1 23)"/></symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

482
clock-widget.html Normal file
View File

@@ -0,0 +1,482 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clock Widget</title>
<style>
:root {
--bg: #0f1218;
--panel: #171c24;
--panel-2: #131820;
--border: rgba(255, 255, 255, 0.72);
--text: #eef3ff;
--muted: #9aa5ba;
--accent: #dbe7ff;
--digit: #d8f6b8;
--digit-dim: #76906a;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: radial-gradient(circle at 20% 15%, #1f2a3b 0%, var(--bg) 50%, #090c12 100%);
color: var(--text);
font-family: "Segoe UI", Tahoma, sans-serif;
padding: 18px;
}
.clock-widget {
width: min(980px, 100%);
border: 1px solid var(--border);
box-shadow: 0 10px 34px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.08) inset;
background: linear-gradient(180deg, #141a22 0%, #0f131a 100%);
border-radius: 8px;
overflow: hidden;
}
.row {
width: 100%;
display: grid;
border-top: 1px solid rgba(255, 255, 255, 0.3);
}
.row:first-child {
border-top: 0;
}
.cell {
border-left: 1px solid rgba(255, 255, 255, 0.3);
min-height: 72px;
padding: 10px;
background: var(--panel);
}
.cell:first-child {
border-left: 0;
}
.row-1 {
grid-template-columns: 1fr;
}
.row-2 {
grid-template-columns: repeat(4, 1fr);
}
.row-3 {
grid-template-columns: repeat(3, 1fr);
}
.main-clock {
min-height: 220px;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)),
repeating-linear-gradient(
90deg,
rgba(255, 255, 255, 0.02) 0,
rgba(255, 255, 255, 0.02) 1px,
transparent 1px,
transparent 6px
),
#111822;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
text-align: center;
}
.primary-zone {
color: var(--muted);
letter-spacing: 0.14em;
font-weight: 600;
font-size: 0.82rem;
text-transform: uppercase;
}
.primary-time {
display: inline-flex;
align-items: flex-end;
gap: 6px;
padding: 10px 16px 8px;
border: 1px solid rgba(220, 235, 180, 0.45);
border-radius: 6px;
background: linear-gradient(180deg, #202a1e 0%, #181f16 100%);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04), 0 8px 18px rgba(0, 0, 0, 0.45);
font-family: "Overtime LCD", "DS-Digital", "Digital-7 Mono", "Consolas", monospace;
font-variant-numeric: tabular-nums;
color: var(--digit);
text-shadow: 0 0 5px rgba(190, 240, 150, 0.35), 0 0 1px rgba(180, 220, 145, 0.7);
letter-spacing: 0.06em;
line-height: 1;
}
.primary-time .hh,
.primary-time .mm {
font-size: clamp(3.1rem, 9.2vw, 5.7rem);
font-weight: 500;
}
.primary-time .colon {
display: inline-block;
width: 0.55em;
text-align: center;
font-size: clamp(3.1rem, 9.2vw, 5.7rem);
animation: casioBlink 1.25s linear infinite;
}
.primary-time .ss {
min-width: 2ch;
font-size: clamp(1.35rem, 3.1vw, 2rem);
margin-left: 2px;
margin-bottom: 0.5em;
color: var(--digit);
}
.date-cell {
display: flex;
justify-content: center;
align-items: center;
background: var(--panel-2);
color: #e8ecf6;
font-family: "Consolas", "Menlo", monospace;
font-size: clamp(0.92rem, 1.5vw, 1.1rem);
letter-spacing: 0.08em;
font-weight: 700;
text-transform: uppercase;
text-align: center;
}
.placeholder {
background: linear-gradient(180deg, #161d27 0%, #121923 100%);
}
.analog-cell {
min-height: 250px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 12px;
background: radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 55%), #121923;
}
.tz-label {
margin-top: 2px;
font-size: 0.82rem;
letter-spacing: 0.14em;
color: var(--muted);
text-transform: uppercase;
font-weight: 700;
}
.wall-clock {
width: min(94%, 220px);
aspect-ratio: 1 / 1;
border-radius: 50%;
position: relative;
border: 4px solid rgba(239, 243, 252, 0.78);
background:
radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0) 36%),
radial-gradient(circle at 50% 50%, #f6f8fc 0%, #e5ebf6 62%, #d3dceb 100%);
box-shadow:
0 12px 24px rgba(0, 0, 0, 0.45),
inset -8px -8px 18px rgba(0, 0, 0, 0.12),
inset 8px 10px 14px rgba(255, 255, 255, 0.4);
}
.roman {
position: absolute;
left: 50%;
top: 50%;
width: 20px;
height: 20px;
margin: -10px;
transform: rotate(calc(var(--i) * 30deg)) translateY(calc(var(--radius) * -1)) rotate(calc(var(--i) * -30deg));
font-family: "Times New Roman", serif;
font-size: clamp(0.76rem, 1.1vw, 0.95rem);
font-weight: 700;
color: #2a3447;
text-align: center;
user-select: none;
display: grid;
place-items: center;
}
.hand {
position: absolute;
left: 50%;
top: 50%;
transform-origin: 50% 100%;
transform: translate(-50%, -100%) rotate(0deg);
border-radius: 999px;
transition: transform 0.1s linear;
}
.hand.hour {
width: 6px;
height: 28%;
background: #1f2f46;
}
.hand.minute {
width: 4px;
height: 38%;
background: #2e4767;
}
.hand.second {
width: 2px;
height: 42%;
background: #b63b3b;
}
.pin {
position: absolute;
left: 50%;
top: 50%;
width: 12px;
height: 12px;
margin-left: -6px;
margin-top: -6px;
border-radius: 50%;
background: #111928;
border: 2px solid #f3f6ff;
z-index: 10;
}
.digital-subtime {
margin-top: auto;
margin-bottom: 3px;
font-family: "Consolas", monospace;
font-size: 0.9rem;
color: #d6deef;
letter-spacing: 0.08em;
}
@keyframes casioBlink {
0%, 20% {
opacity: 0;
}
20.01%, 60% {
opacity: 1;
}
60.01%, 100% {
opacity: 0;
}
}
@media (max-width: 900px) {
.row-2 {
grid-template-columns: repeat(2, 1fr);
}
.row-3 {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="clock-widget" id="clockWidget">
<div class="row row-1">
<div class="cell main-clock">
<div class="primary-zone" id="primaryZoneLabel"></div>
<div class="primary-time" aria-label="Primary time">
<span class="hh" id="primaryHH">00</span>
<span class="colon">:</span>
<span class="mm" id="primaryMM">00</span>
<span class="ss" id="primarySS">00</span>
</div>
</div>
</div>
<div class="row row-2">
<div class="cell placeholder"></div>
<div class="cell placeholder"></div>
<div class="cell placeholder"></div>
<div class="cell date-cell" id="dateCell">MON 01 JAN 2026</div>
</div>
<div class="row row-3" id="secondaryRow"></div>
</div>
<script>
(function () {
var CITY_LABELS = {
"America/New_York": "NEW YORK",
"Europe/Moscow": "MOSCOW",
"Asia/Shanghai": "BEIJING",
"Africa/Johannesburg": "CAPETOWN"
};
var ROMAN = ["XII", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI"];
var WEEK = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
var MONTH = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
function pad2(v) {
return String(v).padStart(2, "0");
}
function getSystemTimeZone() {
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
}
function computeSecondaryZones(primaryZone) {
var preferred = ["America/New_York", "Europe/Moscow", "Asia/Shanghai"];
var secondaries = preferred.filter(function (z) { return z !== primaryZone; });
while (secondaries.length < 3) {
if (!secondaries.includes("Africa/Johannesburg") && primaryZone !== "Africa/Johannesburg") {
secondaries.push("Africa/Johannesburg");
} else {
secondaries.push("UTC");
}
}
return secondaries.slice(0, 3);
}
function formatDatePrimary(date, zone) {
var parts = new Intl.DateTimeFormat("en-US", {
timeZone: zone,
weekday: "short",
day: "2-digit",
month: "short",
year: "numeric"
}).formatToParts(date);
var map = {};
parts.forEach(function (p) {
if (p.type !== "literal") map[p.type] = p.value;
});
var day = (map.weekday || "---").slice(0, 3).toUpperCase();
var dd = map.day || "00";
var mon = (map.month || "---").slice(0, 3).toUpperCase();
var yyyy = map.year || "0000";
return day + " " + dd + " " + mon + " " + yyyy;
}
function getTimeParts(date, zone) {
var parts = new Intl.DateTimeFormat("en-US", {
timeZone: zone,
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
}).formatToParts(date);
var map = {};
parts.forEach(function (p) {
if (p.type !== "literal") map[p.type] = p.value;
});
return {
hh: map.hour || "00",
mm: map.minute || "00",
ss: map.second || "00"
};
}
function buildSecondaryClock(tz) {
var cell = document.createElement("div");
cell.className = "cell analog-cell";
var label = document.createElement("div");
label.className = "tz-label";
label.textContent = CITY_LABELS[tz] || tz.toUpperCase();
cell.appendChild(label);
var clock = document.createElement("div");
clock.className = "wall-clock";
for (var i = 0; i < 12; i++) {
var numeral = document.createElement("div");
numeral.className = "roman";
numeral.style.setProperty("--i", String(i));
numeral.style.setProperty("--radius", "41%");
numeral.textContent = ROMAN[i];
clock.appendChild(numeral);
}
var hour = document.createElement("div");
hour.className = "hand hour";
var minute = document.createElement("div");
minute.className = "hand minute";
var second = document.createElement("div");
second.className = "hand second";
var pin = document.createElement("div");
pin.className = "pin";
clock.appendChild(hour);
clock.appendChild(minute);
clock.appendChild(second);
clock.appendChild(pin);
cell.appendChild(clock);
var digital = document.createElement("div");
digital.className = "digital-subtime";
digital.textContent = "--:--:--";
cell.appendChild(digital);
return {
tz: tz,
root: cell,
hour: hour,
minute: minute,
second: second,
digital: digital
};
}
var primaryZone = getSystemTimeZone();
var secondaryZones = computeSecondaryZones(primaryZone);
var secondaryRow = document.getElementById("secondaryRow");
var secondaryClocks = secondaryZones.map(function (tz) {
var c = buildSecondaryClock(tz);
secondaryRow.appendChild(c.root);
return c;
});
document.getElementById("primaryZoneLabel").textContent = "PRIMARY TZ: " + (CITY_LABELS[primaryZone] || primaryZone.toUpperCase());
function update() {
var now = new Date();
var primary = getTimeParts(now, primaryZone);
document.getElementById("primaryHH").textContent = primary.hh;
document.getElementById("primaryMM").textContent = primary.mm;
document.getElementById("primarySS").textContent = primary.ss;
document.getElementById("dateCell").textContent = formatDatePrimary(now, primaryZone);
secondaryClocks.forEach(function (clock) {
var p = getTimeParts(now, clock.tz);
var hNum = Number(p.hh);
var mNum = Number(p.mm);
var sNum = Number(p.ss);
var hourDeg = ((hNum % 12) + mNum / 60 + sNum / 3600) * 30;
var minuteDeg = (mNum + sNum / 60) * 6;
var secondDeg = sNum * 6;
clock.hour.style.transform = "translate(-50%, -100%) rotate(" + hourDeg + "deg)";
clock.minute.style.transform = "translate(-50%, -100%) rotate(" + minuteDeg + "deg)";
clock.second.style.transform = "translate(-50%, -100%) rotate(" + secondDeg + "deg)";
clock.digital.textContent = p.hh + ":" + p.mm + ":" + p.ss;
});
}
update();
setInterval(update, 1000);
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,15 @@
# Replace the broken hub block in /etc/nginx/conf.d/apps.conf on Alpine BE.
# Remove duplicate "return 301 .../crashes/" and alias-to-index.html lines.
location = /app/androidcast_project {
return 301 /app/androidcast_project/;
}
location /app/androidcast_project/ {
alias /var/www/localhost/htdocs/apps/app/androidcast_project/;
index index.php index.html;
}
location ^~ /app/androidcast_project/crashes/assets/ {
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
}

476
hub-landing.css Normal file
View File

@@ -0,0 +1,476 @@
/* Multipage landing shell — f0xx.org / lovable-style layout on hub tokens */
.hub-landing {
--landing-header-h: 56px;
--landing-footer-h: 40px;
--landing-left-w: 56px;
--landing-rail-w: 44px;
width: 100%;
max-width: 100%;
height: 100vh;
height: 100dvh;
overflow: hidden;
}
/* (1) Top header */
.landing-top {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 20;
height: var(--landing-header-h);
display: flex;
align-items: center;
gap: 16px;
padding: 0 16px 0 12px;
background: rgba(15, 20, 25, 0.72);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(45, 58, 79, 0.65);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
}
[data-theme="light"] .landing-top {
background: rgba(255, 255, 255, 0.82);
border-bottom-color: rgba(197, 208, 224, 0.9);
}
.landing-top-logo {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
text-decoration: none;
color: var(--text);
}
.landing-top-logo:hover { color: var(--text); text-decoration: none; }
.landing-top-logo img {
width: 36px;
height: 27px;
}
.landing-top-logo strong {
font-size: 1.05rem;
font-weight: 800;
letter-spacing: -0.02em;
}
.landing-top-nav {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
min-width: 0;
}
.landing-menu-item {
position: relative;
}
.landing-menu-btn,
.landing-menu-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: none;
border-radius: 8px;
background: transparent;
color: var(--text);
font: inherit;
font-size: 0.92rem;
cursor: pointer;
text-decoration: none;
transition: background 0.15s ease, box-shadow 0.15s ease;
}
.landing-menu-link:hover,
.landing-menu-btn:hover,
.landing-menu-item:focus-within > .landing-menu-btn {
background: rgba(61, 139, 253, 0.14);
text-decoration: none;
}
.landing-menu-btn::after {
content: '';
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid currentColor;
opacity: 0.7;
}
.landing-menu-link::after { display: none; }
.landing-dropdown {
position: absolute;
top: calc(100% + 4px);
left: 50%;
transform: translateX(-50%) translateY(6px);
min-width: 168px;
padding: 6px;
border-radius: 10px;
background: var(--surface);
border: 1px solid var(--border);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35), 0 0 20px rgba(61, 139, 253, 0.12);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s;
z-index: 30;
}
.landing-menu-item:hover .landing-dropdown,
.landing-menu-item:focus-within .landing-dropdown,
.landing-menu-item.is-open .landing-dropdown {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: translateX(-50%) translateY(0);
}
.landing-dropdown a {
display: block;
padding: 8px 12px;
border-radius: 6px;
color: var(--text);
text-decoration: none;
font-size: 0.9rem;
}
.landing-dropdown a:hover,
.landing-dropdown a.is-active {
background: rgba(61, 139, 253, 0.16);
color: var(--accent);
}
.landing-top-tools {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.landing-top-tools .toolbar-select {
font-size: 13px;
}
/* Frame: (2) left + (3) body + space for (4) rail */
.landing-frame {
position: fixed;
top: var(--landing-header-h);
left: 0;
right: 0;
bottom: var(--landing-footer-h);
display: flex;
z-index: 5;
pointer-events: none;
}
/* (2) Left shell — console nav-pane */
.landing-left.nav-pane {
position: relative;
display: flex;
flex-direction: column;
flex: 0 0 var(--landing-left-w);
width: var(--landing-left-w);
height: 100%;
max-height: 100%;
overflow: hidden;
z-index: 3;
pointer-events: auto;
border-right: 1px solid var(--border);
background: var(--surface);
}
.landing-left .nav-list {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
.landing-left .nav-user {
margin-top: auto;
border-top: 1px solid var(--border);
}
.landing-left.nav-pane.open {
width: 240px;
flex-basis: 240px;
}
/* (3) Body — full viewport pages, scroll snap */
.landing-scroll {
flex: 1;
min-width: 0;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
pointer-events: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.landing-scroll::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
.landing-page {
scroll-snap-align: start;
scroll-snap-stop: always;
min-height: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 48px 32px 24px;
position: relative;
}
.landing-page-inner {
width: min(920px, 100%);
max-height: 100%;
overflow-y: auto;
padding: 8px 4px;
}
.landing-page-inner.card.card--lift {
padding: 28px 32px;
background: rgba(26, 35, 50, 0.78);
backdrop-filter: blur(8px);
border: 1px solid rgba(45, 58, 79, 0.85);
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.35), 0 0 32px rgba(61, 139, 253, 0.08);
}
[data-theme="light"] .landing-page-inner.card.card--lift {
background: rgba(255, 255, 255, 0.88);
}
.landing-page h2 {
margin: 0 0 12px;
font-size: clamp(1.75rem, 4vw, 2.5rem);
font-weight: 800;
letter-spacing: -0.02em;
}
.landing-page .landing-lead {
margin: 0 0 20px;
font-size: 1.08rem;
line-height: 1.55;
color: var(--muted);
max-width: 52ch;
}
.landing-page .btn { margin-left: 0; }
.landing-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
margin-top: 20px;
}
.landing-gallery img {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 10px;
border: 1px solid var(--border);
}
.landing-feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 14px;
margin-top: 20px;
}
.landing-feature-grid .card {
padding: 16px;
margin: 0;
}
.landing-price-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin-top: 20px;
}
.landing-price-card {
padding: 20px;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--surface2);
}
.landing-price-card h3 { margin: 0 0 8px; }
.landing-price-card .price {
font-size: 1.75rem;
font-weight: 800;
color: var(--accent);
margin-bottom: 12px;
}
.landing-contact-form {
display: grid;
gap: 12px;
max-width: 420px;
margin-top: 16px;
}
.landing-contact-form label {
display: grid;
gap: 4px;
font-size: 0.9rem;
}
.landing-contact-form input,
.landing-contact-form textarea {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--surface2);
color: var(--text);
font: inherit;
}
/* Page 1 hero — lighter card, more globe visible */
.landing-page--hero .landing-page-inner {
background: transparent;
border: none;
box-shadow: none;
backdrop-filter: none;
text-align: center;
max-width: 640px;
}
.landing-page--hero h2 {
font-size: clamp(2rem, 5vw, 3rem);
}
/* (4) Right page rail — hidden until handle hover/drag */
.landing-rail {
position: fixed;
top: var(--landing-header-h);
right: 0;
bottom: var(--landing-footer-h);
width: var(--landing-rail-w);
z-index: 15;
pointer-events: none;
transform: translateX(calc(var(--landing-rail-w) - 6px));
transition: transform 0.22s ease;
}
.landing-rail.is-visible,
.landing-rail:hover,
.landing-rail:focus-within {
transform: translateX(0);
pointer-events: auto;
}
.landing-rail-handle {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 72px;
padding: 0;
border: 1px solid var(--border);
border-right: none;
border-radius: 10px 0 0 10px;
background: rgba(26, 35, 50, 0.92);
cursor: grab;
pointer-events: auto;
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.25);
}
.landing-rail-handle:active { cursor: grabbing; }
.landing-rail-handle::before {
content: '';
display: block;
width: 4px;
height: 28px;
margin: 0 auto;
border-radius: 2px;
background: repeating-linear-gradient(
to bottom,
var(--muted) 0,
var(--muted) 4px,
transparent 4px,
transparent 7px
);
}
.landing-rail-dots {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px 6px;
border-radius: 12px;
background: rgba(15, 20, 25, 0.82);
border: 1px solid var(--border);
backdrop-filter: blur(8px);
}
.landing-rail-dot {
width: 10px;
height: 10px;
padding: 0;
border: 2px solid var(--muted);
border-radius: 50%;
background: transparent;
cursor: pointer;
transition: border-color 0.15s, background 0.15s, transform 0.15s;
}
.landing-rail-dot:hover {
border-color: var(--accent);
transform: scale(1.15);
}
.landing-rail-dot.is-active {
background: var(--accent);
border-color: var(--accent);
box-shadow: 0 0 10px rgba(61, 139, 253, 0.55);
}
/* (5) Footer stays platform-footer; lift above globe */
.hub-landing .bottom-bar.platform-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 12;
height: var(--landing-footer-h);
min-height: var(--landing-footer-h);
max-height: var(--landing-footer-h);
pointer-events: none;
}
.hub-landing .bottom-bar.platform-footer a,
.hub-landing .bottom-bar.platform-footer button {
pointer-events: auto;
}
/* Clock widget below header */
.hub-landing .hub-clock-widget {
top: calc(var(--landing-header-h) + 12px);
z-index: 18;
}
@media (max-width: 900px) {
.landing-top-nav { display: none; }
.landing-page { padding: 24px 16px 24px 12px; }
.landing-rail { --landing-rail-w: 36px; }
}
.landing-top-tools .hub-locale {
margin: 0;
}
.landing-top-tools .hub-locale .locale-flag {
font-size: 1rem;
line-height: 1;
}
.landing-top-tools .hub-locale select {
padding: 6px 8px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--bg);
color: var(--text);
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
/* Globe stays fixed behind all landing chrome */
.hub-landing #hub-logo-stage {
z-index: 0;
}
@media (prefers-reduced-motion: reduce) {
.landing-scroll { scroll-behavior: auto; }
.landing-rail { transition: none; }
}

92
hub-logo-layers.css Normal file
View File

@@ -0,0 +1,92 @@
/* Logo stage: fragments are injected as <img> layers (same box, object-fit: cover). */
.hub-logo-stage {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
opacity: 0.75;
}
.hub-logo-stage[hidden] {
display: none;
}
.hub-logo-layer {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
}
.hub-logo-layer--space {
z-index: 1;
}
/* Interactive globe canvas replaces globe + continents + gridOverlay when globe3d=1 */
.hub-globe-canvas {
position: fixed;
z-index: 1;
pointer-events: none;
cursor: grab;
touch-action: none;
border-radius: 50%;
}
.hub-globe-canvas.is-dragging {
pointer-events: auto;
cursor: grabbing;
}
.hub-logo-layer--globe {
z-index: 2;
}
.hub-logo-layer--gridOverlay {
z-index: 4;
}
.hub-logo-layer--continents {
z-index: 5;
}
.hub-logo-layer--compass {
z-index: 6;
left: 54.7%;
top: 75.9%;
width: 11.5%;
height: auto;
inset: auto;
transform: translate(-50%, -50%);
pointer-events: auto;
cursor: pointer;
transition: transform 0.22s ease, filter 0.22s ease;
}
.hub-logo-layer--compass:hover,
.hub-logo-layer--compass:focus-visible {
transform: translate(-50%, -50%) scale(1.12);
filter: drop-shadow(0 0 16px rgba(186, 228, 255, 0.55));
outline: none;
}
.hub-logo-layer--tail {
z-index: 5;
}
.hub-logo-layer--rocket {
z-index: 6;
}
[data-theme="light"] .hub-logo-stage {
opacity: 0.75;
}
@media (prefers-reduced-motion: reduce) {
.hub-globe-canvas {
cursor: default;
}
}

1100
hub.css Normal file

File diff suppressed because it is too large Load Diff

1
index.html Normal file
View File

@@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=index.php"><title>Redirect</title></head><body><p><a href="index.php">Continue</a></p></body></html>

1154
index.php Normal file

File diff suppressed because it is too large Load Diff

130
landing-pages.inc.php Normal file
View File

@@ -0,0 +1,130 @@
<?php declare(strict_types=1); ?>
<section class="landing-page landing-page--hero" id="landing-page-1" data-page="1" aria-label="Home">
<div class="landing-page-inner">
<h2 data-i18n="page1.title">Android Cast</h2>
<p class="landing-lead" data-i18n="page1.lead">Screen casting, crash intelligence, and CI — one ecosystem.</p>
<button type="button" class="btn btn-primary" data-goto-page="2" data-i18n="page1.cta">Explore AndroidCast</button>
</div>
</section>
<section class="landing-page" id="landing-page-2" data-page="2" aria-label="AndroidCast">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page2.title">AndroidCast</h2>
<p class="landing-lead" data-i18n="page2.lead">Mirror Android screens with low latency, native codecs, and operator-grade tooling.</p>
<div class="landing-feature-grid">
<article class="card card--lift">
<h3>Cast</h3>
<p class="muted">WiFi and USB transport with adaptive bitrate and session metrics.</p>
</article>
<article class="card card--lift">
<h3>Issues</h3>
<p class="muted">Ingest, triage tickets, and correlate fingerprints across releases.</p>
</article>
<article class="card card--lift">
<h3>Builder</h3>
<p class="muted">Docker CI for APK baking, OTA artifacts, and pipeline history.</p>
</article>
</div>
</div>
</section>
<section class="landing-page" id="landing-page-3" data-page="3" aria-label="About">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page3.title">About</h2>
<p class="landing-lead" data-i18n="page3.lead">Android Cast connects devices, backends, and operators across your stack.</p>
<div class="landing-gallery">
<img src="https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=400&amp;h=300&amp;fit=crop" alt="" loading="lazy" decoding="async">
<img src="https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=400&amp;h=300&amp;fit=crop" alt="" loading="lazy" decoding="async">
<img src="https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=400&amp;h=300&amp;fit=crop" alt="" loading="lazy" decoding="async">
</div>
</div>
</section>
<section class="landing-page" id="landing-page-4" data-page="4" aria-label="Why">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page4.title">Why AndroidCast</h2>
<p class="landing-lead" data-i18n="page4.lead">Built for teams who need reliability, observability, and control — not another black box.</p>
<ul class="muted" style="line-height:1.7;margin:0;padding-left:1.2rem">
<li>Self-hosted consoles on your BE — no mandatory cloud lock-in</li>
<li>Graphs, remote access, and builder share one auth and theme</li>
<li>Green <code>master</code> git flow with tagged releases</li>
</ul>
</div>
</section>
<section class="landing-page" id="landing-page-5" data-page="5" aria-label="Technologies">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page5.title">Technologies</h2>
<p class="landing-lead" data-i18n="page5.lead">WebRTC, Opus/Speex, WireGuard remote access, PHP consoles, and Docker CI pipelines.</p>
<div class="landing-feature-grid">
<article class="card"><strong>WebRTC / UDP</strong><p class="muted">Low-latency mirror paths</p></article>
<article class="card"><strong>Opus · Speex</strong><p class="muted">Native codec validation</p></article>
<article class="card"><strong>WireGuard</strong><p class="muted">Remote device sessions</p></article>
<article class="card"><strong>PHP · SQLite</strong><p class="muted">Crash + ticket console</p></article>
</div>
</div>
</section>
<section class="landing-page" id="landing-page-6" data-page="6" aria-label="Resources">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page6.title">Resources</h2>
<p class="landing-lead" data-i18n="page6.lead">Documentation, deployment guides, and integration patterns for your environment.</p>
<p><a class="btn" href="issues/">Issues console</a> <a class="btn" href="build/">Builder</a> <a class="btn" href="git/">Git browser</a></p>
</div>
</section>
<section class="landing-page" id="landing-page-7" data-page="7" aria-label="People">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page7.title">People</h2>
<p class="landing-lead" data-i18n="page7.lead">Engineers and operators shipping cast, crash, and build workflows every day.</p>
<div class="landing-gallery">
<img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=400&amp;h=300&amp;fit=crop" alt="" loading="lazy" decoding="async">
<img src="https://images.unsplash.com/photo-1600880292203-757bb62b4baf?w=400&amp;h=300&amp;fit=crop" alt="" loading="lazy" decoding="async">
</div>
</div>
</section>
<section class="landing-page" id="landing-page-8" data-page="8" aria-label="Culture">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page8.title">Culture</h2>
<p class="landing-lead" data-i18n="page8.lead">Open tooling, green master branches, and honest postmortems.</p>
<p class="muted">We ship on <code>next</code>, tag on <code>master</code>, and keep infra docs next to the code.</p>
</div>
</section>
<section class="landing-page" id="landing-page-9" data-page="9" aria-label="Pricing">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page9.title">Pricing</h2>
<p class="landing-lead" data-i18n="page9.lead">Self-hosted core; enterprise options for SLA, SSO, and dedicated builders.</p>
<div class="landing-price-grid">
<div class="landing-price-card">
<h3>Community</h3>
<div class="price">$0</div>
<p class="muted">Full source, self-hosted BE, community support.</p>
</div>
<div class="landing-price-card">
<h3>Team</h3>
<div class="price">Custom</div>
<p class="muted">Priority fixes, deployment assistance, staging builders.</p>
</div>
<div class="landing-price-card">
<h3>Enterprise</h3>
<div class="price">Custom</div>
<p class="muted">SLA, SSO, dedicated infra review, on-call options.</p>
</div>
</div>
</div>
</section>
<section class="landing-page" id="landing-page-10" data-page="10" aria-label="Contact">
<div class="landing-page-inner card card--lift">
<h2 data-i18n="page10.title">Contact us</h2>
<p class="landing-lead" data-i18n="page10.lead">Reach the maintainer for partnerships, support, or deployment help.</p>
<form class="landing-contact-form" action="#" method="post" onsubmit="return false">
<label>Name<input type="text" name="name" autocomplete="name"></label>
<label>Email<input type="email" name="email" autocomplete="email"></label>
<label>Message<textarea name="message" rows="4"></textarea></label>
<button type="submit" class="btn btn-primary">Send (placeholder)</button>
</form>
</div>
</section>

View File

@@ -0,0 +1,15 @@
<?php declare(strict_types=1); ?>
<div id="cookie-consent-banner" class="cookie-consent" hidden role="dialog" aria-labelledby="cookie-consent-title" aria-modal="false">
<div class="cookie-consent-inner">
<p id="cookie-consent-title" class="cookie-consent-title">Cookies on AndroidCast</p>
<p class="cookie-consent-text muted">
We use necessary session cookies to sign you in. With your consent we also load analytics to improve the service.
You can change this later in your browser by clearing site data.
</p>
<div class="cookie-consent-actions">
<button type="button" class="btn btn--primary" id="cookie-consent-all">Accept all</button>
<button type="button" class="btn" id="cookie-consent-necessary">Necessary only</button>
<button type="button" class="btn btn--ghost" id="cookie-consent-reject">Reject optional</button>
</div>
</div>
</div>

100
scripts/sync-landing-to-be.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# Sync multipage landing from repo to BE hub root.
# Usage:
# ./scripts/sync-landing-to-be.sh
# ./scripts/sync-landing-to-be.sh /path/to/BE/.../androidcast_project
# REMOTE=alpine-be ./scripts/sync-landing-to-be.sh
#
# After sync, verify:
# curl -sS 'https://apps.f0xx.org/app/androidcast_project/' | head -20
# Expect: title "Android Cast — Landing", link hub-landing.css, body hub-landing
set -euo pipefail
SRC="$(cd "$(dirname "$0")/.." && pwd)"
REPO="$(cd "$SRC/../.." && pwd)"
DEFAULT_DEST="$REPO/tmp/BE_alpine/var/www/localhost/htdocs/apps/app/androidcast_project"
DEST="${1:-$DEFAULT_DEST}"
REMOTE="${REMOTE:-}"
hub_files=(
index.php
index.html
hub.css
hub-logo-layers.css
hub-landing.css
landing-pages.inc.php
)
asset_files=(
assets/hub-landing.js
assets/hub-i18n.js
assets/hub-globe.js
assets/analytics.config.js
assets/i18n/hub-en.json
assets/i18n/hub-ru.json
)
sync_local() {
local dest="$1"
if [[ ! -d "$dest" ]]; then
echo "DEST not found: $dest" >&2
echo "Mount BE sshfs or pass the BE androidcast_project path as arg 1." >&2
exit 1
fi
mkdir -p "$dest/assets/i18n" "$dest/partials"
for f in "${hub_files[@]}"; do
install -m 0644 "$SRC/$f" "$dest/$f"
echo " $f"
done
install -D -m 0644 "$SRC/partials/cookie_consent.php" "$dest/partials/cookie_consent.php"
echo " partials/cookie_consent.php"
for f in "${asset_files[@]}"; do
install -D -m 0644 "$SRC/$f" "$dest/$f"
echo " $f"
done
PLATFORM_SRC="$REPO/examples/platform"
PLATFORM_DEST="$dest/android_cast/examples/platform"
if [[ -d "$PLATFORM_SRC" ]]; then
mkdir -p "$PLATFORM_DEST"
rsync -a --no-group --no-owner "$PLATFORM_SRC/" "$PLATFORM_DEST/"
echo " platform/ (footer)"
fi
}
verify_hint() {
cat <<'EOF'
Verify on BE or public URL:
curl -sS 'https://apps.f0xx.org/app/androidcast_project/' | grep -E 'Landing|hub-landing|landing-top'
curl -sS -o /dev/null -w '%{http_code}\n' 'https://apps.f0xx.org/app/androidcast_project/hub-landing.css'
Expect: title "Android Cast — Landing", HTTP 200 for hub-landing.css.
Then hard-refresh the browser (Ctrl+Shift+R).
EOF
}
echo "Sync landing hub → $DEST"
if [[ -n "$REMOTE" ]]; then
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
for f in "${hub_files[@]}"; do cp "$SRC/$f" "$tmp/"; done
mkdir -p "$tmp/assets/i18n"
for f in "${asset_files[@]}"; do
mkdir -p "$tmp/$(dirname "$f")"
cp "$SRC/$f" "$tmp/$f"
done
scp "$tmp/index.php" "$tmp/index.html" "$tmp/hub.css" "$tmp/hub-logo-layers.css" \
"$tmp/hub-landing.css" "$tmp/landing-pages.inc.php" \
"$REMOTE:/var/www/localhost/htdocs/apps/app/androidcast_project/"
scp "$SRC/partials/cookie_consent.php" \
"$REMOTE:/var/www/localhost/htdocs/apps/app/androidcast_project/partials/"
scp -r "$tmp/assets/hub-landing.js" "$tmp/assets/hub-i18n.js" "$tmp/assets/hub-globe.js" \
"$tmp/assets/analytics.config.js" "$tmp/assets/i18n" \
"$REMOTE:/var/www/localhost/htdocs/apps/app/androidcast_project/assets/"
echo "Remote sync via $REMOTE complete."
else
sync_local "$DEST"
echo "Local sync complete."
fi
verify_hint