Fix iPhone responsive layout: threshold, MutationObserver, map hiding
Browse files- Changed stacking threshold from screen.width*0.50 to fixed 768px
breakpoint (old formula gave ~195px on iPhone — stacking never fired)
- Added MutationObserver to re-run stacking when Streamlit injects cards
and map via websocket after page load
- Map now hidden via inline JS style.setProperty in addition to CSS
- Card collapse retriggers on DOM mutations, not just window.load
- src/styles/dark_theme.py +49 -6
src/styles/dark_theme.py
CHANGED
|
@@ -834,22 +834,25 @@ FORCE_DARK_WRAPPER_JS = """<!DOCTYPE html>
|
|
| 834 |
} catch(e) {
|
| 835 |
/* Cross-origin iframe — can't access parent. This is expected. */
|
| 836 |
}
|
| 837 |
-
})();
|
| 838 |
</script>"""
|
| 839 |
|
| 840 |
STACK_CONTROLS_JS = """<!DOCTYPE html>
|
|
|
|
|
|
|
| 841 |
<script>
|
| 842 |
(function() {
|
| 843 |
try {
|
| 844 |
var p = window.parent;
|
| 845 |
if (!p) return;
|
| 846 |
|
| 847 |
-
|
|
|
|
| 848 |
|
| 849 |
function updateStack() {
|
| 850 |
var body = p.document.body;
|
| 851 |
if (!body) return;
|
| 852 |
-
var isNarrow = p.window.innerWidth <=
|
| 853 |
body.classList.toggle('stack-columns', isNarrow);
|
| 854 |
|
| 855 |
// Apply 4-row layout to search controls (first horizontal block) only
|
|
@@ -881,14 +884,41 @@ STACK_CONTROLS_JS = """<!DOCTYPE html>
|
|
| 881 |
for (var i = 0; i < expanders.length; i++) {
|
| 882 |
expanders[i].removeAttribute('open');
|
| 883 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 884 |
}
|
| 885 |
}
|
| 886 |
|
| 887 |
// Run on load
|
| 888 |
if (document.readyState === 'complete') {
|
| 889 |
-
|
| 890 |
} else {
|
| 891 |
-
p.window.addEventListener('load',
|
|
|
|
|
|
|
| 892 |
}
|
| 893 |
|
| 894 |
// Debounced resize
|
|
@@ -897,11 +927,24 @@ STACK_CONTROLS_JS = """<!DOCTYPE html>
|
|
| 897 |
clearTimeout(timer);
|
| 898 |
timer = setTimeout(updateStack, 100);
|
| 899 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
} catch(e) {
|
| 901 |
/* Cross-origin — silently skip */
|
| 902 |
}
|
| 903 |
})();
|
| 904 |
-
</script>
|
|
|
|
|
|
|
| 905 |
|
| 906 |
def apply_dark_theme():
|
| 907 |
"""Inject dark-theme CSS, flexible panel JS, card↔map hover JS, and smart image positioning JS."""
|
|
|
|
| 834 |
} catch(e) {
|
| 835 |
/* Cross-origin iframe — can't access parent. This is expected. */
|
| 836 |
}
|
| 837 |
+
})();
|
| 838 |
</script>"""
|
| 839 |
|
| 840 |
STACK_CONTROLS_JS = """<!DOCTYPE html>
|
| 841 |
+
<html>
|
| 842 |
+
<body>
|
| 843 |
<script>
|
| 844 |
(function() {
|
| 845 |
try {
|
| 846 |
var p = window.parent;
|
| 847 |
if (!p) return;
|
| 848 |
|
| 849 |
+
// Fixed breakpoint matching CSS @media (max-width: 768px)
|
| 850 |
+
var BREAKPOINT = 768;
|
| 851 |
|
| 852 |
function updateStack() {
|
| 853 |
var body = p.document.body;
|
| 854 |
if (!body) return;
|
| 855 |
+
var isNarrow = p.window.innerWidth <= BREAKPOINT;
|
| 856 |
body.classList.toggle('stack-columns', isNarrow);
|
| 857 |
|
| 858 |
// Apply 4-row layout to search controls (first horizontal block) only
|
|
|
|
| 884 |
for (var i = 0; i < expanders.length; i++) {
|
| 885 |
expanders[i].removeAttribute('open');
|
| 886 |
}
|
| 887 |
+
|
| 888 |
+
// Hide the map container directly (more reliable than CSS alone)
|
| 889 |
+
var mapContainer = body.querySelector('.stCustomComponentV1');
|
| 890 |
+
if (mapContainer) {
|
| 891 |
+
mapContainer.style.setProperty('display', 'none', 'important');
|
| 892 |
+
}
|
| 893 |
+
// Hide the map title
|
| 894 |
+
var mapTitles = body.querySelectorAll('h3');
|
| 895 |
+
for (var i = 0; i < mapTitles.length; i++) {
|
| 896 |
+
if (mapTitles[i].textContent.trim() === '🗺️ Map') {
|
| 897 |
+
mapTitles[i].style.setProperty('display', 'none', 'important');
|
| 898 |
+
}
|
| 899 |
+
}
|
| 900 |
+
} else {
|
| 901 |
+
// Restore map visibility
|
| 902 |
+
var mapContainer = body.querySelector('.stCustomComponentV1');
|
| 903 |
+
if (mapContainer) {
|
| 904 |
+
mapContainer.style.display = '';
|
| 905 |
+
}
|
| 906 |
+
var mapTitles = body.querySelectorAll('h3');
|
| 907 |
+
for (var i = 0; i < mapTitles.length; i++) {
|
| 908 |
+
if (mapTitles[i].textContent.trim() === '🗺️ Map') {
|
| 909 |
+
mapTitles[i].style.display = '';
|
| 910 |
+
}
|
| 911 |
+
}
|
| 912 |
}
|
| 913 |
}
|
| 914 |
|
| 915 |
// Run on load
|
| 916 |
if (document.readyState === 'complete') {
|
| 917 |
+
setTimeout(updateStack, 100);
|
| 918 |
} else {
|
| 919 |
+
p.window.addEventListener('load', function() {
|
| 920 |
+
setTimeout(updateStack, 100);
|
| 921 |
+
});
|
| 922 |
}
|
| 923 |
|
| 924 |
// Debounced resize
|
|
|
|
| 927 |
clearTimeout(timer);
|
| 928 |
timer = setTimeout(updateStack, 100);
|
| 929 |
});
|
| 930 |
+
|
| 931 |
+
// Watch for DOM changes — Streamlit renders content via websocket
|
| 932 |
+
// after the page loads, so we need to re-run when cards/map appear
|
| 933 |
+
var moTimer;
|
| 934 |
+
var observer = new MutationObserver(function() {
|
| 935 |
+
clearTimeout(moTimer);
|
| 936 |
+
moTimer = setTimeout(updateStack, 200);
|
| 937 |
+
});
|
| 938 |
+
if (p.document.body) {
|
| 939 |
+
observer.observe(p.document.body, { childList: true, subtree: true });
|
| 940 |
+
}
|
| 941 |
} catch(e) {
|
| 942 |
/* Cross-origin — silently skip */
|
| 943 |
}
|
| 944 |
})();
|
| 945 |
+
</script>
|
| 946 |
+
</body>
|
| 947 |
+
</html>"""
|
| 948 |
|
| 949 |
def apply_dark_theme():
|
| 950 |
"""Inject dark-theme CSS, flexible panel JS, card↔map hover JS, and smart image positioning JS."""
|