diff --git a/scripts/migrate_fa_to_lucide.py b/scripts/migrate_fa_to_lucide.py
new file mode 100644
index 00000000..9515adc8
--- /dev/null
+++ b/scripts/migrate_fa_to_lucide.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+"""
+migrate_fa_to_lucide.py — Replace FontAwesome with Lucide components.
+
+Usage:
+ python3 scripts/migrate_fa_to_lucide.py src/routes/events/[event_id]/\(pres_mgmt\)/
+
+Skips content inside HTML comments. Adds/merges lucide-svelte imports.
+"""
+import re
+import sys
+import os
+from pathlib import Path
+
+# ── FA icon → Lucide component name ─────────────────────────────────────────
+FA_TO_LUCIDE = {
+ 'fa-spinner': 'LoaderCircle',
+ 'fa-cog': 'LoaderCircle', # only when fa-spin
+ 'fa-sync-alt': 'RefreshCw',
+ 'fa-times': 'X',
+ 'fa-exclamation-triangle': 'TriangleAlert',
+ 'fa-check': 'Check',
+ 'fa-check-circle': 'CheckCircle',
+ 'fa-plus': 'Plus',
+ 'fa-minus': 'Minus',
+ 'fa-save': 'Save',
+ 'fa-edit': 'Pencil',
+ 'fa-eye': 'Eye',
+ 'fa-eye-slash': 'EyeOff',
+ 'fa-toggle-on': 'ToggleRight',
+ 'fa-toggle-off': 'ToggleLeft',
+ 'fa-star-of-life': 'Asterisk',
+ 'fa-id-card': 'IdCard',
+ 'fa-paper-plane': 'Send',
+ 'fa-map-marker-alt': 'MapPin',
+ 'fa-file-alt': 'FileText',
+ 'fa-envelope': 'Mail',
+ 'fa-book': 'BookOpen',
+ 'fa-angle-right': 'ChevronRight',
+ 'fa-user': 'User',
+ 'fa-tasks': 'ListChecks',
+ 'fa-plane': 'Plane',
+ 'fa-list': 'List',
+ 'fa-link': 'Link',
+ 'fa-file-archive': 'Archive',
+ 'fa-comment-dots': 'MessageCircle',
+ 'fa-chevron-up': 'ChevronUp',
+ 'fa-chevron-down': 'ChevronDown',
+ 'fa-camera': 'Camera',
+ 'fa-barcode': 'Barcode',
+ 'fa-upload': 'Upload',
+ 'fa-search': 'Search',
+ 'fa-mail-bulk': 'Mails',
+ 'fa-laptop-code': 'Laptop',
+ 'fa-copy': 'Copy',
+ 'fa-user-tag': 'Tag',
+ 'fa-user-secret': 'UserRound',
+ 'fa-users': 'Users',
+ 'fa-user-circle': 'CircleUser',
+ 'fa-sort': 'ArrowUpDown',
+ 'fa-question': 'HelpCircle',
+ 'fa-map-marked': 'MapPinned',
+ 'fa-list-ol': 'ListOrdered',
+ 'fa-laptop': 'Laptop',
+ 'fa-info': 'Info',
+ 'fa-building': 'Building2',
+ 'fa-user-slash': 'UserX',
+ 'fa-user-check': 'UserCheck',
+ 'fa-unlink': 'Unlink',
+ 'fa-star': 'Star',
+ 'fa-search-location': 'MapPin',
+ 'fa-remove-format': 'RemoveFormatting',
+ 'fa-qrcode': 'QrCode',
+ 'fa-key': 'Key',
+ 'fa-heartbeat': 'HeartPulse',
+ 'fa-hat-wizard': 'Wand2',
+ 'fa-fingerprint': 'Fingerprint',
+ 'fa-file-csv': 'FileSpreadsheet',
+ 'fa-file': 'File',
+ 'fa-clock': 'Clock',
+ 'fa-clipboard-list': 'ClipboardList',
+ 'fa-chart-line': 'TrendingUp',
+ 'fa-chalkboard-teacher': 'Presentation',
+ 'fa-calendar-day': 'CalendarDays',
+ 'fa-bell-slash': 'BellOff',
+ 'fa-bell': 'Bell',
+}
+
+# Skip modifiers — not real icon names
+FA_MODIFIERS = {'fas', 'far', 'fab', 'fa-spin', 'fa-fw', 'fa-lg', 'fa-2x', 'fa-sm'}
+
+# ── Pattern: ───────────
+# [^>]* matches newlines too (character class, not dot)
+SPAN_RE = re.compile(
+ r']*>\s*'
+)
+
+# ── Comment splitter ─────────────────────────────────────────────────────────
+COMMENT_RE = re.compile(r'()')
+
+# ── Lucide import line ────────────────────────────────────────────────────────
+IMPORT_RE = re.compile(r"import\s*\{([^}]+)\}\s*from\s*'lucide-svelte'\s*;?")
+
+
+def parse_fa_class(class_str):
+ """Return (icon_name, extra_classes, has_spin) from a FA class string."""
+ parts = class_str.split()
+ icon_name = None
+ has_spin = 'fa-spin' in parts
+ extra = []
+ for p in parts:
+ if p in FA_MODIFIERS:
+ continue
+ elif p.startswith('fa-'):
+ if icon_name is None:
+ icon_name = p # first real icon name wins
+ else:
+ extra.append(p)
+ return icon_name, extra, has_spin
+
+
+def replace_span(m):
+ """Regex sub callback: replace a single FA span with a Lucide component."""
+ icon_name, extra, has_spin = parse_fa_class(m.group(1))
+ if icon_name is None:
+ return m.group(0)
+
+ lucide = FA_TO_LUCIDE.get(icon_name)
+ if lucide is None:
+ print(f' ⚠ no mapping for {icon_name!r} — left as-is', file=sys.stderr)
+ return m.group(0)
+
+ classes = extra[:]
+ if has_spin:
+ classes.append('animate-spin')
+
+ class_attr = f' class="{" ".join(classes)}"' if classes else ''
+ return f'<{lucide} size="1em"{class_attr} />'
+
+
+def process_content(content):
+ """Replace FA spans, skip HTML comments. Return (new_content, used_icons)."""
+ used_icons = set()
+
+ def track_and_replace(m):
+ result = replace_span(m)
+ if result != m.group(0):
+ # Extract lucide name from result
+ lucide_name = result.split()[0].lstrip('<')
+ used_icons.add(lucide_name)
+ return result
+
+ # Split by comments; only process non-comment segments
+ parts = COMMENT_RE.split(content)
+ new_parts = []
+ for part in parts:
+ if part.startswith('
@@ -147,7 +148,7 @@
class="text-sm text-gray-500 bg-yellow-100 p-1 rounded-md border border-yellow-200"
title="Device code {event_device_obj?.code}"
>
-
+
{event_device_obj?.code ?? ''}
{/if}
@@ -161,9 +162,7 @@
class="text-red-500 bg-red-200 p-1 rounded-md border border-red-200 text-center"
title={event_device_obj.alert_msg}
>
-
+
{/if}
{#if event_device_obj?.status}
@@ -172,8 +171,7 @@
class="text-blue-500 bg-blue-200 p-1 rounded-md border border-blue-200 text-center"
title={event_device_obj.status_msg}
>
-
+
{/if}
{#if event_device_obj?.record_status}
@@ -182,7 +180,7 @@
class="text-orange-500 bg-orange-200 p-1 rounded-md border border-orange-200 text-center"
title={event_device_obj.record_status_msg}
>
-
+
{/if}
@@ -209,14 +207,11 @@
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 transition-all hover:transition-all *:hover:inline text-xs"
>
{#await ae_promises.load_ae_obj_id__event_device[event_device_obj.event_device_id]}
-
+
{:then load_results}
-
+
{:catch error}
-
+
Error
{/await}
Refresh
@@ -258,11 +253,10 @@
>
{#if $events_loc.pres_mgmt.device_kv && $events_loc.pres_mgmt.device_kv[event_device_obj.event_device_id]?.collapse}
-
+
Show?
{:else}
-
+
{/if}
@@ -285,21 +279,16 @@
-
+
{event_device_obj?.alert
? 'Alert'
: 'No Alert'}
-
+
-
+
{@html event_device_obj?.alert_msg ??
'No message'}
@@ -321,19 +310,16 @@
-
+
{event_device_obj?.status
? event_device_obj?.status
: 'No Status'}
-
+
-
+
{@html event_device_obj?.status_msg ??
'No message'}
@@ -355,7 +341,7 @@
-
+
{#if event_device_obj?.record_status && event_device_obj?.record_status.length > 10}
{ae_util.iso_datetime_formatter(
@@ -368,11 +354,10 @@
{:else}
No Recording Status
{/if}
-
+
-
+
{@html event_device_obj?.record_status_msg ??
'No message'}
@@ -446,7 +431,7 @@
{#if event_device_obj?.heartbeat}
-
+
Heartbeat:
{#if $events_sess.pres_mgmt.show_content__device_description == event_device_obj.event_device_id}
-
+
Hide Description
{:else}
-
+
Show
{/if}
diff --git a/src/routes/events/[event_id]/(pres_mgmt)/event_page_menu.svelte b/src/routes/events/[event_id]/(pres_mgmt)/event_page_menu.svelte
index 23afc5f2..a98550ee 100644
--- a/src/routes/events/[event_id]/(pres_mgmt)/event_page_menu.svelte
+++ b/src/routes/events/[event_id]/(pres_mgmt)/event_page_menu.svelte
@@ -25,6 +25,7 @@
import Comp__events_menu_nav from '../../ae_comp__events_menu_nav.svelte';
import Comp__pres_mgmt_menu_opts from '../../ae_comp__events_menu_opts.svelte';
import AE_Record_Controls from '$lib/ae_elements/AE_Record_Controls.svelte';
+ import { MapPin, Plane, Save, Send } from 'lucide-svelte';
let show_modal = $state(false);
let show_help = $state(false);
@@ -165,7 +166,7 @@
>
-
+
{$events_loc.pres_mgmt.save_search_text ? 'Do Not Save Search' : 'Save Search Text?'}
@@ -180,7 +181,7 @@
>
-
+
{$events_loc.pres_mgmt.hide__launcher_link ? 'Show Launcher Links' : 'Hide Launcher Links?'}
@@ -195,7 +196,7 @@
>
-
+
{$events_loc.pres_mgmt.hide__launcher_link_legacy ? 'Show Legacy Launcher Links' : 'Hide Legacy Launcher Links?'}
@@ -210,7 +211,7 @@
>
-
+
{$events_loc.pres_mgmt.hide__location_link ? 'Show Location Links' : 'Hide Location Links?'}
diff --git a/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/+page.svelte b/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/+page.svelte
index fdd2f23e..c7521af0 100644
--- a/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/+page.svelte
+++ b/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/+page.svelte
@@ -36,6 +36,7 @@
import Element_manage_event_file_li_wrap from '$lib/elements/element_manage_event_file_li_direct.svelte';
import Location_view from './location_view.svelte';
import Location_page_menu from './location_page_menu.svelte';
+ import { LoaderCircle, TriangleAlert } from 'lucide-svelte';
// Variables
// Quickly save the data passed from the parent(s) to the Svelte stores, localStorage, and other.
@@ -111,14 +112,14 @@
{#if !$lq__event_location_obj}
-
+
Loading location...
{:else}
{#if !$lq__event_location_obj.enable && !$ae_loc.trusted_access}
-
+
Location Disabled
This location is currently disabled. Please contact the event organizer for more information.
diff --git a/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_page_menu.svelte b/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_page_menu.svelte
index bd7dc5a8..826b399d 100644
--- a/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_page_menu.svelte
+++ b/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_page_menu.svelte
@@ -29,6 +29,7 @@
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Comp__events_menu_nav from '../../../../ae_comp__events_menu_nav.svelte';
import AE_Record_Controls from '$lib/ae_elements/AE_Record_Controls.svelte';
+ import { List, MapPin, Pencil, Plane, Send, Wand2 } from 'lucide-svelte';
let show_modal = $state(false);
let show_help = $state(false);
@@ -134,7 +135,7 @@
>
-
+
{$events_loc.pres_mgmt.hide__launcher_link ? 'Show Launcher Links' : 'Hide Launcher Links?'}
@@ -149,7 +150,7 @@
>
-
+
{$events_loc.pres_mgmt.hide__launcher_link_legacy ? 'Show Legacy Launcher Links' : 'Hide Legacy Launcher Links?'}
@@ -164,7 +165,7 @@
>
-
+
{$events_loc.pres_mgmt.hide__location_link ? 'Show Location Links' : 'Hide Location Links?'}
@@ -183,7 +184,7 @@
>
-
+
{$events_loc.pres_mgmt.show_content__session_files ? 'Hide Linked Files (testing)' : 'Show Linked Files? (testing)'}
@@ -201,7 +202,7 @@
>
-
+
{$events_loc.pres_mgmt.show_content__session_presentations ? 'Hide Linked Presentations (testing)' : 'Show Linked Presentations? (testing)'}
@@ -221,7 +222,7 @@
class:ae_btn_warning={$ae_loc.edit_mode}
class:ae_btn_warning_outlined={!$ae_loc.edit_mode}
>
-
+
{$ae_loc.edit_mode ? 'Edit Mode On' : 'Edit Mode?'}
@@ -232,7 +233,7 @@
class:ae_btn_warning={$ae_loc.adv_mode}
class:ae_btn_warning_outlined={!$ae_loc.adv_mode}
>
-
+
{$ae_loc.adv_mode ? 'Advanced Mode On' : 'Advanced Mode?'}
diff --git a/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_view.svelte b/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_view.svelte
index 79591cde..caf2c43e 100644
--- a/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_view.svelte
+++ b/src/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_view.svelte
@@ -45,6 +45,7 @@
events_trig_kv
} from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events_functions';
+ import { Barcode, Eye, EyeOff, Key, Plane, Send } from 'lucide-svelte';
// Exports
// export let event_location_id: string;
@@ -200,7 +201,7 @@
title="Location code {$lq__event_location_obj.code}"
>
code:
-
+
{$lq__event_location_obj.code}
@@ -214,7 +215,7 @@
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500"
title="Launcher: {$lq__event_location_obj?.name} {$lq__event_location_obj?.event_location_id}"
>
-
+
{@html $lq__event_location_obj?.name
? $lq__event_location_obj?.name
: ae_snip.html__not_set}
@@ -228,7 +229,7 @@
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500"
title="Launcher: {$lq__event_location_obj?.name} {$lq__event_location_obj?.event_location_id}"
>
-
+
@@ -236,7 +237,7 @@
{#if $ae_loc.administrator_access}
Location passcode:
-
+
{@html $lq__event_location_obj.passcode
? $lq__event_location_obj.passcode
: ae_snip.html__not_set}
@@ -279,10 +280,10 @@
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 text-xs"
>
{#if $events_loc.pres_mgmt.show_content__location_description}
-
+
Hide Description
{:else}
-
+
Show
{/if}
diff --git a/src/routes/events/[event_id]/(pres_mgmt)/locations/+page.svelte b/src/routes/events/[event_id]/(pres_mgmt)/locations/+page.svelte
index 0584c024..dbb19353 100644
--- a/src/routes/events/[event_id]/(pres_mgmt)/locations/+page.svelte
+++ b/src/routes/events/[event_id]/(pres_mgmt)/locations/+page.svelte
@@ -46,6 +46,7 @@
// import Sign_in_out from './../../sign_in_out.svelte';
import { browser } from '$app/environment';
+ import { LoaderCircle, MapPinned } from 'lucide-svelte';
// Variables
$effect(() => {
@@ -106,7 +107,7 @@
{#if !$lq__event_location_obj_li}
-
+
Loading locations...
{:else}
@@ -118,8 +119,7 @@
"
>
-
+
Locations/Rooms
diff --git a/src/routes/events/[event_id]/(pres_mgmt)/locations/ae_comp__event_location_obj_li.svelte b/src/routes/events/[event_id]/(pres_mgmt)/locations/ae_comp__event_location_obj_li.svelte
index 4c61fc58..4adddc73 100644
--- a/src/routes/events/[event_id]/(pres_mgmt)/locations/ae_comp__event_location_obj_li.svelte
+++ b/src/routes/events/[event_id]/(pres_mgmt)/locations/ae_comp__event_location_obj_li.svelte
@@ -38,6 +38,7 @@
import Comp_event_session_obj_li from '../../../ae_comp__event_session_obj_li_wrapper.svelte';
import Element_ae_obj_field_editor_v3 from '$lib/elements/element_ae_obj_field_editor_v3.svelte';
import Comp_event_device_obj_li from '../device/device/ae_comp__event_device_obj_li_wrapper.svelte';
+ import { Barcode, ChevronDown, ChevronUp, Eye, EyeOff, MapPin, MapPinned, Plane, Plus, Send, TriangleAlert } from 'lucide-svelte';
// if (log_lvl) {
// console.log(`link_to_type: ${link_to_type}; link_to_id: ${link_to_id}`);
@@ -95,7 +96,7 @@
}}
class="btn btn-sm preset-tonal-warning hover:preset-filled-warning-500"
>
-
+
Add Location
{/if}
@@ -108,7 +109,7 @@
class:hidden={!$lq__event_location_obj_li?.length}
title="Locations: {$lq__event_location_obj_li?.length ?? 'None'}"
>
-
+
{@html $lq__event_location_obj_li?.length
? `${$lq__event_location_obj_li?.length}×`
: ''}
@@ -118,7 +119,7 @@
-
+
No locations available.
{/if}
@@ -150,9 +151,7 @@
class_li={'m-1'}
on_success={() => events_func.load_ae_obj_id__event_location({ api_cfg: $ae_api, event_location_id: event_location_obj.event_location_id })}
>
-
+
"{event_location_obj?.name ?? '-- not set --'}"
@@ -162,7 +161,7 @@
class="text-sm text-gray-500 bg-yellow-100 p-1 rounded-md border border-yellow-200"
title="Location code {event_location_obj?.code}"
>
-
+
{event_location_obj?.code ?? ''}
{/if}
@@ -174,7 +173,7 @@
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all hover:transition-all *:hover:inline text-xs"
title="View this location"
>
-
+
View
@@ -183,7 +182,7 @@
class="btn btn-sm preset-tonal-secondary hover:preset-filled-secondary-500 transition-all hover:transition-all *:hover:inline text-xs"
title="The legacy launcher that not actively being developed. Use with the native launcher for oral sessions."
>
-
+
Launcher
@@ -192,7 +191,7 @@
class="btn btn-sm preset-tonal-tertiary border border-tertiary-500 hover:preset-filled-secondary-500 transition-all hover:transition-all *:hover:inline text-xs"
title="The new launcher that is actively being developed. Do not use with the native launcher."
>
-
+
Dev Launcher
@@ -233,10 +232,10 @@
>
{#if $events_loc.pres_mgmt.location_kv && $events_loc.pres_mgmt.location_kv[event_location_obj.event_location_id]?.collapse}
-
+
Show?
{:else}
-
+
{/if}
@@ -360,12 +359,10 @@
class="btn btn-sm preset-tonal-surface hover:preset-filled-surface-500 text-xs"
>
{#if $events_sess.pres_mgmt.show_content__location_description == event_location_obj.event_location_id}
-
+
Hide Description
{:else}
-
+
Show
{/if}
diff --git a/src/routes/events/[event_id]/(pres_mgmt)/pres_mgmt/+page.svelte b/src/routes/events/[event_id]/(pres_mgmt)/pres_mgmt/+page.svelte
index 262157ea..ec4b2fbb 100644
--- a/src/routes/events/[event_id]/(pres_mgmt)/pres_mgmt/+page.svelte
+++ b/src/routes/events/[event_id]/(pres_mgmt)/pres_mgmt/+page.svelte
@@ -37,6 +37,7 @@
import Comp_event_files_upload from '../../../ae_comp__event_files_upload.svelte';
import Element_manage_event_file_li_wrap from '$lib/elements/element_manage_event_file_li_direct.svelte';
import Event_page_menu from '../event_page_menu.svelte';
+ import { Archive, CalendarDays, ChevronRight, FileText, ListChecks, LoaderCircle, Mails, MapPin, RemoveFormatting, Search, TriangleAlert, Upload } from 'lucide-svelte';
// Quickly save the data passed from the parent(s) to the Svelte stores, localStorage, and other.
// NOTE: Derived from data.account_id (prop) instead of $slct.account_id (store)
@@ -364,13 +365,13 @@
{#if !$lq__event_obj}
-
+
Loading event information...
{:else if $lq__event_obj?.enable || $ae_loc.trusted_access}