1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 06:39:09 +03:00
This commit is contained in:
Anton Afanasyeu
2026-05-30 17:04:13 +02:00
parent 3b0a694aa6
commit 95f521c4a0
4 changed files with 275 additions and 54 deletions

Binary file not shown.

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

@@ -1,5 +1,13 @@
/* Project hub — matches crash console tokens (app.css) */ /* Project hub — matches crash console tokens (app.css) */
@font-face {
font-family: "Clockopia";
src: url("assets/fonts/Clockopia.ttf") format("truetype");
font-weight: normal;
font-style: normal;
font-display: swap;
}
.hub-page { .hub-page {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
@@ -21,7 +29,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
min-height: calc(100vh - 40px); min-height: calc(100vh - 40px);
padding: 32px 28px 48px; padding: 32px 28px 48px;
box-sizing: border-box; box-sizing: border-box;
@@ -87,6 +95,84 @@
justify-content: center; justify-content: center;
} }
.hub-nav-dock,
#hub-nav-dock {
position: fixed;
z-index: 2;
display: flex !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
justify-content: flex-end;
align-items: stretch;
gap: 5px;
margin: 0;
padding: 0;
box-sizing: border-box;
pointer-events: none;
grid-template-columns: none !important;
grid-auto-flow: row !important;
max-width: none;
width: auto;
}
/* app.css .cards { display:grid } must not flatten the dock if classes linger */
#hub-nav-dock.cards,
#hub-nav-dock.hub-cards,
.hub-nav-dock.cards,
.hub-nav-dock.hub-cards {
display: flex !important;
flex-direction: column !important;
grid-template-columns: none !important;
}
.hub-nav-dock .hub-card,
#hub-nav-dock .hub-card,
#hub-nav-dock > a {
display: flex !important;
flex-direction: row !important;
align-items: center;
gap: 14px;
padding: 14px 16px;
width: 100% !important;
max-width: 100%;
flex: 0 0 auto !important;
margin: 0 !important;
opacity: 0.85;
pointer-events: auto;
color: var(--text);
text-decoration: none;
cursor: pointer;
transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease, filter 0.22s ease;
}
#hub-nav-dock > .hub-card + .hub-card,
.hub-nav-dock > .hub-card + .hub-card {
margin-top: 0 !important;
}
.hub-nav-dock .hub-card:hover,
.hub-nav-dock .hub-card:focus-visible {
opacity: 1;
color: var(--text);
text-decoration: none;
outline: none;
transform: translateY(-2px);
border-color: rgba(160, 220, 255, 0.95);
box-shadow:
0 0 18px rgba(120, 200, 255, 0.55),
0 0 36px rgba(90, 170, 255, 0.35),
0 10px 28px rgba(0, 0, 0, 0.42);
filter: drop-shadow(0 0 10px rgba(140, 210, 255, 0.45));
}
[data-theme="light"] .hub-nav-dock .hub-card:hover,
[data-theme="light"] .hub-nav-dock .hub-card:focus-visible {
box-shadow:
0 0 16px rgba(70, 130, 210, 0.45),
0 0 30px rgba(60, 120, 200, 0.28),
0 10px 24px rgba(26, 35, 50, 0.22);
}
.hub-cards { .hub-cards {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr)); grid-template-columns: repeat(3, minmax(200px, 1fr));
@@ -182,6 +268,26 @@
box-sizing: border-box; box-sizing: border-box;
} }
.nav-icon--builder {
width: 18px;
height: 18px;
border: 2px solid currentColor;
border-radius: 3px;
box-sizing: border-box;
position: relative;
}
.nav-icon--builder::before {
content: "";
position: absolute;
left: 3px;
top: 7px;
width: 10px;
height: 2px;
background: currentColor;
box-shadow: 0 -4px 0 currentColor, 0 4px 0 currentColor;
}
.hub-page .bottom-bar { .hub-page .bottom-bar {
position: relative; position: relative;
z-index: 1; z-index: 1;
@@ -199,16 +305,17 @@
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.48); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.48);
border-radius: 18px; border-radius: 18px;
overflow: visible; overflow: visible;
background: rgba(226, 34, 34, 0.5); background: rgba(18, 24, 34, 0.88);
backdrop-filter: blur(1.5px); backdrop-filter: blur(1.5px);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
cursor: move; cursor: move;
user-select: none; user-select: none;
touch-action: none; touch-action: none;
padding: 40px; padding: 28px 32px;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: "Clockopia", "DS-Digital", "Consolas", monospace;
} }
.cw-clock-inner { .cw-clock-inner {
@@ -251,7 +358,7 @@
align-content: start; align-content: start;
row-gap: 0; row-gap: 0;
overflow: hidden; overflow: hidden;
background: rgba(0, 180, 80, 0.35); background: transparent;
} }
.cw-row { .cw-row {
@@ -284,26 +391,43 @@
} }
.cw-time-main { .cw-time-main {
display: grid; display: inline-flex;
grid-template-columns: auto auto auto auto; align-items: baseline;
align-items: end;
justify-content: center; justify-content: center;
column-gap: 6px; column-gap: 4px;
line-height: 1; line-height: 1;
font-weight: 700; font-weight: 400;
width: fit-content; width: fit-content;
max-width: 100%; max-width: 100%;
margin: 0 auto; margin: 0 auto;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(170, 255, 200, 0.95); border: 0;
border-radius: 8px; border-radius: 0;
padding: 4px 6px; padding: 0;
font-family: "Clockopia", "DS-Digital", "Consolas", monospace;
font-variant-numeric: tabular-nums;
color: #d8f6b8;
text-shadow: 0 0 10px rgba(190, 240, 150, 0.42);
}
#cw-hm-lcd {
font-size: clamp(2.4rem, 4.8vw, 3.6rem);
letter-spacing: 0.04em;
}
#cw-colon-lcd,
#cw-ss-lcd {
font-size: clamp(1.6rem, 3.2vw, 2.4rem);
}
#cw-tz-text {
font-size: clamp(0.95rem, 1.6vw, 1.15rem);
margin-left: 10px;
opacity: 0.92;
} }
.ot-lcd-line { .ot-lcd-line {
display: inline-flex; display: inline;
align-items: flex-end;
gap: 2px;
} }
#cw-hm-lcd .ot-glyph { #cw-hm-lcd .ot-glyph {
@@ -404,18 +528,20 @@
.cw-line-region { .cw-line-region {
color: #b8c2d4; color: #b8c2d4;
font-size: 12px; font-size: clamp(0.62rem, 1vw, 0.75rem);
letter-spacing: 0.16em; letter-spacing: 0.16em;
font-weight: 300; font-weight: 400;
text-transform: uppercase; text-transform: uppercase;
font-family: "Clockopia", "Consolas", monospace;
} }
.cw-line-date { .cw-line-date {
color: #c5d0e2; color: #c5d0e2;
font-size: 24px; font-size: clamp(0.95rem, 1.6vw, 1.15rem);
letter-spacing: 0.12em; letter-spacing: 0.12em;
font-weight: 400; font-weight: 400;
text-transform: uppercase; text-transform: uppercase;
font-family: "Clockopia", "Consolas", monospace;
} }
.cw-separator { .cw-separator {
@@ -438,7 +564,7 @@
max-width: 100%; max-width: 100%;
border-top: 0; border-top: 0;
justify-items: center; justify-items: center;
background: rgba(40, 110, 255, 0.35); background: transparent;
} }
.cw-world-cell { .cw-world-cell {
@@ -465,6 +591,7 @@
letter-spacing: 0.12em; letter-spacing: 0.12em;
margin-top: 0; margin-top: 0;
margin-bottom: 8px; margin-bottom: 8px;
font-family: "Clockopia", "Consolas", monospace;
} }
.cw-dial { .cw-dial {
@@ -782,13 +909,19 @@
width: min(94vw, 567px); width: min(94vw, 567px);
min-width: 0; min-width: 0;
} }
.hub-cards { .hub-nav-dock .hub-card {
grid-template-columns: 1fr; padding: 14px 16px;
max-width: 420px;
} }
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.hub-nav-dock .hub-card {
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.hub-nav-dock .hub-card:hover,
.hub-nav-dock .hub-card:focus-visible {
transform: none;
}
.hub-cards .hub-card { .hub-cards .hub-card {
transition: border-color 0.15s ease, box-shadow 0.15s ease; transition: border-color 0.15s ease, box-shadow 0.15s ease;
} }

View File

@@ -11,7 +11,7 @@
})(); })();
</script> </script>
<link rel="stylesheet" href="/app/androidcast_project/crashes/assets/css/app.css"> <link rel="stylesheet" href="/app/androidcast_project/crashes/assets/css/app.css">
<link rel="stylesheet" href="hub.css?v=20260529a"> <link rel="stylesheet" href="hub.css?v=20260530h">
<link rel="stylesheet" href="hub-logo-layers.css"> <link rel="stylesheet" href="hub-logo-layers.css">
<script src="assets/analytics.config.js"></script> <script src="assets/analytics.config.js"></script>
<script src="/app/androidcast_project/crashes/assets/js/analytics.js" defer></script> <script src="/app/androidcast_project/crashes/assets/js/analytics.js" defer></script>
@@ -72,13 +72,9 @@
</select> </select>
</label> </label>
</header> </header>
<nav class="hub-cards cards cards--lift" aria-label="Android Cast apps"> </main>
<a class="hub-card card card--lift" href="crashes/?view=reports"> </div>
<span class="hub-card-icon" aria-hidden="true"> <nav class="hub-nav-dock" id="hub-nav-dock" aria-label="Android Cast apps">
<span class="nav-icon nav-icon--reports"></span>
</span>
<span class="hub-card-label">AndroidCast reports</span>
</a>
<a class="hub-card card card--lift" href="git/"> <a class="hub-card card card--lift" href="git/">
<span class="hub-card-icon" aria-hidden="true"> <span class="hub-card-icon" aria-hidden="true">
<span class="nav-icon nav-icon--git"></span> <span class="nav-icon nav-icon--git"></span>
@@ -91,10 +87,57 @@
</span> </span>
<span class="hub-card-label">AndroidCast tickets</span> <span class="hub-card-label">AndroidCast tickets</span>
</a> </a>
<a class="hub-card card card--lift" href="crashes/?view=reports">
<span class="hub-card-icon" aria-hidden="true">
<span class="nav-icon nav-icon--reports"></span>
</span>
<span class="hub-card-label">AndroidCast crashes</span>
</a>
<a class="hub-card card card--lift" href="build/">
<span class="hub-card-icon" aria-hidden="true">
<span class="nav-icon nav-icon--builder"></span>
</span>
<span class="hub-card-label">AndroidCast builder</span>
</a>
</nav> </nav>
</main>
</div>
<footer class="bottom-bar">(c) Anton Afanaasyeu</footer> <footer class="bottom-bar">(c) Anton Afanaasyeu</footer>
<script>
(function () {
var GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 330 };
function globeCoverRect() {
var vw = window.innerWidth;
var vh = window.innerHeight;
var scale = Math.max(vw / GLOBE_VIEW.imgW, vh / GLOBE_VIEW.imgH);
var drawW = GLOBE_VIEW.imgW * scale;
var drawH = GLOBE_VIEW.imgH * scale;
var offX = (vw - drawW) / 2;
var offY = (vh - drawH) / 2;
var left = offX + (GLOBE_VIEW.cx - GLOBE_VIEW.r) * scale;
var top = offY + (GLOBE_VIEW.cy - GLOBE_VIEW.r) * scale;
var bottom = offY + (GLOBE_VIEW.cy + GLOBE_VIEW.r) * scale;
var right = offX + (GLOBE_VIEW.cx + GLOBE_VIEW.r) * scale;
return { left: left, top: top, bottom: bottom, right: right, width: right - left, height: bottom - top };
}
function layoutHubNavDock() {
var dock = document.getElementById('hub-nav-dock');
if (!dock) return;
var globe = globeCoverRect();
var insetX = Math.max(16, globe.width * 0.06);
var dockW = Math.min(300, Math.max(210, globe.width * 0.38));
dock.style.width = dockW + 'px';
dock.style.left = (globe.left + insetX) + 'px';
dock.style.top = globe.top + 'px';
dock.style.height = globe.height + 'px';
dock.style.display = 'flex';
dock.style.flexDirection = 'column';
dock.style.flexWrap = 'nowrap';
dock.style.gap = '5px';
dock.style.justifyContent = 'flex-end';
}
layoutHubNavDock();
window.addEventListener('resize', layoutHubNavDock);
})();
</script>
<script> <script>
(function () { (function () {
var sel = document.getElementById('theme-select'); var sel = document.getElementById('theme-select');
@@ -116,7 +159,7 @@
var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover')); var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover'));
if (!supportsSvg || !supportsLayout) return; if (!supportsSvg || !supportsLayout) return;
var logoBuild = '20260529a'; var logoBuild = '20260530h';
var fragments = { var fragments = {
space: 'assets/hub-logo-fragment-space.svg', space: 'assets/hub-logo-fragment-space.svg',
globe: 'assets/hub-logo-fragment-globe.svg', globe: 'assets/hub-logo-fragment-globe.svg',
@@ -167,6 +210,44 @@
stage.hidden = false; stage.hidden = false;
stage.setAttribute('aria-hidden', 'true'); stage.setAttribute('aria-hidden', 'true');
/* Nav dock overlaps globe left hemisphere; stack top → bottom. */
var GLOBE_VIEW = { imgW: 1920, imgH: 1080, cx: 720, cy: 560, r: 330 };
function globeCoverRect() {
var vw = window.innerWidth;
var vh = window.innerHeight;
var scale = Math.max(vw / GLOBE_VIEW.imgW, vh / GLOBE_VIEW.imgH);
var drawW = GLOBE_VIEW.imgW * scale;
var drawH = GLOBE_VIEW.imgH * scale;
var offX = (vw - drawW) / 2;
var offY = (vh - drawH) / 2;
var left = offX + (GLOBE_VIEW.cx - GLOBE_VIEW.r) * scale;
var top = offY + (GLOBE_VIEW.cy - GLOBE_VIEW.r) * scale;
var bottom = offY + (GLOBE_VIEW.cy + GLOBE_VIEW.r) * scale;
var right = offX + (GLOBE_VIEW.cx + GLOBE_VIEW.r) * scale;
return { left: left, top: top, bottom: bottom, right: right, width: right - left, height: bottom - top };
}
function layoutHubNavDock() {
var dock = document.getElementById('hub-nav-dock');
if (!dock) return;
var globe = globeCoverRect();
var insetX = Math.max(16, globe.width * 0.06);
var dockW = Math.min(300, Math.max(210, globe.width * 0.38));
dock.style.width = dockW + 'px';
dock.style.left = (globe.left + insetX) + 'px';
dock.style.top = globe.top + 'px';
dock.style.height = globe.height + 'px';
dock.style.right = 'auto';
dock.style.bottom = 'auto';
dock.style.display = 'flex';
dock.style.flexDirection = 'column';
dock.style.flexWrap = 'nowrap';
dock.style.alignItems = 'stretch';
dock.style.gap = '5px';
dock.style.justifyContent = 'flex-end';
}
layoutHubNavDock();
window.addEventListener('resize', layoutHubNavDock);
var docsModal = document.getElementById('hub-docs-modal'); var docsModal = document.getElementById('hub-docs-modal');
var docsToc = document.getElementById('hub-docs-toc'); var docsToc = document.getElementById('hub-docs-toc');
var docsPanels = document.getElementById('hub-docs-panels'); var docsPanels = document.getElementById('hub-docs-panels');
@@ -737,15 +818,20 @@
return model; return model;
}); });
function setClockopiaText(el, text) {
if (!el) return;
el.textContent = text;
}
function updateClockWidget() { function updateClockWidget() {
var now = new Date(); var now = new Date();
var p = timeParts(now, primaryTz); var p = timeParts(now, primaryTz);
renderOvertimeDigits(document.getElementById('cw-hm-lcd'), p.hh + p.mm, false, false); setClockopiaText(document.getElementById('cw-hm-lcd'), p.hh + ':' + p.mm);
renderOvertimeDigits(document.getElementById('cw-colon-lcd'), ':', false, false); setClockopiaText(document.getElementById('cw-colon-lcd'), ':');
renderOvertimeDigits(document.getElementById('cw-ss-lcd'), p.ss, false, false); setClockopiaText(document.getElementById('cw-ss-lcd'), p.ss);
renderOvertimeText(document.getElementById('cw-tz-text'), gmtOffsetLabel(now, primaryTz), { bold: false }); setClockopiaText(document.getElementById('cw-tz-text'), gmtOffsetLabel(now, primaryTz));
renderOvertimeText(document.getElementById('cw-region'), regionLabel(primaryTz), { bold: false }); setClockopiaText(document.getElementById('cw-region'), regionLabel(primaryTz));
renderOvertimeText(document.getElementById('cw-date-line'), formatDateLine(now, primaryTz), { bold: false }); setClockopiaText(document.getElementById('cw-date-line'), formatDateLine(now, primaryTz));
secondary.forEach(function (clock) { secondary.forEach(function (clock) {
var t = timeParts(now, clock.tz); var t = timeParts(now, clock.tz);
@@ -836,10 +922,8 @@
widget.releasePointerCapture(ev.pointerId); widget.releasePointerCapture(ev.pointerId);
}); });
})(); })();
loadOvertimeAlphabet(function () {
updateClockWidget(); updateClockWidget();
setInterval(updateClockWidget, 1000); setInterval(updateClockWidget, 1000);
});
})(); })();
</script> </script>
</body> </body>