Compare commits

..

No commits in common. "f60f2b24e6c0aa12865d23ba061b78838498de81" and "0641183ecd37b4728f98ebcd7fcdb5948e154ccf" have entirely different histories.

5 changed files with 26 additions and 205 deletions

View file

@ -617,19 +617,6 @@ export default function TradeInV2Page() {
rightContent = <SummaryPlaceholder />;
}
// Polite SR announcement reflecting the estimate lifecycle. Same precedence as
// middleContent: loading → error → insufficient → ready → silent.
let statusMessage = "";
if (loading) {
statusMessage = "Идёт расчёт оценки…";
} else if (restoreError) {
statusMessage = "Ошибка загрузки отчёта";
} else if (estimate && insufficient) {
statusMessage = "Недостаточно данных для оценки";
} else if (estimate && !insufficient) {
statusMessage = "Оценка готова";
}
return (
<div style={{ width: 1536 * artboardScale, height: 1024 * artboardScale }}>
<div
@ -647,25 +634,6 @@ export default function TradeInV2Page() {
>
<style>{HELPER_STYLES}</style>
{/* 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={{
@ -693,7 +661,6 @@ export default function TradeInV2Page() {
{/* CONTENT WRAP */}
<div
inert={nav !== 0 || drawerOpen ? true : undefined}
style={{
position: "absolute",
inset: 14,

View file

@ -39,7 +39,7 @@ export function ObjectSummary({
}}
>
<style>{`
.os-row { background: none; border: none; transition: background .12s; }
.os-row { transition: background .12s; }
.os-row:hover { background: rgba(46,139,255,.07); }
`}</style>
@ -159,26 +159,21 @@ export function ObjectSummary({
{/* Per-section totals (clickable) */}
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
{data.summary.rows.map((row, i) => (
<button
<div
key={row.label}
type="button"
className="os-row"
onClick={() => onNavigate(row.nav)}
aria-label={`${row.label} — подробнее`}
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "9px 6px",
cursor: "pointer",
borderRadius: 5,
width: "100%",
fontFamily: "inherit",
textAlign: "left",
borderBottom:
i < data.summary.rows.length - 1
? `1px dotted ${tokens.lineGradient}`
: undefined,
cursor: "pointer",
borderRadius: 5,
}}
>
<span
@ -216,7 +211,7 @@ export function ObjectSummary({
</span>
<span style={{ color: tokens.accent, fontSize: 11 }}></span>
</span>
</button>
</div>
))}
</div>

View file

@ -273,11 +273,9 @@ export default function ResultPanel({
/>
))}
</div>
<button
type="button"
<div
className="rp-more"
onClick={() => onNavigate(card.nav)}
aria-label={`Подробнее — ${card.title.join(" ")}`}
style={{
display: "flex",
alignItems: "center",
@ -287,24 +285,18 @@ export default function ResultPanel({
letterSpacing: 0.3,
color: accent,
whiteSpace: "nowrap",
background: "none",
border: "none",
padding: 0,
margin: 0,
fontFamily: "inherit",
}}
>
Подробнее <span></span>
</button>
</div>
</div>
) : (
<>
<button
type="button"
<div
className="rp-more"
onClick={() => onNavigate(card.nav)}
aria-label={`Подробнее — ${card.title.join(" ")}`}
style={{
marginTop: 16,
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
@ -313,17 +305,10 @@ export default function ResultPanel({
fontSize: 9,
letterSpacing: 0.3,
color: accent,
width: "100%",
background: "none",
border: "none",
padding: 0,
margin: 0,
marginTop: 16,
fontFamily: "inherit",
}}
>
Подробнее <span></span>
</button>
</div>
<div
style={{
position: "absolute",
@ -650,11 +635,9 @@ export default function ResultPanel({
объект
</span>
</div>
<button
type="button"
<div
className="rp-more"
onClick={() => onNavigate(3)}
aria-label="Подробнее — цена и срок продажи"
style={{
display: "flex",
alignItems: "center",
@ -663,16 +646,11 @@ export default function ResultPanel({
fontSize: 9,
letterSpacing: 0.3,
color: accent,
background: "none",
border: "none",
padding: 0,
margin: 0,
marginTop: 2,
fontFamily: "inherit",
}}
>
Подробнее <span></span>
</button>
</div>
</div>
{/* right range — deals */}
@ -778,11 +756,9 @@ export default function ResultPanel({
>
ИСТОЧНИКИ ДАННЫХ
</span>
<button
type="button"
<span
className="rp-more"
onClick={() => onNavigate(2)}
aria-label="Подробнее — источники данных"
style={{
display: "flex",
alignItems: "center",
@ -791,15 +767,10 @@ export default function ResultPanel({
fontSize: 9.5,
letterSpacing: 0.3,
color: accent,
background: "none",
border: "none",
padding: 0,
margin: 0,
fontFamily: "inherit",
}}
>
Подробнее <span></span>
</button>
</span>
</div>
<div
style={{

View file

@ -6,8 +6,6 @@
// with HUD corner brackets, a header (section number + title + "← К ОЦЕНКЕ"
// button) and a scrollable body that swaps the active view.
import { useEffect, useRef } from "react";
import { tokens } from "./tokens";
import { overlayNums, overlayTitles } from "./fixtures";
import HistoryView from "./HistoryView";
@ -39,78 +37,8 @@ export default function SectionOverlay({
analytics,
cache,
}: SectionOverlayProps) {
// Modal dialog plumbing. `dialogRef` is the dialog root, `lastFocused` keeps
// the element that had focus before the overlay opened so we can restore it on
// close, and `onCloseRef` holds the latest onClose so the mount-only effect
// (deps []) never re-runs / re-traps focus on parent re-renders even though
// page.tsx passes a fresh arrow each render.
const dialogRef = useRef<HTMLDivElement>(null);
const lastFocused = useRef<Element | null>(null);
const onCloseRef = useRef(onClose);
onCloseRef.current = onClose;
useEffect(() => {
const dialog = dialogRef.current;
// Remember the trigger, then move focus into the dialog.
lastFocused.current = document.activeElement;
dialog?.focus();
function getFocusable(): HTMLElement[] {
if (!dialog) return [];
const nodes = dialog.querySelectorAll<HTMLElement>(
'button, [href], input, select, [tabindex]:not([tabindex="-1"])',
);
return Array.from(nodes).filter((el) => !el.hasAttribute("disabled"));
}
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") {
e.preventDefault();
onCloseRef.current();
return;
}
if (e.key !== "Tab" || !dialog) return;
const focusable = getFocusable();
if (focusable.length === 0) {
// Nothing tabbable inside — keep focus pinned on the dialog itself.
e.preventDefault();
dialog.focus();
return;
}
const first = focusable[0];
const last = focusable[focusable.length - 1];
const activeEl = document.activeElement;
const inside = dialog.contains(activeEl);
if (e.shiftKey) {
if (activeEl === first || activeEl === dialog || !inside) {
e.preventDefault();
last.focus();
}
} else if (activeEl === last || !inside) {
e.preventDefault();
first.focus();
}
}
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
// Return focus to whatever opened the overlay.
(lastFocused.current as HTMLElement | null)?.focus?.();
};
// Mount-only: refs (dialogRef/onCloseRef) are stable, so no extra deps.
}, []);
return (
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby="v2-overlay-title"
tabIndex={-1}
style={{
position: "absolute",
left: 28,
@ -131,7 +59,7 @@ export default function SectionOverlay({
}}
>
<style>{`
.tiv2-so-back { color: ${tokens.ink}; transition: all .15s; }
.tiv2-so-back { transition: all .15s; }
.tiv2-so-back:hover { border-color: ${tokens.accent}; color: ${tokens.accent}; }
`}</style>
@ -183,7 +111,6 @@ export default function SectionOverlay({
{overlayNums[active]}
</span>
<span
id="v2-overlay-title"
style={{
fontSize: 14,
fontWeight: 600,
@ -194,8 +121,7 @@ export default function SectionOverlay({
{overlayTitles[active]}
</span>
</div>
<button
type="button"
<div
onClick={onClose}
className="tiv2-so-back"
style={{
@ -207,9 +133,6 @@ export default function SectionOverlay({
borderRadius: 6,
padding: "6px 12px",
background: tokens.surface.w60,
// reset UA button defaults while keeping the original box look;
// base colour lives in .tiv2-so-back so :hover can win
fontFamily: "inherit",
}}
>
<span style={{ fontSize: 13 }}></span>
@ -218,7 +141,7 @@ export default function SectionOverlay({
>
К ОЦЕНКЕ
</span>
</button>
</div>
</div>
{/* overlay body */}

View file

@ -80,10 +80,8 @@ export default function TopNav({
.tnav-tab { color: ${tokens.muted}; font-weight: 400; cursor: pointer; transition: color .2s; }
.tnav-tab:hover { color: ${tokens.ink}; }
.tnav-tab--active { color: ${tokens.ink}; font-weight: 600; }
.tnav-userbtn { background: none; border: none; }
.tnav-userbtn:hover { background: rgba(46,139,255,.07); }
.tnav-menuitem:hover { background: rgba(46,139,255,.09); }
.tnav-logout { background: none; border: none; }
.tnav-logout:hover { background: rgba(205,104,104,.1); }
`}</style>
@ -186,27 +184,11 @@ export default function TopNav({
}}
>
{navLabels.map((label, i) => (
<button
type="button"
<div
key={label}
onClick={() => onNavigate(i)}
aria-current={active === i ? "page" : undefined}
className={`tnav-tab${active === i ? " tnav-tab--active" : ""}`}
style={{
position: "relative",
whiteSpace: "nowrap",
background: "none",
border: "none",
padding: 0,
margin: 0,
// NB: family/size/letterSpacing inherit for chrome reset, but do
// NOT set `font`/`color` shorthand — the .tnav-tab(--active)/:hover
// CSS must keep driving tab colour + weight.
fontFamily: "inherit",
fontSize: "inherit",
letterSpacing: "inherit",
cursor: "pointer",
}}
style={{ position: "relative", whiteSpace: "nowrap" }}
>
{label}
<div
@ -238,23 +220,15 @@ export default function TopNav({
}}
/>
)}
</button>
</div>
))}
</div>
{/* User menu */}
<div
style={{ position: "relative" }}
onKeyDown={(e) => {
if (e.key === "Escape") setUserOpen(false);
}}
>
<button
type="button"
<div style={{ position: "relative" }}>
<div
onClick={() => setUserOpen((o) => !o)}
className="tnav-userbtn"
aria-haspopup="true"
aria-expanded={userOpen}
style={{
display: "flex",
alignItems: "center",
@ -263,8 +237,6 @@ export default function TopNav({
padding: "5px 8px 5px 6px",
borderRadius: "9px",
transition: "background .15s",
fontFamily: "inherit",
textAlign: "left",
}}
>
<div
@ -309,7 +281,7 @@ export default function TopNav({
</div>
</div>
<span style={{ color: tokens.muted2, fontSize: "9px" }}></span>
</button>
</div>
{userOpen && (
<div
@ -453,20 +425,13 @@ export default function TopNav({
}}
/>
<button
type="button"
<div
onClick={() => {
setUserOpen(false);
onLogout?.();
}}
className="tnav-logout"
style={{
...menuItemStyle,
color: tokens.danger,
width: "100%",
fontFamily: "inherit",
textAlign: "left",
}}
style={{ ...menuItemStyle, color: tokens.danger }}
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
<path
@ -476,7 +441,7 @@ export default function TopNav({
/>
</svg>
Выйти
</button>
</div>
</div>
)}
</div>