Unify helium mobile controls and export all helium object types

This commit is contained in:
chris 2026-02-28 14:25:50 -05:00
parent a22c752458
commit 19b173a492
8 changed files with 2032 additions and 147 deletions

View File

@ -3010,7 +3010,10 @@ function scaledRowOffsetsFor(pattern, rowCount, patternName, lengthFt) {
const updateFullscreenLabel = () => { const updateFullscreenLabel = () => {
if (!fullscreenBtn) return; if (!fullscreenBtn) return;
const active = !!document.fullscreenElement; const active = !!document.fullscreenElement;
fullscreenBtn.textContent = active ? 'Exit Fullscreen' : 'Fullscreen'; fullscreenBtn.innerHTML = active
? '<i class="fa-solid fa-compress" aria-hidden="true"></i><span class="sr-only">Exit fullscreen</span>'
: '<i class="fa-solid fa-expand" aria-hidden="true"></i><span class="sr-only">Enter fullscreen</span>';
fullscreenBtn.setAttribute('aria-label', active ? 'Exit fullscreen' : 'Enter fullscreen');
}; };
fullscreenBtn?.addEventListener('click', async () => { fullscreenBtn?.addEventListener('click', async () => {
try { try {

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="166.7193"
height="180.81715"
viewBox="0 0 166.7193 180.81716"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="g1"
transform="translate(-44.072952,-33.660285)">
<path
style="fill:#000000;stroke-width:1.16411"
d="m 119.0579,214.47745 c -5.41466,0 -6.17803,-2.31476 -3.2796,-9.94455 l 2.44066,-6.42475 -8.69721,-2.25367 C 77.667488,187.60024 52.246065,158.86694 45.266294,123.22788 40.292594,97.831943 51.10133,70.663512 73.299766,52.764004 121.63388,13.790232 195.14377,37.472698 208.80616,96.419699 c 2.35926,10.179171 2.52903,14.397851 0.97252,24.167171 -5.10541,32.04372 -25.94795,59.64891 -53.9394,71.44079 -5.86202,2.46947 -11.72546,4.49947 -13.02988,4.51111 -5.8898,0.0525 -6.89009,2.0379 -4.13905,8.21513 2.92396,6.56552 1.44634,9.72355 -4.54959,9.72355 z"
id="path1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

18
images/weight-mask.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 41 KiB

BIN
images/weight.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -34,14 +34,18 @@
<div class="text-xs text-indigo-500 font-bold uppercase tracking-wider">Professional Design Tool</div> <div class="text-xs text-indigo-500 font-bold uppercase tracking-wider">Professional Design Tool</div>
</div> </div>
</div> </div>
<div class="flex items-center gap-4"> <div class="flex flex-wrap lg:flex-nowrap items-center gap-2 lg:gap-4 w-full lg:w-auto">
<nav id="mode-tabs" class="flex gap-2"> <nav id="mode-tabs" class="flex flex-wrap items-center gap-2">
<button type="button" class="tab-btn tab-active" data-target="#tab-organic" aria-pressed="true">Organic</button> <button type="button" class="tab-btn tab-active" data-target="#tab-organic" aria-pressed="true">Organic</button>
<button type="button" class="tab-btn tab-idle" data-target="#tab-classic" aria-pressed="false">Classic</button> <button type="button" class="tab-btn tab-idle" data-target="#tab-classic" aria-pressed="false">Classic</button>
<button type="button" class="tab-btn tab-idle" data-target="#tab-wall" aria-pressed="false">Wall</button> <button type="button" class="tab-btn tab-idle" data-target="#tab-wall" aria-pressed="false">Wall</button>
<button type="button" class="tab-btn tab-idle" data-target="#tab-helium" aria-pressed="false">Helium</button>
</nav> </nav>
<div class="flex items-center gap-3"> <div class="flex items-center gap-2 lg:gap-3 ml-auto">
<button id="app-fullscreen-toggle" class="btn-dark text-xs px-3 py-2" aria-label="Toggle fullscreen">Fullscreen</button> <button id="app-fullscreen-toggle" class="btn-dark text-xs px-3 py-2" aria-label="Enter fullscreen">
<i class="fa-solid fa-expand" aria-hidden="true"></i>
<span class="sr-only">Enter fullscreen</span>
</button>
<button id="clear-canvas-btn-top" class="btn-danger text-xs px-3 py-2">Start Fresh</button> <button id="clear-canvas-btn-top" class="btn-danger text-xs px-3 py-2">Start Fresh</button>
</div> </div>
</div> </div>
@ -86,6 +90,11 @@
<svg viewBox="0 0 24 24"><path d="M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.92-1.92 3.12-3.12c.4-.4.4-1.03.01-1.42zM6.92 19L5 17.08l8.06-8.06 1.92 1.92L6.92 19z"/></svg> <svg viewBox="0 0 24 24"><path d="M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.92-1.92 3.12-3.12c.4-.4.4-1.03.01-1.42zM6.92 19L5 17.08l8.06-8.06 1.92 1.92L6.92 19z"/></svg>
<span class="hidden sm:inline">Picker</span> <span class="hidden sm:inline">Picker</span>
</button> </button>
</div>
<div class="mb-3 flex items-center gap-2">
<span class="text-xs font-semibold text-gray-700">Color</span>
<div id="quick-color-chip" class="current-color-chip cursor-pointer !w-8 !h-8 shrink-0" title="Choose active color"></div>
<button type="button" id="quick-color-btn" class="btn-blue text-xs px-3 py-2">Change Color</button>
</div> </div>
<p class="hint mt-1">Use Path to click-drag a line; balloons will be auto-placed along it.</p> <p class="hint mt-1">Use Path to click-drag a line; balloons will be auto-placed along it.</p>
<div id="garland-controls" class="mt-2 flex flex-col gap-3 text-sm text-gray-700"> <div id="garland-controls" class="mt-2 flex flex-col gap-3 text-sm text-gray-700">
@ -126,6 +135,16 @@
<input type="range" id="selected-size" min="5" max="32" step="0.5" value="11" class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" disabled> <input type="range" id="selected-size" min="5" max="32" step="0.5" value="11" class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" disabled>
<span id="selected-size-label" class="text-xs w-12 text-right">0\"</span> <span id="selected-size-label" class="text-xs w-12 text-right">0\"</span>
</div> </div>
<div class="mt-2 grid grid-cols-3 gap-2">
<button type="button" class="btn-dark text-sm py-2" id="rotate-selected-left" disabled>Rotate -15°</button>
<button type="button" class="btn-dark text-sm py-2" id="rotate-selected-reset" disabled>Reset</button>
<button type="button" class="btn-dark text-sm py-2" id="rotate-selected-right" disabled>Rotate +15°</button>
</div>
<div class="mt-2 grid grid-cols-3 gap-2">
<button type="button" class="btn-dark text-sm py-2" id="ribbon-length-down" disabled>Ribbon Shorter</button>
<button type="button" class="btn-dark text-sm py-2" id="ribbon-attach-weight" disabled>Attach to Weight</button>
<button type="button" class="btn-dark text-sm py-2" id="ribbon-length-up" disabled>Ribbon Longer</button>
</div>
<div class="mt-2 grid grid-cols-2 gap-2"> <div class="mt-2 grid grid-cols-2 gap-2">
<button type="button" class="btn-dark text-sm py-2" id="bring-forward" disabled>Bring Forward</button> <button type="button" class="btn-dark text-sm py-2" id="bring-forward" disabled>Bring Forward</button>
<button type="button" class="btn-dark text-sm py-2" id="send-backward" disabled>Send Backward</button> <button type="button" class="btn-dark text-sm py-2" id="send-backward" disabled>Send Backward</button>
@ -141,6 +160,15 @@
<div class="panel-heading mt-4">Size & Shine</div> <div class="panel-heading mt-4">Size & Shine</div>
<div class="panel-card"> <div class="panel-card">
<div id="size-preset-group" class="grid grid-cols-5 gap-2 mb-2"></div> <div id="size-preset-group" class="grid grid-cols-5 gap-2 mb-2"></div>
<div id="helium-placement-row" class="hidden mt-2 mb-2">
<div class="text-xs font-semibold text-gray-700 mb-2">Place</div>
<div class="flex gap-2">
<button type="button" id="helium-place-balloon" class="tab-btn tab-active text-xs px-3 py-2" aria-pressed="true">Balloon</button>
<button type="button" id="helium-place-curl" class="tab-btn tab-idle text-xs px-3 py-2" aria-pressed="false">Curl 260</button>
<button type="button" id="helium-place-ribbon" class="tab-btn tab-idle text-xs px-3 py-2" aria-pressed="false">Ribbon</button>
<button type="button" id="helium-place-weight" class="tab-btn tab-idle text-xs px-3 py-2" aria-pressed="false">Weight</button>
</div>
</div>
<p class="hint mb-3">Size presets adjust the diameter for new balloons.</p> <p class="hint mb-3">Size presets adjust the diameter for new balloons.</p>
<label class="text-sm inline-flex items-center gap-2 font-medium"> <label class="text-sm inline-flex items-center gap-2 font-medium">
<input id="toggle-shine-checkbox" type="checkbox" class="align-middle" checked> <input id="toggle-shine-checkbox" type="checkbox" class="align-middle" checked>
@ -152,7 +180,6 @@
</label> </label>
<button type="button" id="fit-view-btn" class="btn-dark text-sm mt-3 w-full">Fit to Design</button> <button type="button" id="fit-view-btn" class="btn-dark text-sm mt-3 w-full">Fit to Design</button>
</div> </div>
</div>
<div class="control-stack" data-mobile-tab="colors"> <div class="control-stack" data-mobile-tab="colors">
<div class="panel-heading">Organic Colors</div> <div class="panel-heading">Organic Colors</div>
@ -613,6 +640,25 @@
</section> </section>
</section> </section>
<section id="tab-helium" class="hidden flex flex-col lg:flex-row gap-4 lg:h-[calc(100vh-10rem)]">
<aside id="helium-controls-panel" class="control-sheet lg:static lg:w-[360px] lg:max-h-none lg:overflow-y-auto">
<div class="panel-header-row">
<h2 class="panel-title">Helium Controls</h2>
</div>
<div class="control-stack">
<div class="panel-heading">Coming Soon</div>
<div class="panel-card space-y-2">
<p class="text-sm text-gray-700">Helium layouts are in progress.</p>
<p class="hint">We can add bouquet builders, ceiling clusters, and tied columns here.</p>
</div>
</div>
</aside>
<section class="order-1 w-full lg:flex-1 flex items-center justify-center rounded-2xl bg-white/70 ring-1 ring-black/5">
<div class="text-sm text-gray-500">Helium canvas coming soon.</div>
</section>
</section>
</div> </div>
<div id="mobile-tabbar" class="mobile-tabbar"> <div id="mobile-tabbar" class="mobile-tabbar">

1987
organic.js

File diff suppressed because it is too large Load Diff

View File

@ -28,9 +28,11 @@
const orgSheet = document.getElementById('controls-panel'); const orgSheet = document.getElementById('controls-panel');
const claSheet = document.getElementById('classic-controls-panel'); const claSheet = document.getElementById('classic-controls-panel');
const wallSheet = document.getElementById('wall-controls-panel'); const wallSheet = document.getElementById('wall-controls-panel');
const heliumSheet = document.getElementById('helium-controls-panel');
const orgSection = document.getElementById('tab-organic'); const orgSection = document.getElementById('tab-organic');
const claSection = document.getElementById('tab-classic'); const claSection = document.getElementById('tab-classic');
const wallSection = document.getElementById('tab-wall'); const wallSection = document.getElementById('tab-wall');
const heliumSection = document.getElementById('tab-helium');
const tabBtns = Array.from(document.querySelectorAll('#mode-tabs .tab-btn')); const tabBtns = Array.from(document.querySelectorAll('#mode-tabs .tab-btn'));
const mobileActionBar = document.getElementById('mobile-action-bar'); const mobileActionBar = document.getElementById('mobile-action-bar');
@ -383,12 +385,14 @@
const classicVisible = !document.getElementById('tab-classic')?.classList.contains('hidden'); const classicVisible = !document.getElementById('tab-classic')?.classList.contains('hidden');
const organicVisible = !document.getElementById('tab-organic')?.classList.contains('hidden'); const organicVisible = !document.getElementById('tab-organic')?.classList.contains('hidden');
const wallVisible = !document.getElementById('tab-wall')?.classList.contains('hidden'); const wallVisible = !document.getElementById('tab-wall')?.classList.contains('hidden');
const heliumVisible = !document.getElementById('tab-helium')?.classList.contains('hidden');
let id = bodyActive || activeBtn?.dataset?.target; let id = bodyActive || activeBtn?.dataset?.target;
if (!id) { if (!id) {
if (classicVisible && !organicVisible && !wallVisible) id = '#tab-classic'; if (classicVisible && !organicVisible && !wallVisible && !heliumVisible) id = '#tab-classic';
else if (organicVisible && !classicVisible && !wallVisible) id = '#tab-organic'; else if (organicVisible && !classicVisible && !wallVisible && !heliumVisible) id = '#tab-organic';
else if (wallVisible && !classicVisible && !organicVisible) id = '#tab-wall'; else if (wallVisible && !classicVisible && !organicVisible && !heliumVisible) id = '#tab-wall';
else if (heliumVisible && !classicVisible && !organicVisible && !wallVisible) id = '#tab-helium';
} }
if (!id) id = '#tab-organic'; if (!id) id = '#tab-organic';
if (document.body) document.body.dataset.activeTab = id; if (document.body) document.body.dataset.activeTab = id;
@ -399,17 +403,30 @@
function updateSheets() { function updateSheets() {
const tab = detectCurrentTab(); const tab = detectCurrentTab();
const hide = !window.matchMedia('(min-width: 1024px)').matches && document.body?.dataset?.controlsHidden === '1'; const hide = !window.matchMedia('(min-width: 1024px)').matches && document.body?.dataset?.controlsHidden === '1';
if (orgSheet) orgSheet.classList.toggle('hidden', hide || tab !== '#tab-organic'); const usesOrganicWorkspace = tab === '#tab-organic' || tab === '#tab-helium';
if (orgSheet) orgSheet.classList.toggle('hidden', hide || !usesOrganicWorkspace);
if (claSheet) claSheet.classList.toggle('hidden', hide || tab !== '#tab-classic'); if (claSheet) claSheet.classList.toggle('hidden', hide || tab !== '#tab-classic');
if (wallSheet) wallSheet.classList.toggle('hidden', hide || tab !== '#tab-wall'); if (wallSheet) wallSheet.classList.toggle('hidden', hide || tab !== '#tab-wall');
if (heliumSheet) heliumSheet.classList.add('hidden');
} }
function updateMobileStacks(tabName) { function getCurrentMobilePanel(currentTab) {
const orgPanel = document.getElementById('controls-panel'); const orgPanel = document.getElementById('controls-panel');
const claPanel = document.getElementById('classic-controls-panel'); const claPanel = document.getElementById('classic-controls-panel');
const wallPanel = document.getElementById('wall-controls-panel'); const wallPanel = document.getElementById('wall-controls-panel');
const heliumPanel = document.getElementById('helium-controls-panel');
if (currentTab === '#tab-classic') return claPanel;
if (currentTab === '#tab-wall') return wallPanel;
if (currentTab === '#tab-helium') {
const heliumHasStacks = !!heliumPanel?.querySelector?.('.control-stack[data-mobile-tab]');
return heliumHasStacks ? heliumPanel : orgPanel;
}
return orgPanel;
}
function updateMobileStacks(tabName) {
const currentTab = detectCurrentTab(); const currentTab = detectCurrentTab();
const panel = currentTab === '#tab-classic' ? claPanel : (currentTab === '#tab-wall' ? wallPanel : orgPanel); const panel = getCurrentMobilePanel(currentTab);
const target = tabName || document.body?.dataset?.mobileTab || MOBILE_TAB_DEFAULT; const target = tabName || document.body?.dataset?.mobileTab || MOBILE_TAB_DEFAULT;
const isHidden = document.body?.dataset?.controlsHidden === '1'; const isHidden = document.body?.dataset?.controlsHidden === '1';
const isDesktop = window.matchMedia('(min-width: 1024px)').matches; const isDesktop = window.matchMedia('(min-width: 1024px)').matches;
@ -440,11 +457,7 @@
delete document.body.dataset.controlsHidden; delete document.body.dataset.controlsHidden;
} }
// Ensure the current panel is not minimized/hidden when we select a tab. // Ensure the current panel is not minimized/hidden when we select a tab.
const panel = activeMainTab === '#tab-classic' const panel = getCurrentMobilePanel(activeMainTab);
? document.getElementById('classic-controls-panel')
: (activeMainTab === '#tab-wall'
? document.getElementById('wall-controls-panel')
: document.getElementById('controls-panel'));
panel?.classList.remove('minimized'); panel?.classList.remove('minimized');
if (panel) panel.style.display = ''; if (panel) panel.style.display = '';
updateSheets(); updateSheets();
@ -524,15 +537,20 @@
// Tab switching // Tab switching
if (orgSection && claSection && tabBtns.length > 0) { if (orgSection && claSection && tabBtns.length > 0) {
let current = '#tab-organic'; let current = '#tab-organic';
const usesOrganicWorkspace = () => current === '#tab-organic' || current === '#tab-helium';
const syncOrganicWorkspaceLabels = () => {
const title = document.querySelector('#controls-panel .panel-title');
if (title) title.textContent = current === '#tab-helium' ? 'Helium Controls' : 'Organic Controls';
};
const isMobileView = () => window.matchMedia('(max-width: 1023px)').matches; const isMobileView = () => window.matchMedia('(max-width: 1023px)').matches;
const updateMobileActionBarVisibility = () => { const updateMobileActionBarVisibility = () => {
const modalOpen = !!document.querySelector('.color-modal:not(.hidden)'); const modalOpen = !!document.querySelector('.color-modal:not(.hidden)');
const isMobile = isMobileView(); const isMobile = isMobileView();
const showOrganic = isMobile && !modalOpen && current === '#tab-organic'; const showOrganic = isMobile && !modalOpen && usesOrganicWorkspace();
if (mobileActionBar) mobileActionBar.classList.toggle('hidden', !showOrganic); if (mobileActionBar) mobileActionBar.classList.toggle('hidden', !showOrganic);
}; };
const wireMobileActionButtons = () => { const wireMobileActionButtons = () => {
const guardOrganic = () => current === '#tab-organic'; const guardOrganic = () => usesOrganicWorkspace();
const clickBtn = (sel) => { if (!guardOrganic()) return; document.querySelector(sel)?.click(); }; const clickBtn = (sel) => { if (!guardOrganic()) return; document.querySelector(sel)?.click(); };
const on = (id, fn) => document.getElementById(id)?.addEventListener('click', fn); const on = (id, fn) => document.getElementById(id)?.addEventListener('click', fn);
on('mobile-act-undo', () => clickBtn('#tool-undo')); on('mobile-act-undo', () => clickBtn('#tool-undo'));
@ -558,9 +576,11 @@
orgSheet?.classList.remove('minimized'); orgSheet?.classList.remove('minimized');
claSheet?.classList.remove('minimized'); claSheet?.classList.remove('minimized');
wallSheet?.classList.remove('minimized'); wallSheet?.classList.remove('minimized');
orgSection.classList.toggle('hidden', id !== '#tab-organic'); const useOrganicWorkspace = id === '#tab-organic' || id === '#tab-helium';
orgSection.classList.toggle('hidden', !useOrganicWorkspace);
claSection.classList.toggle('hidden', id !== '#tab-classic'); claSection.classList.toggle('hidden', id !== '#tab-classic');
wallSection?.classList.toggle('hidden', id !== '#tab-wall'); wallSection?.classList.toggle('hidden', id !== '#tab-wall');
heliumSection?.classList.add('hidden');
updateSheets(); updateSheets();
updateFloatingNudge(); updateFloatingNudge();
tabBtns.forEach(btn => { tabBtns.forEach(btn => {
@ -573,7 +593,7 @@
try { localStorage.setItem(ACTIVE_TAB_KEY, id); } catch {} try { localStorage.setItem(ACTIVE_TAB_KEY, id); } catch {}
} }
if (document.body) delete document.body.dataset.controlsHidden; if (document.body) delete document.body.dataset.controlsHidden;
const isOrganic = id === '#tab-organic'; const isOrganic = useOrganicWorkspace;
const showHeaderColor = id !== '#tab-classic'; const showHeaderColor = id !== '#tab-classic';
const clearTop = document.getElementById('clear-canvas-btn-top'); const clearTop = document.getElementById('clear-canvas-btn-top');
if (clearTop) { if (clearTop) {
@ -588,9 +608,11 @@
})(); })();
if (document.body) document.body.dataset.mobileTab = savedMobile; if (document.body) document.body.dataset.mobileTab = savedMobile;
setMobileTab(savedMobile, id, true); setMobileTab(savedMobile, id, true);
orgSheet?.classList.toggle('hidden', id !== '#tab-organic'); orgSheet?.classList.toggle('hidden', !useOrganicWorkspace);
claSheet?.classList.toggle('hidden', id !== '#tab-classic'); claSheet?.classList.toggle('hidden', id !== '#tab-classic');
wallSheet?.classList.toggle('hidden', id !== '#tab-wall'); wallSheet?.classList.toggle('hidden', id !== '#tab-wall');
heliumSheet?.classList.add('hidden');
syncOrganicWorkspaceLabels();
window.updateExportButtonVisibility(); window.updateExportButtonVisibility();
updateMobileActionBarVisibility(); updateMobileActionBarVisibility();
} }
@ -625,12 +647,9 @@
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
const tab = btn.dataset.mobileTab || 'controls'; const tab = btn.dataset.mobileTab || 'controls';
const activeTabId = detectCurrentTab(); const activeTabId = detectCurrentTab();
const panel = activeTabId === '#tab-classic' const panel = getCurrentMobilePanel(activeTabId);
? document.getElementById('classic-controls-panel')
: (activeTabId === '#tab-wall'
? document.getElementById('wall-controls-panel')
: document.getElementById('controls-panel'));
const currentTab = document.body.dataset.mobileTab; const currentTab = document.body.dataset.mobileTab;
if (!panel) return;
if (tab === currentTab) { if (tab === currentTab) {
panel.classList.toggle('minimized'); panel.classList.toggle('minimized');
} else { } else {

View File

@ -1,7 +1,8 @@
/* Minimal extras (Tailwind handles most styling) */ /* Minimal extras (Tailwind handles most styling) */
body { color: #1f2937; } body { color: #1f2937; }
body[data-active-tab="#tab-classic"] #clear-canvas-btn-top, body[data-active-tab="#tab-classic"] #clear-canvas-btn-top,
body[data-active-tab="#tab-wall"] #clear-canvas-btn-top { body[data-active-tab="#tab-wall"] #clear-canvas-btn-top,
body[data-active-tab="#tab-helium"] #clear-canvas-btn-top {
display: none !important; display: none !important;
} }
@ -399,7 +400,7 @@ height: 95%}
z-index: 30; z-index: 30;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1); transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
height: 92%; height: auto;
} }
.control-sheet.hidden { display: none; } .control-sheet.hidden { display: none; }
.control-sheet.minimized { transform: translateY(100%); } .control-sheet.minimized { transform: translateY(100%); }
@ -433,7 +434,7 @@ height: 92%;
} }
@media (max-width: 1023px) { @media (max-width: 1023px) {
body { padding-bottom: 0; overflow: auto; } body { padding-bottom: calc(6rem + env(safe-area-inset-bottom, 0px)); overflow: auto; }
html, body { height: auto; overflow: auto; } html, body { height: auto; overflow: auto; }
#current-color-chip-global { display: none; } #current-color-chip-global { display: none; }
#clear-canvas-btn-top { display: none !important; } #clear-canvas-btn-top { display: none !important; }
@ -454,10 +455,16 @@ height: 92%;
body[data-mobile-tab="save"] #classic-controls-panel [data-mobile-tab="save"], body[data-mobile-tab="save"] #classic-controls-panel [data-mobile-tab="save"],
body[data-mobile-tab="controls"] #wall-controls-panel [data-mobile-tab="controls"], body[data-mobile-tab="controls"] #wall-controls-panel [data-mobile-tab="controls"],
body[data-mobile-tab="colors"] #wall-controls-panel [data-mobile-tab="colors"], body[data-mobile-tab="colors"] #wall-controls-panel [data-mobile-tab="colors"],
body[data-mobile-tab="save"] #wall-controls-panel [data-mobile-tab="save"] { body[data-mobile-tab="save"] #wall-controls-panel [data-mobile-tab="save"],
body[data-mobile-tab="controls"] #helium-controls-panel [data-mobile-tab="controls"],
body[data-mobile-tab="colors"] #helium-controls-panel [data-mobile-tab="colors"],
body[data-mobile-tab="save"] #helium-controls-panel [data-mobile-tab="save"] {
display: block; display: block;
} }
.control-sheet { bottom: 4.5rem; max-height: 55vh; } .control-sheet {
bottom: calc(4.5rem + env(safe-area-inset-bottom, 0px));
max-height: min(72vh, calc(100dvh - 8.5rem - env(safe-area-inset-bottom, 0px)));
}
.control-sheet.minimized { transform: translateY(115%); } .control-sheet.minimized { transform: translateY(115%); }
/* Larger tap targets and spacing */ /* Larger tap targets and spacing */
@ -484,8 +491,8 @@ height: 92%;
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
bottom: 4.75rem; bottom: calc(4.75rem + env(safe-area-inset-bottom, 0px));
padding: 0.35rem 0.75rem 0.7rem; padding: 0.35rem 0.75rem calc(0.7rem + env(safe-area-inset-bottom, 0px));
background: linear-gradient(180deg, rgba(255,255,255,0.72) 0%, rgba(255,255,255,0.96) 100%); background: linear-gradient(180deg, rgba(255,255,255,0.72) 0%, rgba(255,255,255,0.96) 100%);
backdrop-filter: blur(18px); backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
@ -679,7 +686,7 @@ height: 92%;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
align-items: center; align-items: center;
padding: .6rem .9rem .9rem; padding: .6rem .9rem calc(.9rem + env(safe-area-inset-bottom, 0px));
background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(224,242,254,0.92)); background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(224,242,254,0.92));
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
@ -700,11 +707,8 @@ height: 92%;
#wall-display, #wall-display,
#balloon-canvas { #balloon-canvas {
margin-bottom: 0; margin-bottom: 0;
height: calc(100vh - 190px) !important; /* tie to viewport minus header/controls */ height: calc(100dvh - 190px) !important; /* tie to viewport minus header/controls */
max-height: calc(100vh - 190px) !important; max-height: calc(100dvh - 190px) !important;
}
#classic-display{
height: 92%;
} }
/* Keep the main canvas panels above the tabbar/action bar */ /* Keep the main canvas panels above the tabbar/action bar */
#canvas-panel, #canvas-panel,