diff --git a/docs/N1MM-SETUP.md b/docs/N1MM-SETUP.md index d7e12a67..8dafd14b 100644 --- a/docs/N1MM-SETUP.md +++ b/docs/N1MM-SETUP.md @@ -53,12 +53,12 @@ If you're running directly with Node, restart the server after changing your `.e All settings go in your `.env` or `.env.local` file: -| Variable | Default | Description | -|----------|---------|-------------| -| `N1MM_UDP_ENABLED` | `false` | Set to `true` to enable the UDP listener | -| `N1MM_UDP_PORT` | `12060` | UDP port to listen on (must match N1MM+ config) | -| `N1MM_MAX_QSOS` | `200` | Maximum QSOs to keep in memory | -| `N1MM_QSO_MAX_AGE_MINUTES` | `360` | QSOs older than this (6 hours) are pruned automatically | +| Variable | Default | Description | +| -------------------------- | ------- | ------------------------------------------------------- | +| `N1MM_UDP_ENABLED` | `false` | Set to `true` to enable the UDP listener | +| `N1MM_UDP_PORT` | `12060` | UDP port to listen on (must match N1MM+ config) | +| `N1MM_MAX_QSOS` | `200` | Maximum QSOs to keep in memory | +| `N1MM_QSO_MAX_AGE_MINUTES` | `360` | QSOs older than this (6 hours) are pruned automatically | ## Docker Users diff --git a/public/img/ohc-logo-254x114.png b/public/img/ohc-logo-254x114.png new file mode 100644 index 00000000..d9c20b00 Binary files /dev/null and b/public/img/ohc-logo-254x114.png differ diff --git a/src/App.jsx b/src/App.jsx index e44b7ec7..e8622075 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -13,6 +13,7 @@ import ModernLayout from './layouts/ModernLayout.jsx'; import { resetLayout } from './store/layoutStore.js'; import { RigProvider } from './contexts/RigContext.jsx'; +import { AppMenuProvider } from './contexts/AppMenuContext'; import { useSpaceWeather, @@ -49,6 +50,7 @@ import WhatsNew from './components/WhatsNew.jsx'; import { initCtyLookup } from './utils/ctyLookup.js'; import { getAllLayers } from './plugins/layerRegistry.js'; import ActivateFilterManager from './components/ActivateFilterManager.jsx'; +import AppMenu from './components/menus/AppMenu'; // Load DXCC entity database on app startup (non-blocking) initCtyLookup(); @@ -521,91 +523,104 @@ const App = () => { }; return ( -
- - {config.layout === 'dockable' ? ( - - ) : config.layout === 'classic' || config.layout === 'tablet' || config.layout === 'compact' ? ( - - ) : ( - - )} - - - {/* Modals */} - setShowSettings(false)} - config={config} - onSave={handleSaveConfig} - onResetLayout={handleResetLayout} - satellites={satellites.data} - satelliteFilters={satelliteFilters} - onSatelliteFiltersChange={setSatelliteFilters} - mapLayers={mapLayers} - onToggleDeDxMarkers={toggleDeDxMarkers} - onToggleDXNews={toggleDXNews} - wakeLockStatus={wakeLockStatus} - /> - setShowDXFilters(false)} - /> - setShowPSKFilters(false)} - callsign={config.callsign} - locator={config.locator} - /> - setShowKeybindings(false)} - keybindings={keybindingsList} - /> - setShowPotaFilters(false)} - /> - setShowSotaFilters(false)} - /> - setShowWwffFilters(false)} - /> - setShowWwbotaFilters(false)} - /> - -
+ +
+ + {config.layout === 'dockable' ? ( + + ) : config.layout === 'classic' || config.layout === 'tablet' || config.layout === 'compact' ? ( + + ) : ( + + )} + + + {/* Modals */} + setShowSettings(false)} + config={config} + onSave={handleSaveConfig} + onResetLayout={handleResetLayout} + satellites={satellites.data} + satelliteFilters={satelliteFilters} + onSatelliteFiltersChange={setSatelliteFilters} + mapLayers={mapLayers} + onToggleDeDxMarkers={toggleDeDxMarkers} + onToggleDXNews={toggleDXNews} + wakeLockStatus={wakeLockStatus} + /> + setShowDXFilters(false)} + /> + setShowPSKFilters(false)} + callsign={config.callsign} + locator={config.locator} + /> + setShowKeybindings(false)} + keybindings={keybindingsList} + /> + setShowPotaFilters(false)} + /> + setShowSotaFilters(false)} + /> + setShowWwffFilters(false)} + /> + setShowWwbotaFilters(false)} + /> + + setShowSettings(true)} + onFullscreenToggle={handleFullscreenToggle} + isFullscreen={isFullscreen} + /> +
+
); }; diff --git a/src/components/DonateButton.jsx b/src/components/DonateButton.jsx index f37c79fd..36094637 100644 --- a/src/components/DonateButton.jsx +++ b/src/components/DonateButton.jsx @@ -8,7 +8,7 @@ const PAYPAL_URL = 'https://www.paypal.com/donate/?hosted_button_id=MMYPQBLA6SW6 const COFFEE_URL = 'https://buymeacoffee.com/k0cjh'; const MERCH_URL = 'https://openhamclock.printify.me'; -export default function DonateButton({ compact = false, fontSize = '12px', padding = '6px 10px' }) { +export default function DonateButton({ compact = false, className = '' }) { const [open, setOpen] = useState(false); const close = useCallback(() => setOpen(false), []); @@ -25,24 +25,7 @@ export default function DonateButton({ compact = false, fontSize = '12px', paddi return ( <> - @@ -56,7 +39,7 @@ export default function DonateButton({ compact = false, fontSize = '12px', paddi display: 'flex', alignItems: 'center', justifyContent: 'center', - zIndex: 100000, + zIndex: 10001, backdropFilter: 'blur(3px)', }} > diff --git a/src/components/Header.jsx b/src/components/Header.jsx index e6ad3f9e..8db8b67e 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -3,11 +3,12 @@ * Top bar with callsign, clocks, weather, and controls. * Responsive: wraps gracefully on tablet, collapses to essentials on mobile. */ -import { IconGear, IconExpand, IconShrink } from './Icons.jsx'; -import DonateButton from './DonateButton.jsx'; +import { useState, useEffect, useCallback } from 'react'; import { QRZToggle } from './CallsignLink.jsx'; import { ctyLookup, isCtyLoaded } from '../utils/ctyLookup'; import { getFlagUrl } from '../utils/countryFlags'; +import MenuToggleButton from './menus/MenuToggleButton'; +import AppMenuButtons from './menus/AppMenuButtons'; export const Header = ({ config, @@ -31,6 +32,7 @@ export const Header = ({ }) => { const isMobile = breakpoint === 'mobile'; const isTablet = breakpoint === 'tablet'; + const isDesktop = breakpoint === 'desktop'; const callsignSize = config.headerSize > 0.1 && config.headerSize <= 2 @@ -47,270 +49,238 @@ export const Header = ({ return (
- {/* Callsign */} -
- - {config.callsign} - - {(() => { - const info = isCtyLoaded() ? ctyLookup(config.callsign) : null; - const flagUrl = info ? getFlagUrl(info.entity) : null; - return flagUrl ? ( - {info.entity} - ) : null; - })()} - {config.version && !isMobile && ( +
+ {/* Callsign */} +
window.dispatchEvent(new Event('openhamclock-show-whatsnew'))} - style={{ fontSize: '11px', color: 'var(--text-muted)', cursor: 'pointer' }} - title="What's new in this version" + style={{ + fontSize: callsignSize, + fontWeight: '900', + color: 'var(--accent-amber)', + cursor: 'pointer', + fontFamily: 'Orbitron, monospace', + whiteSpace: 'nowrap', + lineHeight: 1, + }} + onClick={onSettingsClick} + title="Click for settings" > - v{config.version} + {config.callsign} - )} - {!isMobile && } -
- - {/* UTC Clock */} -
- - UTC - - - {utcTime} - - {!isMobile && ( - {utcDate} - )} -
+ {(() => { + const info = isCtyLoaded() ? ctyLookup(config.callsign) : null; + const flagUrl = info ? getFlagUrl(info.entity) : null; + return flagUrl ? ( + {info.entity} + ) : null; + })()} + {config.version && !isMobile && ( + window.dispatchEvent(new Event('openhamclock-show-whatsnew'))} + style={{ fontSize: '11px', color: 'var(--text-muted)', cursor: 'pointer' }} + title="What's new in this version" + > + v{config.version} + + )} + {!isMobile && } +
- {/* Local Clock */} -
- - LOCAL - - - {localTime} - - {!isMobile && ( - {localDate} - )} -
+ {/* UTC Clock */} +
+ + UTC + + + {utcTime} + + {!isMobile && ( + {utcDate} + )} +
- {/* Weather & Solar Stats — hidden on mobile */} - {!isMobile && ( + {/* Local Clock */}
- {localWeather?.data && - (() => { - const rawC = localWeather.data.rawTempC; - return ( -
- {localWeather.data.icon} - - {Math.round((rawC * 9) / 5 + 32)}°F/{Math.round(rawC)}°C - -
- ); - })()} -
- SFI - - {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} - -
-
- K - = 4 - ? 'var(--accent-red)' - : 'var(--accent-green)', - fontWeight: '700', - }} - > - {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} - -
-
- SSN - - {solarIndices?.data?.ssn?.current ?? spaceWeather?.data?.sunspotNumber ?? '--'} - -
- {!isTablet && bandConditions?.extras?.aIndex && ( + + LOCAL + + + {localTime} + + {!isMobile && ( + {localDate} + )} +
+ + {/* Weather & Solar Stats — hidden on mobile */} + {!isMobile && ( +
+ {localWeather?.data && + (() => { + const rawC = localWeather.data.rawTempC; + return ( +
+ {localWeather.data.icon} + + {Math.round((rawC * 9) / 5 + 32)}°F/{Math.round(rawC)}°C + +
+ ); + })()} +
+ SFI + + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + +
- A + K = 20 + parseInt(solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex) >= 4 ? 'var(--accent-red)' - : parseInt(bandConditions.extras.aIndex) >= 10 - ? 'var(--accent-amber)' - : 'var(--accent-green)', + : 'var(--accent-green)', fontWeight: '700', }} > - {bandConditions.extras.aIndex} + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}
- )} - {!isTablet && bandConditions?.extras?.geomagField && (
- - {bandConditions.extras.geomagField} + SSN + + {solarIndices?.data?.ssn?.current ?? spaceWeather?.data?.sunspotNumber ?? '--'}
- )} -
- )} - - {/* Buttons */} -
- {!isFullscreen && !isMobile && ( - + {!isTablet && bandConditions?.extras?.aIndex && ( +
+ A + = 20 + ? 'var(--accent-red)' + : parseInt(bandConditions.extras.aIndex) >= 10 + ? 'var(--accent-amber)' + : 'var(--accent-green)', + fontWeight: '700', + }} + > + {bandConditions.extras.aIndex} + +
+ )} + {!isTablet && bandConditions?.extras?.geomagField && ( +
+ + {bandConditions.extras.geomagField} + +
+ )} +
)} - {showUpdateButton && !isMobile && ( - +
+
+ {isDesktop ? ( + + ) : ( + )} - -
); diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index c2c15725..5dc1f300 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -427,7 +427,7 @@ export const SettingsPanel = ({ display: 'flex', justifyContent: 'center', alignItems: 'center', - zIndex: 10000, + zIndex: 10001, }} >
{ + if (!menuOpen) return; + + const handleKeyDown = (event) => { + if (event.key === 'Escape') { + closeMenu(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [menuOpen, closeMenu]); + + if (!menuOpen) return null; + + return ( +
+
e.stopPropagation()} + style={{ + position: 'absolute', + right: '0', + top: '0', + margin: '3em', + padding: '1em', + }} + > + + + Open Ham Clock logo +

+ window.dispatchEvent(new Event('openhamclock-show-whatsnew'))}> + v{config.version} ℹ️ + +

+ + +
+
+ ); +} diff --git a/src/components/menus/AppMenuButtons.jsx b/src/components/menus/AppMenuButtons.jsx new file mode 100644 index 00000000..23cb0ef4 --- /dev/null +++ b/src/components/menus/AppMenuButtons.jsx @@ -0,0 +1,35 @@ +import DonateButton from '../DonateButton'; +import { IconGear, IconExpand, IconShrink } from '../Icons'; + +export default function AppMenuButtons({ + config, + showUpdateButton, + updateInProgress, + onUpdateClick, + onSettingsClick, + onFullscreenToggle, + isFullscreen, + className = 'menu-button-container', +}) { + return ( +
+ + + {showUpdateButton && ( + + )} + + + + +
+ ); +} diff --git a/src/components/menus/MenuToggleButton.jsx b/src/components/menus/MenuToggleButton.jsx new file mode 100644 index 00000000..e2b74a93 --- /dev/null +++ b/src/components/menus/MenuToggleButton.jsx @@ -0,0 +1,11 @@ +import { useAppMenu } from '../../contexts/AppMenuContext'; + +export default function MenuToggleButton() { + const { openMenu } = useAppMenu(); + + return ( + + ); +} diff --git a/src/contexts/AppMenuContext.jsx b/src/contexts/AppMenuContext.jsx new file mode 100644 index 00000000..b1e35fd4 --- /dev/null +++ b/src/contexts/AppMenuContext.jsx @@ -0,0 +1,14 @@ +import { createContext, useContext, useState, useCallback } from 'react'; + +const AppMenuContext = createContext(); + +export const useAppMenu = () => useContext(AppMenuContext); + +export const AppMenuProvider = ({ children }) => { + const [menuOpen, setMenuOpen] = useState(false); + + const openMenu = useCallback(() => setMenuOpen(true), []); + const closeMenu = useCallback(() => setMenuOpen(false), []); + + return {children}; +}; diff --git a/src/layouts/ClassicLayout.jsx b/src/layouts/ClassicLayout.jsx index 0f266045..079f1978 100644 --- a/src/layouts/ClassicLayout.jsx +++ b/src/layouts/ClassicLayout.jsx @@ -1,12 +1,16 @@ /** * Classic HamClock-style layout */ +import { useState, useEffect, useCallback } from 'react'; import { DXNewsTicker, WorldMap } from '../components'; import { DXGridInput } from '../components/DXGridInput.jsx'; import { getBandColor, getBandColorForBand } from '../utils'; import CallsignLink from '../components/CallsignLink.jsx'; import DonateButton from '../components/DonateButton.jsx'; import { useRig } from '../contexts/RigContext.jsx'; +import useLocalInstall from '../hooks/app/useLocalInstall.js'; +import { IconGear, IconExpand, IconShrink } from '../components/Icons.jsx'; +import MenuToggleButton from '../components/menus/MenuToggleButton'; export default function ClassicLayout(props) { const { @@ -22,6 +26,8 @@ export default function ClassicLayout(props) { handleFullscreenToggle, isFullscreen, setShowSettings, + handleUpdateClick, + updateInProgress, dxClusterData, hoveredSpot, setHoveredSpot, @@ -57,6 +63,8 @@ export default function ClassicLayout(props) { toggleSatellites, } = props; + const showUpdateButton = useLocalInstall(); + const liveSpotBands = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '8m', '6m', '4m']; const mapLegendBands = ['160', '80', '40', '30', '20', '17', '15', '12', '10', '8', '6', '4']; @@ -85,7 +93,6 @@ export default function ClassicLayout(props) { style={{ display: 'grid', gridTemplateColumns: '280px 1fr 300px', - height: '130px', borderBottom: '2px solid #333', background: '#000', }} @@ -545,193 +552,179 @@ export default function ClassicLayout(props) { background: 'var(--bg-panel)', borderBottom: '1px solid var(--border-color)', padding: '6px 12px', - height: '52px', flexShrink: 0, gap: '10px', + marginBottom: '6px', }} > - {/* Callsign */} - setShowSettings(true)} - title={t('app.settings.title')} - > - {config.callsign} - - - {/* UTC */}
+ {/* Callsign */} - {t('app.time.utc')} - - setShowSettings(true)} + title={t('app.settings.title')} > - {utcTime} + {config.callsign} -
- {/* Local */} -
- - {t('app.time.locShort')} - - + {t('app.time.utc')} + + + {utcTime} + +
+ + {/* Local */} +
- {localTime} - -
- - {/* Solar Quick Stats */} -
- - {t('app.solar.sfiShort')} - - {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + + {t('app.time.locShort')} - - - {t('app.solar.kpShort')} = 4 - ? 'var(--accent-red)' - : 'var(--accent-green)', + fontSize: '24px', fontWeight: '700', + color: 'var(--accent-amber)', }} > - {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} + {localTime} - - - {t('app.solar.ssnShort')} - - {solarIndices?.data?.ssn?.current ?? '--'} +
+ + {/* Solar Quick Stats */} +
+ + {t('app.solar.sfiShort')} + + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + - - {bandConditions?.extras?.aIndex && ( - A + {t('app.solar.kpShort')} = 20 + parseInt(solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex) >= 4 ? 'var(--accent-red)' - : parseInt(bandConditions.extras.aIndex) >= 10 - ? 'var(--accent-amber)' - : 'var(--accent-green)', + : 'var(--accent-green)', fontWeight: '700', }} > - {bandConditions.extras.aIndex} + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} - )} - {bandConditions?.extras?.geomagField && ( - - {bandConditions.extras.geomagField} + + {t('app.solar.ssnShort')} + + {solarIndices?.data?.ssn?.current ?? '--'} + - )} + {bandConditions?.extras?.aIndex && ( + + A + = 20 + ? 'var(--accent-red)' + : parseInt(bandConditions.extras.aIndex) >= 10 + ? 'var(--accent-amber)' + : 'var(--accent-green)', + fontWeight: '700', + }} + > + {bandConditions.extras.aIndex} + + + )} + {bandConditions?.extras?.geomagField && ( + + {bandConditions.extras.geomagField} + + )} +
- - {/* Controls */} -
- {!isFullscreen && } - - +
+
@@ -1077,216 +1070,206 @@ export default function ClassicLayout(props) { > {/* TOP: Callsign + Times + Solar */}
- {/* Row 1: Callsign + Times */}
- setShowSettings(true)} - title={t('app.settings.title')} > - {config.callsign} - -
-
-
- {t('app.time.utc')} -
-
- {utcTime} -
-
{utcDate}
-
-
setShowSettings(true)} + title={t('app.settings.title')} > -
- {t('app.time.local')} + {config.callsign} + +
+
+
+ {t('app.time.utc')} +
+
+ {utcTime} +
+
{utcDate}
- {localTime} +
+ {t('app.time.local')} +
+
+ {localTime} +
+
{localDate}
-
{localDate}
-
- {!isFullscreen && } - - -
-
- {/* Row 2: Solar indices inline */} -
- - {t('app.solar.sfiShort')} - - {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} - - - - {t('app.solar.kpShort')} - = 4 - ? 'var(--accent-red)' - : 'var(--accent-green)', - fontWeight: '700', - }} - > - {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} - - - - {t('app.solar.ssnShort')} - - {solarIndices?.data?.ssn?.current ?? '--'} + {/* Row 2: Solar indices inline */} +
+ + {t('app.solar.sfiShort')} + + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + - - {bandConditions?.extras?.aIndex && ( - A + {t('app.solar.kpShort')} = 20 + parseInt(solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex) >= 4 ? 'var(--accent-red)' - : parseInt(bandConditions.extras.aIndex) >= 10 - ? 'var(--accent-amber)' - : 'var(--accent-green)', + : 'var(--accent-green)', fontWeight: '700', }} > - {bandConditions.extras.aIndex} + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} - )} - {bandConditions?.extras?.geomagField && ( - - {bandConditions.extras.geomagField} + + {t('app.solar.ssnShort')} + + {solarIndices?.data?.ssn?.current ?? '--'} + - )} - {propagation.data && ( - <> + {bandConditions?.extras?.aIndex && ( - {t('app.propagation.muf')} - - {propagation.data.muf || '?'} {t('app.units.mhz')} + A + = 20 + ? 'var(--accent-red)' + : parseInt(bandConditions.extras.aIndex) >= 10 + ? 'var(--accent-amber)' + : 'var(--accent-green)', + fontWeight: '700', + }} + > + {bandConditions.extras.aIndex} + )} + {bandConditions?.extras?.geomagField && ( + + {bandConditions.extras.geomagField} + + )} + {propagation.data && ( + <> + + {t('app.propagation.muf')} + + {propagation.data.muf || '?'} {t('app.units.mhz')} + + + + {t('app.propagation.luf')} + + {propagation.data.luf || '?'} {t('app.units.mhz')} + + + + )} + {localWeather?.data && ( - {t('app.propagation.luf')} - - {propagation.data.luf || '?'} {t('app.units.mhz')} + {localWeather.data.icon} + + {localWeather.data.temp}°{localWeather.data.tempUnit || tempUnit} - - )} - {localWeather?.data && ( - - {localWeather.data.icon} - - {localWeather.data.temp}°{localWeather.data.tempUnit || tempUnit} - - - )} + )} +
+
+
+
diff --git a/src/styles/main.css b/src/styles/main.css index eb012b85..a355f8ea 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -306,6 +306,132 @@ body::before { border-top-color: #808080; } +/* ============================================ + HEADER + ============================================ */ +.header-menu { + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 10px; +} + +.header-menu-toggle { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + padding: 0; + border-radius: 4px; + color: var(--text-secondary); + cursor: pointer; + white-space: nowrap; + height: 100%; + text-align: center; + line-height: 40px; + width: 40px; + font-size: 20px; +} + +#app-container.compact .header-menu-toggle, +#app-container.tablet .header-menu-toggle { + height: 40px; +} + +.header-menu .close-x { + font-weight: 400; + position: absolute; + right: 0.5em; + top: 0.5em; + background: none; + border: none; + color: var(--text-muted); + font-size: 20px; + cursor: pointer; + padding: 2px 6px; + line-height: 1; +} + +.header-menu .close-x:hover, +.header-menu .close-x:active { + font-weight: 700; +} + +.header-menu .header-menu-logo { + margin: 1em auto 0 auto; + display: block; +} + +.header-menu .version-information { + text-align: center; + margin-top: 0; + padding: 0; + color: var(--text-muted); +} + +.header-menu .version-information span { + cursor: pointer; +} + +.header-menu-button-container { + margin-top: 0.5em; +} + +.header-menu-button-container .menu-button { + margin-bottom: 0.5em; + width: 100%; + padding: 6px 10px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 12px; + cursor: pointer; + font-weight: 400; + font-family: 'JetBrains Mono', monospace; + line-height: 1.2; + text-align: center; + white-space: nowrap; +} + +.desktop-header-button-container { +} + +.desktop-header-button-container .menu-button { + margin-bottom: 0.5em; + margin-right: 5px; + padding: 6px 10px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--text-secondary); + font-size: 12px; + cursor: pointer; + font-weight: 400; + font-family: 'JetBrains Mono', monospace; + line-height: 1.2; + text-align: center; + white-space: nowrap; +} + +.desktop-header-button-container .menu-button:last-child { + margin-right: 0; +} + +.header-menu-button-container .support-us-button, +.desktop-header-button-container .support-us-button { + background: linear-gradient(135deg, #ff813f 0%, #ffdd00 100%); + border: none; + border-radius: 4px; + color: #000; + cursor: pointer; + font-weight: 600; + display: flex; + justify-content: center; + align-items: center; + gap: 4px; + white-space: nowrap; + width: 100%; +} + /* ============================================ LEAFLET MAP CUSTOMIZATIONS ============================================ */