4 Commits
v1.0.0 ... main

Author SHA1 Message Date
2281c98b1b fix: 兼容 Web 同源部署地址 2026-06-24 21:10:21 +08:00
7cb51e70a3 updated website icons and docker server 2026-06-24 16:18:20 +08:00
44318e0e4d updated website style 2026-06-24 10:44:38 +08:00
da577299a0 website updated 2026-06-24 10:25:09 +08:00
11 changed files with 1650 additions and 53 deletions

View File

@@ -3,11 +3,16 @@ import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class SettingsProvider with ChangeNotifier { class SettingsProvider with ChangeNotifier {
// Use 10.0.2.2 for Android emulator to access host's localhost // On web: use empty string so API calls use same origin (works behind any proxy)
static String get _defaultUrl => // On Android emulator: 10.0.2.2 maps to host localhost
(defaultTargetPlatform == TargetPlatform.android && !kIsWeb) // On other platforms: localhost
? "http://10.0.2.2:8080" static String get _defaultUrl {
: "http://localhost:8080"; if (kIsWeb) return "";
if (defaultTargetPlatform == TargetPlatform.android) {
return "http://10.0.2.2:8080";
}
return "http://localhost:8080";
}
String _baseUrl = _defaultUrl; String _baseUrl = _defaultUrl;
Color _themeColor = Colors.blue; Color _themeColor = Colors.blue;
@@ -38,7 +43,7 @@ class SettingsProvider with ChangeNotifier {
} }
_livePreviewThumbnailsEnabled = _livePreviewThumbnailsEnabled =
prefs.getBool('livePreviewThumbnailsEnabled') ?? false; prefs.getBool('livePreviewThumbnailsEnabled') ?? false;
final languageCode = prefs.getString('languageCode'); final languageCode = prefs.getString('languageCode');
final scriptCode = prefs.getString('scriptCode'); final scriptCode = prefs.getString('scriptCode');
final countryCode = prefs.getString('countryCode'); final countryCode = prefs.getString('countryCode');
@@ -49,7 +54,7 @@ class SettingsProvider with ChangeNotifier {
countryCode: countryCode, countryCode: countryCode,
); );
} }
notifyListeners(); notifyListeners();
} }
@@ -106,8 +111,18 @@ class SettingsProvider with ChangeNotifier {
// Also provide the RTMP URL based on the same hostname // Also provide the RTMP URL based on the same hostname
String get rtmpUrl { String get rtmpUrl {
final uri = Uri.parse(_baseUrl); final host = _baseUrl.isEmpty ? _effectiveHost : Uri.parse(_baseUrl).host;
return "rtmp://${uri.host}:1935/live"; return "rtmp://$host:1935/live";
}
// Fallback hostname when baseUrl is empty (web same-origin mode)
String get _effectiveHost {
if (kIsWeb) {
final host = Uri.base.host;
if (host.isNotEmpty) return host;
return 'localhost';
}
return 'localhost';
} }
String playbackUrl(String roomId, {String? quality}) { String playbackUrl(String roomId, {String? quality}) {

View File

@@ -45,7 +45,7 @@ class ChatService {
Stream<ChatMessage> get messages => _messageController.stream; Stream<ChatMessage> get messages => _messageController.stream;
void connect(String baseUrl, String roomId, String username) { void connect(String baseUrl, String roomId, String username) {
final wsUri = Uri.parse(baseUrl).replace( final wsUri = _webSocketUri(baseUrl).replace(
scheme: 'ws', scheme: 'ws',
path: '/api/ws/room/$roomId', path: '/api/ws/room/$roomId',
queryParameters: {'username': username}, queryParameters: {'username': username},
@@ -83,6 +83,21 @@ class ChatService {
} }
} }
Uri _webSocketUri(String baseUrl) {
if (baseUrl.isEmpty) {
if (kIsWeb) {
return Uri.base.replace(
scheme: Uri.base.scheme == 'https' ? 'wss' : 'ws',
);
}
return Uri.parse('http://localhost:8080');
}
final uri = Uri.parse(baseUrl);
final scheme = uri.scheme == 'https' ? 'wss' : 'ws';
return uri.replace(scheme: scheme);
}
void dispose() { void dispose() {
_channel?.sink.close(); _channel?.sink.close();
_messageController.close(); _messageController.close();

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

395
website/hightube/index.html Normal file

File diff suppressed because one or more lines are too long

804
website/hightube/styles.css Normal file
View File

@@ -0,0 +1,804 @@
:root {
color-scheme: light dark;
--primary: #0b57d0;
--on-primary: #ffffff;
--primary-container: #d7e3ff;
--on-primary-container: #001b3f;
--secondary: #565f71;
--tertiary: #705575;
--surface: #fbfcff;
--surface-rgb: 251 252 255;
--surface-container: #eef3fb;
--surface-container-high: #e5ebf5;
--outline: #727782;
--outline-rgb: 114 119 130;
--text: #191c20;
--muted: #42474f;
--success: #146c2e;
--warning: #7a5900;
--shadow: 0 24px 60px rgba(11, 87, 208, 0.16);
--topbar-bg: rgba(251, 252, 255, 0.68);
--topbar-border: rgba(114, 119, 130, 0.18);
--topbar-shadow: 0 12px 36px rgba(11, 87, 208, 0.08);
--grid-divider: rgba(114, 119, 130, 0.24);
--card-border: rgba(114, 119, 130, 0.24);
--device-border: rgba(114, 119, 130, 0.32);
--status-available-bg: rgba(20, 108, 46, 0.12);
--status-planned-bg: rgba(122, 89, 0, 0.12);
}
/* ---- Dark theme: forced ---- */
[data-theme="dark"] {
--primary: #a8c7ff;
--on-primary: #001b3f;
--primary-container: #003a7a;
--on-primary-container: #d7e3ff;
--secondary: #bcc7db;
--tertiary: #d7bde0;
--surface: #111318;
--surface-rgb: 17 19 24;
--surface-container: #1a1d25;
--surface-container-high: #21242d;
--outline: #8b909c;
--outline-rgb: 139 144 156;
--text: #e3e3e8;
--muted: #b0b3bd;
--success: #81c784;
--warning: #ffe08a;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
--topbar-bg: rgba(17, 19, 24, 0.72);
--topbar-border: rgba(139, 144, 156, 0.18);
--topbar-shadow: 0 12px 36px rgba(0, 0, 0, 0.28);
--grid-divider: rgba(139, 144, 156, 0.2);
--card-border: rgba(139, 144, 156, 0.2);
--device-border: rgba(139, 144, 156, 0.28);
--status-available-bg: rgba(129, 199, 132, 0.15);
--status-planned-bg: rgba(255, 224, 138, 0.15);
}
/* ---- Dark theme: auto (system preference, no manual override) ---- */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--primary: #a8c7ff;
--on-primary: #001b3f;
--primary-container: #003a7a;
--on-primary-container: #d7e3ff;
--secondary: #bcc7db;
--tertiary: #d7bde0;
--surface: #111318;
--surface-rgb: 17 19 24;
--surface-container: #1a1d25;
--surface-container-high: #21242d;
--outline: #8b909c;
--outline-rgb: 139 144 156;
--text: #e3e3e8;
--muted: #b0b3bd;
--success: #81c784;
--warning: #ffe08a;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
--topbar-bg: rgba(17, 19, 24, 0.72);
--topbar-border: rgba(139, 144, 156, 0.18);
--topbar-shadow: 0 12px 36px rgba(0, 0, 0, 0.28);
--grid-divider: rgba(139, 144, 156, 0.2);
--card-border: rgba(139, 144, 156, 0.2);
--device-border: rgba(139, 144, 156, 0.28);
--status-available-bg: rgba(129, 199, 132, 0.15);
--status-planned-bg: rgba(255, 224, 138, 0.15);
}
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
min-width: 320px;
background: var(--surface);
color: var(--text);
font-family:
Inter, Roboto, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
line-height: 1.6;
transition: background-color 300ms ease, color 300ms ease;
}
a {
color: inherit;
}
.topbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 14px clamp(20px, 5vw, 72px);
background: var(--topbar-bg);
border-bottom: 1px solid var(--topbar-border);
box-shadow: var(--topbar-shadow);
-webkit-backdrop-filter: blur(22px) saturate(160%);
backdrop-filter: blur(22px) saturate(160%);
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--text);
font-weight: 800;
text-decoration: none;
}
.brand img {
width: 36px;
height: 36px;
border-radius: 10px;
}
.nav {
display: flex;
align-items: center;
gap: 6px;
}
.nav a {
min-height: 40px;
padding: 8px 14px;
border-radius: 20px;
color: var(--muted);
font-size: 0.94rem;
font-weight: 650;
text-decoration: none;
}
.nav a:hover {
background: var(--surface-container);
color: var(--primary);
}
/* 立即尝试 CTA button in nav */
.nav-cta {
background: var(--primary) !important;
color: var(--on-primary) !important;
margin-left: 8px;
font-weight: 800 !important;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2);
transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
.nav-cta:hover {
filter: brightness(0.88);
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.26);
}
/* Hamburger menu button — hidden on desktop */
.hamburger {
display: none;
flex-direction: column;
justify-content: center;
gap: 5px;
width: 40px;
height: 40px;
padding: 8px;
border: none;
border-radius: 10px;
background: transparent;
cursor: pointer;
z-index: 20;
}
.hamburger span {
display: block;
width: 100%;
height: 2.5px;
border-radius: 2px;
background: var(--text);
transition: transform 200ms ease, opacity 200ms ease;
}
.hamburger[aria-expanded="true"] span:nth-child(1) {
transform: translateY(7.5px) rotate(45deg);
}
.hamburger[aria-expanded="true"] span:nth-child(2) {
opacity: 0;
}
.hamburger[aria-expanded="true"] span:nth-child(3) {
transform: translateY(-7.5px) rotate(-45deg);
}
/* Right-side controls group (theme + hamburger) */
.topbar-actions {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
/* Theme toggle button */
.theme-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--text);
font-size: 1.25rem;
cursor: pointer;
transition: background 200ms ease, transform 200ms ease;
flex-shrink: 0;
}
.theme-toggle:hover {
background: var(--surface-container);
transform: scale(1.08);
}
.theme-toggle:active {
transform: scale(0.94);
}
.theme-toggle svg {
width: 22px;
height: 22px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* hide all icons by default, show based on data-state */
.theme-toggle .icon-sun,
.theme-toggle .icon-moon,
.theme-toggle .icon-auto {
display: none;
}
.theme-toggle[data-state="light"] .icon-sun {
display: block;
}
.theme-toggle[data-state="dark"] .icon-moon {
display: block;
}
.theme-toggle[data-state="auto"] .icon-auto {
display: block;
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.95fr);
gap: clamp(28px, 6vw, 84px);
align-items: center;
min-height: calc(100vh - 68px);
padding: clamp(48px, 7vw, 96px) clamp(20px, 5vw, 72px);
}
.hero-copy {
max-width: 680px;
}
.eyebrow {
margin: 0 0 12px;
color: var(--primary);
font-size: 0.85rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1,
h2,
h3,
p {
overflow-wrap: anywhere;
}
h1 {
margin: 0;
color: var(--text);
font-size: clamp(3.5rem, 11vw, 8rem);
line-height: 0.9;
letter-spacing: 0;
}
h2 {
margin: 0;
font-size: clamp(2rem, 4vw, 3.5rem);
line-height: 1.05;
letter-spacing: 0;
}
h3 {
margin: 0;
font-size: 1.2rem;
line-height: 1.2;
letter-spacing: 0;
}
.lead {
max-width: 620px;
margin: 28px 0 0;
color: var(--muted);
font-size: clamp(1.1rem, 2vw, 1.35rem);
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 34px;
}
.button,
.download-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 48px;
padding: 12px 22px;
border-radius: 24px;
font-weight: 800;
text-decoration: none;
transition:
transform 160ms ease,
box-shadow 160ms ease,
background 160ms ease;
}
.button:hover,
.download-link:hover {
transform: translateY(-1px);
}
.primary {
background: var(--primary);
color: var(--on-primary);
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18);
}
.secondary {
background: var(--primary-container);
color: var(--on-primary-container);
}
.hero-panel {
display: flex;
justify-content: center;
}
.device-window {
width: min(100%, 560px);
overflow: hidden;
border: 1px solid var(--device-border);
border-radius: 28px;
background: var(--surface-container);
box-shadow: var(--shadow);
}
.window-bar {
display: flex;
gap: 8px;
padding: 16px 18px;
border-bottom: 1px solid var(--card-border);
}
.window-bar span {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--outline);
}
.window-bar span:first-child {
background: #ba1a1a;
}
.window-bar span:nth-child(2) {
background: #a46700;
}
.window-bar span:nth-child(3) {
background: #146c2e;
}
.stream-preview {
position: relative;
display: grid;
min-height: 280px;
place-items: center;
background:
linear-gradient(135deg, rgba(11, 87, 208, 0.94), rgba(112, 85, 117, 0.9)),
radial-gradient(circle at 30% 30%, #d7e3ff, transparent 32%);
}
.live-badge {
position: absolute;
top: 18px;
left: 18px;
padding: 6px 12px;
border-radius: 16px;
background: #ba1a1a;
color: #ffffff;
font-size: 0.8rem;
font-weight: 900;
}
.play-symbol {
width: 86px;
height: 86px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.88);
clip-path: polygon(28% 18%, 28% 82%, 82% 50%);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1px;
background: var(--grid-divider);
}
.stats-grid div {
min-width: 0;
padding: 18px 14px;
background: var(--surface);
}
.stats-grid strong,
.stats-grid span {
display: block;
}
.stats-grid strong {
color: var(--primary);
font-size: 1.02rem;
}
.stats-grid span {
color: var(--muted);
font-size: 0.88rem;
}
.section {
padding: clamp(64px, 8vw, 112px) clamp(20px, 5vw, 72px);
}
.section-heading {
max-width: 820px;
margin-bottom: 32px;
}
.section-heading p:not(.eyebrow) {
max-width: 760px;
color: var(--muted);
font-size: 1.06rem;
}
.architecture-grid,
.download-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.architecture-card,
.download-card {
min-width: 0;
padding: 24px;
border: 1px solid var(--card-border);
border-radius: 24px;
background: var(--surface-container);
}
.architecture-card .icon {
display: grid;
width: 48px;
height: 48px;
margin-bottom: 20px;
place-items: center;
border-radius: 16px;
background: var(--primary);
color: var(--on-primary);
}
.architecture-card .icon svg {
width: 26px;
height: 26px;
}
.architecture-card p,
.download-card p,
.feature-item p,
.source-section p {
color: var(--muted);
}
.feature-band {
background: var(--surface-container);
}
.feature-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
overflow: hidden;
border: 1px solid var(--card-border);
border-radius: 28px;
background: var(--grid-divider);
}
.feature-item {
min-width: 0;
padding: 28px;
background: var(--surface);
}
.feature-icon {
width: 36px;
height: 36px;
margin-bottom: 14px;
color: var(--primary);
}
.feature-icon svg {
width: 100%;
height: 100%;
display: block;
}
.download-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.download-card {
display: flex;
min-height: 240px;
flex-direction: column;
align-items: flex-start;
}
.download-icon {
width: 32px;
height: 32px;
margin-bottom: 14px;
color: var(--muted);
}
.download-icon svg {
width: 100%;
height: 100%;
display: block;
}
.status {
margin-bottom: 18px;
padding: 5px 10px;
border-radius: 14px;
background: var(--status-available-bg);
color: var(--success);
font-size: 0.78rem;
font-weight: 850;
}
.status.muted {
background: var(--status-planned-bg);
color: var(--warning);
}
.download-link {
margin-top: auto;
background: var(--primary);
color: var(--on-primary);
}
.planned {
background: var(--surface-container-high);
}
.source-section {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
background: #001b3f;
color: #ffffff;
}
.source-section .eyebrow,
.source-section p {
color: #d7e3ff;
}
.source-section div {
max-width: 820px;
}
.footer {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 16px;
padding: 28px clamp(20px, 5vw, 72px);
background: #001533;
color: #d7e3ff;
font-size: 0.92rem;
}
.footer span:nth-child(2) {
text-align: center;
}
.footer span:last-child {
text-align: right;
}
@media (max-width: 900px) {
.topbar {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
.hamburger {
display: flex;
}
.nav {
display: none;
width: 100%;
flex-direction: column;
gap: 2px;
padding: 8px 0 4px;
border-top: 1px solid var(--topbar-border);
margin-top: 10px;
}
.nav.open {
display: flex;
}
.nav a {
width: 100%;
padding: 12px 16px;
border-radius: 14px;
font-size: 1rem;
}
.nav-cta {
margin-left: 0 !important;
margin-top: 6px;
text-align: center;
}
.hero {
grid-template-columns: 1fr;
min-height: auto;
}
.architecture-grid,
.download-grid {
grid-template-columns: 1fr 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.source-section {
align-items: flex-start;
flex-direction: column;
}
}
@media (max-width: 640px) {
.topbar {
padding: 10px 16px;
}
.hero,
.section {
padding-right: 16px;
padding-left: 16px;
}
.hero {
padding-top: 32px;
padding-bottom: 32px;
gap: 24px;
}
h1 {
font-size: clamp(2.8rem, 10vw, 4rem);
}
h2 {
font-size: clamp(1.6rem, 5vw, 2.4rem);
}
.lead {
font-size: 1rem;
margin-top: 16px;
}
.hero-panel {
width: 100%;
}
.device-window {
width: 100%;
border-radius: 20px;
}
.stream-preview {
min-height: 180px;
}
.play-symbol {
width: 60px;
height: 60px;
}
.architecture-grid,
.download-grid,
.feature-list {
grid-template-columns: 1fr;
}
.architecture-card,
.download-card {
padding: 20px;
border-radius: 18px;
}
.feature-item {
padding: 20px;
}
.actions {
flex-direction: column;
}
.button,
.download-link {
width: 100%;
justify-content: center;
}
.nav a {
min-height: 48px;
padding: 14px 16px;
font-size: 1.05rem;
}
.nav-cta {
min-height: 50px;
font-size: 1.05rem;
}
.footer {
grid-template-columns: 1fr;
gap: 8px;
padding: 24px 16px;
}
.footer-hide-mobile {
display: none;
}
}

View File

@@ -22,7 +22,20 @@
<a href="#features">Features</a> <a href="#features">Features</a>
<a href="#downloads">Downloads</a> <a href="#downloads">Downloads</a>
<a href="#source">Source</a> <a href="#source">Source</a>
<a class="nav-cta" href="https://stream.nudt.space" target="_blank" rel="noopener">Try Now</a>
</nav> </nav>
<div class="topbar-actions">
<button class="theme-toggle" data-state="auto" aria-label="Theme: Auto" title="Theme: Auto — click to force Light">
<svg class="icon-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
<svg class="icon-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<svg class="icon-auto" viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
</button>
<button class="hamburger" aria-label="Toggle navigation" aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
</div>
</header> </header>
<main id="home"> <main id="home">
@@ -159,9 +172,8 @@
<p class="eyebrow">Downloads</p> <p class="eyebrow">Downloads</p>
<h2>Get the current executable builds</h2> <h2>Get the current executable builds</h2>
<p> <p>
These files are hosted in the local website resources directory. All builds are distributed through the project release page. Apple
Windows server and client builds will be added later. Apple device device builds are not provided at this time.
builds are not provided at this time.
</p> </p>
</div> </div>
<div class="download-grid"> <div class="download-grid">
@@ -171,8 +183,8 @@
<p>Backend service for self-hosted streaming deployments.</p> <p>Backend service for self-hosted streaming deployments.</p>
<a <a
class="download-link" class="download-link"
href="resources/hightube-server/hightube-server_amd64_v1.0.0" href="https://git.nudt.space/Highground-Soft/Hightube/releases/download/v1.0.0/hightube-server_amd64_v1.0.0"
download rel="noopener"
> >
Download Linux server Download Linux server
</a> </a>
@@ -183,8 +195,8 @@
<p>Desktop Flutter client packaged as an AppImage.</p> <p>Desktop Flutter client packaged as an AppImage.</p>
<a <a
class="download-link" class="download-link"
href="resources/hightube-client/hightube-linux-amd64-v1.0.0.AppImage" href="https://git.nudt.space/Highground-Soft/Hightube/releases/download/v1.0.0/hightube-linux-amd64-v1.0.0.AppImage"
download rel="noopener"
> >
Download Linux AppImage Download Linux AppImage
</a> </a>
@@ -195,8 +207,8 @@
<p>ARM64 Android APK build for mobile viewing and interaction.</p> <p>ARM64 Android APK build for mobile viewing and interaction.</p>
<a <a
class="download-link" class="download-link"
href="resources/hightube-client/hightube-android-arm64-v8a-v1.0.0.apk" href="https://git.nudt.space/Highground-Soft/Hightube/releases/download/v1.0.0/hightube-android-arm64-v8a-v1.0.0.apk"
download rel="noopener"
> >
Download Android APK Download Android APK
</a> </a>
@@ -207,21 +219,35 @@
<p>Static web build archive for hosting the Flutter web frontend.</p> <p>Static web build archive for hosting the Flutter web frontend.</p>
<a <a
class="download-link" class="download-link"
href="resources/hightube-client/hightube-web-v1.0.0.tar.gz" href="https://git.nudt.space/Highground-Soft/Hightube/releases/download/v1.0.0/hightube-web-v1.0.0.tar.gz"
download rel="noopener"
> >
Download Web archive Download Web archive
</a> </a>
</article> </article>
<article class="download-card planned"> <article class="download-card available">
<span class="status muted">Coming later</span> <span class="status">Available</span>
<h3>Windows server</h3> <h3>Windows server</h3>
<p>Planned server executable for Windows environments.</p> <p>Server executable for Windows environments.</p>
<a
class="download-link"
href="https://git.nudt.space/Highground-Soft/Hightube/releases/download/v1.0.0/hightube-server_amd64_v1.0.0.exe"
rel="noopener"
>
Download Windows server
</a>
</article> </article>
<article class="download-card planned"> <article class="download-card available">
<span class="status muted">Coming later</span> <span class="status">Available</span>
<h3>Windows client</h3> <h3>Windows client</h3>
<p>Planned desktop client build for Windows users.</p> <p>Desktop client build for Windows users.</p>
<a
class="download-link"
href="https://git.nudt.space/Highground-Soft/Hightube/releases/download/v1.0.0/hightube-win_amd64_v1.0.0.zip"
rel="noopener"
>
Download Windows client
</a>
</article> </article>
</div> </div>
</section> </section>
@@ -246,10 +272,73 @@
</section> </section>
</main> </main>
<script>
(function () {
/* ---- hamburger menu ---- */
var btn = document.querySelector('.hamburger');
var nav = document.querySelector('.nav');
if (btn && nav) {
btn.addEventListener('click', function () {
var open = nav.classList.toggle('open');
btn.setAttribute('aria-expanded', open);
});
}
/* ---- theme toggle ---- */
var toggle = document.querySelector('.theme-toggle');
if (!toggle) return;
var STATES = ['auto', 'light', 'dark'];
var LABELS = {
auto: 'Theme: Auto — click to force Light',
light: 'Theme: Light — click to force Dark',
dark: 'Theme: Dark — click to return to Auto'
};
function getTheme() {
return localStorage.getItem('theme') || 'auto';
}
function applyTheme(state) {
if (state === 'light') {
document.documentElement.setAttribute('data-theme', 'light');
} else if (state === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
toggle.setAttribute('data-state', state);
toggle.setAttribute('aria-label', 'Theme: ' + state.charAt(0).toUpperCase() + state.slice(1));
toggle.setAttribute('title', LABELS[state]);
localStorage.setItem('theme', state);
}
function nextTheme(current) {
var idx = STATES.indexOf(current);
return STATES[(idx + 1) % STATES.length];
}
// init
var current = getTheme();
applyTheme(current);
toggle.addEventListener('click', function () {
applyTheme(nextTheme(getTheme()));
});
// listen for system changes (only matters in auto mode)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
if (getTheme() === 'auto') {
// force a repaint by re-applying auto
applyTheme('auto');
}
});
})();
</script>
<footer class="footer"> <footer class="footer">
<span>Hightube</span> <span class="footer-hide-mobile">Hightube</span>
<span>Built with ❤️ by HighGround-soft 2026</span> <span>Built with ❤️ by HighGround-soft 2026</span>
<span>Open Source Live Platform</span> <span class="footer-hide-mobile">Open Source Live Platform</span>
</footer> </footer>
</body> </body>
</html> </html>

View File

@@ -1,19 +1,90 @@
:root { :root {
color-scheme: light; color-scheme: light dark;
--primary: #0b57d0; --primary: #0b57d0;
--on-primary: #ffffff; --on-primary: #ffffff;
--primary-container: #d7e3ff; --primary-container: #d7e3ff;
--on-primary-container: #001b3f;
--secondary: #565f71; --secondary: #565f71;
--tertiary: #705575; --tertiary: #705575;
--surface: #fbfcff; --surface: #fbfcff;
--surface-rgb: 251 252 255;
--surface-container: #eef3fb; --surface-container: #eef3fb;
--surface-container-high: #e5ebf5; --surface-container-high: #e5ebf5;
--outline: #727782; --outline: #727782;
--outline-rgb: 114 119 130;
--text: #191c20; --text: #191c20;
--muted: #42474f; --muted: #42474f;
--success: #146c2e; --success: #146c2e;
--warning: #7a5900; --warning: #7a5900;
--shadow: 0 24px 60px rgba(11, 87, 208, 0.16); --shadow: 0 24px 60px rgba(11, 87, 208, 0.16);
--topbar-bg: rgba(251, 252, 255, 0.68);
--topbar-border: rgba(114, 119, 130, 0.18);
--topbar-shadow: 0 12px 36px rgba(11, 87, 208, 0.08);
--grid-divider: rgba(114, 119, 130, 0.24);
--card-border: rgba(114, 119, 130, 0.24);
--device-border: rgba(114, 119, 130, 0.32);
--status-available-bg: rgba(20, 108, 46, 0.12);
--status-planned-bg: rgba(122, 89, 0, 0.12);
}
/* ---- Dark theme: forced ---- */
[data-theme="dark"] {
--primary: #a8c7ff;
--on-primary: #001b3f;
--primary-container: #003a7a;
--on-primary-container: #d7e3ff;
--secondary: #bcc7db;
--tertiary: #d7bde0;
--surface: #111318;
--surface-rgb: 17 19 24;
--surface-container: #1a1d25;
--surface-container-high: #21242d;
--outline: #8b909c;
--outline-rgb: 139 144 156;
--text: #e3e3e8;
--muted: #b0b3bd;
--success: #81c784;
--warning: #ffe08a;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
--topbar-bg: rgba(17, 19, 24, 0.72);
--topbar-border: rgba(139, 144, 156, 0.18);
--topbar-shadow: 0 12px 36px rgba(0, 0, 0, 0.28);
--grid-divider: rgba(139, 144, 156, 0.2);
--card-border: rgba(139, 144, 156, 0.2);
--device-border: rgba(139, 144, 156, 0.28);
--status-available-bg: rgba(129, 199, 132, 0.15);
--status-planned-bg: rgba(255, 224, 138, 0.15);
}
/* ---- Dark theme: auto (system preference, no manual override) ---- */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--primary: #a8c7ff;
--on-primary: #001b3f;
--primary-container: #003a7a;
--on-primary-container: #d7e3ff;
--secondary: #bcc7db;
--tertiary: #d7bde0;
--surface: #111318;
--surface-rgb: 17 19 24;
--surface-container: #1a1d25;
--surface-container-high: #21242d;
--outline: #8b909c;
--outline-rgb: 139 144 156;
--text: #e3e3e8;
--muted: #b0b3bd;
--success: #81c784;
--warning: #ffe08a;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
--topbar-bg: rgba(17, 19, 24, 0.72);
--topbar-border: rgba(139, 144, 156, 0.18);
--topbar-shadow: 0 12px 36px rgba(0, 0, 0, 0.28);
--grid-divider: rgba(139, 144, 156, 0.2);
--card-border: rgba(139, 144, 156, 0.2);
--device-border: rgba(139, 144, 156, 0.28);
--status-available-bg: rgba(129, 199, 132, 0.15);
--status-planned-bg: rgba(255, 224, 138, 0.15);
}
} }
* { * {
@@ -33,6 +104,7 @@ body {
Inter, Roboto, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif; sans-serif;
line-height: 1.6; line-height: 1.6;
transition: background-color 300ms ease, color 300ms ease;
} }
a { a {
@@ -48,9 +120,9 @@ a {
justify-content: space-between; justify-content: space-between;
gap: 24px; gap: 24px;
padding: 14px clamp(20px, 5vw, 72px); padding: 14px clamp(20px, 5vw, 72px);
background: rgba(251, 252, 255, 0.68); background: var(--topbar-bg);
border-bottom: 1px solid rgba(114, 119, 130, 0.18); border-bottom: 1px solid var(--topbar-border);
box-shadow: 0 12px 36px rgba(11, 87, 208, 0.08); box-shadow: var(--topbar-shadow);
-webkit-backdrop-filter: blur(22px) saturate(160%); -webkit-backdrop-filter: blur(22px) saturate(160%);
backdrop-filter: blur(22px) saturate(160%); backdrop-filter: blur(22px) saturate(160%);
} }
@@ -91,6 +163,123 @@ a {
color: var(--primary); color: var(--primary);
} }
/* 立即尝试 CTA button in nav */
.nav-cta {
background: var(--primary) !important;
color: var(--on-primary) !important;
margin-left: 8px;
font-weight: 800 !important;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2);
transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
.nav-cta:hover {
filter: brightness(0.88);
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.26);
}
/* Hamburger menu button — hidden on desktop */
.hamburger {
display: none;
flex-direction: column;
justify-content: center;
gap: 5px;
width: 40px;
height: 40px;
padding: 8px;
border: none;
border-radius: 10px;
background: transparent;
cursor: pointer;
z-index: 20;
}
.hamburger span {
display: block;
width: 100%;
height: 2.5px;
border-radius: 2px;
background: var(--text);
transition: transform 200ms ease, opacity 200ms ease;
}
.hamburger[aria-expanded="true"] span:nth-child(1) {
transform: translateY(7.5px) rotate(45deg);
}
.hamburger[aria-expanded="true"] span:nth-child(2) {
opacity: 0;
}
.hamburger[aria-expanded="true"] span:nth-child(3) {
transform: translateY(-7.5px) rotate(-45deg);
}
/* Right-side controls group (theme + hamburger) */
.topbar-actions {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
/* Theme toggle button */
.theme-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--text);
font-size: 1.25rem;
cursor: pointer;
transition: background 200ms ease, transform 200ms ease;
flex-shrink: 0;
}
.theme-toggle:hover {
background: var(--surface-container);
transform: scale(1.08);
}
.theme-toggle:active {
transform: scale(0.94);
}
.theme-toggle svg {
width: 22px;
height: 22px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* hide all icons by default, show based on data-state */
.theme-toggle .icon-sun,
.theme-toggle .icon-moon,
.theme-toggle .icon-auto {
display: none;
}
.theme-toggle[data-state="light"] .icon-sun {
display: block;
}
.theme-toggle[data-state="dark"] .icon-moon {
display: block;
}
.theme-toggle[data-state="auto"] .icon-auto {
display: block;
}
.hero { .hero {
display: grid; display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.95fr); grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.95fr);
@@ -180,12 +369,12 @@ h3 {
.primary { .primary {
background: var(--primary); background: var(--primary);
color: var(--on-primary); color: var(--on-primary);
box-shadow: 0 10px 24px rgba(11, 87, 208, 0.22); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18);
} }
.secondary { .secondary {
background: var(--primary-container); background: var(--primary-container);
color: #001b3f; color: var(--on-primary-container);
} }
.hero-panel { .hero-panel {
@@ -196,7 +385,7 @@ h3 {
.device-window { .device-window {
width: min(100%, 560px); width: min(100%, 560px);
overflow: hidden; overflow: hidden;
border: 1px solid rgba(114, 119, 130, 0.32); border: 1px solid var(--device-border);
border-radius: 28px; border-radius: 28px;
background: var(--surface-container); background: var(--surface-container);
box-shadow: var(--shadow); box-shadow: var(--shadow);
@@ -206,7 +395,7 @@ h3 {
display: flex; display: flex;
gap: 8px; gap: 8px;
padding: 16px 18px; padding: 16px 18px;
border-bottom: 1px solid rgba(114, 119, 130, 0.24); border-bottom: 1px solid var(--card-border);
} }
.window-bar span { .window-bar span {
@@ -262,7 +451,7 @@ h3 {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 1px; gap: 1px;
background: rgba(114, 119, 130, 0.24); background: var(--grid-divider);
} }
.stats-grid div { .stats-grid div {
@@ -312,7 +501,7 @@ h3 {
.download-card { .download-card {
min-width: 0; min-width: 0;
padding: 24px; padding: 24px;
border: 1px solid rgba(114, 119, 130, 0.24); border: 1px solid var(--card-border);
border-radius: 24px; border-radius: 24px;
background: var(--surface-container); background: var(--surface-container);
} }
@@ -345,9 +534,9 @@ h3 {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px; gap: 1px;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(114, 119, 130, 0.24); border: 1px solid var(--card-border);
border-radius: 28px; border-radius: 28px;
background: rgba(114, 119, 130, 0.24); background: var(--grid-divider);
} }
.feature-item { .feature-item {
@@ -371,14 +560,14 @@ h3 {
margin-bottom: 18px; margin-bottom: 18px;
padding: 5px 10px; padding: 5px 10px;
border-radius: 14px; border-radius: 14px;
background: rgba(20, 108, 46, 0.12); background: var(--status-available-bg);
color: var(--success); color: var(--success);
font-size: 0.78rem; font-size: 0.78rem;
font-weight: 850; font-weight: 850;
} }
.status.muted { .status.muted {
background: rgba(122, 89, 0, 0.12); background: var(--status-planned-bg);
color: var(--warning); color: var(--warning);
} }
@@ -431,14 +620,41 @@ h3 {
@media (max-width: 900px) { @media (max-width: 900px) {
.topbar { .topbar {
align-items: flex-start; flex-direction: row;
flex-direction: column; flex-wrap: wrap;
align-items: center;
justify-content: space-between;
}
.hamburger {
display: flex;
} }
.nav { .nav {
display: none;
width: 100%; width: 100%;
overflow-x: auto; flex-direction: column;
padding-bottom: 2px; gap: 2px;
padding: 8px 0 4px;
border-top: 1px solid var(--topbar-border);
margin-top: 10px;
}
.nav.open {
display: flex;
}
.nav a {
width: 100%;
padding: 12px 16px;
border-radius: 14px;
font-size: 1rem;
}
.nav-cta {
margin-left: 0 !important;
margin-top: 6px;
text-align: center;
} }
.hero { .hero {
@@ -462,34 +678,97 @@ h3 {
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.topbar {
padding: 10px 16px;
}
.hero, .hero,
.section { .section {
padding-right: 16px; padding-right: 16px;
padding-left: 16px; padding-left: 16px;
} }
.hero {
padding-top: 32px;
padding-bottom: 32px;
gap: 24px;
}
h1 {
font-size: clamp(2.8rem, 10vw, 4rem);
}
h2 {
font-size: clamp(1.6rem, 5vw, 2.4rem);
}
.lead {
font-size: 1rem;
margin-top: 16px;
}
.hero-panel {
width: 100%;
}
.device-window {
width: 100%;
border-radius: 20px;
}
.stream-preview {
min-height: 180px;
}
.play-symbol {
width: 60px;
height: 60px;
}
.architecture-grid, .architecture-grid,
.download-grid, .download-grid,
.feature-list { .feature-list {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.actions, .architecture-card,
.download-card {
padding: 20px;
border-radius: 18px;
}
.feature-item {
padding: 20px;
}
.actions {
flex-direction: column;
}
.button, .button,
.download-link { .download-link {
width: 100%; width: 100%;
justify-content: center;
} }
.stream-preview { .nav a {
min-height: 220px; min-height: 48px;
padding: 14px 16px;
font-size: 1.05rem;
}
.nav-cta {
min-height: 50px;
font-size: 1.05rem;
} }
.footer { .footer {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 8px;
padding: 24px 16px;
} }
.footer span, .footer-hide-mobile {
.footer span:last-child { display: none;
text-align: left;
} }
} }