fix(tradein/v2): mobile layout — remove overflow floor + honest stacked mobile block (#2275) (#2389)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m20s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 47s
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m20s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 47s
This commit is contained in:
parent
4f0cae5c18
commit
293184ebc6
2 changed files with 597 additions and 488 deletions
|
|
@ -411,16 +411,25 @@ export default function TradeInV2Page() {
|
||||||
useEffect(() => setMounted(true), []);
|
useEffect(() => setMounted(true), []);
|
||||||
|
|
||||||
// Scale-fit: shrink the fixed 1536×1024 artboard to fit smaller viewports
|
// Scale-fit: shrink the fixed 1536×1024 artboard to fit smaller viewports
|
||||||
// instead of clipping. Never upscale (capped at 1); recomputed on resize.
|
// instead of clipping. Never upscale (capped at 1). #2275: this used to floor
|
||||||
|
// at 0.88, which on a 390px phone still rendered the artboard ~1350px wide —
|
||||||
|
// page-wide horizontal scroll + a hero clipped at the left edge + the summary
|
||||||
|
// rail pushed off-screen. Width is now the only constraint (a scaled artboard
|
||||||
|
// taller than the viewport just scrolls the page vertically, which is normal
|
||||||
|
// and not the bug being fixed here).
|
||||||
const [artboardScale, setArtboardScale] = useState(1);
|
const [artboardScale, setArtboardScale] = useState(1);
|
||||||
|
// #2275: below this viewport width the fixed-width artboard can no longer
|
||||||
|
// read at a usable size even scaled-to-fit, so HeroBar + ObjectSummary (the
|
||||||
|
// "at a glance" content — report meta, PDF CTA, per-section totals) get a real
|
||||||
|
// fluid mobile block above the artboard instead of a tiny scaled-down copy.
|
||||||
|
// The rest of the dashboard (ParamsPanel / ResultPanel / overlays) stays
|
||||||
|
// inside the scaled artboard — smaller on phones, but no longer overflowing.
|
||||||
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const compute = () =>
|
const compute = () => {
|
||||||
setArtboardScale(
|
setArtboardScale(Math.min(1, window.innerWidth / 1536));
|
||||||
Math.max(
|
setIsMobile(window.innerWidth < 768);
|
||||||
0.88,
|
};
|
||||||
Math.min(1, window.innerWidth / 1536, window.innerHeight / 1024),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
compute();
|
compute();
|
||||||
window.addEventListener("resize", compute);
|
window.addEventListener("resize", compute);
|
||||||
return () => window.removeEventListener("resize", compute);
|
return () => window.removeEventListener("resize", compute);
|
||||||
|
|
@ -573,7 +582,8 @@ export default function TradeInV2Page() {
|
||||||
const topNavUser = useMemo(() => {
|
const topNavUser = useMemo(() => {
|
||||||
const scope = me.data;
|
const scope = me.data;
|
||||||
if (!scope) return undefined;
|
if (!scope) return undefined;
|
||||||
const nameParts = scope.display_name?.trim().split(/\s+/).filter(Boolean) ?? [];
|
const nameParts =
|
||||||
|
scope.display_name?.trim().split(/\s+/).filter(Boolean) ?? [];
|
||||||
const initials =
|
const initials =
|
||||||
nameParts.length >= 2
|
nameParts.length >= 2
|
||||||
? (nameParts[0][0] + nameParts[1][0]).toUpperCase()
|
? (nameParts[0][0] + nameParts[1][0]).toUpperCase()
|
||||||
|
|
@ -693,172 +703,221 @@ export default function TradeInV2Page() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: 1536 * artboardScale, height: 1024 * artboardScale }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* #2275 mobile quick-view — a real fluid, native-size block for the "at a
|
||||||
|
glance" content (HeroBar's report meta + PDF CTA, ObjectSummary's
|
||||||
|
per-section totals). Inside the scaled artboard below, these would read
|
||||||
|
as tiny/illegible text on a phone rather than the honest mobile layout
|
||||||
|
the design deserves — so under the mobile breakpoint they render here
|
||||||
|
instead (and are skipped inside the artboard, see `!isMobile` below).
|
||||||
|
The rest of the dashboard (ParamsPanel / ResultPanel / overlays) has no
|
||||||
|
equivalent fluid mobile layout yet (follow-up) and stays inside the
|
||||||
|
scaled artboard: smaller on phones, but — with the 0.88 floor removed
|
||||||
|
above — never wider than the viewport. */}
|
||||||
|
{isMobile && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: 480,
|
||||||
|
margin: "0 auto",
|
||||||
|
padding: "14px 14px 20px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
fontFamily: tokens.font.sans,
|
||||||
|
color: tokens.ink,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeroBar
|
||||||
|
compact
|
||||||
|
data={{ report, object: objectInfo }}
|
||||||
|
estimateId={mounted ? currentEstimateId : null}
|
||||||
|
onOpenInfo={() => setDrawerOpen(true)}
|
||||||
|
hasEstimate={hasEstimate}
|
||||||
|
/>
|
||||||
|
<div style={{ marginTop: 14 }}>{rightContent}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{ width: 1536 * artboardScale, height: 1024 * artboardScale }}
|
||||||
position: "relative",
|
|
||||||
width: 1536,
|
|
||||||
height: 1024,
|
|
||||||
overflow: "hidden",
|
|
||||||
background: tokens.gradientBg,
|
|
||||||
fontFamily: tokens.font.sans,
|
|
||||||
color: tokens.ink,
|
|
||||||
transform: `scale(${artboardScale})`,
|
|
||||||
transformOrigin: "top left",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<style>{HELPER_STYLES}</style>
|
<div
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
width: 1536,
|
||||||
|
height: 1024,
|
||||||
|
overflow: "hidden",
|
||||||
|
background: tokens.gradientBg,
|
||||||
|
fontFamily: tokens.font.sans,
|
||||||
|
color: tokens.ink,
|
||||||
|
transform: `scale(${artboardScale})`,
|
||||||
|
transformOrigin: "top left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<style>{HELPER_STYLES}</style>
|
||||||
|
|
||||||
{/* #2264 C5: the page's single visible "title" is the SVG logo in TopNav,
|
{/* #2264 C5: the page's single visible "title" is the SVG logo in TopNav,
|
||||||
so give the document a real, visually-hidden <h1> (page-has-heading-one
|
so give the document a real, visually-hidden <h1> (page-has-heading-one
|
||||||
+ a top of the heading order for the overlay/panel <h2>s). */}
|
+ a top of the heading order for the overlay/panel <h2>s). */}
|
||||||
<h1
|
<h1
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
padding: 0,
|
|
||||||
margin: -1,
|
|
||||||
overflow: "hidden",
|
|
||||||
clip: "rect(0 0 0 0)",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
border: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Мера — оценка квартиры
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{/* Polite, visually-hidden status announcer for SR users. */}
|
|
||||||
<div
|
|
||||||
aria-live="polite"
|
|
||||||
role="status"
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
padding: 0,
|
|
||||||
margin: -1,
|
|
||||||
overflow: "hidden",
|
|
||||||
clip: "rect(0 0 0 0)",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
border: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{statusMessage}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* OUTER HUD FRAME */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: FRAME_INSET,
|
|
||||||
border: `1px solid ${tokens.line}`,
|
|
||||||
borderRadius: 4,
|
|
||||||
clipPath: FRAME_CLIP,
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 4 corner brackets */}
|
|
||||||
{brackets.map((b) => (
|
|
||||||
<div
|
|
||||||
key={b.key}
|
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
width: BRACKET_ARM,
|
width: 1,
|
||||||
height: BRACKET_ARM,
|
height: 1,
|
||||||
|
padding: 0,
|
||||||
|
margin: -1,
|
||||||
|
overflow: "hidden",
|
||||||
|
clip: "rect(0 0 0 0)",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
border: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Мера — оценка квартиры
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Polite, visually-hidden status announcer for SR users. */}
|
||||||
|
<div
|
||||||
|
aria-live="polite"
|
||||||
|
role="status"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
padding: 0,
|
||||||
|
margin: -1,
|
||||||
|
overflow: "hidden",
|
||||||
|
clip: "rect(0 0 0 0)",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
border: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{statusMessage}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* OUTER HUD FRAME */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: FRAME_INSET,
|
||||||
|
border: `1px solid ${tokens.line}`,
|
||||||
|
borderRadius: 4,
|
||||||
|
clipPath: FRAME_CLIP,
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
...b.style,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
{/* 4 corner brackets */}
|
||||||
|
{brackets.map((b) => (
|
||||||
|
<div
|
||||||
|
key={b.key}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
width: BRACKET_ARM,
|
||||||
|
height: BRACKET_ARM,
|
||||||
|
pointerEvents: "none",
|
||||||
|
...b.style,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* CONTENT WRAP */}
|
{/* CONTENT WRAP */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
inset: 14,
|
inset: 14,
|
||||||
padding: "16px 28px",
|
padding: "16px 28px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* TopNav stays interactive while a section overlay is open so the
|
{/* TopNav stays interactive while a section overlay is open so the
|
||||||
user can switch sections directly from the overlay. Only the
|
user can switch sections directly from the overlay. Only the
|
||||||
LocationDrawer (a full modal over the whole HUD) inerts it. The
|
LocationDrawer (a full modal over the whole HUD) inerts it. The
|
||||||
dashboard body (<main>/<footer>) is still inerted behind ANY
|
dashboard body (<main>/<footer>) is still inerted behind ANY
|
||||||
overlay/drawer — preserving the focus + click guard from a11y
|
overlay/drawer — preserving the focus + click guard from a11y
|
||||||
audit #2081 for everything except the top tabs. */}
|
audit #2081 for everything except the top tabs. */}
|
||||||
<nav
|
<nav
|
||||||
aria-label="Разделы"
|
aria-label="Разделы"
|
||||||
inert={drawerOpen ? true : undefined}
|
inert={drawerOpen ? true : undefined}
|
||||||
style={{ display: "contents" }}
|
style={{ display: "contents" }}
|
||||||
>
|
|
||||||
<TopNav
|
|
||||||
active={nav}
|
|
||||||
onNavigate={setNav}
|
|
||||||
reports={reportsCount ?? 0}
|
|
||||||
user={topNavUser}
|
|
||||||
onLogout={logout}
|
|
||||||
/>
|
|
||||||
</nav>
|
|
||||||
<main
|
|
||||||
inert={nav !== 0 || drawerOpen ? true : undefined}
|
|
||||||
style={{ display: "contents" }}
|
|
||||||
>
|
|
||||||
<HeroBar
|
|
||||||
data={{ report, object: objectInfo }}
|
|
||||||
estimateId={mounted ? currentEstimateId : null}
|
|
||||||
onOpenInfo={() => setDrawerOpen(true)}
|
|
||||||
hasEstimate={hasEstimate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "474px 1fr 250px",
|
|
||||||
gap: 18,
|
|
||||||
flex: 1,
|
|
||||||
minHeight: 0,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ParamsPanel
|
<TopNav
|
||||||
key={estimate?.estimate_id ?? "new"}
|
active={nav}
|
||||||
onSubmit={handleSubmit}
|
onNavigate={setNav}
|
||||||
isPending={mutation.isPending}
|
reports={reportsCount ?? 0}
|
||||||
hasEstimate={hasEstimate}
|
user={topNavUser}
|
||||||
error={apiError}
|
onLogout={logout}
|
||||||
initialValues={initialValues}
|
|
||||||
markers={markers}
|
|
||||||
/>
|
/>
|
||||||
{middleContent}
|
</nav>
|
||||||
{rightContent}
|
<main
|
||||||
</div>
|
inert={nav !== 0 || drawerOpen ? true : undefined}
|
||||||
</main>
|
style={{ display: "contents" }}
|
||||||
|
>
|
||||||
|
{/* #2275: on mobile the readable HeroBar lives in the fluid
|
||||||
|
quick-view block above instead of a tiny scaled-down copy. */}
|
||||||
|
{!isMobile && (
|
||||||
|
<HeroBar
|
||||||
|
data={{ report, object: objectInfo }}
|
||||||
|
estimateId={mounted ? currentEstimateId : null}
|
||||||
|
onOpenInfo={() => setDrawerOpen(true)}
|
||||||
|
hasEstimate={hasEstimate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<footer
|
<div
|
||||||
inert={nav !== 0 || drawerOpen ? true : undefined}
|
style={{
|
||||||
style={{ display: "contents" }}
|
display: "grid",
|
||||||
>
|
gridTemplateColumns: "474px 1fr 250px",
|
||||||
<Footer data={report} hasEstimate={hasEstimate} />
|
gap: 18,
|
||||||
</footer>
|
flex: 1,
|
||||||
</div>
|
minHeight: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ParamsPanel
|
||||||
|
key={estimate?.estimate_id ?? "new"}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
isPending={mutation.isPending}
|
||||||
|
hasEstimate={hasEstimate}
|
||||||
|
error={apiError}
|
||||||
|
initialValues={initialValues}
|
||||||
|
markers={markers}
|
||||||
|
/>
|
||||||
|
{middleContent}
|
||||||
|
{/* #2275: on mobile ObjectSummary is rendered fluid in the
|
||||||
|
quick-view block above instead of here (tiny scaled copy). */}
|
||||||
|
{!isMobile && rightContent}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
{nav !== 0 && (
|
<footer
|
||||||
<SectionOverlay
|
inert={nav !== 0 || drawerOpen ? true : undefined}
|
||||||
active={nav}
|
style={{ display: "contents" }}
|
||||||
onClose={() => setNav(0)}
|
>
|
||||||
onNavigate={setNav}
|
<Footer data={report} hasEstimate={hasEstimate} />
|
||||||
history={historyData}
|
</footer>
|
||||||
analytics={analyticsViewData}
|
</div>
|
||||||
sources={sourcesData}
|
|
||||||
cache={cacheData}
|
{nav !== 0 && (
|
||||||
|
<SectionOverlay
|
||||||
|
active={nav}
|
||||||
|
onClose={() => setNav(0)}
|
||||||
|
onNavigate={setNav}
|
||||||
|
history={historyData}
|
||||||
|
analytics={analyticsViewData}
|
||||||
|
sources={sourcesData}
|
||||||
|
cache={cacheData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<LocationDrawer
|
||||||
|
open={drawerOpen}
|
||||||
|
onClose={() => setDrawerOpen(false)}
|
||||||
|
data={locationData}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
<LocationDrawer
|
|
||||||
open={drawerOpen}
|
|
||||||
onClose={() => setDrawerOpen(false)}
|
|
||||||
data={locationData}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,12 @@ interface HeroBarProps {
|
||||||
// are hidden, mirroring how the PDF control already gates on estimate presence.
|
// are hidden, mirroring how the PDF control already gates on estimate presence.
|
||||||
hasEstimate: boolean;
|
hasEstimate: boolean;
|
||||||
onOpenInfo: () => void;
|
onOpenInfo: () => void;
|
||||||
|
// #2275 mobile quick-view: a real fluid layout instead of the fixed-width
|
||||||
|
// desktop one — meta/buttons stack, the decorative building photo (and the
|
||||||
|
// address/coef card baked into it) is dropped since it assumes a 560×152 box
|
||||||
|
// that cannot reflow. «КАК РАССЧИТАНО» still opens the same location-coef
|
||||||
|
// drawer, so no functionality is lost, only the redundant photo-card copy.
|
||||||
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdfBtnStyle: CSSProperties = {
|
const pdfBtnStyle: CSSProperties = {
|
||||||
|
|
@ -80,6 +86,7 @@ export default function HeroBar({
|
||||||
estimateId,
|
estimateId,
|
||||||
hasEstimate,
|
hasEstimate,
|
||||||
onOpenInfo,
|
onOpenInfo,
|
||||||
|
compact = false,
|
||||||
}: HeroBarProps) {
|
}: HeroBarProps) {
|
||||||
const pdfHref = hasEstimate ? pdfDownloadHref(estimateId) : null;
|
const pdfHref = hasEstimate ? pdfDownloadHref(estimateId) : null;
|
||||||
// A downloadable report exists ⇔ the estimate is ready ⇒ the PDF button is the
|
// A downloadable report exists ⇔ the estimate is ready ⇒ the PDF button is the
|
||||||
|
|
@ -93,7 +100,13 @@ export default function HeroBar({
|
||||||
const rule = filled ? "rgba(255,255,255,.75)" : "#6f8195";
|
const rule = filled ? "rgba(255,255,255,.75)" : "#6f8195";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" aria-hidden="true">
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 18 20"
|
||||||
|
fill="none"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<path d="M2 1h9l5 5v13H2z" stroke={frame} strokeWidth="1.2" />
|
<path d="M2 1h9l5 5v13H2z" stroke={frame} strokeWidth="1.2" />
|
||||||
<path d="M11 1v5h5" stroke={frame} strokeWidth="1.2" />
|
<path d="M11 1v5h5" stroke={frame} strokeWidth="1.2" />
|
||||||
<line x1="5" y1="11" x2="13" y2="11" stroke={rule} />
|
<line x1="5" y1="11" x2="13" y2="11" stroke={rule} />
|
||||||
|
|
@ -110,8 +123,9 @@ export default function HeroBar({
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: 30,
|
flexDirection: compact ? "column" : "row",
|
||||||
padding: "14px 2px 12px",
|
gap: compact ? 16 : 30,
|
||||||
|
padding: compact ? "0" : "14px 2px 12px",
|
||||||
flex: "0 0 auto",
|
flex: "0 0 auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -134,10 +148,18 @@ export default function HeroBar({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
gap: 22,
|
gap: compact ? 14 : 22,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex", gap: 26, alignItems: "flex-start" }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: compact ? "wrap" : "nowrap",
|
||||||
|
gap: compact ? 18 : 26,
|
||||||
|
rowGap: compact ? 8 : undefined,
|
||||||
|
alignItems: "flex-start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -215,7 +237,15 @@ export default function HeroBar({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: "flex", gap: 12, width: "100%", maxWidth: 440 }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: compact ? "column" : "row",
|
||||||
|
gap: 12,
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: compact ? "none" : 440,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{pdfHref ? (
|
{pdfHref ? (
|
||||||
<a
|
<a
|
||||||
className={pdfFilled ? "hero-pdf-btn-filled" : "hero-pdf-btn"}
|
className={pdfFilled ? "hero-pdf-btn-filled" : "hero-pdf-btn"}
|
||||||
|
|
@ -265,367 +295,387 @@ export default function HeroBar({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT: photo + overlays */}
|
{/* RIGHT: photo + overlays — dropped in compact mode (#2275): the box is
|
||||||
<div
|
a fixed 560×152 with several absolutely-positioned children (address
|
||||||
style={{
|
card, compass, distance scale) pinned to that size, so it cannot
|
||||||
position: "relative",
|
reflow to a phone width. «КАК РАССЧИТАНО» above still opens the same
|
||||||
width: 560,
|
location-coef drawer, so no functionality is lost. */}
|
||||||
height: 152,
|
{!compact && (
|
||||||
flex: "0 0 auto",
|
|
||||||
border: `1px solid ${tokens.line3}`,
|
|
||||||
borderRadius: 6,
|
|
||||||
overflow: "hidden",
|
|
||||||
background: tokens.photoBg,
|
|
||||||
boxShadow: "0 6px 26px rgba(40,80,130,.10)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!imgFailed && (
|
|
||||||
<img
|
|
||||||
src={`${BP}/trade-in-v2/building.png`}
|
|
||||||
alt=""
|
|
||||||
onError={() => setImgFailed(true)}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: 0,
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain",
|
|
||||||
objectPosition: "right center",
|
|
||||||
filter: "saturate(.25) brightness(1.06) contrast(.95)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* blue duotone tint toward HUD accent — recedes the photo (only over
|
|
||||||
the real image; skip when it 404s so the placeholder stays clean) */}
|
|
||||||
{!imgFailed && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
inset: 0,
|
|
||||||
background: "rgba(46,139,255,.14)",
|
|
||||||
mixBlendMode: "multiply",
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "relative",
|
||||||
inset: 0,
|
width: 560,
|
||||||
background:
|
height: 152,
|
||||||
"linear-gradient(90deg,rgba(238,244,250,.96) 0%,rgba(238,244,250,.5) 22%,transparent 40%)",
|
flex: "0 0 auto",
|
||||||
}}
|
border: `1px solid ${tokens.line3}`,
|
||||||
/>
|
borderRadius: 6,
|
||||||
<div
|
overflow: "hidden",
|
||||||
style={{
|
background: tokens.photoBg,
|
||||||
position: "absolute",
|
boxShadow: "0 6px 26px rgba(40,80,130,.10)",
|
||||||
inset: 0,
|
|
||||||
background:
|
|
||||||
"linear-gradient(0deg,rgba(230,240,250,.45),transparent 40%)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: "34%",
|
|
||||||
height: 1,
|
|
||||||
background:
|
|
||||||
"linear-gradient(90deg,transparent,rgba(46,139,255,.5),transparent)",
|
|
||||||
animation: "hero-scanv 6s linear infinite",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* address card */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
left: 16,
|
|
||||||
top: "50%",
|
|
||||||
transform: "translateY(-50%)",
|
|
||||||
width: 182,
|
|
||||||
background: tokens.surface.w72,
|
|
||||||
backdropFilter: "blur(5px)",
|
|
||||||
border: `1px solid ${tokens.line}`,
|
|
||||||
borderRadius: 5,
|
|
||||||
padding: "11px 13px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
{!imgFailed && (
|
||||||
style={{
|
<img
|
||||||
fontSize: 9,
|
src={`${BP}/trade-in-v2/building.png`}
|
||||||
letterSpacing: "2.5px",
|
alt=""
|
||||||
color: tokens.muted2,
|
onError={() => setImgFailed(true)}
|
||||||
marginBottom: 3,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
АДРЕС
|
|
||||||
</div>
|
|
||||||
<div style={{ fontSize: 14, fontWeight: 600, lineHeight: 1.3 }}>
|
|
||||||
{data.object.address}
|
|
||||||
<br />
|
|
||||||
<span
|
|
||||||
style={{ fontSize: 11, fontWeight: 400, color: tokens.muted }}
|
|
||||||
>
|
|
||||||
{data.object.city}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{ height: 1, background: tokens.lineSoft, margin: "8px 0" }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontFamily: tokens.font.mono,
|
|
||||||
fontSize: 9,
|
|
||||||
color: tokens.muted,
|
|
||||||
lineHeight: 1.55,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{data.object.streetView}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{ height: 1, background: tokens.lineSoft, margin: "8px 0" }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
// role=button + tabIndex + Enter/Space (#2264 C6): keyboard-operable
|
|
||||||
// without switching the element to a <button> (which, with preflight
|
|
||||||
// off, would need a UA-chrome reset and risk the row's exact box
|
|
||||||
// geometry / negative-margin hover bleed). Opens the methodology drawer.
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className="hero-coef-row"
|
|
||||||
onClick={onOpenInfo}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
e.preventDefault();
|
|
||||||
onOpenInfo();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
aria-label="Пояснение к расчёту коэффициента локации"
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
cursor: "pointer",
|
|
||||||
borderRadius: 4,
|
|
||||||
margin: "-3px -5px",
|
|
||||||
padding: "3px 5px",
|
|
||||||
transition: "background .15s",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: "8.5px",
|
position: "absolute",
|
||||||
letterSpacing: "1.5px",
|
inset: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "contain",
|
||||||
|
objectPosition: "right center",
|
||||||
|
filter: "saturate(.25) brightness(1.06) contrast(.95)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* blue duotone tint toward HUD accent — recedes the photo (only over
|
||||||
|
the real image; skip when it 404s so the placeholder stays clean) */}
|
||||||
|
{!imgFailed && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
background: "rgba(46,139,255,.14)",
|
||||||
|
mixBlendMode: "multiply",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
background:
|
||||||
|
"linear-gradient(90deg,rgba(238,244,250,.96) 0%,rgba(238,244,250,.5) 22%,transparent 40%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
background:
|
||||||
|
"linear-gradient(0deg,rgba(230,240,250,.45),transparent 40%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: "34%",
|
||||||
|
height: 1,
|
||||||
|
background:
|
||||||
|
"linear-gradient(90deg,transparent,rgba(46,139,255,.5),transparent)",
|
||||||
|
animation: "hero-scanv 6s linear infinite",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* address card */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: 16,
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
width: 182,
|
||||||
|
background: tokens.surface.w72,
|
||||||
|
backdropFilter: "blur(5px)",
|
||||||
|
border: `1px solid ${tokens.line}`,
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: "11px 13px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
letterSpacing: "2.5px",
|
||||||
color: tokens.muted2,
|
color: tokens.muted2,
|
||||||
|
marginBottom: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
КОЭФ. ЛОКАЦИИ
|
АДРЕС
|
||||||
</span>
|
</div>
|
||||||
<span style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
<div style={{ fontSize: 14, fontWeight: 600, lineHeight: 1.3 }}>
|
||||||
{data.object.locationCoef === "—" ? (
|
{data.object.address}
|
||||||
<span
|
<br />
|
||||||
style={{
|
<span
|
||||||
fontSize: "9px",
|
style={{ fontSize: 11, fontWeight: 400, color: tokens.muted }}
|
||||||
letterSpacing: ".5px",
|
>
|
||||||
// body2 (not muted2): муть на badgeTint давала 2.3:1 < AA;
|
{data.object.city}
|
||||||
// body2 на badgeTint ≈ 6.3:1 — читаемо, остаётся «приглушённым».
|
</span>
|
||||||
color: tokens.body2,
|
</div>
|
||||||
background: tokens.badgeTint,
|
<div
|
||||||
border: `1px solid ${tokens.line2}`,
|
style={{
|
||||||
borderRadius: 10,
|
height: 1,
|
||||||
padding: "1px 8px",
|
background: tokens.lineSoft,
|
||||||
}}
|
margin: "8px 0",
|
||||||
>
|
}}
|
||||||
{/* #2317: coef is a live feature now (GET /location-coef) — a
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontFamily: tokens.font.mono,
|
||||||
|
fontSize: 9,
|
||||||
|
color: tokens.muted,
|
||||||
|
lineHeight: 1.55,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.object.streetView}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 1,
|
||||||
|
background: tokens.lineSoft,
|
||||||
|
margin: "8px 0",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
// role=button + tabIndex + Enter/Space (#2264 C6): keyboard-operable
|
||||||
|
// without switching the element to a <button> (which, with preflight
|
||||||
|
// off, would need a UA-chrome reset and risk the row's exact box
|
||||||
|
// geometry / negative-margin hover bleed). Opens the methodology drawer.
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="hero-coef-row"
|
||||||
|
onClick={onOpenInfo}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
|
e.preventDefault();
|
||||||
|
onOpenInfo();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-label="Пояснение к расчёту коэффициента локации"
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: 4,
|
||||||
|
margin: "-3px -5px",
|
||||||
|
padding: "3px 5px",
|
||||||
|
transition: "background .15s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "8.5px",
|
||||||
|
letterSpacing: "1.5px",
|
||||||
|
color: tokens.muted2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
КОЭФ. ЛОКАЦИИ
|
||||||
|
</span>
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
||||||
|
{data.object.locationCoef === "—" ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "9px",
|
||||||
|
letterSpacing: ".5px",
|
||||||
|
// body2 (not muted2): муть на badgeTint давала 2.3:1 < AA;
|
||||||
|
// body2 на badgeTint ≈ 6.3:1 — читаемо, остаётся «приглушённым».
|
||||||
|
color: tokens.body2,
|
||||||
|
background: tokens.badgeTint,
|
||||||
|
border: `1px solid ${tokens.line2}`,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: "1px 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* #2317: coef is a live feature now (GET /location-coef) — a
|
||||||
dash here means unavailable/loading for THIS estimate
|
dash here means unavailable/loading for THIS estimate
|
||||||
(unavailable geo_source, no lat/lon, or query pending),
|
(unavailable geo_source, no lat/lon, or query pending),
|
||||||
never "not built yet", so «скоро» would be stale/false. */}
|
never "not built yet", so «скоро» would be stale/false. */}
|
||||||
нет данных
|
нет данных
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily: tokens.font.mono,
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: tokens.accent,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.object.locationCoef}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontFamily: tokens.font.mono,
|
display: "flex",
|
||||||
fontSize: 15,
|
alignItems: "center",
|
||||||
fontWeight: 500,
|
justifyContent: "center",
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
border: `1px solid ${tokens.accent}`,
|
||||||
|
borderRadius: "50%",
|
||||||
|
fontSize: "8.5px",
|
||||||
color: tokens.accent,
|
color: tokens.accent,
|
||||||
|
fontWeight: 600,
|
||||||
}}
|
}}
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{data.object.locationCoef}
|
?
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
border: `1px solid ${tokens.accent}`,
|
|
||||||
borderRadius: "50%",
|
|
||||||
fontSize: "8.5px",
|
|
||||||
color: tokens.accent,
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
?
|
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* compass */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 16,
|
||||||
|
top: 14,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 2,
|
||||||
|
color: tokens.accent,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="22"
|
||||||
|
height="22"
|
||||||
|
viewBox="0 0 22 22"
|
||||||
|
fill="none"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="10" stroke="#2e8bff" strokeWidth="1" />
|
||||||
|
<path d="M11 3 L13 11 L11 9 L9 11 Z" fill="#2e8bff" />
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily: tokens.font.mono,
|
||||||
|
fontSize: 8,
|
||||||
|
letterSpacing: ".5px",
|
||||||
|
color: tokens.muted,
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.object.compass}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* compass */}
|
{/* distance scale */}
|
||||||
<div
|
<div
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
right: 16,
|
|
||||||
top: 14,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 2,
|
|
||||||
color: tokens.accent,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
|
|
||||||
<circle cx="11" cy="11" r="10" stroke="#2e8bff" strokeWidth="1" />
|
|
||||||
<path d="M11 3 L13 11 L11 9 L9 11 Z" fill="#2e8bff" />
|
|
||||||
</svg>
|
|
||||||
<span
|
|
||||||
style={{
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
bottom: 16,
|
||||||
|
width: 170,
|
||||||
fontFamily: tokens.font.mono,
|
fontFamily: tokens.font.mono,
|
||||||
fontSize: 8,
|
|
||||||
letterSpacing: ".5px",
|
|
||||||
color: tokens.muted,
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.object.compass}
|
<div
|
||||||
</span>
|
style={{
|
||||||
</div>
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
height: 8,
|
||||||
|
borderBottom: "1px solid rgba(46,139,255,.55)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 8,
|
||||||
|
background: "rgba(46,139,255,.55)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 5,
|
||||||
|
background: "rgba(46,139,255,.45)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 5,
|
||||||
|
background: "rgba(46,139,255,.45)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 5,
|
||||||
|
background: "rgba(46,139,255,.45)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 8,
|
||||||
|
background: "rgba(46,139,255,.55)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
fontSize: "7.5px",
|
||||||
|
color: tokens.muted,
|
||||||
|
marginTop: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>0</span>
|
||||||
|
<span>25</span>
|
||||||
|
<span>50</span>
|
||||||
|
<span>75</span>
|
||||||
|
<span>100м</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* distance scale */}
|
{/* corner brackets */}
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
bottom: 16,
|
|
||||||
width: 170,
|
|
||||||
fontFamily: tokens.font.mono,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
position: "absolute",
|
||||||
justifyContent: "space-between",
|
left: 8,
|
||||||
alignItems: "flex-end",
|
top: 8,
|
||||||
height: 8,
|
width: 14,
|
||||||
borderBottom: "1px solid rgba(46,139,255,.55)",
|
height: 14,
|
||||||
|
borderLeft: `1.5px solid ${tokens.accent}`,
|
||||||
|
borderTop: `1.5px solid ${tokens.accent}`,
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
width: 1,
|
|
||||||
height: 8,
|
|
||||||
background: "rgba(46,139,255,.55)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
width: 1,
|
|
||||||
height: 5,
|
|
||||||
background: "rgba(46,139,255,.45)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
width: 1,
|
|
||||||
height: 5,
|
|
||||||
background: "rgba(46,139,255,.45)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
width: 1,
|
|
||||||
height: 5,
|
|
||||||
background: "rgba(46,139,255,.45)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
width: 1,
|
|
||||||
height: 8,
|
|
||||||
background: "rgba(46,139,255,.55)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
position: "absolute",
|
||||||
justifyContent: "space-between",
|
right: 8,
|
||||||
fontSize: "7.5px",
|
top: 8,
|
||||||
color: tokens.muted,
|
width: 14,
|
||||||
marginTop: 3,
|
height: 14,
|
||||||
|
borderRight: `1.5px solid ${tokens.accent}`,
|
||||||
|
borderTop: `1.5px solid ${tokens.accent}`,
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<span>0</span>
|
<div
|
||||||
<span>25</span>
|
style={{
|
||||||
<span>50</span>
|
position: "absolute",
|
||||||
<span>75</span>
|
left: 8,
|
||||||
<span>100м</span>
|
bottom: 8,
|
||||||
</div>
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
borderLeft: `1.5px solid ${tokens.accent}`,
|
||||||
|
borderBottom: `1.5px solid ${tokens.accent}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 8,
|
||||||
|
bottom: 8,
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
borderRight: `1.5px solid ${tokens.accent}`,
|
||||||
|
borderBottom: `1.5px solid ${tokens.accent}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{/* corner brackets */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
left: 8,
|
|
||||||
top: 8,
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
borderLeft: `1.5px solid ${tokens.accent}`,
|
|
||||||
borderTop: `1.5px solid ${tokens.accent}`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
right: 8,
|
|
||||||
top: 8,
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
borderRight: `1.5px solid ${tokens.accent}`,
|
|
||||||
borderTop: `1.5px solid ${tokens.accent}`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
left: 8,
|
|
||||||
bottom: 8,
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
borderLeft: `1.5px solid ${tokens.accent}`,
|
|
||||||
borderBottom: `1.5px solid ${tokens.accent}`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
right: 8,
|
|
||||||
bottom: 8,
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
borderRight: `1.5px solid ${tokens.accent}`,
|
|
||||||
borderBottom: `1.5px solid ${tokens.accent}`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue