From 4fb35307174f30db24168cc028f38d13cf6756fd Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Fri, 15 Mar 2024 17:30:34 -0400
Subject: [PATCH 01/16] -feat: check for minimal_mode url parameter -feat:
 render minimal_mode version when url parameter is true -feat: remove hover
 listener (always keep visualization active) -style: remove box-shadow

---
 src/App.tsx | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 312 insertions(+), 3 deletions(-)

diff --git a/src/App.tsx b/src/App.tsx
index e307c00a..9b180efd 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -31,7 +31,7 @@ const DATABSE_THUMBNAILS = await db.get();
 const GENERATED_THUMBNAILS = {};
 
 const INIT_VIS_PANEL_WIDTH = window.innerWidth;
-const VIS_PADDING = 60;
+let VIS_PADDING = 60;
 const ZOOM_PADDING = 200;
 const ZOOM_DURATION = 500;
 
@@ -48,6 +48,13 @@ const allDrivers = [
 function App(props: RouteComponentProps) {
     // URL parameters
     const urlParams = new URLSearchParams(props.location.search);
+
+    // Flag URL Parameter for "minimal_mode", which if true only shows the visualization panel
+    const minimal_mode = urlParams.get('minimal_mode');
+
+    // Overwrite the padding
+    VIS_PADDING = 0;
+
     // !! instead of using `urlParams.get('external')`, we directly parse the external URL in order to include
     // any inlined parameters of the external link (e.g., private AWS link with authentication info.)
     let externalUrl = null;
@@ -94,7 +101,7 @@ function App(props: RouteComponentProps) {
     const [filteredSamples, setFilteredSamples] = useState(selectedSamples);
     const [showOverview, setShowOverview] = useState(true);
     const [showPutativeDriver, setShowPutativeDriver] = useState(true);
-    const [interactiveMode, setInteractiveMode] = useState(false);
+    const [interactiveMode, setInteractiveMode] = useState(minimal_mode ?? false);
     const [visPanelWidth, setVisPanelWidth] = useState(INIT_VIS_PANEL_WIDTH - VIS_PADDING * 2);
     const [overviewChr, setOverviewChr] = useState('');
     const [genomeViewChr, setGenomeViewChr] = useState('');
@@ -104,7 +111,7 @@ function App(props: RouteComponentProps) {
     const [selectedSvId, setSelectedSvId] = useState<string>('');
     const [breakpoints, setBreakpoints] = useState<[number, number, number, number]>([1, 100, 1, 100]);
     const [bpIntervals, setBpIntervals] = useState<[number, number, number, number] | undefined>();
-    const [mouseOnVis, setMouseOnVis] = useState(false);
+    const [mouseOnVis, setMouseOnVis] = useState(minimal_mode ?? false);
     const [jumpButtonInfo, setJumpButtonInfo] =
         useState<{ id: string; x: number; y: number; direction: 'leftward' | 'rightward'; zoomTo: () => void }>();
     const mousePos = useRef({ x: -100, y: -100 });
@@ -609,6 +616,308 @@ function App(props: RouteComponentProps) {
         };
     });
 
+    if (minimal_mode) {
+        return (
+            <ErrorBoundary>
+                <div
+                    className="vis-panel-container"
+                    style={{ width: '100%', height: '100%' }}
+                    onMouseMove={e => {
+                        const top = e.clientY;
+                        const left = e.clientX;
+                        const width = window.innerWidth;
+                        const height = window.innerHeight;
+                        mousePos.current = { x: left, y: top };
+                    }}
+                    onWheel={() => setJumpButtonInfo(undefined)}
+                    onClick={() => {
+                        setJumpButtonInfo(undefined);
+                    }}
+                >
+                    <div id="vis-panel" className="vis-panel">
+                        <div
+                            id="gosling-panel"
+                            className="gosling-panel"
+                            style={{
+                                width: `calc(100% - ${VIS_PADDING * 2}px)`,
+                                height: `calc(100% - ${VIS_PADDING * 2}px)`,
+                                padding: VIS_PADDING
+                            }}
+                        >
+                            {goslingComponent}
+                            {jumpButtonInfo ? (
+                                <button
+                                    className="jump-to-bp-btn"
+                                    style={{
+                                        position: 'fixed',
+                                        left: `${
+                                            jumpButtonInfo.x + 20 + (jumpButtonInfo.direction === 'leftward' ? -60 : 0)
+                                        }px`,
+                                        top: `${jumpButtonInfo.y}px`
+                                    }}
+                                    onClick={() => jumpButtonInfo.zoomTo()}
+                                >
+                                    {jumpButtonInfo.direction === 'leftward' ? '←' : '→'}
+                                </button>
+                            ) : null}
+                            <div
+                                style={{
+                                    width: '100%',
+                                    height: '100%',
+                                    top: VIS_PADDING,
+                                    left: VIS_PADDING,
+                                    opacity: 0.9,
+                                    zIndex: 2,
+                                    pointerEvents: interactiveMode ? 'none' : 'auto'
+                                }}
+                            />
+                            <div
+                                style={{
+                                    pointerEvents: 'none',
+                                    width: '100%',
+                                    height: '100%',
+                                    position: 'relative',
+                                    zIndex: 998
+                                }}
+                            >
+                                <select
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        top: '3px'
+                                    }}
+                                    className="nav-dropdown"
+                                    onChange={e => {
+                                        setShowSamples(false);
+                                        const chr = e.currentTarget.value;
+                                        setTimeout(() => setOverviewChr(chr), 300);
+                                    }}
+                                    value={overviewChr}
+                                    disabled={!showOverview}
+                                >
+                                    {[WHOLE_CHROMOSOME_STR, ...CHROMOSOMES].map(chr => {
+                                        return (
+                                            <option key={chr} value={chr}>
+                                                {chr}
+                                            </option>
+                                        );
+                                    })}
+                                </select>
+                                <img
+                                    src={legend}
+                                    style={{
+                                        position: 'absolute',
+                                        right: '3px',
+                                        top: '3px',
+                                        zIndex: 998,
+                                        width: '120px'
+                                    }}
+                                />
+                                <select
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        // !! This should be identical to how the height of circos determined.
+                                        top: `${Math.min(visPanelWidth, 600)}px`
+                                    }}
+                                    className="nav-dropdown"
+                                    onChange={e => {
+                                        setShowSamples(false);
+                                        const chr = e.currentTarget.value;
+                                        setTimeout(() => setGenomeViewChr(chr), 300);
+                                    }}
+                                    value={genomeViewChr}
+                                    disabled={!showOverview}
+                                >
+                                    {CHROMOSOMES.map(chr => {
+                                        return (
+                                            <option key={chr} value={chr}>
+                                                {chr}
+                                            </option>
+                                        );
+                                    })}
+                                </select>
+                                <svg
+                                    className="gene-search-icon"
+                                    viewBox="0 0 16 16"
+                                    style={{
+                                        top: `${Math.min(visPanelWidth, 600) + 6}px`
+                                        // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
+                                    }}
+                                >
+                                    <path
+                                        fillRule="evenodd"
+                                        d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
+                                    />
+                                </svg>
+                                <input
+                                    type="text"
+                                    className="gene-search"
+                                    placeholder="Search Gene (e.g., MYC)"
+                                    // alt={demo.assembly === 'hg38' ? 'Search Gene' : 'Not currently available for this assembly.'}
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        top: `${Math.min(visPanelWidth, 600)}px`
+                                        // cursor: demo.assembly === 'hg38' ? 'auto' : 'not-allowed',
+                                        // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
+                                    }}
+                                    // disabled={demo.assembly === 'hg38' ? false : true}
+                                    // onChange={(e) => {
+                                    //     const keyword = e.target.value;
+                                    //     if(keyword !== "" && !keyword.startsWith("c")) {
+                                    //         gosRef.current.api.suggestGene(keyword, (suggestions) => {
+                                    //             setGeneSuggestions(suggestions);
+                                    //         });
+                                    //         setSuggestionPosition({
+                                    //             left: searchBoxRef.current.getBoundingClientRect().left,
+                                    //             top: searchBoxRef.current.getBoundingClientRect().top + searchBoxRef.current.getBoundingClientRect().height,
+                                    //         });
+                                    //     } else {
+                                    //         setGeneSuggestions([]);
+                                    //     }
+                                    //     setSearchKeyword(keyword);
+                                    // }}
+                                    onKeyDown={e => {
+                                        const keyword = (e.target as HTMLTextAreaElement).value;
+                                        switch (e.key) {
+                                            case 'ArrowUp':
+                                                break;
+                                            case 'ArrowDown':
+                                                break;
+                                            case 'Enter':
+                                                // https://github.com/gosling-lang/gosling.js/blob/7555ab711023a0c3e2076a448756a9ba3eeb04f7/src/core/api.ts#L156
+                                                gosRef.current.hgApi.api.zoomToGene(
+                                                    `${demo.id}-mid-ideogram`,
+                                                    keyword,
+                                                    10000,
+                                                    1000
+                                                );
+                                                break;
+                                            case 'Esc':
+                                            case 'Escape':
+                                                break;
+                                        }
+                                    }}
+                                />
+                                <button
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        // !! This should be identical to how the height of circos determined.
+                                        top: `${Math.min(visPanelWidth, 600)}px`
+                                    }}
+                                    className="zoom-in-button"
+                                    onClick={e => {
+                                        const trackId = `${demo.id}-mid-ideogram`;
+                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                        if (end - start < 100) return;
+                                        const delta = (end - start) / 3.0;
+                                        gosRef.current.api.zoomTo(
+                                            trackId,
+                                            `chr1:${start + delta}-${end - delta}`,
+                                            0,
+                                            ZOOM_DURATION
+                                        );
+                                    }}
+                                >
+                                    +
+                                </button>
+                                <button
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        // !! This should be identical to how the height of circos determined.
+                                        top: `${Math.min(visPanelWidth, 600)}px`
+                                    }}
+                                    className="zoom-out-button"
+                                    onClick={e => {
+                                        const trackId = `${demo.id}-mid-ideogram`;
+                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                        const delta = (end - start) / 2.0;
+                                        gosRef.current.api.zoomTo(
+                                            trackId,
+                                            `chr1:${start}-${end}`,
+                                            delta,
+                                            ZOOM_DURATION
+                                        );
+                                    }}
+                                >
+                                    -
+                                </button>
+                                <button
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        // !! This should be identical to how the height of circos determined.
+                                        top: `${Math.min(visPanelWidth, 600)}px`
+                                    }}
+                                    className="zoom-left-button"
+                                    onClick={e => {
+                                        const trackId = `${demo.id}-mid-ideogram`;
+                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                        if (end - start < 100) return;
+                                        const delta = (end - start) / 4.0;
+                                        gosRef.current.api.zoomTo(
+                                            trackId,
+                                            `chr1:${start - delta}-${end - delta}`,
+                                            0,
+                                            ZOOM_DURATION
+                                        );
+                                    }}
+                                >
+                                    ←
+                                </button>
+                                <button
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        // !! This should be identical to how the height of circos determined.
+                                        top: `${Math.min(visPanelWidth, 600)}px`
+                                    }}
+                                    className="zoom-right-button"
+                                    onClick={e => {
+                                        const trackId = `${demo.id}-mid-ideogram`;
+                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                        const delta = (end - start) / 4.0;
+                                        gosRef.current.api.zoomTo(
+                                            trackId,
+                                            `chr1:${start + delta}-${end + delta}`,
+                                            0,
+                                            ZOOM_DURATION
+                                        );
+                                    }}
+                                >
+                                    →
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                    <div
+                        style={{
+                            width: '100%',
+                            height: '100%',
+                            visibility: 'collapse',
+                            boxShadow: interactiveMode ? 'inset 0 0 4px 2px #2399DB' : 'none',
+                            zIndex: 9999,
+                            background: 'none',
+                            position: 'absolute',
+                            top: 0,
+                            left: 0,
+                            pointerEvents: 'none'
+                        }}
+                    />
+                    <div
+                        style={{
+                            background: 'none',
+                            position: 'absolute',
+                            bottom: 20,
+                            left: VIS_PADDING,
+                            pointerEvents: 'none',
+                            visibility: demo.bam ? 'collapse' : 'visible'
+                        }}
+                    >
+                        {'ⓘ No read alignment data available for this sample.'}
+                    </div>
+                    <div id="hidden-gosling" style={{ visibility: 'collapse', position: 'fixed' }} />
+                </div>
+            </ErrorBoundary>
+        );
+    }
+
     return (
         <ErrorBoundary>
             <div

From 84fc1aad34f18cba14cd31c5ca651031a2a65a45 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 19 Mar 2024 14:14:03 -0400
Subject: [PATCH 02/16] fix: adjust padding in minimal mode

---
 src/App.tsx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/App.tsx b/src/App.tsx
index 9b180efd..0aed3f96 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -52,8 +52,10 @@ function App(props: RouteComponentProps) {
     // Flag URL Parameter for "minimal_mode", which if true only shows the visualization panel
     const minimal_mode = urlParams.get('minimal_mode');
 
-    // Overwrite the padding
-    VIS_PADDING = 0;
+    // Overwrite the padding in minimal mode
+    if (minimal_mode) {
+        VIS_PADDING = 0;
+    }
 
     // !! instead of using `urlParams.get('external')`, we directly parse the external URL in order to include
     // any inlined parameters of the external link (e.g., private AWS link with authentication info.)

From dd045e3e0f049de986e9de044531e3f19898f680 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 19 Mar 2024 17:25:19 -0400
Subject: [PATCH 03/16] -feat: create object for vis padding -feat: add buttons
 for scrolling to top and bottom of navigation

---
 src/App.tsx | 413 ++++++++--------------------------------------------
 1 file changed, 63 insertions(+), 350 deletions(-)

diff --git a/src/App.tsx b/src/App.tsx
index 0aed3f96..0acad18d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -31,7 +31,6 @@ const DATABSE_THUMBNAILS = await db.get();
 const GENERATED_THUMBNAILS = {};
 
 const INIT_VIS_PANEL_WIDTH = window.innerWidth;
-let VIS_PADDING = 60;
 const ZOOM_PADDING = 200;
 const ZOOM_DURATION = 500;
 
@@ -48,14 +47,13 @@ const allDrivers = [
 function App(props: RouteComponentProps) {
     // URL parameters
     const urlParams = new URLSearchParams(props.location.search);
-
-    // Flag URL Parameter for "minimal_mode", which if true only shows the visualization panel
-    const minimal_mode = urlParams.get('minimal_mode');
-
-    // Overwrite the padding in minimal mode
-    if (minimal_mode) {
-        VIS_PADDING = 0;
-    }
+    const isMinimalMode = urlParams.get('minimal_mode');
+    const VIS_PADDING = {
+        top: 60,
+        right: isMinimalMode ? 20 : 60,
+        bottom: isMinimalMode ? 0 : 60,
+        left: isMinimalMode ? 20 : 60
+    };
 
     // !! instead of using `urlParams.get('external')`, we directly parse the external URL in order to include
     // any inlined parameters of the external link (e.g., private AWS link with authentication info.)
@@ -103,8 +101,8 @@ function App(props: RouteComponentProps) {
     const [filteredSamples, setFilteredSamples] = useState(selectedSamples);
     const [showOverview, setShowOverview] = useState(true);
     const [showPutativeDriver, setShowPutativeDriver] = useState(true);
-    const [interactiveMode, setInteractiveMode] = useState(minimal_mode ?? false);
-    const [visPanelWidth, setVisPanelWidth] = useState(INIT_VIS_PANEL_WIDTH - VIS_PADDING * 2);
+    const [interactiveMode, setInteractiveMode] = useState(isMinimalMode ?? false);
+    const [visPanelWidth, setVisPanelWidth] = useState(INIT_VIS_PANEL_WIDTH - VIS_PADDING.left * 2);
     const [overviewChr, setOverviewChr] = useState('');
     const [genomeViewChr, setGenomeViewChr] = useState('');
     const [drivers, setDrivers] = useState(
@@ -113,7 +111,7 @@ function App(props: RouteComponentProps) {
     const [selectedSvId, setSelectedSvId] = useState<string>('');
     const [breakpoints, setBreakpoints] = useState<[number, number, number, number]>([1, 100, 1, 100]);
     const [bpIntervals, setBpIntervals] = useState<[number, number, number, number] | undefined>();
-    const [mouseOnVis, setMouseOnVis] = useState(minimal_mode ?? false);
+    const [mouseOnVis, setMouseOnVis] = useState(isMinimalMode ?? false);
     const [jumpButtonInfo, setJumpButtonInfo] =
         useState<{ id: string; x: number; y: number; direction: 'leftward' | 'rightward'; zoomTo: () => void }>();
     const mousePos = useRef({ x: -100, y: -100 });
@@ -311,7 +309,7 @@ function App(props: RouteComponentProps) {
         window.addEventListener(
             'resize',
             debounce(() => {
-                setVisPanelWidth(window.innerWidth - VIS_PADDING * 2);
+                setVisPanelWidth(window.innerWidth - VIS_PADDING.left * 2);
             }, 500)
         );
     }, []);
@@ -618,308 +616,6 @@ function App(props: RouteComponentProps) {
         };
     });
 
-    if (minimal_mode) {
-        return (
-            <ErrorBoundary>
-                <div
-                    className="vis-panel-container"
-                    style={{ width: '100%', height: '100%' }}
-                    onMouseMove={e => {
-                        const top = e.clientY;
-                        const left = e.clientX;
-                        const width = window.innerWidth;
-                        const height = window.innerHeight;
-                        mousePos.current = { x: left, y: top };
-                    }}
-                    onWheel={() => setJumpButtonInfo(undefined)}
-                    onClick={() => {
-                        setJumpButtonInfo(undefined);
-                    }}
-                >
-                    <div id="vis-panel" className="vis-panel">
-                        <div
-                            id="gosling-panel"
-                            className="gosling-panel"
-                            style={{
-                                width: `calc(100% - ${VIS_PADDING * 2}px)`,
-                                height: `calc(100% - ${VIS_PADDING * 2}px)`,
-                                padding: VIS_PADDING
-                            }}
-                        >
-                            {goslingComponent}
-                            {jumpButtonInfo ? (
-                                <button
-                                    className="jump-to-bp-btn"
-                                    style={{
-                                        position: 'fixed',
-                                        left: `${
-                                            jumpButtonInfo.x + 20 + (jumpButtonInfo.direction === 'leftward' ? -60 : 0)
-                                        }px`,
-                                        top: `${jumpButtonInfo.y}px`
-                                    }}
-                                    onClick={() => jumpButtonInfo.zoomTo()}
-                                >
-                                    {jumpButtonInfo.direction === 'leftward' ? '←' : '→'}
-                                </button>
-                            ) : null}
-                            <div
-                                style={{
-                                    width: '100%',
-                                    height: '100%',
-                                    top: VIS_PADDING,
-                                    left: VIS_PADDING,
-                                    opacity: 0.9,
-                                    zIndex: 2,
-                                    pointerEvents: interactiveMode ? 'none' : 'auto'
-                                }}
-                            />
-                            <div
-                                style={{
-                                    pointerEvents: 'none',
-                                    width: '100%',
-                                    height: '100%',
-                                    position: 'relative',
-                                    zIndex: 998
-                                }}
-                            >
-                                <select
-                                    style={{
-                                        pointerEvents: 'auto',
-                                        top: '3px'
-                                    }}
-                                    className="nav-dropdown"
-                                    onChange={e => {
-                                        setShowSamples(false);
-                                        const chr = e.currentTarget.value;
-                                        setTimeout(() => setOverviewChr(chr), 300);
-                                    }}
-                                    value={overviewChr}
-                                    disabled={!showOverview}
-                                >
-                                    {[WHOLE_CHROMOSOME_STR, ...CHROMOSOMES].map(chr => {
-                                        return (
-                                            <option key={chr} value={chr}>
-                                                {chr}
-                                            </option>
-                                        );
-                                    })}
-                                </select>
-                                <img
-                                    src={legend}
-                                    style={{
-                                        position: 'absolute',
-                                        right: '3px',
-                                        top: '3px',
-                                        zIndex: 998,
-                                        width: '120px'
-                                    }}
-                                />
-                                <select
-                                    style={{
-                                        pointerEvents: 'auto',
-                                        // !! This should be identical to how the height of circos determined.
-                                        top: `${Math.min(visPanelWidth, 600)}px`
-                                    }}
-                                    className="nav-dropdown"
-                                    onChange={e => {
-                                        setShowSamples(false);
-                                        const chr = e.currentTarget.value;
-                                        setTimeout(() => setGenomeViewChr(chr), 300);
-                                    }}
-                                    value={genomeViewChr}
-                                    disabled={!showOverview}
-                                >
-                                    {CHROMOSOMES.map(chr => {
-                                        return (
-                                            <option key={chr} value={chr}>
-                                                {chr}
-                                            </option>
-                                        );
-                                    })}
-                                </select>
-                                <svg
-                                    className="gene-search-icon"
-                                    viewBox="0 0 16 16"
-                                    style={{
-                                        top: `${Math.min(visPanelWidth, 600) + 6}px`
-                                        // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
-                                    }}
-                                >
-                                    <path
-                                        fillRule="evenodd"
-                                        d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
-                                    />
-                                </svg>
-                                <input
-                                    type="text"
-                                    className="gene-search"
-                                    placeholder="Search Gene (e.g., MYC)"
-                                    // alt={demo.assembly === 'hg38' ? 'Search Gene' : 'Not currently available for this assembly.'}
-                                    style={{
-                                        pointerEvents: 'auto',
-                                        top: `${Math.min(visPanelWidth, 600)}px`
-                                        // cursor: demo.assembly === 'hg38' ? 'auto' : 'not-allowed',
-                                        // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
-                                    }}
-                                    // disabled={demo.assembly === 'hg38' ? false : true}
-                                    // onChange={(e) => {
-                                    //     const keyword = e.target.value;
-                                    //     if(keyword !== "" && !keyword.startsWith("c")) {
-                                    //         gosRef.current.api.suggestGene(keyword, (suggestions) => {
-                                    //             setGeneSuggestions(suggestions);
-                                    //         });
-                                    //         setSuggestionPosition({
-                                    //             left: searchBoxRef.current.getBoundingClientRect().left,
-                                    //             top: searchBoxRef.current.getBoundingClientRect().top + searchBoxRef.current.getBoundingClientRect().height,
-                                    //         });
-                                    //     } else {
-                                    //         setGeneSuggestions([]);
-                                    //     }
-                                    //     setSearchKeyword(keyword);
-                                    // }}
-                                    onKeyDown={e => {
-                                        const keyword = (e.target as HTMLTextAreaElement).value;
-                                        switch (e.key) {
-                                            case 'ArrowUp':
-                                                break;
-                                            case 'ArrowDown':
-                                                break;
-                                            case 'Enter':
-                                                // https://github.com/gosling-lang/gosling.js/blob/7555ab711023a0c3e2076a448756a9ba3eeb04f7/src/core/api.ts#L156
-                                                gosRef.current.hgApi.api.zoomToGene(
-                                                    `${demo.id}-mid-ideogram`,
-                                                    keyword,
-                                                    10000,
-                                                    1000
-                                                );
-                                                break;
-                                            case 'Esc':
-                                            case 'Escape':
-                                                break;
-                                        }
-                                    }}
-                                />
-                                <button
-                                    style={{
-                                        pointerEvents: 'auto',
-                                        // !! This should be identical to how the height of circos determined.
-                                        top: `${Math.min(visPanelWidth, 600)}px`
-                                    }}
-                                    className="zoom-in-button"
-                                    onClick={e => {
-                                        const trackId = `${demo.id}-mid-ideogram`;
-                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                        if (end - start < 100) return;
-                                        const delta = (end - start) / 3.0;
-                                        gosRef.current.api.zoomTo(
-                                            trackId,
-                                            `chr1:${start + delta}-${end - delta}`,
-                                            0,
-                                            ZOOM_DURATION
-                                        );
-                                    }}
-                                >
-                                    +
-                                </button>
-                                <button
-                                    style={{
-                                        pointerEvents: 'auto',
-                                        // !! This should be identical to how the height of circos determined.
-                                        top: `${Math.min(visPanelWidth, 600)}px`
-                                    }}
-                                    className="zoom-out-button"
-                                    onClick={e => {
-                                        const trackId = `${demo.id}-mid-ideogram`;
-                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                        const delta = (end - start) / 2.0;
-                                        gosRef.current.api.zoomTo(
-                                            trackId,
-                                            `chr1:${start}-${end}`,
-                                            delta,
-                                            ZOOM_DURATION
-                                        );
-                                    }}
-                                >
-                                    -
-                                </button>
-                                <button
-                                    style={{
-                                        pointerEvents: 'auto',
-                                        // !! This should be identical to how the height of circos determined.
-                                        top: `${Math.min(visPanelWidth, 600)}px`
-                                    }}
-                                    className="zoom-left-button"
-                                    onClick={e => {
-                                        const trackId = `${demo.id}-mid-ideogram`;
-                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                        if (end - start < 100) return;
-                                        const delta = (end - start) / 4.0;
-                                        gosRef.current.api.zoomTo(
-                                            trackId,
-                                            `chr1:${start - delta}-${end - delta}`,
-                                            0,
-                                            ZOOM_DURATION
-                                        );
-                                    }}
-                                >
-                                    ←
-                                </button>
-                                <button
-                                    style={{
-                                        pointerEvents: 'auto',
-                                        // !! This should be identical to how the height of circos determined.
-                                        top: `${Math.min(visPanelWidth, 600)}px`
-                                    }}
-                                    className="zoom-right-button"
-                                    onClick={e => {
-                                        const trackId = `${demo.id}-mid-ideogram`;
-                                        const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                        const delta = (end - start) / 4.0;
-                                        gosRef.current.api.zoomTo(
-                                            trackId,
-                                            `chr1:${start + delta}-${end + delta}`,
-                                            0,
-                                            ZOOM_DURATION
-                                        );
-                                    }}
-                                >
-                                    →
-                                </button>
-                            </div>
-                        </div>
-                    </div>
-                    <div
-                        style={{
-                            width: '100%',
-                            height: '100%',
-                            visibility: 'collapse',
-                            boxShadow: interactiveMode ? 'inset 0 0 4px 2px #2399DB' : 'none',
-                            zIndex: 9999,
-                            background: 'none',
-                            position: 'absolute',
-                            top: 0,
-                            left: 0,
-                            pointerEvents: 'none'
-                        }}
-                    />
-                    <div
-                        style={{
-                            background: 'none',
-                            position: 'absolute',
-                            bottom: 20,
-                            left: VIS_PADDING,
-                            pointerEvents: 'none',
-                            visibility: demo.bam ? 'collapse' : 'visible'
-                        }}
-                    >
-                        {'ⓘ No read alignment data available for this sample.'}
-                    </div>
-                    <div id="hidden-gosling" style={{ visibility: 'collapse', position: 'fixed' }} />
-                </div>
-            </ErrorBoundary>
-        );
-    }
-
     return (
         <ErrorBoundary>
             <div
@@ -930,10 +626,10 @@ function App(props: RouteComponentProps) {
                     const width = window.innerWidth;
                     const height = window.innerHeight;
                     if (
-                        VIS_PADDING < top &&
-                        top < height - VIS_PADDING &&
-                        VIS_PADDING < left &&
-                        left < width - VIS_PADDING
+                        VIS_PADDING.top < top &&
+                        top < height - VIS_PADDING.top &&
+                        VIS_PADDING.left < left &&
+                        left < width - VIS_PADDING.left
                     ) {
                         setMouseOnVis(true);
                     } else {
@@ -1258,9 +954,9 @@ function App(props: RouteComponentProps) {
                         id="gosling-panel"
                         className="gosling-panel"
                         style={{
-                            width: `calc(100% - ${VIS_PADDING * 2}px)`,
-                            height: `calc(100% - ${VIS_PADDING * 2}px)`,
-                            padding: VIS_PADDING
+                            width: `calc(100% - ${VIS_PADDING.left * 2}px)`,
+                            height: `calc(100% - ${VIS_PADDING.top * 2}px)`,
+                            padding: `${VIS_PADDING.top}px ${VIS_PADDING.right}px ${VIS_PADDING.bottom}px ${VIS_PADDING.left}px`
                         }}
                     >
                         {goslingComponent}
@@ -1292,55 +988,72 @@ function App(props: RouteComponentProps) {
                                         ? '#00000000'
                                         : 'lightgray'
                                 }`,
-                                top: VIS_PADDING,
-                                left: VIS_PADDING,
+                                top: VIS_PADDING.top,
+                                left: VIS_PADDING.left,
                                 opacity: 0.9,
                                 zIndex: 2,
                                 pointerEvents: interactiveMode ? 'none' : 'auto'
                             }}
                         />
+                        {isMinimalMode ? (
+                            <div
+                                className="navigation-buttons"
+                                style={{
+                                    position: 'fixed',
+                                    height: '500px',
+                                    width: '500px',
+                                    zIndex: 997
+                                }}
+                            >
+                                <button
+                                    onClick={() => {
+                                        setTimeout(
+                                            () =>
+                                                document
+                                                    .getElementById('gosling-panel')
+                                                    ?.scrollTo({ top: 0, behavior: 'smooth' }),
+                                            0
+                                        );
+                                    }}
+                                >
+                                    Circular View
+                                </button>
+                                <button
+                                    onClick={() => {
+                                        setTimeout(
+                                            () =>
+                                                document
+                                                    .getElementById('gosling-panel')
+                                                    ?.scrollTo({ top: 1000, behavior: 'smooth' }),
+                                            0
+                                        );
+                                    }}
+                                >
+                                    Linear View
+                                </button>
+                            </div>
+                        ) : null}
                         <div
                             style={{
                                 pointerEvents: 'none',
                                 width: '100%',
                                 height: '100%',
                                 position: 'relative',
-                                zIndex: 998
+                                zIndex: 997
                             }}
                         >
-                            <select
-                                style={{
-                                    pointerEvents: 'auto',
-                                    top: '3px'
-                                }}
-                                className="nav-dropdown"
-                                onChange={e => {
-                                    setShowSamples(false);
-                                    const chr = e.currentTarget.value;
-                                    setTimeout(() => setOverviewChr(chr), 300);
-                                }}
-                                value={overviewChr}
-                                disabled={!showOverview}
-                            >
-                                {[WHOLE_CHROMOSOME_STR, ...CHROMOSOMES].map(chr => {
-                                    return (
-                                        <option key={chr} value={chr}>
-                                            {chr}
-                                        </option>
-                                    );
-                                })}
-                            </select>
                             <img
                                 src={legend}
                                 style={{
                                     position: 'absolute',
                                     right: '3px',
                                     top: '3px',
-                                    zIndex: 998,
+                                    zIndex: 997,
                                     width: '120px'
                                 }}
                             />
                             <select
+                                id="linear-view"
                                 style={{
                                     pointerEvents: 'auto',
                                     // !! This should be identical to how the height of circos determined.
@@ -1516,7 +1229,7 @@ function App(props: RouteComponentProps) {
                                 ? 'visible'
                                 : 'collapse',
                         position: 'absolute',
-                        right: `${VIS_PADDING}px`,
+                        right: `${VIS_PADDING.right}px`,
                         top: '60px',
                         background: 'lightgray',
                         color: 'black',
@@ -1549,7 +1262,7 @@ function App(props: RouteComponentProps) {
                         background: 'none',
                         position: 'absolute',
                         bottom: 20,
-                        left: VIS_PADDING,
+                        left: VIS_PADDING.left,
                         pointerEvents: 'none',
                         visibility: demo.bam ? 'collapse' : 'visible'
                     }}

From 702ac68a54e7461b476c6e4f14665b1744a67895 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 26 Mar 2024 11:47:06 -0400
Subject: [PATCH 04/16] style: navigation button styles

---
 src/App.css | 32 ++++++++++++++++++++++++++++++++
 src/App.tsx | 30 +++++++++++++-----------------
 2 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/src/App.css b/src/App.css
index 533e4501..497cf0ac 100644
--- a/src/App.css
+++ b/src/App.css
@@ -230,6 +230,38 @@ a:hover {
     border-radius: 0px;
 }
 
+.navigation-buttons {
+    position: fixed;
+    z-index: 998;
+    display: flex;
+    margin-top: 20px;
+    margin-left: 25px;
+}
+
+.navigation-button {
+    cursor: pointer;
+    font-size: 16px;
+    height: 38px;
+    width: 140px;
+    padding: 2px 10px;
+    border: 1px solid lightgrey;
+    box-shadow: #0000001a 5px 5px 10px;
+}
+
+.navigation-button:hover {
+    background: lightgrey;
+}
+.navigation-button:active {
+    background: rgb(197, 197, 197);
+}
+
+.navigation-button-circular {
+    border-radius: 8px 0px 0px 8px;
+}
+.navigation-button-linear {
+    border-radius: 0px 8px 8px 0px;
+}
+
 .zoom-in-button,
 .zoom-out-button,
 .zoom-left-button,
diff --git a/src/App.tsx b/src/App.tsx
index 0acad18d..c603e9a6 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -996,17 +996,11 @@ function App(props: RouteComponentProps) {
                             }}
                         />
                         {isMinimalMode ? (
-                            <div
-                                className="navigation-buttons"
-                                style={{
-                                    position: 'fixed',
-                                    height: '500px',
-                                    width: '500px',
-                                    zIndex: 997
-                                }}
-                            >
+                            <div className="navigation-buttons">
                                 <button
-                                    onClick={() => {
+                                    className=" navigation-button navigation-button-circular"
+                                    onClick={e => {
+                                        console.log(e);
                                         setTimeout(
                                             () =>
                                                 document
@@ -1019,14 +1013,16 @@ function App(props: RouteComponentProps) {
                                     Circular View
                                 </button>
                                 <button
+                                    className="navigation-button navigation-button-linear"
                                     onClick={() => {
-                                        setTimeout(
-                                            () =>
-                                                document
-                                                    .getElementById('gosling-panel')
-                                                    ?.scrollTo({ top: 1000, behavior: 'smooth' }),
-                                            0
-                                        );
+                                        setTimeout(() => {
+                                            const scroll_height = document.getElementById('gosling-panel').scrollHeight;
+                                            console.log(scroll_height);
+                                            document
+                                                .getElementById('gosling-panel')
+                                                ?.scrollTo({ top: scroll_height, behavior: 'smooth' }),
+                                                0;
+                                        });
                                     }}
                                 >
                                     Linear View

From 399f5bae243a84d28318358e142b8962f8c0f02e Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 26 Mar 2024 19:34:31 -0400
Subject: [PATCH 05/16] -style: interaction styles for buttons -feat: add
 export buttons

---
 src/App.css |  19 +-
 src/App.tsx | 731 +++++++++++++++++++++++++++-------------------------
 2 files changed, 395 insertions(+), 355 deletions(-)

diff --git a/src/App.css b/src/App.css
index 497cf0ac..4048997b 100644
--- a/src/App.css
+++ b/src/App.css
@@ -224,6 +224,7 @@ a:hover {
     border: 1px solid grey;
     position: absolute;
     left: 3px;
+    scroll-margin-top: 50px;
 }
 
 .nav-dropdown:focus {
@@ -234,8 +235,6 @@ a:hover {
     position: fixed;
     z-index: 998;
     display: flex;
-    margin-top: 20px;
-    margin-left: 25px;
 }
 
 .navigation-button {
@@ -248,17 +247,20 @@ a:hover {
     box-shadow: #0000001a 5px 5px 10px;
 }
 
-.navigation-button:hover {
+.navigation-button:hover:not(:disabled) {
     background: lightgrey;
 }
-.navigation-button:active {
+.navigation-button:active:not(:disabled) {
     background: rgb(197, 197, 197);
 }
+.navigation-button:disabled:hover {
+    cursor: default;
+}
 
-.navigation-button-circular {
+.navigation-button:first-of-type {
     border-radius: 8px 0px 0px 8px;
 }
-.navigation-button-linear {
+.navigation-button:last-of-type {
     border-radius: 0px 8px 8px 0px;
 }
 
@@ -542,6 +544,11 @@ a:hover {
     /* background: #ffffff99; */
 }
 
+.sample-label.minimal-mode {
+    left: 300px;
+    top: 8px;
+}
+
 .menu-title svg {
     vertical-align: middle;
 }
diff --git a/src/App.tsx b/src/App.tsx
index c603e9a6..57af6275 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -48,11 +48,12 @@ function App(props: RouteComponentProps) {
     // URL parameters
     const urlParams = new URLSearchParams(props.location.search);
     const isMinimalMode = urlParams.get('minimal_mode');
+    const [currentSection, setCurrentSection] = useState('circular');
     const VIS_PADDING = {
-        top: 60,
-        right: isMinimalMode ? 20 : 60,
+        top: isMinimalMode ? 0 : 60,
+        right: isMinimalMode ? 0 : 60,
         bottom: isMinimalMode ? 0 : 60,
-        left: isMinimalMode ? 20 : 60
+        left: isMinimalMode ? 0 : 60
     };
 
     // !! instead of using `urlParams.get('external')`, we directly parse the external URL in order to include
@@ -625,15 +626,17 @@ function App(props: RouteComponentProps) {
                     const left = e.clientX;
                     const width = window.innerWidth;
                     const height = window.innerHeight;
-                    if (
-                        VIS_PADDING.top < top &&
-                        top < height - VIS_PADDING.top &&
-                        VIS_PADDING.left < left &&
-                        left < width - VIS_PADDING.left
-                    ) {
-                        setMouseOnVis(true);
-                    } else {
-                        setMouseOnVis(false);
+                    if (!isMinimalMode) {
+                        if (
+                            VIS_PADDING.top < top &&
+                            top < height - VIS_PADDING.top &&
+                            VIS_PADDING.left < left &&
+                            left < width - VIS_PADDING.left
+                        ) {
+                            setMouseOnVis(true);
+                        } else {
+                            setMouseOnVis(false);
+                        }
                     }
                     mousePos.current = { x: left, y: top };
                 }}
@@ -644,55 +647,63 @@ function App(props: RouteComponentProps) {
                     setJumpButtonInfo(undefined);
                 }}
             >
-                <span
-                    style={{
-                        height: '50px',
-                        width: '100%',
-                        background: 'white',
-                        position: 'absolute',
-                        zIndex: 999,
-                        opacity: 0.8
-                    }}
-                ></span>
-                <svg
-                    className="config-button"
-                    viewBox="0 0 16 16"
-                    visibility={showSmallMultiples ? 'visible' : 'collapse'}
-                    onClick={() => {
-                        setShowSamples(true);
-                    }}
-                >
-                    <title>Menu</title>
-                    <path
-                        fillRule="evenodd"
-                        d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"
-                    />
-                </svg>
-                <div className="sample-label">
-                    <a className="chromoscope-title" href="./">
-                        CHROMOSCOPE
-                    </a>
-                    <a
-                        className="title-about-link"
+                {!isMinimalMode && (
+                    <span
+                        style={{
+                            height: '50px',
+                            width: '100%',
+                            background: 'white',
+                            position: 'absolute',
+                            zIndex: 999,
+                            opacity: 0.8
+                        }}
+                    ></span>
+                )}
+                {!isMinimalMode && (
+                    <svg
+                        className="config-button"
+                        viewBox="0 0 16 16"
+                        visibility={showSmallMultiples ? 'visible' : 'collapse'}
                         onClick={() => {
-                            setShowAbout(true);
+                            setShowSamples(true);
                         }}
                     >
-                        <svg
-                            xmlns="http://www.w3.org/2000/svg"
-                            width="20"
-                            height="20"
-                            fill="currentColor"
-                            viewBox="0 0 16 16"
-                        >
-                            <path d="M5.933.87a2.89 2.89 0 0 1 4.134 0l.622.638.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636zM7.002 11a1 1 0 1 0 2 0 1 1 0 0 0-2 0zm1.602-2.027c.04-.534.198-.815.846-1.26.674-.475 1.05-1.09 1.05-1.986 0-1.325-.92-2.227-2.262-2.227-1.02 0-1.792.492-2.1 1.29A1.71 1.71 0 0 0 6 5.48c0 .393.203.64.545.64.272 0 .455-.147.564-.51.158-.592.525-.915 1.074-.915.61 0 1.03.446 1.03 1.084 0 .563-.208.885-.822 1.325-.619.433-.926.914-.926 1.64v.111c0 .428.208.745.585.745.336 0 .504-.24.554-.627z" />
-                        </svg>
-                        About
-                    </a>
-                    <span className="dimed">{' | '}</span>
-                    {/* {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1) + ' • ' + demo.id} */}
-                    {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1)}
-                    <small>{demo.id}</small>
+                        <title>Menu</title>
+                        <path
+                            fillRule="evenodd"
+                            d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"
+                        />
+                    </svg>
+                )}
+                <div className={'sample-label' + (isMinimalMode ? ' minimal-mode' : '')}>
+                    {!isMinimalMode && (
+                        <>
+                            <a className="chromoscope-title" href="./">
+                                CHROMOSCOPE
+                            </a>
+                            <a
+                                className="title-about-link"
+                                onClick={() => {
+                                    setShowAbout(true);
+                                }}
+                            >
+                                <svg
+                                    xmlns="http://www.w3.org/2000/svg"
+                                    width="20"
+                                    height="20"
+                                    fill="currentColor"
+                                    viewBox="0 0 16 16"
+                                >
+                                    <path d="M5.933.87a2.89 2.89 0 0 1 4.134 0l.622.638.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636zM7.002 11a1 1 0 1 0 2 0 1 1 0 0 0-2 0zm1.602-2.027c.04-.534.198-.815.846-1.26.674-.475 1.05-1.09 1.05-1.986 0-1.325-.92-2.227-2.262-2.227-1.02 0-1.792.492-2.1 1.29A1.71 1.71 0 0 0 6 5.48c0 .393.203.64.545.64.272 0 .455-.147.564-.51.158-.592.525-.915 1.074-.915.61 0 1.03.446 1.03 1.084 0 .563-.208.885-.822 1.325-.619.433-.926.914-.926 1.64v.111c0 .428.208.745.585.745.336 0 .504-.24.554-.627z" />
+                                </svg>
+                                About
+                            </a>
+                            <span className="dimed">{' | '}</span>
+                            {/* {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1) + ' • ' + demo.id} */}
+                            {demo.cancer.charAt(0).toUpperCase() + demo.cancer.slice(1)}
+                            <small>{demo.id}</small>
+                        </>
+                    )}
                     <span className="title-btn" onClick={() => gosRef.current?.api.exportPng()}>
                         <svg className="button" viewBox="0 0 16 16">
                             <title>Export Image</title>
@@ -790,86 +801,10 @@ function App(props: RouteComponentProps) {
                             ⚠️ Chromoscope is optimized for Google Chrome
                         </a>
                     ) : null}
-                    <a
-                        className="title-github-link"
-                        href="https://github.com/hms-dbmi/chromoscope"
-                        target="_blank"
-                        rel="noreferrer"
-                    >
-                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
-                            <title>GitHub</title>
-                            <path
-                                fill="currentColor"
-                                d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
-                            ></path>
-                        </svg>
-                        GitHub
-                    </a>
-                    <a className="title-doc-link" href="https://chromoscope.bio/" target="_blank" rel="noreferrer">
-                        <svg
-                            xmlns="http://www.w3.org/2000/svg"
-                            width="20"
-                            height="20"
-                            fill="currentColor"
-                            viewBox="0 0 16 16"
-                        >
-                            <path d="M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM5 4h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zM5 8h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1zm0 2h3a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1z" />
-                        </svg>
-                        Documentation
-                    </a>
-                </div>
-                <div id="vis-panel" className="vis-panel">
-                    <div className={'vis-overview-panel ' + (!showSamples ? 'hide' : '')}>
-                        <div
-                            className="title"
-                            onClick={e => {
-                                if (e.target === e.currentTarget) setShowSamples(false);
-                            }}
-                        >
-                            <svg
-                                className="config-button"
-                                viewBox="0 0 16 16"
-                                onClick={() => {
-                                    setShowSamples(false);
-                                }}
-                            >
-                                <title>Close</title>
-                                <path
-                                    d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
-                                    fill="currentColor"
-                                ></path>
-                            </svg>
-                            <div className="sample-label">
-                                <b>CHROMOSCOPE</b>
-                                <a
-                                    className="title-about-link"
-                                    onClick={() => {
-                                        setShowAbout(true);
-                                    }}
-                                >
-                                    <svg
-                                        xmlns="http://www.w3.org/2000/svg"
-                                        width="20"
-                                        height="20"
-                                        fill="currentColor"
-                                        viewBox="0 0 16 16"
-                                    >
-                                        <path d="M5.933.87a2.89 2.89 0 0 1 4.134 0l.622.638.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636zM7.002 11a1 1 0 1 0 2 0 1 1 0 0 0-2 0zm1.602-2.027c.04-.534.198-.815.846-1.26.674-.475 1.05-1.09 1.05-1.986 0-1.325-.92-2.227-2.262-2.227-1.02 0-1.792.492-2.1 1.29A1.71 1.71 0 0 0 6 5.48c0 .393.203.64.545.64.272 0 .455-.147.564-.51.158-.592.525-.915 1.074-.915.61 0 1.03.446 1.03 1.084 0 .563-.208.885-.822 1.325-.619.433-.926.914-.926 1.64v.111c0 .428.208.745.585.745.336 0 .504-.24.554-.627z" />
-                                    </svg>
-                                    About
-                                </a>
-                                <span className="dimed">{' | '}</span> Samples
-                                <input
-                                    type="text"
-                                    className="sample-text-box"
-                                    placeholder="Search samples by ID"
-                                    onChange={e => setFilterSampleBy(e.target.value)}
-                                    hidden
-                                />
-                            </div>
+                    {!isMinimalMode && (
+                        <>
                             <a
                                 className="title-github-link"
-                                style={{ position: 'absolute' }}
                                 href="https://github.com/hms-dbmi/chromoscope"
                                 target="_blank"
                                 rel="noreferrer"
@@ -888,7 +823,6 @@ function App(props: RouteComponentProps) {
                                 href="https://chromoscope.bio/"
                                 target="_blank"
                                 rel="noreferrer"
-                                style={{ position: 'absolute' }}
                             >
                                 <svg
                                     xmlns="http://www.w3.org/2000/svg"
@@ -901,55 +835,143 @@ function App(props: RouteComponentProps) {
                                 </svg>
                                 Documentation
                             </a>
-                            <button
-                                className="thumbnail-generate-button"
-                                onClick={() => setGenerateThumbnails(!generateThumbnails)}
-                                style={{ visibility: doneGeneratingThumbnails ? 'hidden' : 'visible' }}
+                        </>
+                    )}
+                </div>
+                <div id="vis-panel" className="vis-panel">
+                    {!isMinimalMode && (
+                        <div className={'vis-overview-panel ' + (!showSamples ? 'hide' : '')}>
+                            <div
+                                className="title"
+                                onClick={e => {
+                                    if (e.target === e.currentTarget) setShowSamples(false);
+                                }}
                             >
-                                {generateThumbnails ? 'Stop Generating Thumbnails' : 'Generate Missing Thumbnails'}
-                            </button>
-                        </div>
-                        <div className="overview-root">
-                            <div className="overview-left">
-                                <CancerSelector
-                                    onChange={url => {
-                                        fetch(url).then(response =>
-                                            response.text().then(d => {
-                                                let externalDemo = JSON.parse(d);
-                                                if (Array.isArray(externalDemo) && externalDemo.length >= 0) {
-                                                    setFilteredSamples(externalDemo);
-                                                    externalDemo =
-                                                        externalDemo[
-                                                            demoIndex.current < externalDemo.length
-                                                                ? demoIndex.current
-                                                                : 0
-                                                        ];
-                                                }
-                                                if (externalDemo) {
-                                                    externalDemoUrl.current = url;
-                                                    setDemo(externalDemo);
-                                                }
-                                            })
-                                        );
-                                    }}
-                                />
-                                <HorizontalLine />
-                                <SampleConfigForm
-                                    onAdd={config => {
-                                        setFilteredSamples([
-                                            {
-                                                ...config,
-                                                group: 'default'
-                                            },
-                                            ...filteredSamples
-                                        ]);
+                                <svg
+                                    className="config-button"
+                                    viewBox="0 0 16 16"
+                                    onClick={() => {
+                                        setShowSamples(false);
                                     }}
-                                />
+                                >
+                                    <title>Close</title>
+                                    <path
+                                        d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
+                                        fill="currentColor"
+                                    ></path>
+                                </svg>
+                                <div className="sample-label">
+                                    <b>CHROMOSCOPE</b>
+                                    <a
+                                        className="title-about-link"
+                                        onClick={() => {
+                                            setShowAbout(true);
+                                        }}
+                                    >
+                                        <svg
+                                            xmlns="http://www.w3.org/2000/svg"
+                                            width="20"
+                                            height="20"
+                                            fill="currentColor"
+                                            viewBox="0 0 16 16"
+                                        >
+                                            <path d="M5.933.87a2.89 2.89 0 0 1 4.134 0l.622.638.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636zM7.002 11a1 1 0 1 0 2 0 1 1 0 0 0-2 0zm1.602-2.027c.04-.534.198-.815.846-1.26.674-.475 1.05-1.09 1.05-1.986 0-1.325-.92-2.227-2.262-2.227-1.02 0-1.792.492-2.1 1.29A1.71 1.71 0 0 0 6 5.48c0 .393.203.64.545.64.272 0 .455-.147.564-.51.158-.592.525-.915 1.074-.915.61 0 1.03.446 1.03 1.084 0 .563-.208.885-.822 1.325-.619.433-.926.914-.926 1.64v.111c0 .428.208.745.585.745.336 0 .504-.24.554-.627z" />
+                                        </svg>
+                                        About
+                                    </a>
+                                    <span className="dimed">{' | '}</span> Samples
+                                    <input
+                                        type="text"
+                                        className="sample-text-box"
+                                        placeholder="Search samples by ID"
+                                        onChange={e => setFilterSampleBy(e.target.value)}
+                                        hidden
+                                    />
+                                </div>
+                                <a
+                                    className="title-github-link"
+                                    style={{ position: 'absolute' }}
+                                    href="https://github.com/hms-dbmi/chromoscope"
+                                    target="_blank"
+                                    rel="noreferrer"
+                                >
+                                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+                                        <title>GitHub</title>
+                                        <path
+                                            fill="currentColor"
+                                            d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
+                                        ></path>
+                                    </svg>
+                                    GitHub
+                                </a>
+                                <a
+                                    className="title-doc-link"
+                                    href="https://chromoscope.bio/"
+                                    target="_blank"
+                                    rel="noreferrer"
+                                    style={{ position: 'absolute' }}
+                                >
+                                    <svg
+                                        xmlns="http://www.w3.org/2000/svg"
+                                        width="20"
+                                        height="20"
+                                        fill="currentColor"
+                                        viewBox="0 0 16 16"
+                                    >
+                                        <path d="M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM5 4h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zM5 8h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1zm0 2h3a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1z" />
+                                    </svg>
+                                    Documentation
+                                </a>
+                                <button
+                                    className="thumbnail-generate-button"
+                                    onClick={() => setGenerateThumbnails(!generateThumbnails)}
+                                    style={{ visibility: doneGeneratingThumbnails ? 'hidden' : 'visible' }}
+                                >
+                                    {generateThumbnails ? 'Stop Generating Thumbnails' : 'Generate Missing Thumbnails'}
+                                </button>
+                            </div>
+                            <div className="overview-root">
+                                <div className="overview-left">
+                                    <CancerSelector
+                                        onChange={url => {
+                                            fetch(url).then(response =>
+                                                response.text().then(d => {
+                                                    let externalDemo = JSON.parse(d);
+                                                    if (Array.isArray(externalDemo) && externalDemo.length >= 0) {
+                                                        setFilteredSamples(externalDemo);
+                                                        externalDemo =
+                                                            externalDemo[
+                                                                demoIndex.current < externalDemo.length
+                                                                    ? demoIndex.current
+                                                                    : 0
+                                                            ];
+                                                    }
+                                                    if (externalDemo) {
+                                                        externalDemoUrl.current = url;
+                                                        setDemo(externalDemo);
+                                                    }
+                                                })
+                                            );
+                                        }}
+                                    />
+                                    <HorizontalLine />
+                                    <SampleConfigForm
+                                        onAdd={config => {
+                                            setFilteredSamples([
+                                                {
+                                                    ...config,
+                                                    group: 'default'
+                                                },
+                                                ...filteredSamples
+                                            ]);
+                                        }}
+                                    />
+                                </div>
+                                <div className="overview-status">{`Total of ${filteredSamples.length} samples loaded`}</div>
+                                <div className="overview-container">{smallOverviewWrapper}</div>
                             </div>
-                            <div className="overview-status">{`Total of ${filteredSamples.length} samples loaded`}</div>
-                            <div className="overview-container">{smallOverviewWrapper}</div>
                         </div>
-                    </div>
+                    )}
                     <div
                         id="gosling-panel"
                         className="gosling-panel"
@@ -975,32 +997,34 @@ function App(props: RouteComponentProps) {
                                 {jumpButtonInfo.direction === 'leftward' ? '←' : '→'}
                             </button>
                         ) : null}
-                        <div
-                            style={{
-                                width: '100%',
-                                height: '100%',
-                                boxShadow: `inset 0 0 0 3px ${
-                                    interactiveMode && mouseOnVis
-                                        ? '#2399DB'
-                                        : !interactiveMode && mouseOnVis
-                                        ? 'lightgray'
-                                        : !interactiveMode && !mouseOnVis
-                                        ? '#00000000'
-                                        : 'lightgray'
-                                }`,
-                                top: VIS_PADDING.top,
-                                left: VIS_PADDING.left,
-                                opacity: 0.9,
-                                zIndex: 2,
-                                pointerEvents: interactiveMode ? 'none' : 'auto'
-                            }}
-                        />
+                        {!isMinimalMode && (
+                            <div
+                                style={{
+                                    width: '100%',
+                                    height: '100%',
+                                    boxShadow: `inset 0 0 0 3px ${
+                                        interactiveMode && mouseOnVis
+                                            ? '#2399DB'
+                                            : !interactiveMode && mouseOnVis
+                                            ? 'lightgray'
+                                            : !interactiveMode && !mouseOnVis
+                                            ? '#00000000'
+                                            : 'lightgray'
+                                    }`,
+                                    top: VIS_PADDING.top,
+                                    left: VIS_PADDING.left,
+                                    opacity: 0.9,
+                                    zIndex: 2,
+                                    pointerEvents: interactiveMode ? 'none' : 'auto'
+                                }}
+                            />
+                        )}
                         {isMinimalMode ? (
                             <div className="navigation-buttons">
                                 <button
-                                    className=" navigation-button navigation-button-circular"
-                                    onClick={e => {
-                                        console.log(e);
+                                    className="navigation-button navigation-button-circular"
+                                    onClick={() => {
+                                        setCurrentSection('circular');
                                         setTimeout(
                                             () =>
                                                 document
@@ -1009,21 +1033,24 @@ function App(props: RouteComponentProps) {
                                             0
                                         );
                                     }}
+                                    disabled={currentSection === 'circular'}
                                 >
                                     Circular View
                                 </button>
                                 <button
                                     className="navigation-button navigation-button-linear"
                                     onClick={() => {
+                                        setCurrentSection('linear');
                                         setTimeout(() => {
-                                            const scroll_height = document.getElementById('gosling-panel').scrollHeight;
-                                            console.log(scroll_height);
-                                            document
-                                                .getElementById('gosling-panel')
-                                                ?.scrollTo({ top: scroll_height, behavior: 'smooth' }),
+                                            document.getElementById('linear-view')?.scrollIntoView({
+                                                block: 'start',
+                                                inline: 'nearest',
+                                                behavior: 'smooth'
+                                            }),
                                                 0;
                                         });
                                     }}
+                                    disabled={currentSection === 'linear'}
                                 >
                                     Linear View
                                 </button>
@@ -1218,41 +1245,45 @@ function App(props: RouteComponentProps) {
                         </div>
                     </div>
                 </div>
-                <div
-                    style={{
-                        visibility:
-                            ((!interactiveMode && !mouseOnVis) || (interactiveMode && mouseOnVis)) && !showSamples
-                                ? 'visible'
-                                : 'collapse',
-                        position: 'absolute',
-                        right: `${VIS_PADDING.right}px`,
-                        top: '60px',
-                        background: 'lightgray',
-                        color: 'black',
-                        padding: '6px',
-                        pointerEvents: 'none',
-                        zIndex: 9999,
-                        boxShadow: '0 0 20px 2px rgba(0, 0, 0, 0.2)'
-                    }}
-                >
-                    {!interactiveMode
-                        ? 'Click inside to use interactions on visualizations'
-                        : 'Click outside to deactivate interactions and scroll the page'}
-                </div>
-                <div
-                    style={{
-                        width: '100%',
-                        height: '100%',
-                        visibility: 'collapse',
-                        boxShadow: interactiveMode ? 'inset 0 0 4px 2px #2399DB' : 'none',
-                        zIndex: 9999,
-                        background: 'none',
-                        position: 'absolute',
-                        top: 0,
-                        left: 0,
-                        pointerEvents: 'none'
-                    }}
-                />
+                {!isMinimalMode && (
+                    <div
+                        style={{
+                            visibility:
+                                ((!interactiveMode && !mouseOnVis) || (interactiveMode && mouseOnVis)) && !showSamples
+                                    ? 'visible'
+                                    : 'collapse',
+                            position: 'absolute',
+                            right: `${VIS_PADDING.right}px`,
+                            top: '60px',
+                            background: 'lightgray',
+                            color: 'black',
+                            padding: '6px',
+                            pointerEvents: 'none',
+                            zIndex: 9999,
+                            boxShadow: '0 0 20px 2px rgba(0, 0, 0, 0.2)'
+                        }}
+                    >
+                        {!interactiveMode
+                            ? 'Click inside to use interactions on visualizations'
+                            : 'Click outside to deactivate interactions and scroll the page'}
+                    </div>
+                )}
+                {!isMinimalMode && (
+                    <div
+                        style={{
+                            width: '100%',
+                            height: '100%',
+                            visibility: 'collapse',
+                            boxShadow: interactiveMode ? 'inset 0 0 4px 2px #2399DB' : 'none',
+                            zIndex: 9999,
+                            background: 'none',
+                            position: 'absolute',
+                            top: 0,
+                            left: 0,
+                            pointerEvents: 'none'
+                        }}
+                    />
+                )}
                 <div
                     style={{
                         background: 'none',
@@ -1269,114 +1300,116 @@ function App(props: RouteComponentProps) {
                     className={showAbout ? 'about-modal-container' : 'about-modal-container-hidden'}
                     onClick={() => setShowAbout(false)}
                 />
-                <div className={showAbout ? 'about-modal' : 'about-modal-hidden'}>
-                    <button className="about-modal-close-button" onClick={() => setShowAbout(false)}>
-                        <svg
-                            xmlns="http://www.w3.org/2000/svg"
-                            width="30"
-                            height="30"
-                            viewBox="0 0 16 16"
-                            strokeWidth="2"
-                            stroke="none"
-                            fill="currentColor"
-                            strokeLinecap="round"
-                            strokeLinejoin="round"
-                        >
-                            <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"></path>
-                        </svg>
-                    </button>
-                    <p>
-                        <b>Chromoscope</b>
-                        <span className="dimed">{' | '}</span>About
-                    </p>
+                {!isMinimalMode && (
+                    <div className={showAbout ? 'about-modal' : 'about-modal-hidden'}>
+                        <button className="about-modal-close-button" onClick={() => setShowAbout(false)}>
+                            <svg
+                                xmlns="http://www.w3.org/2000/svg"
+                                width="30"
+                                height="30"
+                                viewBox="0 0 16 16"
+                                strokeWidth="2"
+                                stroke="none"
+                                fill="currentColor"
+                                strokeLinecap="round"
+                                strokeLinejoin="round"
+                            >
+                                <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"></path>
+                            </svg>
+                        </button>
+                        <p>
+                            <b>Chromoscope</b>
+                            <span className="dimed">{' | '}</span>About
+                        </p>
 
-                    <p>
-                        Whole genome sequencing is now routinely used to profile mutations in DNA in the soma and in the
-                        germline, informing molecular diagnoses of disease and therapeutic decisions. Structural
-                        variants (SVs) are the main new type of alterations we see more of, and they are often
-                        diagnostic, prognostic, or therapy-informing. However, the size and complexity of SV data,
-                        combined with the difficulty of obtaining accurate SV calls, pose challenges in the
-                        interpretation of SVs, requiring tedious visual inspection of potentially pathogenic variants
-                        with multiple visualization tools.
-                    </p>
+                        <p>
+                            Whole genome sequencing is now routinely used to profile mutations in DNA in the soma and in
+                            the germline, informing molecular diagnoses of disease and therapeutic decisions. Structural
+                            variants (SVs) are the main new type of alterations we see more of, and they are often
+                            diagnostic, prognostic, or therapy-informing. However, the size and complexity of SV data,
+                            combined with the difficulty of obtaining accurate SV calls, pose challenges in the
+                            interpretation of SVs, requiring tedious visual inspection of potentially pathogenic
+                            variants with multiple visualization tools.
+                        </p>
 
-                    <p>
-                        To overcome the problems with the interpretation of SVs, we developed Chromoscope, an
-                        open-source web-based application for the interactive visualization of structural variants.
-                        Chromoscope has several innovative features which unlock the insights from whole genome
-                        sequencing: visualization at multiple scale levels simultaneously, effective navigation across
-                        scales, easy setup for loading users&apos; large datasets, and a feature to export, share, and
-                        further customize visualizations. We anticipate that Chromoscope will accelerate the exploration
-                        and interpretation of SVs by a broad range of scientists and clinicians, leading to new insights
-                        into genomic biomarkers.
-                    </p>
-                    <h4>Learn more about Chromoscope</h4>
-                    <ul>
-                        <li>
-                            <b>GitHub:</b>{' '}
-                            <a href="https://github.com/hms-dbmi/chromoscope" target="_blank" rel="noreferrer">
-                                https://github.com/hms-dbmi/chromoscope
-                            </a>
-                        </li>
-                        <li>
-                            <b>Documentation:</b>{' '}
-                            <a href="https://chromoscope.bio/" target="_blank" rel="noreferrer">
-                                https://chromoscope.bio/
-                            </a>
-                        </li>
-                        <li>
-                            <b>Preprint:</b>{' '}
-                            <a href="https://osf.io/pyqrx/" target="_blank" rel="noreferrer">
-                                L&apos;Yi et al. Chromoscope: interactive multiscale visualization for structural
-                                variation in human genomes, OSF, 2023.
+                        <p>
+                            To overcome the problems with the interpretation of SVs, we developed Chromoscope, an
+                            open-source web-based application for the interactive visualization of structural variants.
+                            Chromoscope has several innovative features which unlock the insights from whole genome
+                            sequencing: visualization at multiple scale levels simultaneously, effective navigation
+                            across scales, easy setup for loading users&apos; large datasets, and a feature to export,
+                            share, and further customize visualizations. We anticipate that Chromoscope will accelerate
+                            the exploration and interpretation of SVs by a broad range of scientists and clinicians,
+                            leading to new insights into genomic biomarkers.
+                        </p>
+                        <h4>Learn more about Chromoscope</h4>
+                        <ul>
+                            <li>
+                                <b>GitHub:</b>{' '}
+                                <a href="https://github.com/hms-dbmi/chromoscope" target="_blank" rel="noreferrer">
+                                    https://github.com/hms-dbmi/chromoscope
+                                </a>
+                            </li>
+                            <li>
+                                <b>Documentation:</b>{' '}
+                                <a href="https://chromoscope.bio/" target="_blank" rel="noreferrer">
+                                    https://chromoscope.bio/
+                                </a>
+                            </li>
+                            <li>
+                                <b>Preprint:</b>{' '}
+                                <a href="https://osf.io/pyqrx/" target="_blank" rel="noreferrer">
+                                    L&apos;Yi et al. Chromoscope: interactive multiscale visualization for structural
+                                    variation in human genomes, OSF, 2023.
+                                </a>
+                            </li>
+                        </ul>
+                        <h4>The Team</h4>
+                        <ul>
+                            <li>
+                                <b>Sehi L&apos;Yi</b>
+                                {hidiveLabRef}
+                            </li>
+                            <li>
+                                <b>Dominika Maziec</b>
+                                {parkLabRef}
+                            </li>
+                            <li>
+                                <b>Victoria Stevens</b>
+                                {parkLabRef}
+                            </li>
+                            <li>
+                                <b>Trevor Manz</b>
+                                {hidiveLabRef}
+                            </li>
+                            <li>
+                                <b>Alexander Veit</b>
+                                {parkLabRef}
+                            </li>
+                            <li>
+                                <b>Michele Berselli</b>
+                                {parkLabRef}
+                            </li>
+                            <li>
+                                <b>Peter J Park</b>
+                                {parkLabRef}
+                            </li>
+                            <li>
+                                <b>Dominik Glodzik</b>
+                                {parkLabRef}
+                            </li>
+                            <li>
+                                <b>Nils Gehlenborg</b>
+                                {hidiveLabRef}
+                            </li>
+                        </ul>
+                        <div className="about-modal-footer">
+                            <a href="https://dbmi.hms.harvard.edu/" target="_blank" rel="noreferrer">
+                                Department of Biomedical Informatics, Harvard Medical School
                             </a>
-                        </li>
-                    </ul>
-                    <h4>The Team</h4>
-                    <ul>
-                        <li>
-                            <b>Sehi L&apos;Yi</b>
-                            {hidiveLabRef}
-                        </li>
-                        <li>
-                            <b>Dominika Maziec</b>
-                            {parkLabRef}
-                        </li>
-                        <li>
-                            <b>Victoria Stevens</b>
-                            {parkLabRef}
-                        </li>
-                        <li>
-                            <b>Trevor Manz</b>
-                            {hidiveLabRef}
-                        </li>
-                        <li>
-                            <b>Alexander Veit</b>
-                            {parkLabRef}
-                        </li>
-                        <li>
-                            <b>Michele Berselli</b>
-                            {parkLabRef}
-                        </li>
-                        <li>
-                            <b>Peter J Park</b>
-                            {parkLabRef}
-                        </li>
-                        <li>
-                            <b>Dominik Glodzik</b>
-                            {parkLabRef}
-                        </li>
-                        <li>
-                            <b>Nils Gehlenborg</b>
-                            {hidiveLabRef}
-                        </li>
-                    </ul>
-                    <div className="about-modal-footer">
-                        <a href="https://dbmi.hms.harvard.edu/" target="_blank" rel="noreferrer">
-                            Department of Biomedical Informatics, Harvard Medical School
-                        </a>
+                        </div>
                     </div>
-                </div>
+                )}
                 <div
                     className="move-to-top-btn"
                     onClick={() => {

From 7153266814c5fa29ee24caa59adf1825998ad6bb Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 2 Apr 2024 14:05:04 -0400
Subject: [PATCH 06/16] feat: keep buttons enabled/remove section state

---
 src/App.tsx | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/App.tsx b/src/App.tsx
index 57af6275..b90cdd02 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -48,7 +48,6 @@ function App(props: RouteComponentProps) {
     // URL parameters
     const urlParams = new URLSearchParams(props.location.search);
     const isMinimalMode = urlParams.get('minimal_mode');
-    const [currentSection, setCurrentSection] = useState('circular');
     const VIS_PADDING = {
         top: isMinimalMode ? 0 : 60,
         right: isMinimalMode ? 0 : 60,
@@ -1024,7 +1023,6 @@ function App(props: RouteComponentProps) {
                                 <button
                                     className="navigation-button navigation-button-circular"
                                     onClick={() => {
-                                        setCurrentSection('circular');
                                         setTimeout(
                                             () =>
                                                 document
@@ -1033,14 +1031,12 @@ function App(props: RouteComponentProps) {
                                             0
                                         );
                                     }}
-                                    disabled={currentSection === 'circular'}
                                 >
                                     Circular View
                                 </button>
                                 <button
                                     className="navigation-button navigation-button-linear"
                                     onClick={() => {
-                                        setCurrentSection('linear');
                                         setTimeout(() => {
                                             document.getElementById('linear-view')?.scrollIntoView({
                                                 block: 'start',
@@ -1050,7 +1046,6 @@ function App(props: RouteComponentProps) {
                                                 0;
                                         });
                                     }}
-                                    disabled={currentSection === 'linear'}
                                 >
                                     Linear View
                                 </button>

From b1ebccb2a71847e38de20daa5b3de02742090b76 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 9 Apr 2024 14:07:55 -0400
Subject: [PATCH 07/16] fix: use boolean to track minimal mode activation

---
 src/App.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/App.tsx b/src/App.tsx
index b90cdd02..97afe1af 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -47,7 +47,7 @@ const allDrivers = [
 function App(props: RouteComponentProps) {
     // URL parameters
     const urlParams = new URLSearchParams(props.location.search);
-    const isMinimalMode = urlParams.get('minimal_mode');
+    const isMinimalMode = urlParams.get('minimal_mode') === 'true';
     const VIS_PADDING = {
         top: isMinimalMode ? 0 : 60,
         right: isMinimalMode ? 0 : 60,

From 92476815366d2dd3bb14c89f59d892a6c51f8377 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Mon, 29 Apr 2024 12:51:15 -0400
Subject: [PATCH 08/16] feat: add triangle down icon

---
 src/icon.ts | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/icon.ts b/src/icon.ts
index 32d46f52..b8fd31ea 100644
--- a/src/icon.ts
+++ b/src/icon.ts
@@ -333,5 +333,16 @@ export const ICONS: Record<string, ICON_INFO> = {
         ],
         stroke: 'currentColor',
         fill: 'none'
+    },
+    TRIANGLE_DOWN: {
+        width: 11,
+        height: 7,
+        viewBox: '0 0 11 7',
+        path: [
+            'M0.5 1H10.5L5.5 6L0.5 1Z',
+            'M5.5 6L0.5 1H10.5L5.5 6ZM5.5 6V5.28571'
+        ],
+        stroke: 'currentColor',
+        fill: 'none'
     }
 };

From e07a17ffb0cc2231ca80142ddbb1f31f10ca7dab Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Mon, 29 Apr 2024 12:57:36 -0400
Subject: [PATCH 09/16] -feat: Dropdown component for export buttons -feat:
 intersection observer for hiding legend -style: styles for minimal mode
 buttons and spacing

---
 src/App.css               | 242 ++++++++++++++++++++++++++++++++------
 src/App.tsx               | 220 ++++++++++++++++++++--------------
 src/ui/ExportDropdown.tsx |  70 +++++++++++
 3 files changed, 405 insertions(+), 127 deletions(-)
 create mode 100644 src/ui/ExportDropdown.tsx

diff --git a/src/App.css b/src/App.css
index 4048997b..94d7a74d 100644
--- a/src/App.css
+++ b/src/App.css
@@ -224,46 +224,13 @@ a:hover {
     border: 1px solid grey;
     position: absolute;
     left: 3px;
-    scroll-margin-top: 50px;
+    scroll-margin-top: 100px;
 }
 
 .nav-dropdown:focus {
     border-radius: 0px;
 }
 
-.navigation-buttons {
-    position: fixed;
-    z-index: 998;
-    display: flex;
-}
-
-.navigation-button {
-    cursor: pointer;
-    font-size: 16px;
-    height: 38px;
-    width: 140px;
-    padding: 2px 10px;
-    border: 1px solid lightgrey;
-    box-shadow: #0000001a 5px 5px 10px;
-}
-
-.navigation-button:hover:not(:disabled) {
-    background: lightgrey;
-}
-.navigation-button:active:not(:disabled) {
-    background: rgb(197, 197, 197);
-}
-.navigation-button:disabled:hover {
-    cursor: default;
-}
-
-.navigation-button:first-of-type {
-    border-radius: 8px 0px 0px 8px;
-}
-.navigation-button:last-of-type {
-    border-radius: 0px 8px 8px 0px;
-}
-
 .zoom-in-button,
 .zoom-out-button,
 .zoom-left-button,
@@ -544,11 +511,6 @@ a:hover {
     /* background: #ffffff99; */
 }
 
-.sample-label.minimal-mode {
-    left: 300px;
-    top: 8px;
-}
-
 .menu-title svg {
     vertical-align: middle;
 }
@@ -786,3 +748,205 @@ a:hover {
     opacity: 1;
     background-color: #7aaded;
 }
+
+/* Minimal Mode styles */
+.minimal_mode {
+
+    .gosling-panel {
+        overflow-y: scroll;
+        overflow-x: hidden;
+    }
+
+    .sample-label {
+        left: 300px;
+        top: 8px;
+    }
+
+    .navigation-buttons {
+        position: fixed;
+        z-index: 998;
+        display: flex;
+        flex-direction: column;
+        top: 3px;
+        left: 3px;
+    }
+
+    .navigation-button {
+        background-color: #F6F6F6;
+        cursor: pointer;
+        font-size: 1rem;
+        font-family: Inter;
+        height: 40px;
+        width: 210px;
+        padding: 2px 10px;
+        border: 1px solid #D3D3D3;
+    }
+
+    .navigation-button:hover:not(:disabled) {
+        background-color: #EBEBEB;
+    }
+    .navigation-button:active:not(:disabled) {
+        background-color: #e6e4e4;
+    }
+    .navigation-button:first-of-type {
+        border-radius: 8px 8px 0px 0px;
+    }
+    .navigation-button:last-of-type {
+        border-radius: 0px 0px 8px 8px;
+    }
+
+    /* Force scrollbar to show */
+    ::-webkit-scrollbar {
+        -webkit-appearance: none;
+        width: 10px;
+    }
+
+    ::-webkit-scrollbar-thumb {
+        width: 10px;
+        border-radius: 4px;
+        background-color: rgba(0, 0, 0, .5);
+        box-shadow: 0 0 1px rgba(255, 255, 255, .5);
+    }
+    ::-webkit-scrollbar:hover {
+        cursor: pointer;
+    }
+
+    /* Styles for the navigation on the right side of visualization */
+    .external-links {
+        position: absolute;
+        z-index: 998;
+        height: auto;
+        width: auto;
+        top: 3px;
+        right: 12px;
+
+        .external-links-nav {
+            display: flex;
+            flex-direction: column;
+            justify-content: space-between;
+            
+
+            .open-in-chromoscope-link {
+                background-color: #F6F6F6;
+                font-size: 0.9rem;
+                font-family: Inter;
+                font-weight: 400;
+                display: flex;
+                height: 35px;
+                justify-content: center;
+                border: 1px solid #D3D3D3;
+                border-radius: 8px;
+
+                .link-group {
+                    margin: auto;
+
+                    .external-link-icon {
+                        margin: auto;
+                        margin-left: 8px;
+                        fill: black;
+                        stroke: black;
+                    }
+                }
+
+            }
+
+            .open-in-chromoscope-link:hover {
+                text-decoration: none;
+                cursor: pointer;
+                background-color: #EBEBEB;
+            }
+            
+            .open-in-chromoscope-link:active {
+                background-color: #e6e4e4;
+            }
+            
+            .export-links {
+                border-radius: 4px;
+                margin-top: 4px;
+
+                .export-dropdown {
+                    height: auto;
+                    background-color: #F6F6F6;
+                    right: 0px;
+                    border-radius: 8px;
+                    border: 1px solid #D3D3D3;
+                    transition: all 100ms;
+                    overflow: hidden;
+        
+                    .export-button {
+                        width: 210px;
+                        height: 35px;
+                        border-radius: inherit;
+                        border: 0px solid;
+                        font-weight: 400;
+                        background-color: transparent;
+        
+                        .export-title {
+                            font-size: .9rem;
+                            font-family: Inter;
+                        }
+        
+                        .button.triangle-down {
+                            width: 11px;
+                            height: 7px;
+                            margin-left: 8px;
+                        }
+                    }
+        
+                    .export-button:hover {
+                        cursor: pointer;
+                        background-color: #EBEBEB;
+                    }
+
+                    .export-button:active {
+                        background-color: #e6e4e4;
+                    }
+
+                    .nav-list {
+                        list-style-type: none;
+                        padding: 0px 10px;
+                        display: flex;
+                        flex-direction: row;
+                        height: 50px;
+                        background-color: white;
+                        margin: 0px 8px 8px 8px;
+                        border-radius: 3px;
+    
+                        .nav-list-item {
+                            display: flex;
+                            margin: auto;
+                        }
+    
+    
+                        .title-btn {
+                            display: flex;
+                            position: relative;
+                            width: 25px;
+                            height: 25px;
+                            margin-left: 0px;
+                        }
+    
+                        .title-btn.png {
+                            padding: 0px;
+                            border: none;
+                            background-color: transparent;
+                        }
+                    }
+        
+                }
+                
+                .export-dropdown.open {
+                    background-color: #EBEBEB;
+                    border-radius: 8px;
+                    border: 1px solid #C3C3C3;
+                    transition: all 100ms;
+        
+                    .export-button {
+                        border: none;
+                    }
+                }
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 97afe1af..4ee18cad 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -22,6 +22,7 @@ import HorizontalLine from './ui/horizontal-line';
 import SampleConfigForm from './ui/sample-config-form';
 import { BrowserDatabase } from './browser-log';
 import legend from './legend.png';
+import { ExportDropdown } from './ui/ExportDropdown';
 
 const db = new Database();
 const log = new BrowserDatabase();
@@ -102,7 +103,7 @@ function App(props: RouteComponentProps) {
     const [showOverview, setShowOverview] = useState(true);
     const [showPutativeDriver, setShowPutativeDriver] = useState(true);
     const [interactiveMode, setInteractiveMode] = useState(isMinimalMode ?? false);
-    const [visPanelWidth, setVisPanelWidth] = useState(INIT_VIS_PANEL_WIDTH - VIS_PADDING.left * 2);
+    const [visPanelWidth, setVisPanelWidth] = useState(INIT_VIS_PANEL_WIDTH - ( isMinimalMode ? 10 : VIS_PADDING.left * 2) );
     const [overviewChr, setOverviewChr] = useState('');
     const [genomeViewChr, setGenomeViewChr] = useState('');
     const [drivers, setDrivers] = useState(
@@ -304,7 +305,7 @@ function App(props: RouteComponentProps) {
         }
     }, [genomeViewChr]);
 
-    // change the width of the visualization panel
+    // change the width of the visualization panel and register intersection observer
     useEffect(() => {
         window.addEventListener(
             'resize',
@@ -312,6 +313,23 @@ function App(props: RouteComponentProps) {
                 setVisPanelWidth(window.innerWidth - VIS_PADDING.left * 2);
             }, 500)
         );
+        
+        // In minimal mode, lower opacity of legend image as circular view 
+        // moves out of the screen
+        if (isMinimalMode) {
+            const legendElement = document.querySelector<HTMLElement>(".circular-view-legend");
+            let options = {
+                root: document.querySelector(".minimal_mode"),
+                rootMargin: "-250px 0px 0px 0px",
+                threshold: [1, 0.5, 0.25, 0],
+            };
+    
+            let observer = new IntersectionObserver((entry) => {
+                legendElement.style.opacity = "" + entry[0].intersectionRatio ** 2;
+            }, options);
+    
+            observer.observe(legendElement);
+        }
     }, []);
 
     const getThumbnail = (d: SampleType) => {
@@ -619,6 +637,7 @@ function App(props: RouteComponentProps) {
     return (
         <ErrorBoundary>
             <div
+                className={isMinimalMode ? "minimal_mode" : ""}
                 style={{ width: '100%', height: '100%' }}
                 onMouseMove={e => {
                     const top = e.clientY;
@@ -674,7 +693,7 @@ function App(props: RouteComponentProps) {
                         />
                     </svg>
                 )}
-                <div className={'sample-label' + (isMinimalMode ? ' minimal-mode' : '')}>
+                <div className="sample-label">
                     {!isMinimalMode && (
                         <>
                             <a className="chromoscope-title" href="./">
@@ -703,90 +722,94 @@ function App(props: RouteComponentProps) {
                             <small>{demo.id}</small>
                         </>
                     )}
-                    <span className="title-btn" onClick={() => gosRef.current?.api.exportPng()}>
-                        <svg className="button" viewBox="0 0 16 16">
-                            <title>Export Image</title>
-                            {ICONS.PNG.path.map(p => (
-                                <path fill="currentColor" key={p} d={p} />
-                            ))}
-                        </svg>
-                    </span>
-                    <span
-                        className="title-btn"
-                        onClick={() => {
-                            const a = document.createElement('a');
-                            a.setAttribute(
-                                'href',
-                                `data:text/plain;charset=utf-8,${encodeURIComponent(
-                                    getHtmlTemplate(currentSpec.current)
-                                )}`
-                            );
-                            a.download = 'visualization.html';
-                            document.body.appendChild(a);
-                            a.click();
-                            document.body.removeChild(a);
-                        }}
-                        style={{ marginLeft: 40 }}
-                    >
-                        <svg className="button" viewBox="0 0 16 16">
-                            <title>Export HTML</title>
-                            {ICONS.HTML.path.map(p => (
-                                <path fill="currentColor" key={p} d={p} />
-                            ))}
-                        </svg>
-                    </span>
-                    <span
-                        className="title-btn"
-                        onClick={() => {
-                            const a = document.createElement('a');
-                            a.setAttribute(
-                                'href',
-                                `data:text/plain;charset=utf-8,${encodeURIComponent(currentSpec.current)}`
-                            );
-                            a.download = 'visualization.json';
-                            document.body.appendChild(a);
-                            a.click();
-                            document.body.removeChild(a);
-                        }}
-                        style={{ marginLeft: 70 }}
-                    >
-                        <svg className="button" viewBox="0 0 16 16">
-                            <title>Export Gosling Spec (JSON)</title>
-                            {ICONS.JSON.path.map(p => (
-                                <path fill="currentColor" key={p} d={p} />
-                            ))}
-                        </svg>
-                    </span>
-                    <span
-                        className="title-btn"
-                        onClick={() => {
-                            const { xDomain } = gosRef.current.hgApi.api.getLocation(`${demo.id}-mid-ideogram`);
-                            if (xDomain) {
-                                // urlParams.set('demoIndex', demoIndex.current + '');
-                                // urlParams.set('domain', xDomain.join('-'));
-                                let newUrl = window.location.origin + window.location.pathname + '?';
-                                newUrl += `demoIndex=${demoIndex.current}`;
-                                newUrl += `&domain=${xDomain.join('-')}`;
-                                if (externalDemoUrl.current) {
-                                    newUrl += `&external=${externalDemoUrl.current}`;
-                                } else if (externalUrl) {
-                                    newUrl += `&external=${externalUrl}`;
-                                }
-                                navigator.clipboard
-                                    .writeText(newUrl)
-                                    .then(() =>
-                                        alert('The URL of the current session has been copied to your clipboard.')
+                    { !isMinimalMode && (
+                        <>
+                            <span className="title-btn" onClick={() => gosRef.current?.api.exportPng()}>
+                                <svg className="button" viewBox="0 0 16 16">
+                                    <title>Export Image</title>
+                                    {ICONS.PNG.path.map(p => (
+                                        <path fill="currentColor" key={p} d={p} />
+                                    ))}
+                                </svg>
+                            </span>
+                            <span
+                                className="title-btn"
+                                onClick={() => {
+                                    const a = document.createElement('a');
+                                    a.setAttribute(
+                                        'href',
+                                        `data:text/plain;charset=utf-8,${encodeURIComponent(
+                                            getHtmlTemplate(currentSpec.current)
+                                        )}`
                                     );
-                            }
-                        }}
-                        style={{ marginLeft: 100 }}
-                    >
-                        <svg className="button" viewBox="0 0 16 16">
-                            <title>Export Link</title>
-                            <path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z" />
-                            <path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z" />
-                        </svg>
-                    </span>
+                                    a.download = 'visualization.html';
+                                    document.body.appendChild(a);
+                                    a.click();
+                                    document.body.removeChild(a);
+                                }}
+                                style={{ marginLeft: 40 }}
+                            >
+                                <svg className="button" viewBox="0 0 16 16">
+                                    <title>Export HTML</title>
+                                    {ICONS.HTML.path.map(p => (
+                                        <path fill="currentColor" key={p} d={p} />
+                                    ))}
+                                </svg>
+                            </span>
+                            <span
+                                className="title-btn"
+                                onClick={() => {
+                                    const a = document.createElement('a');
+                                    a.setAttribute(
+                                        'href',
+                                        `data:text/plain;charset=utf-8,${encodeURIComponent(currentSpec.current)}`
+                                    );
+                                    a.download = 'visualization.json';
+                                    document.body.appendChild(a);
+                                    a.click();
+                                    document.body.removeChild(a);
+                                }}
+                                style={{ marginLeft: 70 }}
+                            >
+                                <svg className="button" viewBox="0 0 16 16">
+                                    <title>Export Gosling Spec (JSON)</title>
+                                    {ICONS.JSON.path.map(p => (
+                                        <path fill="currentColor" key={p} d={p} />
+                                    ))}
+                                </svg>
+                            </span>
+                            <span
+                                className="title-btn"
+                                onClick={() => {
+                                    const { xDomain } = gosRef.current.hgApi.api.getLocation(`${demo.id}-mid-ideogram`);
+                                    if (xDomain) {
+                                        // urlParams.set('demoIndex', demoIndex.current + '');
+                                        // urlParams.set('domain', xDomain.join('-'));
+                                        let newUrl = window.location.origin + window.location.pathname + '?';
+                                        newUrl += `demoIndex=${demoIndex.current}`;
+                                        newUrl += `&domain=${xDomain.join('-')}`;
+                                        if (externalDemoUrl.current) {
+                                            newUrl += `&external=${externalDemoUrl.current}`;
+                                        } else if (externalUrl) {
+                                            newUrl += `&external=${externalUrl}`;
+                                        }
+                                        navigator.clipboard
+                                            .writeText(newUrl)
+                                            .then(() =>
+                                                alert('The URL of the current session has been copied to your clipboard.')
+                                            );
+                                    }
+                                }}
+                                style={{ marginLeft: 100 }}
+                            >
+                                <svg className="button" viewBox="0 0 16 16">
+                                    <title>Export Link</title>
+                                    <path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z" />
+                                    <path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z" />
+                                </svg>
+                            </span>
+                        </>
+                    )}
                     {!isChrome() ? (
                         <a
                             style={{
@@ -1051,6 +1074,26 @@ function App(props: RouteComponentProps) {
                                 </button>
                             </div>
                         ) : null}
+                        {
+                            // External links and export buttons
+                            isMinimalMode ? (
+                                <div className="external-links">
+                                    <nav className="external-links-nav">
+                                        <a className="open-in-chromoscope-link" href="">
+                                            <div className="link-group">
+                                                <span>Open in Chromoscope</span>
+                                                <svg className="external-link-icon" width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
+                                                    <path d="M9.8212 1.73104L10.6894 0.875H9.47015H7.66727C7.55064 0.875 7.46966 0.784774 7.46966 0.6875C7.46966 0.590226 7.55064 0.5 7.66727 0.5H11.1553C11.2719 0.5 11.3529 0.590226 11.3529 0.6875V4.125C11.3529 4.22227 11.2719 4.3125 11.1553 4.3125C11.0387 4.3125 10.9577 4.22228 10.9577 4.125V2.34824V1.15307L10.1067 1.9922L5.71834 6.31907C5.71831 6.3191 5.71828 6.31913 5.71825 6.31916C5.64039 6.39579 5.51053 6.39576 5.43271 6.31907C5.35892 6.24635 5.35892 6.1308 5.43271 6.05808L5.4328 6.05799L9.8212 1.73104ZM1.19116 2.40625C1.19116 1.73964 1.74085 1.1875 2.43519 1.1875H4.87682C4.99345 1.1875 5.07443 1.27773 5.07443 1.375C5.07443 1.47227 4.99345 1.5625 4.87682 1.5625H2.43519C1.97411 1.5625 1.58638 1.93419 1.58638 2.40625V9.28125C1.58638 9.75331 1.97411 10.125 2.43519 10.125H9.41129C9.87237 10.125 10.2601 9.75331 10.2601 9.28125V6.875C10.2601 6.77773 10.3411 6.6875 10.4577 6.6875C10.5743 6.6875 10.6553 6.77773 10.6553 6.875V9.28125C10.6553 9.94786 10.1056 10.5 9.41129 10.5H2.43519C1.74085 10.5 1.19116 9.94786 1.19116 9.28125V2.40625Z" fill="black" stroke="black"/>
+                                                </svg>
+                                            </div>
+                                        </a>
+                                        <div className="export-links">
+                                            <ExportDropdown gosRef={gosRef} currentSpec={currentSpec} />
+                                        </div>
+                                    </nav>
+                                </div>
+                            ) : null
+                        }
                         <div
                             style={{
                                 pointerEvents: 'none',
@@ -1061,11 +1104,12 @@ function App(props: RouteComponentProps) {
                             }}
                         >
                             <img
+                                className="circular-view-legend"
                                 src={legend}
                                 style={{
                                     position: 'absolute',
-                                    right: '3px',
-                                    top: '3px',
+                                    right: isMinimalMode ? '10px' : '3px',
+                                    top: isMinimalMode ? '425px' : '3px',
                                     zIndex: 997,
                                     width: '120px'
                                 }}
diff --git a/src/ui/ExportDropdown.tsx b/src/ui/ExportDropdown.tsx
new file mode 100644
index 00000000..368d0ec6
--- /dev/null
+++ b/src/ui/ExportDropdown.tsx
@@ -0,0 +1,70 @@
+import React, {useState, useEffect} from "react";
+import { ICONS } from "../icon";
+import { getHtmlTemplate } from "../html-template";
+
+
+
+const ExportButton = ({ title, icon }) => {
+    return (
+        <svg className="button" viewBox="0 0 16 16">
+            <title>{title}</title>
+            {ICONS[icon].path.map(p => (
+                <path fill="currentColor" key={p} d={p} />
+            ))}
+        </svg>
+    )
+}
+
+export const ExportDropdown = ({ gosRef, currentSpec }) => {
+    const [isOpen, setIsOpen] = useState(false);
+
+    return (
+        <div 
+            className={"export-dropdown" + (isOpen ? " open" : " closed")}
+            onClick={() => setIsOpen(!isOpen)}
+            aria-expanded={isOpen}
+        >
+            <button className="export-button">
+                <span className="export-title">Export</span>
+                <svg className="button triangle-down" viewBox={ICONS.TRIANGLE_DOWN.viewBox}>
+                    <title>Triange Down</title>
+                    {ICONS.TRIANGLE_DOWN.path.map(p => (
+                        <path fill="currentColor" key={p} d={p} />
+                    ))}
+                </svg>
+            </button>
+            {isOpen ? <nav className="export-options">
+                <ul className="nav-list">
+                    <li className="nav-list-item">
+                        <button className="title-btn png" onClick={(e) =>{ e.stopPropagation(); gosRef.current?.api.exportPng() }}>
+                            <ExportButton title="Export PNG" icon="PNG" />
+                        </button>
+                    </li>
+
+                    <li className="nav-list-item">
+                        <a 
+                            className="title-btn" 
+                            href={`data:text/plain;charset=utf-8,${encodeURIComponent(
+                                getHtmlTemplate(currentSpec.current)
+                            )}`} 
+                            download="visualization.html"
+                            onClick={(e) =>{ e.stopPropagation() }}
+                        >
+                            <ExportButton title="Export HTML" icon="HTML" />
+                        </a>
+                    </li>
+                    <li className="nav-list-item">
+                        <a 
+                            className="title-btn" 
+                            href={`data:text/plain;charset=utf-8,${encodeURIComponent(currentSpec.current)}`} 
+                            download="visualization.json"
+                            onClick={(e) =>{ e.stopPropagation() }}
+                        >
+                            <ExportButton title="Export JSON" icon="JSON" />
+                        </a>
+                    </li>
+                    </ul>
+            </nav> : null}
+        </div>
+    );
+}
\ No newline at end of file

From f51d78af20880e44dfda5dcbe343ab1c2258a2ea Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 30 Apr 2024 14:58:13 -0400
Subject: [PATCH 10/16] feat: add spacing argument to generateSpec()

---
 src/main-spec.ts | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/main-spec.ts b/src/main-spec.ts
index 63720449..d580920f 100644
--- a/src/main-spec.ts
+++ b/src/main-spec.ts
@@ -19,10 +19,11 @@ export interface SpecOption extends SampleType {
     svReads: { name: string; type: string }[];
     crossChr: boolean;
     bpIntervals: [number, number, number, number] | undefined;
+    spacing: number;
 }
 
 function generateSpec(opt: SpecOption): GoslingSpec {
-    const { assembly, id, bam, bai, width, selectedSvId, breakpoints, bpIntervals } = opt;
+    const { assembly, id, bam, bai, width, selectedSvId, breakpoints, bpIntervals, spacing } = opt;
 
     const topViewWidth = Math.min(width, 600);
     const midViewWidth = width;
@@ -39,7 +40,7 @@ function generateSpec(opt: SpecOption): GoslingSpec {
         arrangement: 'vertical',
         centerRadius: 0.5,
         assembly,
-        spacing: 40,
+        spacing,
         style: {
             outlineWidth: 1,
             outline: 'lightgray',

From a566fe928a7a8d371980653c6c7f2f6988733b18 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 30 Apr 2024 14:59:18 -0400
Subject: [PATCH 11/16] -feat: pass spacing variable -feat: reformat the linear
 view controls (add container) -style: adjut spacing of controls

---
 src/App.css |  61 ++++++++-
 src/App.tsx | 347 +++++++++++++++++++++++++++-------------------------
 2 files changed, 239 insertions(+), 169 deletions(-)

diff --git a/src/App.css b/src/App.css
index 94d7a74d..a1c9d9f3 100644
--- a/src/App.css
+++ b/src/App.css
@@ -224,7 +224,7 @@ a:hover {
     border: 1px solid grey;
     position: absolute;
     left: 3px;
-    scroll-margin-top: 100px;
+    scroll-margin-top: 50px;
 }
 
 .nav-dropdown:focus {
@@ -749,6 +749,58 @@ a:hover {
     background-color: #7aaded;
 }
 
+/* Styles for the linear view controls */
+.linear-view-controls {
+    position: absolute;
+    display: flex;
+    flex-direction: row;
+    height: 30px;
+    justify-content: center;
+    left: 50%;
+    transform: translate(-50%, 0px);
+    
+    .chromosome-select {
+        position: relative;
+        height: auto;
+    }
+    .gene-search {
+        position: relative;
+        left: 0px;
+        padding: 0px 0px 0px 10px;
+        width: auto;
+        height: auto;
+        display: flex;
+
+        svg {
+            position: relative;
+            top: auto;
+            left: auto;
+            margin: auto;
+            margin-left: 0px;
+        }
+        input {
+            position: relative;
+            left: 0;
+            margin-left: 0;
+            border: none;
+            width: 175px;
+        }
+    }
+    .directional-controls {
+        display: flex;
+        margin: auto 0px auto 16px;
+        gap: 16px;
+        .control-group {
+            .control {
+                position: relative;
+                left: 0px;
+                margin-left: 0px;
+                line-height: 28px;
+            }
+        }
+    }
+}
+
 /* Minimal Mode styles */
 .minimal_mode {
 
@@ -949,4 +1001,11 @@ a:hover {
         }
 
     }
+
+    .linear-view-controls {
+        .chromosome-select {
+            left: 0px;
+        }
+    }
+    
 }
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 4ee18cad..4e36f1ae 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -513,7 +513,8 @@ function App(props: RouteComponentProps) {
             breakpoints: breakpoints,
             crossChr: false,
             bpIntervals,
-            svReads
+            svReads,
+            spacing: isMinimalMode ? 100 : 40
         });
         currentSpec.current = JSON.stringify(spec);
         // console.log('spec', spec);
@@ -1114,173 +1115,183 @@ function App(props: RouteComponentProps) {
                                     width: '120px'
                                 }}
                             />
-                            <select
-                                id="linear-view"
-                                style={{
-                                    pointerEvents: 'auto',
-                                    // !! This should be identical to how the height of circos determined.
-                                    top: `${Math.min(visPanelWidth, 600)}px`
-                                }}
-                                className="nav-dropdown"
-                                onChange={e => {
-                                    setShowSamples(false);
-                                    const chr = e.currentTarget.value;
-                                    setTimeout(() => setGenomeViewChr(chr), 300);
-                                }}
-                                value={genomeViewChr}
-                                disabled={!showOverview}
-                            >
-                                {CHROMOSOMES.map(chr => {
-                                    return (
-                                        <option key={chr} value={chr}>
-                                            {chr}
-                                        </option>
-                                    );
-                                })}
-                            </select>
-                            <svg
-                                className="gene-search-icon"
-                                viewBox="0 0 16 16"
-                                style={{
-                                    top: `${Math.min(visPanelWidth, 600) + 6}px`
-                                    // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
-                                }}
-                            >
-                                <path
-                                    fillRule="evenodd"
-                                    d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
-                                />
-                            </svg>
-                            <input
-                                type="text"
-                                className="gene-search"
-                                placeholder="Search Gene (e.g., MYC)"
-                                // alt={demo.assembly === 'hg38' ? 'Search Gene' : 'Not currently available for this assembly.'}
-                                style={{
-                                    pointerEvents: 'auto',
-                                    top: `${Math.min(visPanelWidth, 600)}px`
-                                    // cursor: demo.assembly === 'hg38' ? 'auto' : 'not-allowed',
-                                    // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
-                                }}
-                                // disabled={demo.assembly === 'hg38' ? false : true}
-                                // onChange={(e) => {
-                                //     const keyword = e.target.value;
-                                //     if(keyword !== "" && !keyword.startsWith("c")) {
-                                //         gosRef.current.api.suggestGene(keyword, (suggestions) => {
-                                //             setGeneSuggestions(suggestions);
-                                //         });
-                                //         setSuggestionPosition({
-                                //             left: searchBoxRef.current.getBoundingClientRect().left,
-                                //             top: searchBoxRef.current.getBoundingClientRect().top + searchBoxRef.current.getBoundingClientRect().height,
-                                //         });
-                                //     } else {
-                                //         setGeneSuggestions([]);
-                                //     }
-                                //     setSearchKeyword(keyword);
-                                // }}
-                                onKeyDown={e => {
-                                    const keyword = (e.target as HTMLTextAreaElement).value;
-                                    switch (e.key) {
-                                        case 'ArrowUp':
-                                            break;
-                                        case 'ArrowDown':
-                                            break;
-                                        case 'Enter':
-                                            // https://github.com/gosling-lang/gosling.js/blob/7555ab711023a0c3e2076a448756a9ba3eeb04f7/src/core/api.ts#L156
-                                            gosRef.current.hgApi.api.zoomToGene(
-                                                `${demo.id}-mid-ideogram`,
-                                                keyword,
-                                                10000,
-                                                1000
-                                            );
-                                            break;
-                                        case 'Esc':
-                                        case 'Escape':
-                                            break;
-                                    }
-                                }}
-                            />
-                            <button
-                                style={{
-                                    pointerEvents: 'auto',
-                                    // !! This should be identical to how the height of circos determined.
-                                    top: `${Math.min(visPanelWidth, 600)}px`
-                                }}
-                                className="zoom-in-button"
-                                onClick={e => {
-                                    const trackId = `${demo.id}-mid-ideogram`;
-                                    const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                    if (end - start < 100) return;
-                                    const delta = (end - start) / 3.0;
-                                    gosRef.current.api.zoomTo(
-                                        trackId,
-                                        `chr1:${start + delta}-${end - delta}`,
-                                        0,
-                                        ZOOM_DURATION
-                                    );
-                                }}
-                            >
-                                +
-                            </button>
-                            <button
-                                style={{
-                                    pointerEvents: 'auto',
-                                    // !! This should be identical to how the height of circos determined.
-                                    top: `${Math.min(visPanelWidth, 600)}px`
-                                }}
-                                className="zoom-out-button"
-                                onClick={e => {
-                                    const trackId = `${demo.id}-mid-ideogram`;
-                                    const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                    const delta = (end - start) / 2.0;
-                                    gosRef.current.api.zoomTo(trackId, `chr1:${start}-${end}`, delta, ZOOM_DURATION);
-                                }}
-                            >
-                                -
-                            </button>
-                            <button
-                                style={{
-                                    pointerEvents: 'auto',
-                                    // !! This should be identical to how the height of circos determined.
-                                    top: `${Math.min(visPanelWidth, 600)}px`
-                                }}
-                                className="zoom-left-button"
-                                onClick={e => {
-                                    const trackId = `${demo.id}-mid-ideogram`;
-                                    const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                    if (end - start < 100) return;
-                                    const delta = (end - start) / 4.0;
-                                    gosRef.current.api.zoomTo(
-                                        trackId,
-                                        `chr1:${start - delta}-${end - delta}`,
-                                        0,
-                                        ZOOM_DURATION
-                                    );
-                                }}
-                            >
-                                ←
-                            </button>
-                            <button
-                                style={{
-                                    pointerEvents: 'auto',
-                                    // !! This should be identical to how the height of circos determined.
-                                    top: `${Math.min(visPanelWidth, 600)}px`
-                                }}
-                                className="zoom-right-button"
-                                onClick={e => {
-                                    const trackId = `${demo.id}-mid-ideogram`;
-                                    const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
-                                    const delta = (end - start) / 4.0;
-                                    gosRef.current.api.zoomTo(
-                                        trackId,
-                                        `chr1:${start + delta}-${end + delta}`,
-                                        0,
-                                        ZOOM_DURATION
-                                    );
-                                }}
-                            >
-                                →
-                            </button>
+                            <div className="linear-view-controls" style={{ top: `${ Math.min(visPanelWidth, isMinimalMode ? 650 : 600)}px` }}>
+                                <select
+                                    id="linear-view"
+                                    style={{
+                                        pointerEvents: 'auto',
+                                        // !! This should be identical to how the height of circos determined.
+                                        // top: `${Math.min(visPanelWidth, 600)}px`
+                                    }}
+                                    className="nav-dropdown chromosome-select"
+                                    onChange={e => {
+                                        setShowSamples(false);
+                                        const chr = e.currentTarget.value;
+                                        setTimeout(() => setGenomeViewChr(chr), 300);
+                                    }}
+                                    value={genomeViewChr}
+                                    disabled={!showOverview}
+                                >
+                                    {CHROMOSOMES.map(chr => {
+                                        return (
+                                            <option key={chr} value={chr}>
+                                                {chr}
+                                            </option>
+                                        );
+                                    })}
+                                </select>
+                                <div className="gene-search">
+                                    <svg
+                                        className="gene-search-icon"
+                                        viewBox="0 0 16 16"
+                                        style={{
+                                            // top: `${Math.min(visPanelWidth, 600) + 6}px`
+                                            // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
+                                        }}
+                                    >
+                                        <path
+                                            fillRule="evenodd"
+                                            d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
+                                        />
+                                    </svg>
+                                    <input
+                                        type="text"
+                                        className="gene-search"
+                                        placeholder="Search Gene (e.g., MYC)"
+                                        // alt={demo.assembly === 'hg38' ? 'Search Gene' : 'Not currently available for this assembly.'}
+                                        style={{
+                                            pointerEvents: 'auto',
+                                            // top: `${Math.min(visPanelWidth, 600)}px`
+                                            // cursor: demo.assembly === 'hg38' ? 'auto' : 'not-allowed',
+                                            // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
+                                        }}
+                                        // disabled={demo.assembly === 'hg38' ? false : true}
+                                        // onChange={(e) => {
+                                        //     const keyword = e.target.value;
+                                        //     if(keyword !== "" && !keyword.startsWith("c")) {
+                                        //         gosRef.current.api.suggestGene(keyword, (suggestions) => {
+                                        //             setGeneSuggestions(suggestions);
+                                        //         });
+                                        //         setSuggestionPosition({
+                                        //             left: searchBoxRef.current.getBoundingClientRect().left,
+                                        //             top: searchBoxRef.current.getBoundingClientRect().top + searchBoxRef.current.getBoundingClientRect().height,
+                                        //         });
+                                        //     } else {
+                                        //         setGeneSuggestions([]);
+                                        //     }
+                                        //     setSearchKeyword(keyword);
+                                        // }}
+                                        onKeyDown={e => {
+                                            const keyword = (e.target as HTMLTextAreaElement).value;
+                                            switch (e.key) {
+                                                case 'ArrowUp':
+                                                    break;
+                                                case 'ArrowDown':
+                                                    break;
+                                                case 'Enter':
+                                                    // https://github.com/gosling-lang/gosling.js/blob/7555ab711023a0c3e2076a448756a9ba3eeb04f7/src/core/api.ts#L156
+                                                    gosRef.current.hgApi.api.zoomToGene(
+                                                        `${demo.id}-mid-ideogram`,
+                                                        keyword,
+                                                        10000,
+                                                        1000
+                                                    );
+                                                    break;
+                                                case 'Esc':
+                                                case 'Escape':
+                                                    break;
+                                            }
+                                        }}
+                                    />
+                                </div>
+                                <div className='directional-controls'>
+                                    <div className='control-group zoom'>
+                                        <button
+                                            style={{
+                                                pointerEvents: 'auto',
+                                                // !! This should be identical to how the height of circos determined.
+                                                // top: `${Math.min(visPanelWidth, 600)}px`
+                                            }}
+                                            className="zoom-in-button control"
+                                            onClick={e => {
+                                                const trackId = `${demo.id}-mid-ideogram`;
+                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                if (end - start < 100) return;
+                                                const delta = (end - start) / 3.0;
+                                                gosRef.current.api.zoomTo(
+                                                    trackId,
+                                                    `chr1:${start + delta}-${end - delta}`,
+                                                    0,
+                                                    ZOOM_DURATION
+                                                );
+                                            }}
+                                        >
+                                            +
+                                        </button>
+                                        <button
+                                            style={{
+                                                pointerEvents: 'auto',
+                                                // !! This should be identical to how the height of circos determined.
+                                                // top: `${Math.min(visPanelWidth, 600)}px`
+                                            }}
+                                            className="zoom-out-button control"
+                                            onClick={e => {
+                                                const trackId = `${demo.id}-mid-ideogram`;
+                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                const delta = (end - start) / 2.0;
+                                                gosRef.current.api.zoomTo(trackId, `chr1:${start}-${end}`, delta, ZOOM_DURATION);
+                                            }}
+                                        >
+                                            -
+                                        </button>
+                                    </div>
+                                    <div className='control-group pan'>
+                                        <button
+                                            style={{
+                                                pointerEvents: 'auto',
+                                                // !! This should be identical to how the height of circos determined.
+                                                // top: `${Math.min(visPanelWidth, 600)}px`
+                                            }}
+                                            className="zoom-left-button control"
+                                            onClick={e => {
+                                                const trackId = `${demo.id}-mid-ideogram`;
+                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                if (end - start < 100) return;
+                                                const delta = (end - start) / 4.0;
+                                                gosRef.current.api.zoomTo(
+                                                    trackId,
+                                                    `chr1:${start - delta}-${end - delta}`,
+                                                    0,
+                                                    ZOOM_DURATION
+                                                );
+                                            }}
+                                        >
+                                            ←
+                                        </button>
+                                        <button
+                                            style={{
+                                                pointerEvents: 'auto',
+                                                // !! This should be identical to how the height of circos determined.
+                                                // top: `${Math.min(visPanelWidth, 600)}px`
+                                            }}
+                                            className="zoom-right-button control"
+                                            onClick={e => {
+                                                const trackId = `${demo.id}-mid-ideogram`;
+                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                const delta = (end - start) / 4.0;
+                                                gosRef.current.api.zoomTo(
+                                                    trackId,
+                                                    `chr1:${start + delta}-${end + delta}`,
+                                                    0,
+                                                    ZOOM_DURATION
+                                                );
+                                            }}
+                                        >
+                                            →
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
                         </div>
                     </div>
                 </div>

From bacfad8b75926c6f2790ead6456dc4edc3e0076a Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Tue, 30 Apr 2024 15:37:43 -0400
Subject: [PATCH 12/16] -style: correct styling for placeholder input -style:
 center controls only in minimal mode

---
 src/App.css | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/src/App.css b/src/App.css
index a1c9d9f3..8f200363 100644
--- a/src/App.css
+++ b/src/App.css
@@ -756,25 +756,26 @@ a:hover {
     flex-direction: row;
     height: 30px;
     justify-content: center;
-    left: 50%;
-    transform: translate(-50%, 0px);
+    left: 3px;
     
     .chromosome-select {
         position: relative;
         height: auto;
+        left: 0px;
     }
     .gene-search {
         position: relative;
         left: 0px;
-        padding: 0px 0px 0px 10px;
+        padding: 0px;
         width: auto;
         height: auto;
         display: flex;
 
         svg {
-            position: relative;
-            top: auto;
-            left: auto;
+            position: absolute;
+            top: 50%;
+            transform: translate(0px, -50%);
+            left: 8px;
             margin: auto;
             margin-left: 0px;
         }
@@ -783,7 +784,8 @@ a:hover {
             left: 0;
             margin-left: 0;
             border: none;
-            width: 175px;
+            width: 180px;
+            padding-left: 35px;
         }
     }
     .directional-controls {
@@ -791,6 +793,7 @@ a:hover {
         margin: auto 0px auto 16px;
         gap: 16px;
         .control-group {
+            display: flex;
             .control {
                 position: relative;
                 left: 0px;
@@ -1003,9 +1006,9 @@ a:hover {
     }
 
     .linear-view-controls {
-        .chromosome-select {
-            left: 0px;
-        }
+        left: 50%;
+        transform: translate(-50%, 0px);
+        
     }
     
 }
\ No newline at end of file

From 49395f386dc5b7ca6585edee6ae90a158dfb2115 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Wed, 8 May 2024 18:08:08 -0400
Subject: [PATCH 13/16] -feat: export dropdown -chore: adjust opacity
 calculation for legend -chore: update title for navigation buttons

---
 src/App.css               |  68 ++++++++++------------
 src/App.tsx               | 117 ++++++++++++++++++++++++--------------
 src/icon.ts               |   5 +-
 src/ui/ExportDropdown.tsx | 102 ++++++++++++++++++++-------------
 4 files changed, 165 insertions(+), 127 deletions(-)

diff --git a/src/App.css b/src/App.css
index 8f200363..a33deb9f 100644
--- a/src/App.css
+++ b/src/App.css
@@ -749,15 +749,15 @@ a:hover {
     background-color: #7aaded;
 }
 
-/* Styles for the linear view controls */
-.linear-view-controls {
+/* Styles for the variant view controls */
+.variant-view-controls {
     position: absolute;
     display: flex;
     flex-direction: row;
     height: 30px;
     justify-content: center;
     left: 3px;
-    
+
     .chromosome-select {
         position: relative;
         height: auto;
@@ -806,7 +806,6 @@ a:hover {
 
 /* Minimal Mode styles */
 .minimal_mode {
-
     .gosling-panel {
         overflow-y: scroll;
         overflow-x: hidden;
@@ -827,18 +826,18 @@ a:hover {
     }
 
     .navigation-button {
-        background-color: #F6F6F6;
+        background-color: #f6f6f6;
         cursor: pointer;
         font-size: 1rem;
         font-family: Inter;
         height: 40px;
         width: 210px;
         padding: 2px 10px;
-        border: 1px solid #D3D3D3;
+        border: 1px solid #d3d3d3;
     }
 
     .navigation-button:hover:not(:disabled) {
-        background-color: #EBEBEB;
+        background-color: #ebebeb;
     }
     .navigation-button:active:not(:disabled) {
         background-color: #e6e4e4;
@@ -859,8 +858,8 @@ a:hover {
     ::-webkit-scrollbar-thumb {
         width: 10px;
         border-radius: 4px;
-        background-color: rgba(0, 0, 0, .5);
-        box-shadow: 0 0 1px rgba(255, 255, 255, .5);
+        background-color: rgba(0, 0, 0, 0.5);
+        box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
     }
     ::-webkit-scrollbar:hover {
         cursor: pointer;
@@ -879,17 +878,16 @@ a:hover {
             display: flex;
             flex-direction: column;
             justify-content: space-between;
-            
 
             .open-in-chromoscope-link {
-                background-color: #F6F6F6;
+                background-color: #f6f6f6;
                 font-size: 0.9rem;
                 font-family: Inter;
                 font-weight: 400;
                 display: flex;
                 height: 35px;
                 justify-content: center;
-                border: 1px solid #D3D3D3;
+                border: 1px solid #d3d3d3;
                 border-radius: 8px;
 
                 .link-group {
@@ -902,32 +900,31 @@ a:hover {
                         stroke: black;
                     }
                 }
-
             }
 
             .open-in-chromoscope-link:hover {
                 text-decoration: none;
                 cursor: pointer;
-                background-color: #EBEBEB;
+                background-color: #ebebeb;
             }
-            
+
             .open-in-chromoscope-link:active {
                 background-color: #e6e4e4;
             }
-            
+
             .export-links {
                 border-radius: 4px;
                 margin-top: 4px;
 
                 .export-dropdown {
                     height: auto;
-                    background-color: #F6F6F6;
+                    background-color: #f6f6f6;
                     right: 0px;
                     border-radius: 8px;
-                    border: 1px solid #D3D3D3;
+                    border: 1px solid #d3d3d3;
                     transition: all 100ms;
                     overflow: hidden;
-        
+
                     .export-button {
                         width: 210px;
                         height: 35px;
@@ -935,22 +932,22 @@ a:hover {
                         border: 0px solid;
                         font-weight: 400;
                         background-color: transparent;
-        
+
                         .export-title {
-                            font-size: .9rem;
+                            font-size: 0.9rem;
                             font-family: Inter;
                         }
-        
+
                         .button.triangle-down {
                             width: 11px;
                             height: 7px;
                             margin-left: 8px;
                         }
                     }
-        
+
                     .export-button:hover {
                         cursor: pointer;
-                        background-color: #EBEBEB;
+                        background-color: #ebebeb;
                     }
 
                     .export-button:active {
@@ -966,13 +963,12 @@ a:hover {
                         background-color: white;
                         margin: 0px 8px 8px 8px;
                         border-radius: 3px;
-    
+
                         .nav-list-item {
                             display: flex;
                             margin: auto;
                         }
-    
-    
+
                         .title-btn {
                             display: flex;
                             position: relative;
@@ -980,35 +976,31 @@ a:hover {
                             height: 25px;
                             margin-left: 0px;
                         }
-    
+
                         .title-btn.png {
                             padding: 0px;
                             border: none;
                             background-color: transparent;
                         }
                     }
-        
                 }
-                
+
                 .export-dropdown.open {
-                    background-color: #EBEBEB;
+                    background-color: #ebebeb;
                     border-radius: 8px;
-                    border: 1px solid #C3C3C3;
+                    border: 1px solid #c3c3c3;
                     transition: all 100ms;
-        
+
                     .export-button {
                         border: none;
                     }
                 }
             }
         }
-
     }
 
-    .linear-view-controls {
+    .variant-view-controls {
         left: 50%;
         transform: translate(-50%, 0px);
-        
     }
-    
-}
\ No newline at end of file
+}
diff --git a/src/App.tsx b/src/App.tsx
index 4e36f1ae..bd9b3230 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -103,7 +103,9 @@ function App(props: RouteComponentProps) {
     const [showOverview, setShowOverview] = useState(true);
     const [showPutativeDriver, setShowPutativeDriver] = useState(true);
     const [interactiveMode, setInteractiveMode] = useState(isMinimalMode ?? false);
-    const [visPanelWidth, setVisPanelWidth] = useState(INIT_VIS_PANEL_WIDTH - ( isMinimalMode ? 10 : VIS_PADDING.left * 2) );
+    const [visPanelWidth, setVisPanelWidth] = useState(
+        INIT_VIS_PANEL_WIDTH - (isMinimalMode ? 10 : VIS_PADDING.left * 2)
+    );
     const [overviewChr, setOverviewChr] = useState('');
     const [genomeViewChr, setGenomeViewChr] = useState('');
     const [drivers, setDrivers] = useState(
@@ -313,21 +315,21 @@ function App(props: RouteComponentProps) {
                 setVisPanelWidth(window.innerWidth - VIS_PADDING.left * 2);
             }, 500)
         );
-        
-        // In minimal mode, lower opacity of legend image as circular view 
-        // moves out of the screen
+
+        // Lower opacity of legend image as it leaves viewport
         if (isMinimalMode) {
-            const legendElement = document.querySelector<HTMLElement>(".circular-view-legend");
-            let options = {
-                root: document.querySelector(".minimal_mode"),
-                rootMargin: "-250px 0px 0px 0px",
-                threshold: [1, 0.5, 0.25, 0],
+            const legendElement = document.querySelector<HTMLElement>('.genome-view-legend');
+            const options = {
+                root: document.querySelector('.minimal_mode'),
+                rootMargin: '-250px 0px 0px 0px',
+                threshold: [0.9, 0.75, 0.5, 0.25, 0]
             };
-    
-            let observer = new IntersectionObserver((entry) => {
-                legendElement.style.opacity = "" + entry[0].intersectionRatio ** 2;
+
+            const observer = new IntersectionObserver(entry => {
+                // Set intersection ratio as opacity (round up to one decimal place)
+                legendElement.style.opacity = '' + Math.ceil(10 * entry[0].intersectionRatio) / 10;
             }, options);
-    
+
             observer.observe(legendElement);
         }
     }, []);
@@ -638,7 +640,7 @@ function App(props: RouteComponentProps) {
     return (
         <ErrorBoundary>
             <div
-                className={isMinimalMode ? "minimal_mode" : ""}
+                className={isMinimalMode ? 'minimal_mode' : ''}
                 style={{ width: '100%', height: '100%' }}
                 onMouseMove={e => {
                     const top = e.clientY;
@@ -723,7 +725,7 @@ function App(props: RouteComponentProps) {
                             <small>{demo.id}</small>
                         </>
                     )}
-                    { !isMinimalMode && (
+                    {!isMinimalMode && (
                         <>
                             <span className="title-btn" onClick={() => gosRef.current?.api.exportPng()}>
                                 <svg className="button" viewBox="0 0 16 16">
@@ -797,7 +799,9 @@ function App(props: RouteComponentProps) {
                                         navigator.clipboard
                                             .writeText(newUrl)
                                             .then(() =>
-                                                alert('The URL of the current session has been copied to your clipboard.')
+                                                alert(
+                                                    'The URL of the current session has been copied to your clipboard.'
+                                                )
                                             );
                                     }
                                 }}
@@ -1056,13 +1060,13 @@ function App(props: RouteComponentProps) {
                                         );
                                     }}
                                 >
-                                    Circular View
+                                    Genome View
                                 </button>
                                 <button
-                                    className="navigation-button navigation-button-linear"
+                                    className="navigation-button navigation-button-variant"
                                     onClick={() => {
                                         setTimeout(() => {
-                                            document.getElementById('linear-view')?.scrollIntoView({
+                                            document.getElementById('variant-view')?.scrollIntoView({
                                                 block: 'start',
                                                 inline: 'nearest',
                                                 behavior: 'smooth'
@@ -1071,7 +1075,7 @@ function App(props: RouteComponentProps) {
                                         });
                                     }}
                                 >
-                                    Linear View
+                                    Variant View
                                 </button>
                             </div>
                         ) : null}
@@ -1083,8 +1087,19 @@ function App(props: RouteComponentProps) {
                                         <a className="open-in-chromoscope-link" href="">
                                             <div className="link-group">
                                                 <span>Open in Chromoscope</span>
-                                                <svg className="external-link-icon" width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
-                                                    <path d="M9.8212 1.73104L10.6894 0.875H9.47015H7.66727C7.55064 0.875 7.46966 0.784774 7.46966 0.6875C7.46966 0.590226 7.55064 0.5 7.66727 0.5H11.1553C11.2719 0.5 11.3529 0.590226 11.3529 0.6875V4.125C11.3529 4.22227 11.2719 4.3125 11.1553 4.3125C11.0387 4.3125 10.9577 4.22228 10.9577 4.125V2.34824V1.15307L10.1067 1.9922L5.71834 6.31907C5.71831 6.3191 5.71828 6.31913 5.71825 6.31916C5.64039 6.39579 5.51053 6.39576 5.43271 6.31907C5.35892 6.24635 5.35892 6.1308 5.43271 6.05808L5.4328 6.05799L9.8212 1.73104ZM1.19116 2.40625C1.19116 1.73964 1.74085 1.1875 2.43519 1.1875H4.87682C4.99345 1.1875 5.07443 1.27773 5.07443 1.375C5.07443 1.47227 4.99345 1.5625 4.87682 1.5625H2.43519C1.97411 1.5625 1.58638 1.93419 1.58638 2.40625V9.28125C1.58638 9.75331 1.97411 10.125 2.43519 10.125H9.41129C9.87237 10.125 10.2601 9.75331 10.2601 9.28125V6.875C10.2601 6.77773 10.3411 6.6875 10.4577 6.6875C10.5743 6.6875 10.6553 6.77773 10.6553 6.875V9.28125C10.6553 9.94786 10.1056 10.5 9.41129 10.5H2.43519C1.74085 10.5 1.19116 9.94786 1.19116 9.28125V2.40625Z" fill="black" stroke="black"/>
+                                                <svg
+                                                    className="external-link-icon"
+                                                    width="12"
+                                                    height="11"
+                                                    viewBox="0 0 12 11"
+                                                    fill="none"
+                                                    xmlns="http://www.w3.org/2000/svg"
+                                                >
+                                                    <path
+                                                        d="M9.8212 1.73104L10.6894 0.875H9.47015H7.66727C7.55064 0.875 7.46966 0.784774 7.46966 0.6875C7.46966 0.590226 7.55064 0.5 7.66727 0.5H11.1553C11.2719 0.5 11.3529 0.590226 11.3529 0.6875V4.125C11.3529 4.22227 11.2719 4.3125 11.1553 4.3125C11.0387 4.3125 10.9577 4.22228 10.9577 4.125V2.34824V1.15307L10.1067 1.9922L5.71834 6.31907C5.71831 6.3191 5.71828 6.31913 5.71825 6.31916C5.64039 6.39579 5.51053 6.39576 5.43271 6.31907C5.35892 6.24635 5.35892 6.1308 5.43271 6.05808L5.4328 6.05799L9.8212 1.73104ZM1.19116 2.40625C1.19116 1.73964 1.74085 1.1875 2.43519 1.1875H4.87682C4.99345 1.1875 5.07443 1.27773 5.07443 1.375C5.07443 1.47227 4.99345 1.5625 4.87682 1.5625H2.43519C1.97411 1.5625 1.58638 1.93419 1.58638 2.40625V9.28125C1.58638 9.75331 1.97411 10.125 2.43519 10.125H9.41129C9.87237 10.125 10.2601 9.75331 10.2601 9.28125V6.875C10.2601 6.77773 10.3411 6.6875 10.4577 6.6875C10.5743 6.6875 10.6553 6.77773 10.6553 6.875V9.28125C10.6553 9.94786 10.1056 10.5 9.41129 10.5H2.43519C1.74085 10.5 1.19116 9.94786 1.19116 9.28125V2.40625Z"
+                                                        fill="black"
+                                                        stroke="black"
+                                                    />
                                                 </svg>
                                             </div>
                                         </a>
@@ -1105,21 +1120,24 @@ function App(props: RouteComponentProps) {
                             }}
                         >
                             <img
-                                className="circular-view-legend"
+                                className="genome-view-legend"
                                 src={legend}
                                 style={{
                                     position: 'absolute',
                                     right: isMinimalMode ? '10px' : '3px',
-                                    top: isMinimalMode ? '425px' : '3px',
+                                    top: isMinimalMode ? '350px' : '3px',
                                     zIndex: 997,
                                     width: '120px'
                                 }}
                             />
-                            <div className="linear-view-controls" style={{ top: `${ Math.min(visPanelWidth, isMinimalMode ? 650 : 600)}px` }}>
+                            <div
+                                className="variant-view-controls"
+                                style={{ top: `${Math.min(visPanelWidth, isMinimalMode ? 650 : 600)}px` }}
+                            >
                                 <select
-                                    id="linear-view"
+                                    id="variant-view"
                                     style={{
-                                        pointerEvents: 'auto',
+                                        pointerEvents: 'auto'
                                         // !! This should be identical to how the height of circos determined.
                                         // top: `${Math.min(visPanelWidth, 600)}px`
                                     }}
@@ -1144,10 +1162,12 @@ function App(props: RouteComponentProps) {
                                     <svg
                                         className="gene-search-icon"
                                         viewBox="0 0 16 16"
-                                        style={{
-                                            // top: `${Math.min(visPanelWidth, 600) + 6}px`
-                                            // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
-                                        }}
+                                        style={
+                                            {
+                                                // top: `${Math.min(visPanelWidth, 600) + 6}px`
+                                                // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
+                                            }
+                                        }
                                     >
                                         <path
                                             fillRule="evenodd"
@@ -1160,7 +1180,7 @@ function App(props: RouteComponentProps) {
                                         placeholder="Search Gene (e.g., MYC)"
                                         // alt={demo.assembly === 'hg38' ? 'Search Gene' : 'Not currently available for this assembly.'}
                                         style={{
-                                            pointerEvents: 'auto',
+                                            pointerEvents: 'auto'
                                             // top: `${Math.min(visPanelWidth, 600)}px`
                                             // cursor: demo.assembly === 'hg38' ? 'auto' : 'not-allowed',
                                             // visibility: demo.assembly === 'hg38' ? 'visible' : 'hidden'
@@ -1204,18 +1224,19 @@ function App(props: RouteComponentProps) {
                                         }}
                                     />
                                 </div>
-                                <div className='directional-controls'>
-                                    <div className='control-group zoom'>
+                                <div className="directional-controls">
+                                    <div className="control-group zoom">
                                         <button
                                             style={{
-                                                pointerEvents: 'auto',
+                                                pointerEvents: 'auto'
                                                 // !! This should be identical to how the height of circos determined.
                                                 // top: `${Math.min(visPanelWidth, 600)}px`
                                             }}
                                             className="zoom-in-button control"
                                             onClick={e => {
                                                 const trackId = `${demo.id}-mid-ideogram`;
-                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                const [start, end] =
+                                                    gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
                                                 if (end - start < 100) return;
                                                 const delta = (end - start) / 3.0;
                                                 gosRef.current.api.zoomTo(
@@ -1230,32 +1251,39 @@ function App(props: RouteComponentProps) {
                                         </button>
                                         <button
                                             style={{
-                                                pointerEvents: 'auto',
+                                                pointerEvents: 'auto'
                                                 // !! This should be identical to how the height of circos determined.
                                                 // top: `${Math.min(visPanelWidth, 600)}px`
                                             }}
                                             className="zoom-out-button control"
                                             onClick={e => {
                                                 const trackId = `${demo.id}-mid-ideogram`;
-                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                const [start, end] =
+                                                    gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
                                                 const delta = (end - start) / 2.0;
-                                                gosRef.current.api.zoomTo(trackId, `chr1:${start}-${end}`, delta, ZOOM_DURATION);
+                                                gosRef.current.api.zoomTo(
+                                                    trackId,
+                                                    `chr1:${start}-${end}`,
+                                                    delta,
+                                                    ZOOM_DURATION
+                                                );
                                             }}
                                         >
                                             -
                                         </button>
                                     </div>
-                                    <div className='control-group pan'>
+                                    <div className="control-group pan">
                                         <button
                                             style={{
-                                                pointerEvents: 'auto',
+                                                pointerEvents: 'auto'
                                                 // !! This should be identical to how the height of circos determined.
                                                 // top: `${Math.min(visPanelWidth, 600)}px`
                                             }}
                                             className="zoom-left-button control"
                                             onClick={e => {
                                                 const trackId = `${demo.id}-mid-ideogram`;
-                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                const [start, end] =
+                                                    gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
                                                 if (end - start < 100) return;
                                                 const delta = (end - start) / 4.0;
                                                 gosRef.current.api.zoomTo(
@@ -1270,14 +1298,15 @@ function App(props: RouteComponentProps) {
                                         </button>
                                         <button
                                             style={{
-                                                pointerEvents: 'auto',
+                                                pointerEvents: 'auto'
                                                 // !! This should be identical to how the height of circos determined.
                                                 // top: `${Math.min(visPanelWidth, 600)}px`
                                             }}
                                             className="zoom-right-button control"
                                             onClick={e => {
                                                 const trackId = `${demo.id}-mid-ideogram`;
-                                                const [start, end] = gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
+                                                const [start, end] =
+                                                    gosRef.current?.hgApi.api.getLocation(trackId).xDomain;
                                                 const delta = (end - start) / 4.0;
                                                 gosRef.current.api.zoomTo(
                                                     trackId,
diff --git a/src/icon.ts b/src/icon.ts
index b8fd31ea..76a7a041 100644
--- a/src/icon.ts
+++ b/src/icon.ts
@@ -338,10 +338,7 @@ export const ICONS: Record<string, ICON_INFO> = {
         width: 11,
         height: 7,
         viewBox: '0 0 11 7',
-        path: [
-            'M0.5 1H10.5L5.5 6L0.5 1Z',
-            'M5.5 6L0.5 1H10.5L5.5 6ZM5.5 6V5.28571'
-        ],
+        path: ['M0.5 1H10.5L5.5 6L0.5 1Z', 'M5.5 6L0.5 1H10.5L5.5 6ZM5.5 6V5.28571'],
         stroke: 'currentColor',
         fill: 'none'
     }
diff --git a/src/ui/ExportDropdown.tsx b/src/ui/ExportDropdown.tsx
index 368d0ec6..188e20d0 100644
--- a/src/ui/ExportDropdown.tsx
+++ b/src/ui/ExportDropdown.tsx
@@ -1,10 +1,13 @@
-import React, {useState, useEffect} from "react";
-import { ICONS } from "../icon";
-import { getHtmlTemplate } from "../html-template";
+import React, { useState, useEffect } from 'react';
+import { ICONS } from '../icon';
+import { getHtmlTemplate } from '../html-template';
 
+type ExportButtonProps = {
+    title: string;
+    icon: string;
+};
 
-
-const ExportButton = ({ title, icon }) => {
+const ExportButton = ({ title, icon }: ExportButtonProps) => {
     return (
         <svg className="button" viewBox="0 0 16 16">
             <title>{title}</title>
@@ -12,15 +15,20 @@ const ExportButton = ({ title, icon }) => {
                 <path fill="currentColor" key={p} d={p} />
             ))}
         </svg>
-    )
-}
+    );
+};
+
+type ExportDropdownProps = {
+    gosRef: React.RefObject<any>;
+    currentSpec: React.MutableRefObject<string>;
+};
 
-export const ExportDropdown = ({ gosRef, currentSpec }) => {
+export const ExportDropdown = ({ gosRef, currentSpec }: ExportDropdownProps) => {
     const [isOpen, setIsOpen] = useState(false);
 
     return (
-        <div 
-            className={"export-dropdown" + (isOpen ? " open" : " closed")}
+        <div
+            className={'export-dropdown' + (isOpen ? ' open' : ' closed')}
             onClick={() => setIsOpen(!isOpen)}
             aria-expanded={isOpen}
         >
@@ -33,38 +41,50 @@ export const ExportDropdown = ({ gosRef, currentSpec }) => {
                     ))}
                 </svg>
             </button>
-            {isOpen ? <nav className="export-options">
-                <ul className="nav-list">
-                    <li className="nav-list-item">
-                        <button className="title-btn png" onClick={(e) =>{ e.stopPropagation(); gosRef.current?.api.exportPng() }}>
-                            <ExportButton title="Export PNG" icon="PNG" />
-                        </button>
-                    </li>
+            {isOpen ? (
+                <nav className="export-options">
+                    <ul className="nav-list">
+                        <li className="nav-list-item">
+                            <button
+                                className="title-btn png"
+                                onClick={e => {
+                                    e.stopPropagation();
+                                    gosRef.current?.api.exportPng();
+                                }}
+                            >
+                                <ExportButton title="Export PNG" icon="PNG" />
+                            </button>
+                        </li>
 
-                    <li className="nav-list-item">
-                        <a 
-                            className="title-btn" 
-                            href={`data:text/plain;charset=utf-8,${encodeURIComponent(
-                                getHtmlTemplate(currentSpec.current)
-                            )}`} 
-                            download="visualization.html"
-                            onClick={(e) =>{ e.stopPropagation() }}
-                        >
-                            <ExportButton title="Export HTML" icon="HTML" />
-                        </a>
-                    </li>
-                    <li className="nav-list-item">
-                        <a 
-                            className="title-btn" 
-                            href={`data:text/plain;charset=utf-8,${encodeURIComponent(currentSpec.current)}`} 
-                            download="visualization.json"
-                            onClick={(e) =>{ e.stopPropagation() }}
-                        >
-                            <ExportButton title="Export JSON" icon="JSON" />
-                        </a>
-                    </li>
+                        <li className="nav-list-item">
+                            <a
+                                className="title-btn"
+                                href={`data:text/plain;charset=utf-8,${encodeURIComponent(
+                                    getHtmlTemplate(currentSpec.current)
+                                )}`}
+                                download="visualization.html"
+                                onClick={e => {
+                                    e.stopPropagation();
+                                }}
+                            >
+                                <ExportButton title="Export HTML" icon="HTML" />
+                            </a>
+                        </li>
+                        <li className="nav-list-item">
+                            <a
+                                className="title-btn"
+                                href={`data:text/plain;charset=utf-8,${encodeURIComponent(currentSpec.current)}`}
+                                download="visualization.json"
+                                onClick={e => {
+                                    e.stopPropagation();
+                                }}
+                            >
+                                <ExportButton title="Export JSON" icon="JSON" />
+                            </a>
+                        </li>
                     </ul>
-            </nav> : null}
+                </nav>
+            ) : null}
         </div>
     );
-}
\ No newline at end of file
+};

From 2c35c2309ee6c259f3a52591fdcc09dd8ba501a7 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Wed, 8 May 2024 18:17:36 -0400
Subject: [PATCH 14/16] -feat: add open in chromoscope link button -fix: typo

---
 src/App.tsx               | 27 +++++++++++++++++++++++++--
 src/ui/ExportDropdown.tsx |  2 +-
 2 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/src/App.tsx b/src/App.tsx
index bd9b3230..431fb7c8 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1084,7 +1084,30 @@ function App(props: RouteComponentProps) {
                             isMinimalMode ? (
                                 <div className="external-links">
                                     <nav className="external-links-nav">
-                                        <a className="open-in-chromoscope-link" href="">
+                                        <button
+                                            className="open-in-chromoscope-link"
+                                            tabIndex={0}
+                                            onClick={e => {
+                                                e.preventDefault();
+                                                const { xDomain } = gosRef.current.hgApi.api.getLocation(
+                                                    `${demo.id}-mid-ideogram`
+                                                );
+                                                if (xDomain) {
+                                                    // urlParams.set('demoIndex', demoIndex.current + '');
+                                                    // urlParams.set('domain', xDomain.join('-'));
+                                                    let newUrl =
+                                                        window.location.origin + window.location.pathname + '?';
+                                                    newUrl += `demoIndex=${demoIndex.current}`;
+                                                    newUrl += `&domain=${xDomain.join('-')}`;
+                                                    if (externalDemoUrl.current) {
+                                                        newUrl += `&external=${externalDemoUrl.current}`;
+                                                    } else if (externalUrl) {
+                                                        newUrl += `&external=${externalUrl}`;
+                                                    }
+                                                    window.open(newUrl, '_blank');
+                                                }
+                                            }}
+                                        >
                                             <div className="link-group">
                                                 <span>Open in Chromoscope</span>
                                                 <svg
@@ -1102,7 +1125,7 @@ function App(props: RouteComponentProps) {
                                                     />
                                                 </svg>
                                             </div>
-                                        </a>
+                                        </button>
                                         <div className="export-links">
                                             <ExportDropdown gosRef={gosRef} currentSpec={currentSpec} />
                                         </div>
diff --git a/src/ui/ExportDropdown.tsx b/src/ui/ExportDropdown.tsx
index 188e20d0..6bdbbcf4 100644
--- a/src/ui/ExportDropdown.tsx
+++ b/src/ui/ExportDropdown.tsx
@@ -35,7 +35,7 @@ export const ExportDropdown = ({ gosRef, currentSpec }: ExportDropdownProps) =>
             <button className="export-button">
                 <span className="export-title">Export</span>
                 <svg className="button triangle-down" viewBox={ICONS.TRIANGLE_DOWN.viewBox}>
-                    <title>Triange Down</title>
+                    <title>Triangle Down</title>
                     {ICONS.TRIANGLE_DOWN.path.map(p => (
                         <path fill="currentColor" key={p} d={p} />
                     ))}

From fabc541528cc7edd27ff31b4168789885ea4b046 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Thu, 16 May 2024 12:07:54 -0400
Subject: [PATCH 15/16] fix: push track mouseover-menu to top z-index

---
 src/App.css | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/App.css b/src/App.css
index a33deb9f..dfbfc295 100644
--- a/src/App.css
+++ b/src/App.css
@@ -17,6 +17,7 @@ body,
         'Segoe UI Symbol';
     height: 100%;
     overflow: hidden;
+    z-index: 0;
 }
 
 body {
@@ -749,6 +750,10 @@ a:hover {
     background-color: #7aaded;
 }
 
+.track-mouseover-menu {
+    z-index: 999;
+}
+
 /* Styles for the variant view controls */
 .variant-view-controls {
     position: absolute;

From 5c87b6cbc987ef03cd761c53a411d89db722c2d2 Mon Sep 17 00:00:00 2001
From: Cesar Ferreyra-Mansilla <crf85@cornell.edu>
Date: Thu, 16 May 2024 12:09:02 -0400
Subject: [PATCH 16/16] fix: remove typo

---
 src/App.css | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/App.css b/src/App.css
index dfbfc295..03dd2148 100644
--- a/src/App.css
+++ b/src/App.css
@@ -17,7 +17,6 @@ body,
         'Segoe UI Symbol';
     height: 100%;
     overflow: hidden;
-    z-index: 0;
 }
 
 body {