// FloatingTools — a global manager that pops up draggable, closable, liquid-glass
// FLOATING WINDOWS (Price Sheet + EMI Calculator) that persist over ANY screen
// until the user closes them. Mounted once in App (outside the route transition),
// so windows stay open across navigation.
//
// TRIGGER CONTRACT: listens on `window` for CustomEvent 'uni-open-tool' with
// detail.tool === 'price' | 'emi'. Opens (or focuses) that window. Re-firing
// for an already-open tool just brings it to front (no-op otherwise).
//
// BEZEL-SCALE DRAG: the whole app is rendered inside a CSS transform:scale(...)
// element (`.tablet-bezel`), so pointer deltas are divided by the live scale.
//
// Data: TYPOLOGIES, DUMMY_RATE, formatINR(), fmtSqft() (window globals, data.jsx).
const TOOLSF_KEYS_ID = 'uni-tools-float-keys';
function ensureToolsFloatKeys() {
if (typeof document === 'undefined' || document.getElementById(TOOLSF_KEYS_ID)) return;
const s = document.createElement('style'); s.id = TOOLSF_KEYS_ID;
s.textContent = `
@keyframes toolFloatIn {
0% { opacity:0; transform:translate(-50%,-50%) scale(0.86); }
100% { opacity:1; transform:translate(-50%,-50%) scale(1); }
}
.toolf-win::-webkit-scrollbar{ width:9px; height:9px; }
.toolf-win::-webkit-scrollbar-thumb{ background:rgba(232,215,168,0.24); border-radius:8px; }
.toolf-win::-webkit-scrollbar-track{ background:transparent; }
/* gold-fill range slider for the EMI window */
input.toolf-range{ -webkit-appearance:none; appearance:none; width:100%; height:30px;
background:transparent; cursor:pointer; outline:none; }
input.toolf-range::-webkit-slider-runnable-track{ height:8px; border-radius:6px;
background:linear-gradient(90deg, var(--gold) 0%, var(--gold) var(--fill,40%),
rgba(255,248,230,0.12) var(--fill,40%), rgba(255,248,230,0.12) 100%); }
input.toolf-range::-webkit-slider-thumb{ -webkit-appearance:none; appearance:none;
width:26px; height:26px; margin-top:-9px; border-radius:50%;
background:radial-gradient(circle at 32% 30%, #fff6e0 0%, var(--gold) 55%, var(--gold-deep) 100%);
border:2px solid #fff8e0; box-shadow:0 3px 9px rgba(0,0,0,0.5);
transition:transform .14s cubic-bezier(0.34,1.56,0.64,1); }
input.toolf-range:active::-webkit-slider-thumb{ transform:scale(1.14); }
input.toolf-range::-moz-range-thumb{ width:26px; height:26px; border-radius:50%;
background:var(--gold); border:2px solid #fff8e0; box-shadow:0 3px 9px rgba(0,0,0,0.5); }
`;
document.head.appendChild(s);
}
// liquid-glass surface for the window shell (matches the app's glass chrome)
const toolfGlass = {
background: 'linear-gradient(158deg, rgba(30,25,18,0.80) 0%, rgba(18,14,10,0.82) 100%)',
backdropFilter: 'blur(22px) saturate(1.4)', WebkitBackdropFilter: 'blur(22px) saturate(1.4)',
border: '1px solid rgba(255,248,230,0.30)',
boxShadow: '0 36px 96px rgba(3,5,9,0.62), 0 10px 30px rgba(0,0,0,0.4), ' +
'inset 0 1px 0 rgba(255,255,255,0.26), inset 0 -1px 0 rgba(0,0,0,0.34)',
};
// ── live bezel scale (CSS transform:scale on .tablet-bezel) ──────────────────
function bezelScaleF() {
const b = document.querySelector('.tablet-bezel');
if (!b) return 1; const r = b.getBoundingClientRect();
return (r.width / b.offsetWidth) || 1;
}
const CANVAS_W = 2560, CANVAS_H = 1600;
// window registry: default size + staggered default centre position
const TOOL_DEFS = {
price: { w: 920, h: 620, x: 2560 / 2 - 90, y: 1600 / 2 - 60, title: 'Price Sheet' },
emi: { w: 720, h: 560, x: 2560 / 2 + 150, y: 1600 / 2 + 90, title: 'EMI Calculator' },
};
// ════════════════════════════════════════════════════════════════════════
// FloatingTools (manager — mounted once, globally)
// ════════════════════════════════════════════════════════════════════════
function FloatingTools() {
ensureToolsFloatKeys();
// open windows: { price?:{x,y,z}, emi?:{x,y,z} }
const [wins, setWins] = React.useState({});
const zTop = React.useRef(131);
const open = React.useCallback((tool) => {
if (tool !== 'price' && tool !== 'emi') return;
setWins(prev => {
const next = { ...prev };
zTop.current += 1;
if (next[tool]) {
// already open → just bring to front
next[tool] = { ...next[tool], z: zTop.current };
} else {
const d = TOOL_DEFS[tool];
next[tool] = { x: d.x, y: d.y, z: zTop.current };
}
return next;
});
}, []);
const close = React.useCallback((tool) => {
setWins(prev => { const n = { ...prev }; delete n[tool]; return n; });
}, []);
const focus = React.useCallback((tool) => {
setWins(prev => {
if (!prev[tool]) return prev;
zTop.current += 1;
return { ...prev, [tool]: { ...prev[tool], z: zTop.current } };
});
}, []);
const setPos = React.useCallback((tool, x, y) => {
setWins(prev => prev[tool] ? { ...prev, [tool]: { ...prev[tool], x, y } } : prev);
}, []);
// mount-once event listener with cleanup
React.useEffect(() => {
const handler = (e) => { const t = e && e.detail && e.detail.tool; if (t) open(t); };
window.addEventListener('uni-open-tool', handler);
return () => window.removeEventListener('uni-open-tool', handler);
}, [open]);
return (
| = 2 ? 'right' : 'left' }}>{c} | ))}||||
|---|---|---|---|---|
|
{t.name}
{t.tag}
|
{t.pair} | {fmtSqft(t.sqft)} | {fmtRate(DUMMY_RATE)} /sq.ft | {formatINR(t.price)} |