Frame homepage map and auto-focus top opportunity
This commit is contained in:
@@ -67,12 +67,19 @@
|
|||||||
|
|
||||||
/* ── Map ── */
|
/* ── Map ── */
|
||||||
.root :global(.map) {
|
.root :global(.map) {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: calc(var(--header-height) + 16px);
|
||||||
left: 0;
|
right: 418px;
|
||||||
|
bottom: 128px;
|
||||||
|
left: calc(var(--sidebar-width) + 30px);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(34, 211, 238, 0.12);
|
||||||
|
border-radius: 22px;
|
||||||
|
background: #020617;
|
||||||
|
box-shadow:
|
||||||
|
0 28px 80px rgba(0, 0, 0, 0.38),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.weatherAura {
|
.weatherAura {
|
||||||
@@ -890,6 +897,31 @@
|
|||||||
background: rgba(148, 163, 184, 0.1);
|
background: rgba(148, 163, 184, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.root :global(.home-top-opportunity-label) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-self: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid rgba(34, 211, 238, 0.18);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(8, 145, 178, 0.1);
|
||||||
|
color: #67e8f9;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root :global(.home-top-opportunity-label::before) {
|
||||||
|
content: "";
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #22c55e;
|
||||||
|
box-shadow: 0 0 12px rgba(34, 197, 94, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
.root :global(.home-card-titlebar) {
|
.root :global(.home-card-titlebar) {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -1468,6 +1500,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1360px) {
|
@media (max-width: 1360px) {
|
||||||
|
.root :global(.map) {
|
||||||
|
right: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.root :global(.home-intelligence-panel) {
|
.root :global(.home-intelligence-panel) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1478,6 +1514,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1020px) {
|
@media (max-width: 1020px) {
|
||||||
|
.root :global(.map) {
|
||||||
|
left: 18px;
|
||||||
|
bottom: 188px;
|
||||||
|
}
|
||||||
|
|
||||||
.root :global(.home-opportunity-strip) {
|
.root :global(.home-opportunity-strip) {
|
||||||
left: 18px;
|
left: 18px;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -1489,6 +1530,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
.root :global(.map) {
|
||||||
|
inset: var(--header-height) 0 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.root :global(.home-opportunity-strip) {
|
.root :global(.home-opportunity-strip) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import styles from "./Dashboard.module.css";
|
import styles from "./Dashboard.module.css";
|
||||||
import detailChromeStyles from "./DetailPanelChrome.module.css";
|
import detailChromeStyles from "./DetailPanelChrome.module.css";
|
||||||
import modalChromeStyles from "./ModalChrome.module.css";
|
import modalChromeStyles from "./ModalChrome.module.css";
|
||||||
@@ -122,7 +122,13 @@ function buildSnapshot(
|
|||||||
detail?.risk?.level;
|
detail?.risk?.level;
|
||||||
const hitRate = Number(city.deb_recent_hit_rate ?? 0);
|
const hitRate = Number(city.deb_recent_hit_rate ?? 0);
|
||||||
const sampleCount = Number(city.deb_recent_sample_count ?? 0);
|
const sampleCount = Number(city.deb_recent_sample_count ?? 0);
|
||||||
|
const edgePercent = Number(detail?.market_scan?.edge_percent);
|
||||||
|
const normalizedEdge =
|
||||||
|
Number.isFinite(edgePercent) && Math.abs(edgePercent) <= 1
|
||||||
|
? edgePercent * 100
|
||||||
|
: edgePercent;
|
||||||
const score =
|
const score =
|
||||||
|
(Number.isFinite(normalizedEdge) ? 1000 + normalizedEdge * 10 : 0) +
|
||||||
(RISK_SCORE[String(tier || "")] || 0) * 100 +
|
(RISK_SCORE[String(tier || "")] || 0) * 100 +
|
||||||
hitRate * 100 +
|
hitRate * 100 +
|
||||||
Math.min(sampleCount, 60) / 10;
|
Math.min(sampleCount, 60) / 10;
|
||||||
@@ -288,6 +294,10 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div className="home-top-opportunity-label">
|
||||||
|
{locale === "en-US" ? "Today’s Top Opportunity" : "今日最佳机会"}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="home-card-titlebar">
|
<div className="home-card-titlebar">
|
||||||
<div>
|
<div>
|
||||||
<h2>{summary?.display_name || detail?.display_name || city.display_name}</h2>
|
<h2>{summary?.display_name || detail?.display_name || city.display_name}</h2>
|
||||||
@@ -520,6 +530,7 @@ function OpportunityStrip({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
function DashboardScreen() {
|
function DashboardScreen() {
|
||||||
const store = useDashboardStore();
|
const store = useDashboardStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const didAutoFocusRef = useRef(false);
|
||||||
const activeSummary = store.selectedCity
|
const activeSummary = store.selectedCity
|
||||||
? store.citySummariesByName[store.selectedCity] || null
|
? store.citySummariesByName[store.selectedCity] || null
|
||||||
: null;
|
: null;
|
||||||
@@ -581,6 +592,17 @@ function DashboardScreen() {
|
|||||||
const showHomepageChrome =
|
const showHomepageChrome =
|
||||||
!store.isPanelOpen && !store.historyState.isOpen && !store.futureModalDate;
|
!store.isPanelOpen && !store.historyState.isOpen && !store.futureModalDate;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (didAutoFocusRef.current) return;
|
||||||
|
if (!showHomepageChrome) return;
|
||||||
|
if (store.selectedCity) return;
|
||||||
|
const topOpportunity = homepageSnapshots[0]?.city.name;
|
||||||
|
if (!topOpportunity) return;
|
||||||
|
|
||||||
|
didAutoFocusRef.current = true;
|
||||||
|
void store.focusCity(topOpportunity);
|
||||||
|
}, [homepageSnapshots, showHomepageChrome, store]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|||||||
Reference in New Issue
Block a user