/* ===== tweaks-panel.jsx ===== */ // @ds-adherence-ignore -- omelette starter scaffold (raw elements/hex/px by design) /* BEGIN USAGE */ // tweaks-panel.jsx // Reusable Tweaks shell + form-control helpers. // Exports (to window): useTweaks, TweaksPanel, TweakSection, TweakRow, TweakSlider, // TweakToggle, TweakRadio, TweakSelect, TweakText, TweakNumber, TweakColor, TweakButton. // // Owns the host protocol (listens for __activate_edit_mode / __deactivate_edit_mode, // posts __edit_mode_available / __edit_mode_set_keys / __edit_mode_dismissed) so // individual prototypes don't re-roll it. Ships a consistent set of controls so you // don't hand-draw , segmented radios, steppers, etc. // // Usage (in an HTML file that loads React + Babel): // // const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ // "primaryColor": "#D97757", // "palette": ["#D97757", "#29261b", "#f6f4ef"], // "fontSize": 16, // "density": "regular", // "dark": false // }/*EDITMODE-END*/; // // function App() { // const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); // return ( //
// Hello // // // setTweak('fontSize', v)} /> // setTweak('density', v)} /> // // setTweak('primaryColor', v)} /> // setTweak('palette', v)} /> // setTweak('dark', v)} /> // //
// ); // } // // TweakRadio is the segmented control for 2–3 short options (auto-falls-back to // TweakSelect past ~16/~10 chars per label); reach for TweakSelect directly when // options are many or long. For color tweaks always curate 3-4 options rather than // a free picker; an option can also be a whole 2–5 color palette (the stored value // is the array). The Tweak* controls are a floor, not a ceiling — build custom // controls inside the panel if a tweak calls for UI they don't cover. /* END USAGE */ // ───────────────────────────────────────────────────────────────────────────── const __TWEAKS_STYLE = ` .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px; max-height:calc(100vh - 32px);display:flex;flex-direction:column; transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right; background:rgba(250,249,247,.78);color:#29261b; -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%); border:.5px solid rgba(255,255,255,.6);border-radius:14px; box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18); font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden} .twk-hd{display:flex;align-items:center;justify-content:space-between; padding:10px 8px 10px 14px;cursor:move;user-select:none} .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em} .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55); width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1} .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b} .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px; overflow-y:auto;overflow-x:hidden;min-height:0; scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent} .twk-body::-webkit-scrollbar{width:8px} .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px} .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px; border:2px solid transparent;background-clip:content-box} .twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25); border:2px solid transparent;background-clip:content-box} .twk-row{display:flex;flex-direction:column;gap:5px} .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px} .twk-lbl{display:flex;justify-content:space-between;align-items:baseline; color:rgba(41,38,27,.72)} .twk-lbl>span:first-child{font-weight:500} .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums} .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase; color:rgba(41,38,27,.45);padding:10px 0 0} .twk-sect:first-child{padding-top:0} .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px; border:.5px solid rgba(0,0,0,.1);border-radius:7px; background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none} .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)} select.twk-field{padding-right:22px; background-image:url("data:image/svg+xml;utf8,"); background-repeat:no-repeat;background-position:right 8px center} .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0; border-radius:999px;background:rgba(0,0,0,.12);outline:none} .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none; width:14px;height:14px;border-radius:50%;background:#fff; border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default} .twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%; background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default} .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px; background:rgba(0,0,0,.06);user-select:none} .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px; background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12); transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s} .twk-seg.dragging .twk-seg-thumb{transition:none} .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0; background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px; border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2; overflow-wrap:anywhere} .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px; background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0} .twk-toggle[data-on="1"]{background:#34c759} .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%; background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s} .twk-toggle[data-on="1"] i{transform:translateX(14px)} .twk-num{display:flex;align-items:center;box-sizing:border-box;min-width:0;height:26px;padding:0 0 0 8px; border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)} .twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize; user-select:none;padding-right:8px} .twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent; font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0; outline:none;color:inherit;-moz-appearance:textfield} .twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{ -webkit-appearance:none;margin:0} .twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)} .twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px; background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default} .twk-btn:hover{background:rgba(0,0,0,.88)} .twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit} .twk-btn.secondary:hover{background:rgba(0,0,0,.1)} .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px; border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default; background:transparent;flex-shrink:0} .twk-swatch::-webkit-color-swatch-wrapper{padding:0} .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px} .twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px} .twk-chips{display:flex;gap:6px} .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px; padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default; box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06); transition:transform .12s cubic-bezier(.3,.7,.4,1),box-shadow .12s} .twk-chip:hover{transform:translateY(-1px); box-shadow:0 0 0 .5px rgba(0,0,0,.18),0 4px 10px rgba(0,0,0,.12)} .twk-chip[data-on="1"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85), 0 2px 6px rgba(0,0,0,.15)} .twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%; display:flex;flex-direction:column;box-shadow:-1px 0 0 rgba(0,0,0,.1)} .twk-chip>span>i{flex:1;box-shadow:0 -1px 0 rgba(0,0,0,.1)} .twk-chip>span>i:first-child{box-shadow:none} .twk-chip svg{position:absolute;top:6px;left:6px;width:13px;height:13px; filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))} `; // ── useTweaks ─────────────────────────────────────────────────────────────── // Single source of truth for tweak values. setTweak persists via the host // (__edit_mode_set_keys → host rewrites the EDITMODE block on disk). function useTweaks(defaults) { const [values, setValues] = React.useState(defaults); // Accepts either setTweak('key', value) or setTweak({ key: value, ... }) so a // useState-style call doesn't write a "[object Object]" key into the persisted // JSON block. const setTweak = React.useCallback((keyOrEdits, val) => { const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null ? keyOrEdits : { [keyOrEdits]: val }; setValues((prev) => ({ ...prev, ...edits })); window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*'); // Same-window signal so in-page listeners (deck-stage rail thumbnails) // can react — the parent message only reaches the host, not peers. window.dispatchEvent(new CustomEvent('tweakchange', { detail: edits })); }, []); return [values, setTweak]; } // ── TweaksPanel ───────────────────────────────────────────────────────────── // Floating shell. Registers the protocol listener BEFORE announcing // availability — if the announce ran first, the host's activate could land // before our handler exists and the toolbar toggle would silently no-op. // The close button posts __edit_mode_dismissed so the host's toolbar toggle // flips off in lockstep; the host echoes __deactivate_edit_mode back which // is what actually hides the panel. function TweaksPanel({ title = 'Tweaks', children }) { const [open, setOpen] = React.useState(false); const dragRef = React.useRef(null); const offsetRef = React.useRef({ x: 16, y: 16 }); const PAD = 16; const clampToViewport = React.useCallback(() => { const panel = dragRef.current; if (!panel) return; const w = panel.offsetWidth, h = panel.offsetHeight; const maxRight = Math.max(PAD, window.innerWidth - w - PAD); const maxBottom = Math.max(PAD, window.innerHeight - h - PAD); offsetRef.current = { x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)), y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)), }; panel.style.right = offsetRef.current.x + 'px'; panel.style.bottom = offsetRef.current.y + 'px'; }, []); React.useEffect(() => { if (!open) return; clampToViewport(); if (typeof ResizeObserver === 'undefined') { window.addEventListener('resize', clampToViewport); return () => window.removeEventListener('resize', clampToViewport); } const ro = new ResizeObserver(clampToViewport); ro.observe(document.documentElement); return () => ro.disconnect(); }, [open, clampToViewport]); React.useEffect(() => { const onMsg = (e) => { const t = e?.data?.type; if (t === '__activate_edit_mode') setOpen(true); else if (t === '__deactivate_edit_mode') setOpen(false); }; window.addEventListener('message', onMsg); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); return () => window.removeEventListener('message', onMsg); }, []); const dismiss = () => { setOpen(false); window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*'); }; const onDragStart = (e) => { const panel = dragRef.current; if (!panel) return; const r = panel.getBoundingClientRect(); const sx = e.clientX, sy = e.clientY; const startRight = window.innerWidth - r.right; const startBottom = window.innerHeight - r.bottom; const move = (ev) => { offsetRef.current = { x: startRight - (ev.clientX - sx), y: startBottom - (ev.clientY - sy), }; clampToViewport(); }; const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); }; window.addEventListener('mousemove', move); window.addEventListener('mouseup', up); }; if (!open) return null; return ( <>
{title}
{children}
); } // ── Layout helpers ────────────────────────────────────────────────────────── function TweakSection({ label, children }) { return ( <>
{label}
{children} ); } function TweakRow({ label, value, children, inline = false }) { return (
{label} {value != null && {value}}
{children}
); } // ── Controls ──────────────────────────────────────────────────────────────── function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) { return ( onChange(Number(e.target.value))} /> ); } function TweakToggle({ label, value, onChange }) { return (
{label}
); } function TweakRadio({ label, value, options, onChange }) { const trackRef = React.useRef(null); const [dragging, setDragging] = React.useState(false); // The active value is read by pointer-move handlers attached for the lifetime // of a drag — ref it so a stale closure doesn't fire onChange for every move. const valueRef = React.useRef(value); valueRef.current = value; // Segments wrap mid-word once per-segment width runs out. The track is // ~248px (280 panel − 28 body pad − 4 seg pad), each button loses 12px // to its own padding, and 11.5px system-ui averages ~6.3px/char — so 2 // options fit ~16 chars each, 3 fit ~10. Past that (or >3 options), fall // back to a dropdown rather than wrap. const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length; const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0); const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] ?? 0); if (!fitsAsSegments) { // onChange(e.target.value)}> {options.map((o) => { const v = typeof o === 'object' ? o.value : o; const l = typeof o === 'object' ? o.label : o; return ; })} ); } function TweakText({ label, value, placeholder, onChange }) { return ( onChange(e.target.value)} /> ); } function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) { const clamp = (n) => { if (min != null && n < min) return min; if (max != null && n > max) return max; return n; }; const startRef = React.useRef({ x: 0, val: 0 }); const onScrubStart = (e) => { e.preventDefault(); startRef.current = { x: e.clientX, val: value }; const decimals = (String(step).split('.')[1] || '').length; const move = (ev) => { const dx = ev.clientX - startRef.current.x; const raw = startRef.current.val + dx * step; const snapped = Math.round(raw / step) * step; onChange(clamp(Number(snapped.toFixed(decimals)))); }; const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); }; window.addEventListener('pointermove', move); window.addEventListener('pointerup', up); }; return (
{label} onChange(clamp(Number(e.target.value)))} /> {unit && {unit}}
); } // Relative-luminance contrast pick — checkmarks drawn over a swatch need to // read on both #111 and #fafafa without per-option configuration. Hex input // only (#rgb / #rrggbb); named or rgb()/hsl() colors fall through to "light". function __twkIsLight(hex) { const h = String(hex).replace('#', ''); const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0'); const n = parseInt(x.slice(0, 6), 16); if (Number.isNaN(n)) return true; const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255; return r * 299 + g * 587 + b * 114 > 148000; } const __TwkCheck = ({ light }) => ( ); // TweakColor — curated color/palette picker. Each option is either a single // hex string or an array of 1-5 hex strings; the card adapts — a lone color // renders solid, a palette renders colors[0] as the hero (left ~2/3) with the // rest stacked in a sharp column on the right. onChange emits the // option in the shape it was passed (string stays string, array stays array). // Without options it falls back to the native color input for back-compat. function TweakColor({ label, value, options, onChange }) { if (!options || !options.length) { return (
{label}
onChange(e.target.value)} />
); } // Native emits lowercase hex per the HTML spec, so // compare case-insensitively. String() guards JSON.stringify(undefined), // which returns the primitive undefined (no .toLowerCase). const key = (o) => String(JSON.stringify(o)).toLowerCase(); const cur = key(value); return (
{options.map((o, i) => { const colors = Array.isArray(o) ? o : [o]; const [hero, ...rest] = colors; const sup = rest.slice(0, 4); const on = key(o) === cur; return ( ); })}
); } function TweakButton({ label, onClick, secondary = false }) { return ( ); } Object.assign(window, { useTweaks, TweaksPanel, TweakSection, TweakRow, TweakSlider, TweakToggle, TweakRadio, TweakSelect, TweakText, TweakNumber, TweakColor, TweakButton, }); /* ===== hooks.jsx ===== */ // Shared hooks + small primitives const { useState, useEffect, useRef, useCallback } = React; // Reveal-on-scroll: returns a ref to attach. Adds .in when visible. function useReveal(opts = {}) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; const targets = []; if (el.matches?.(".reveal")) targets.push(el); el.querySelectorAll?.(".reveal").forEach((n) => targets.push(n)); const show = (t) => t.classList.add("in"); // Immediately reveal anything already within (or near) the viewport — robust to // capture/offscreen contexts where IntersectionObserver may not fire. const revealInView = () => { const vh = window.innerHeight || 800; targets.forEach((t) => { const r = t.getBoundingClientRect(); if (r.top < vh * 0.96 && r.bottom > 0) show(t); }); }; revealInView(); const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { show(e.target); io.unobserve(e.target); } }); }, { threshold: opts.threshold ?? 0.12, rootMargin: opts.rootMargin ?? "0px 0px -6% 0px" }); targets.forEach((t) => { if (!t.classList.contains("in")) io.observe(t); }); // Safety net: if observers never fire (headless/offscreen), reveal everything. const fallback = setTimeout(() => targets.forEach(show), 1600); return () => { io.disconnect(); clearTimeout(fallback); }; }, []); return ref; } // Count-up number when it scrolls into view function CountUp({ value, decimals = 0, suffix = "", duration = 1500 }) { const ref = useRef(null); const [disp, setDisp] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; let raf, started = false; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting && !started) { started = true; const t0 = performance.now(); const tick = (now) => { const p = Math.min(1, (now - t0) / duration); const eased = 1 - Math.pow(1 - p, 3); setDisp(value * eased); if (p < 1) raf = requestAnimationFrame(tick); else setDisp(value); }; raf = requestAnimationFrame(tick); io.unobserve(e.target); } }); }, { threshold: 0.5 }); io.observe(el); return () => { io.disconnect(); cancelAnimationFrame(raf); }; }, [value]); return {disp.toFixed(decimals)}{suffix}; } // Magnetic wrapper — element drifts slightly toward cursor function Magnetic({ strength = 0.32, children, className, style }) { const ref = useRef(null); const onMove = useCallback((e) => { const el = ref.current; if (!el) return; const r = el.getBoundingClientRect(); const x = (e.clientX - (r.left + r.width / 2)) * strength; const y = (e.clientY - (r.top + r.height / 2)) * strength; el.style.transform = `translate(${x}px, ${y}px)`; }, [strength]); const reset = useCallback(() => { const el = ref.current; if (el) el.style.transform = "translate(0,0)"; }, []); return ( {children} ); } // Section scaffold with eyebrow/number + title function SectionHead({ num, eyebrow, title }) { return (
{num}
{eyebrow &&
{eyebrow}
}

); } Object.assign(window, { useReveal, CountUp, Magnetic, SectionHead }); /* ===== sections-top.jsx ===== */ // Nav + Hero + About const { useState: useStateT, useEffect: useEffectT, useRef: useRefT } = React; const P = window.PORTFOLIO; const NAV_LINKS = [ { id: "research", label: "Research" }, { id: "publications", label: "Publications" }, { id: "experience", label: "Experience" }, { id: "achievements", label: "Recognition" }, { id: "contact", label: "Contact" }, ]; function Nav() { const [solid, setSolid] = useStateT(false); const [open, setOpen] = useStateT(false); useEffectT(() => { const onScroll = () => setSolid(window.scrollY > 80); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); const go = (id) => { setOpen(false); document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" }); }; return (
{open && (
{NAV_LINKS.map((l) => ( { e.preventDefault(); go(l.id); }} style={{ fontFamily: "var(--mono)", fontSize: 13, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-soft)" }}> {l.label} ))} Download CV ↓
)}
); } function Hero() { const ref = useReveal(); const imgWrap = useRefT(null); // parallax drift on the portrait useEffectT(() => { const onScroll = () => { const y = window.scrollY; if (imgWrap.current) imgWrap.current.style.transform = `translateY(${y * 0.06}px)`; }; window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); const keywords = ["Fuzzy MCDM", "Graph Theory", "Machine Learning", "Decision Science", "Topological Indices", "Computational Math"]; return (
{/* faint backdrop glyph */}
); } function About() { const ref = useReveal(); return (
{P.bio.map((para, i) => (

{para}

))}
{P.stats.map((s, i) => (
{s.label}
))}
); } Object.assign(window, { Nav, Hero, About }); /* ===== sections-mid.jsx ===== */ // Research expertise + Publications const { useState: useStateM } = React; const PM = window.PORTFOLIO; function Research() { const ref = useReveal(); return (
{PM.expertise.map((e, i) => (
0{i + 1}

{e.title}

{e.desc}

))}
); } function Publications() { const ref = useReveal(); const [filter, setFilter] = useStateM("All"); const list = PM.publications.map((p, i) => ({ ...p, n: i + 1 })); const shown = filter === "All" ? list : list.filter((p) => p.tags.includes(filter)); return (
peer-reviewed publications
{/* filters */}
{PM.pubFilters.map((f) => { const active = f === filter; const count = f === "All" ? list.length : list.filter((p) => p.tags.includes(f)).length; return ( ); })}
{shown.map((p) => ( {String(p.n).padStart(2, "0")}

{p.title}

{p.journal} {p.tags.map((t) => ( {t} ))}
{p.status}
))}
); } Object.assign(window, { Research, Publications }); /* ===== sections-bottom.jsx ===== */ // Experience, Education, Skills, Recognition, Speaking, Contact, Footer const { useState: useStateB, useEffect: useEffectB, useRef: useRefB } = React; const PB = window.PORTFOLIO; function Experience() { const ref = useReveal(); return (
{/* timeline */}
{PB.experience.map((x, i) => (
{x.period}

{x.role}

{x.org}
    {x.points.map((pt, j) => (
  • {pt}
  • ))}
))}
{/* education + skills */}
Education
{PB.education.map((e, i) => (
{e.period}

{e.degree}

{e.school}
{e.detail}
))}
Tools & Methods
{PB.tools.map((t, i) => ( ))}
); } function SkillBar({ name, level, delay }) { const ref = useRefB(null); const [w, setW] = useStateB(0); useEffectB(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((ents) => { ents.forEach((e) => { if (e.isIntersecting) { setTimeout(() => setW(level), delay); io.unobserve(e.target); } }); }, { threshold: 0.6 }); io.observe(el); return () => io.disconnect(); }, [level, delay]); return (
{name} {w}
); } function Recognition() { const ref = useReveal(); return (
{PB.achievements.map((a, i) => (
{String(i + 1).padStart(2, "0")}

{a}

))}
Speaking
{PB.speaking.map((s, i) => (

{s.title}

{s.venue}

{s.note}

))}
Languages {PB.languages.map((l) => ( {l} ))}
); } function Contact() { const ref = useReveal(); return (
Contact

Let's build something rigorous together.

Open to research collaboration, supervision, speaking, and consulting on fuzzy decision-making, graph theory and applied machine learning.

Email
{PB.email}
WhatsApp
{PB.whatsapp}
Website
{PB.website}
Location
{PB.location}
{PB.links.map((l) => ( {l.label} ↗ ))}
); } function Footer() { return (
© {new Date().getFullYear()} DR. MUHAMMAD UMAR MIRZA PhD Mathematics · Punjab, Pakistan
); } Object.assign(window, { Experience, Recognition, Contact, Footer }); /* ===== app.jsx ===== */ // Mount const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "displayFont": "Newsreader", "accent": "#7fd18a", "showMarquee": true, "showGrid": true, "showCollab": true, "animations": true }/*EDITMODE-END*/; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); useEffect(() => { const root = document.documentElement; root.style.setProperty("--serif", `"${t.displayFont}", Georgia, "Times New Roman", serif`); root.style.setProperty("--dot", t.accent); root.classList.toggle("no-marquee", !t.showMarquee); root.classList.toggle("no-grid", !t.showGrid); root.classList.toggle("no-collab", !t.showCollab); root.classList.toggle("tweak-no-anim", !t.animations); }, [t]); return (