mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Parse tshark GSM field values with int(value, 0) instead of int(value) to auto-detect hex 0x-prefixed output (e.g. 0x039e for TMSI/LAC/CID). Without this, every tshark line with hex values fails to parse, causing 0 devices to be captured during monitoring. Also add API Keys tab to Settings modal for configuring OpenCellID key via the UI (in addition to env var), with status display and usage bar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
463 lines
26 KiB
HTML
463 lines
26 KiB
HTML
<!-- Settings Modal -->
|
|
<div id="settingsModal" class="settings-modal" onclick="if(event.target === this) hideSettings()">
|
|
<div class="settings-content">
|
|
<div class="settings-header">
|
|
<h2>
|
|
<span class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>
|
|
Settings
|
|
</h2>
|
|
<button class="settings-close" onclick="hideSettings()">×</button>
|
|
</div>
|
|
|
|
<div class="settings-tabs">
|
|
<button class="settings-tab active" data-tab="offline" onclick="switchSettingsTab('offline')">Offline</button>
|
|
<button class="settings-tab" data-tab="location" onclick="switchSettingsTab('location')">Location</button>
|
|
<button class="settings-tab" data-tab="display" onclick="switchSettingsTab('display')">Display</button>
|
|
<button class="settings-tab" data-tab="updates" onclick="switchSettingsTab('updates')">Updates</button>
|
|
<button class="settings-tab" data-tab="tools" onclick="switchSettingsTab('tools')">Tools</button>
|
|
<button class="settings-tab" data-tab="alerts" onclick="switchSettingsTab('alerts')">Alerts</button>
|
|
<button class="settings-tab" data-tab="recording" onclick="switchSettingsTab('recording')">Recording</button>
|
|
<button class="settings-tab" data-tab="apikeys" onclick="switchSettingsTab('apikeys')">API Keys</button>
|
|
<button class="settings-tab" data-tab="about" onclick="switchSettingsTab('about')">About</button>
|
|
</div>
|
|
|
|
<!-- Offline Section -->
|
|
<div id="settings-offline" class="settings-section active">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Offline Mode</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Enable Offline Mode</span>
|
|
<span class="settings-label-desc">Use local assets instead of CDN</span>
|
|
</div>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="offlineEnabled" onchange="Settings.toggleOfflineMode(this.checked)">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Asset Sources</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">JavaScript/CSS Libraries</span>
|
|
<span class="settings-label-desc">Leaflet, Chart.js</span>
|
|
</div>
|
|
<select id="assetsSource" class="settings-select" onchange="Settings.setAssetSource(this.value)">
|
|
<option value="cdn">CDN (Online)</option>
|
|
<option value="local">Local</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Web Fonts</span>
|
|
<span class="settings-label-desc">Space Mono</span>
|
|
</div>
|
|
<select id="fontsSource" class="settings-select" onchange="Settings.setFontsSource(this.value)">
|
|
<option value="cdn">Google Fonts (Online)</option>
|
|
<option value="local">Local</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Map Tiles</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Tile Provider</span>
|
|
<span class="settings-label-desc">Map background imagery</span>
|
|
</div>
|
|
<select id="tileProvider" class="settings-select" onchange="Settings.setTileProvider(this.value)">
|
|
<option value="openstreetmap">OpenStreetMap</option>
|
|
<option value="cartodb_dark">CartoDB Dark</option>
|
|
<option value="cartodb_dark_cyan">CartoDB Dark (Cyan Tint)</option>
|
|
<option value="cartodb_light">CartoDB Positron</option>
|
|
<option value="esri_world">ESRI World Imagery</option>
|
|
<option value="custom">Custom URL</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="settings-row custom-url-row" id="customTileUrlRow" style="display: none;">
|
|
<div class="settings-label" style="width: 100%;">
|
|
<span class="settings-label-text">Custom Tile URL</span>
|
|
<span class="settings-label-desc">e.g., http://localhost:8080/{z}/{x}/{y}.png</span>
|
|
<input type="text" id="customTileUrl" class="settings-input"
|
|
placeholder="http://tile-server/{z}/{x}/{y}.png"
|
|
onchange="Settings.setCustomTileUrl(this.value)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Local Asset Status</div>
|
|
<div class="asset-status" id="assetStatus">
|
|
<div class="asset-status-row">
|
|
<span class="asset-name">Leaflet JS/CSS</span>
|
|
<span class="asset-badge checking" id="statusLeaflet">Checking...</span>
|
|
</div>
|
|
<div class="asset-status-row">
|
|
<span class="asset-name">Chart.js</span>
|
|
<span class="asset-badge checking" id="statusChartjs">Checking...</span>
|
|
</div>
|
|
<div class="asset-status-row">
|
|
<span class="asset-name">Inter Font</span>
|
|
<span class="asset-badge checking" id="statusInter">Checking...</span>
|
|
</div>
|
|
<div class="asset-status-row">
|
|
<span class="asset-name">Space Mono</span>
|
|
<span class="asset-badge checking" id="statusJetbrains">Checking...</span>
|
|
</div>
|
|
</div>
|
|
<button class="check-assets-btn" onclick="Settings.checkAssets()">
|
|
Check Assets
|
|
</button>
|
|
</div>
|
|
|
|
<div class="settings-info">
|
|
<strong>Note:</strong> Changes to asset sources require a page reload to take effect.
|
|
Local assets must be available in <code>/static/vendor/</code>.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Location Section -->
|
|
<div id="settings-location" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Observer Location</div>
|
|
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
|
|
Set your geographic coordinates for satellite pass predictions and ISS tracking.
|
|
</p>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Latitude</span>
|
|
<span class="settings-label-desc">Decimal degrees (-90 to 90)</span>
|
|
</div>
|
|
<input type="number" id="observerLatInput" class="settings-input"
|
|
step="0.0001" min="-90" max="90" placeholder="51.5074"
|
|
style="width: 120px; text-align: right;">
|
|
</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Longitude</span>
|
|
<span class="settings-label-desc">Decimal degrees (-180 to 180)</span>
|
|
</div>
|
|
<input type="number" id="observerLonInput" class="settings-input"
|
|
step="0.0001" min="-180" max="180" placeholder="-0.1278"
|
|
style="width: 120px; text-align: right;">
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 10px; margin-top: 15px;">
|
|
<button class="check-assets-btn" onclick="detectLocationGPS(this)" style="flex: 1;">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px; vertical-align: -2px; margin-right: 5px;">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<circle cx="12" cy="12" r="3"/>
|
|
<line x1="12" y1="2" x2="12" y2="6"/>
|
|
<line x1="12" y1="18" x2="12" y2="22"/>
|
|
<line x1="2" y1="12" x2="6" y2="12"/>
|
|
<line x1="18" y1="12" x2="22" y2="12"/>
|
|
</svg>
|
|
Use GPS
|
|
</button>
|
|
<button class="check-assets-btn" onclick="saveObserverLocation()" style="flex: 1; background: var(--accent-cyan); color: #000;">
|
|
Save Location
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Current Location</div>
|
|
<div id="currentLocationDisplay" style="padding: 12px; background: var(--bg-tertiary); border-radius: 6px; font-family: var(--font-mono); font-size: 12px;">
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
|
|
<span style="color: var(--text-dim);">Latitude</span>
|
|
<span id="currentLatDisplay" style="color: var(--accent-cyan);">Not set</span>
|
|
</div>
|
|
<div style="display: flex; justify-content: space-between;">
|
|
<span style="color: var(--text-dim);">Longitude</span>
|
|
<span id="currentLonDisplay" style="color: var(--accent-cyan);">Not set</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-info">
|
|
<strong>Note:</strong> Location is used for ISS pass predictions in SSTV mode and satellite tracking.
|
|
Your location is stored locally and never sent to external servers.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Display Section -->
|
|
<div id="settings-display" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Visual Preferences</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Theme</span>
|
|
<span class="settings-label-desc">Color scheme preference</span>
|
|
</div>
|
|
<select id="themeSelect" class="settings-select" onchange="setThemePreference(this.value)">
|
|
<option value="dark">Dark</option>
|
|
<option value="light">Light</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Animations</span>
|
|
<span class="settings-label-desc">Enable visual effects and animations</span>
|
|
</div>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="animationsEnabled" checked onchange="setAnimationsEnabled(this.checked)">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Updates Section -->
|
|
<div id="settings-updates" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Update Status</div>
|
|
<div id="updateStatusContent" style="padding: 10px 0;">
|
|
<div style="text-align: center; padding: 20px; color: var(--text-dim);">
|
|
Loading update status...
|
|
</div>
|
|
</div>
|
|
<button class="check-assets-btn" onclick="checkForUpdatesManual()" style="margin-top: 10px;">
|
|
Check Now
|
|
</button>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Update Settings</div>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Auto-Check for Updates</span>
|
|
<span class="settings-label-desc">Periodically check GitHub for new releases</span>
|
|
</div>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="updateCheckEnabled" checked onchange="toggleUpdateCheck(this.checked)">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-info">
|
|
<strong>Note:</strong> Updates are fetched from GitHub and applied via git pull.
|
|
Make sure you have git installed and the application is in a git repository.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tools Section -->
|
|
<div id="settings-tools" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Tool Dependencies</div>
|
|
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
|
|
Check which external tools are installed for each mode.
|
|
<span style="color: var(--accent-green);">●</span> = Installed,
|
|
<span style="color: var(--accent-red);">●</span> = Missing
|
|
</p>
|
|
<div id="settingsToolsContent" style="max-height: 45vh; overflow-y: auto;">
|
|
<div style="text-align: center; padding: 30px; color: var(--text-dim);">
|
|
Loading dependencies...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group" style="margin-top: 15px;">
|
|
<div class="settings-group-title">Quick Install (Debian/Ubuntu)</div>
|
|
<div style="background: var(--bg-tertiary); padding: 10px; border-radius: 4px; font-family: var(--font-mono); font-size: 10px; overflow-x: auto;">
|
|
<div>sudo apt install rtl-sdr multimon-ng rtl-433 aircrack-ng bluez dump1090-mutability hcxdumptool hcxtools</div>
|
|
<div style="margin-top: 5px;">pip install skyfield flask</div>
|
|
</div>
|
|
<div style="margin-top: 10px; font-size: 11px; color: var(--text-dim);">
|
|
<strong>Note:</strong> ACARS decoding requires <code>acarsdec</code> which must be built from source.
|
|
See <a href="https://github.com/TLeconte/acarsdec" target="_blank" style="color: var(--accent-cyan);">github.com/TLeconte/acarsdec</a> or run <code>./setup.sh</code> for automated installation.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alerts Section -->
|
|
<div id="settings-alerts" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Alert Feed <span id="alertsFeedCount" style="color: var(--text-dim); font-weight: 500;"></span></div>
|
|
<div id="alertsFeedList" class="settings-feed">
|
|
<div class="settings-feed-empty">No alerts yet</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Quick Rules</div>
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
|
<button class="check-assets-btn" onclick="AlertCenter.enableTrackerAlerts()">Enable Tracker Alerts</button>
|
|
<button class="check-assets-btn" onclick="AlertCenter.disableTrackerAlerts()">Disable Tracker Alerts</button>
|
|
</div>
|
|
<div class="settings-info" style="margin-top: 10px;">
|
|
Use Bluetooth device details to add specific device watchlist alerts.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recording Section -->
|
|
<div id="settings-recording" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Start Recording</div>
|
|
<div class="settings-row" style="border-bottom: none; padding-top: 0;">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Mode</span>
|
|
<span class="settings-label-desc">Record live events for a mode</span>
|
|
</div>
|
|
<select id="recordingModeSelect" class="settings-select" style="width: 200px;">
|
|
<option value="pager">Pager</option>
|
|
<option value="sensor">433 Sensors</option>
|
|
<option value="wifi">WiFi</option>
|
|
<option value="bluetooth">Bluetooth</option>
|
|
<option value="adsb">ADS-B</option>
|
|
<option value="ais">AIS</option>
|
|
<option value="dsc">DSC</option>
|
|
<option value="acars">ACARS</option>
|
|
<option value="aprs">APRS</option>
|
|
<option value="rtlamr">RTLAMR</option>
|
|
<option value="dmr">DMR</option>
|
|
<option value="tscm">TSCM</option>
|
|
<option value="sstv">SSTV</option>
|
|
<option value="sstv_general">SSTV General</option>
|
|
<option value="listening_scanner">Listening Post</option>
|
|
<option value="waterfall">Waterfall</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-row" style="border-bottom: none;">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Label</span>
|
|
<span class="settings-label-desc">Optional note for the session</span>
|
|
</div>
|
|
<input type="text" id="recordingLabelInput" class="settings-input" placeholder="Morning sweep" style="width: 200px;">
|
|
</div>
|
|
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
|
<button class="check-assets-btn" onclick="RecordingUI.start()">Start</button>
|
|
<button class="check-assets-btn" onclick="RecordingUI.stop()">Stop</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Active Sessions</div>
|
|
<div id="recordingActiveList" class="settings-feed">
|
|
<div class="settings-feed-empty">No active recordings</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Recent Recordings</div>
|
|
<div id="recordingList" class="settings-feed">
|
|
<div class="settings-feed-empty">No recordings yet</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- API Keys Section -->
|
|
<div id="settings-apikeys" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">OpenCellID API Key</div>
|
|
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
|
|
Required for GSM cell tower geolocation. Get a free key at
|
|
<a href="https://opencellid.org/register" target="_blank" style="color: var(--accent-cyan);">opencellid.org/register</a>
|
|
(1,000 lookups/day).
|
|
</p>
|
|
|
|
<div class="settings-row">
|
|
<div class="settings-label">
|
|
<span class="settings-label-text">Status</span>
|
|
<span class="settings-label-desc" id="apiKeyStatusDesc">Checking...</span>
|
|
</div>
|
|
<span id="apiKeyStatusBadge" class="asset-badge checking">Checking...</span>
|
|
</div>
|
|
|
|
<div class="settings-row" style="flex-wrap: wrap; gap: 8px;">
|
|
<div class="settings-label" style="width: 100%;">
|
|
<span class="settings-label-text">API Key</span>
|
|
<span class="settings-label-desc">Paste your OpenCellID API token</span>
|
|
</div>
|
|
<div style="display: flex; gap: 8px; width: 100%;">
|
|
<input type="password" id="apiKeyInput" class="settings-input"
|
|
placeholder="Enter your OpenCellID API key"
|
|
style="flex: 1; font-family: var(--font-mono); font-size: 11px;">
|
|
<button class="check-assets-btn" onclick="toggleApiKeyVisibility()" style="padding: 6px 10px; font-size: 11px;" title="Show/Hide">
|
|
<svg id="apiKeyEyeIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px;">
|
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
|
<circle cx="12" cy="12" r="3"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 10px; margin-top: 12px;">
|
|
<button class="check-assets-btn" onclick="saveApiKey()" style="flex: 1; background: var(--accent-cyan); color: #000;">
|
|
Save Key
|
|
</button>
|
|
</div>
|
|
|
|
<div id="apiKeySaveResult" style="margin-top: 10px; font-size: 11px; display: none;"></div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Usage Today</div>
|
|
<div style="padding: 12px; background: var(--bg-tertiary); border-radius: 6px; font-family: var(--font-mono); font-size: 12px;">
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
|
|
<span style="color: var(--text-dim);">API Calls</span>
|
|
<span id="apiKeyUsageCount" style="color: var(--accent-cyan);">-- / --</span>
|
|
</div>
|
|
<div style="background: var(--bg-dark); border-radius: 3px; height: 6px; overflow: hidden; margin-top: 4px;">
|
|
<div id="apiKeyUsageBar" style="height: 100%; background: var(--accent-cyan); width: 0%; transition: width 0.3s;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-info">
|
|
<strong>Note:</strong> The environment variable <code>INTERCEPT_GSM_OPENCELLID_API_KEY</code> takes priority over the saved key.
|
|
Keys saved here persist across restarts.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- About Section -->
|
|
<div id="settings-about" class="settings-section">
|
|
<div class="settings-group">
|
|
<div class="about-info">
|
|
<p><strong>iNTERCEPT</strong> - Signal Intelligence Platform</p>
|
|
<p>Version: <span class="about-version">{{ version }}</span></p>
|
|
<p>
|
|
A unified web interface for software-defined radio (SDR) tools,
|
|
supporting pager decoding, sensor monitoring, aircraft tracking,
|
|
WiFi/Bluetooth scanning, and more.
|
|
</p>
|
|
<p>
|
|
<a href="https://github.com/smittix/intercept" target="_blank">GitHub Repository</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-group">
|
|
<div class="settings-group-title">Support the Project</div>
|
|
<p style="color: var(--text-dim); margin-bottom: 15px; font-size: 12px;">
|
|
If you find iNTERCEPT useful, consider supporting its development.
|
|
</p>
|
|
<a href="https://buymeacoffee.com/smittix" target="_blank" rel="noopener noreferrer" class="donate-btn">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 18px; height: 18px; vertical-align: -3px; margin-right: 8px;">
|
|
<path d="M17 8h1a4 4 0 1 1 0 8h-1"/>
|
|
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/>
|
|
<line x1="6" y1="2" x2="6" y2="4"/>
|
|
<line x1="10" y1="2" x2="10" y2="4"/>
|
|
<line x1="14" y1="2" x2="14" y2="4"/>
|
|
</svg>
|
|
Buy Me a Coffee
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|