diff --git a/assets/controllers/map_controller.js b/assets/controllers/map_controller.js
index 294caf1..ca36d82 100644
--- a/assets/controllers/map_controller.js
+++ b/assets/controllers/map_controller.js
@@ -17,22 +17,11 @@ export default class extends Controller {
maxZoom: 19,
}).addTo(this.map);
- this.pendingLatlng = null;
-
- this.dialog = document.getElementById('add-subject-dialog');
- this.nameInput = document.getElementById('subject-name');
- this.submitBtn = document.getElementById('subject-submit');
- this.cancelBtn = document.getElementById('subject-cancel');
+ this.popup = null;
this.loadSubjects();
- this.map.on('click', (e) => this.openModal(e.latlng));
-
- this.submitBtn.addEventListener('click', () => this.handleSubmit());
- this.cancelBtn.addEventListener('click', () => this.closeModal());
-
- // Esc key / backdrop close — clear pending state
- this.dialog.addEventListener('close', () => this.resetModal());
+ this.map.on('click', (e) => this.openPopup(e.latlng));
}
loadSubjects() {
@@ -47,28 +36,69 @@ export default class extends Controller {
});
}
- openModal(latlng) {
- this.pendingLatlng = latlng;
- this.nameInput.value = '';
- this.dialog.showModal();
- this.nameInput.focus();
+ openPopup(latlng) {
+ if (this.popup) {
+ this.popup.remove();
+ }
+
+ const content = this.buildPopupContent(latlng);
+
+ this.popup = L.popup({ closeOnClick: false, autoClose: false, minWidth: 280 })
+ .setLatLng(latlng)
+ .setContent(content)
+ .openOn(this.map);
+
+ // Autofocus after Leaflet inserts the DOM
+ this.popup.once('add', () => {
+ const input = this.popup.getElement()?.querySelector('.add-place-input');
+ if (input) input.focus();
+ });
}
- closeModal() {
- this.dialog.close();
+ buildPopupContent(latlng) {
+ const container = document.createElement('div');
+ container.className = 'add-place-popup';
+ container.innerHTML = `
+
Add a place
+
+
+
+
+
+ `;
+
+ const input = container.querySelector('.add-place-input');
+ const submitBtn = container.querySelector('.add-place-submit');
+ const cancelBtn = container.querySelector('.add-place-cancel');
+
+ const submit = () => {
+ const name = input.value.trim();
+ if (!name) {
+ input.focus();
+ return;
+ }
+ this.createSubject(name, latlng);
+ };
+
+ submitBtn.addEventListener('click', submit);
+ cancelBtn.addEventListener('click', () => this.popup.remove());
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') submit();
+ if (e.key === 'Escape') this.popup.remove();
+ });
+
+ return container;
}
- resetModal() {
- this.pendingLatlng = null;
- this.nameInput.value = '';
- }
-
- handleSubmit() {
- const name = this.nameInput.value.trim();
- if (!name || !this.pendingLatlng) return;
-
- const latlng = this.pendingLatlng;
-
+ createSubject(name, latlng) {
fetch('/api/subjects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -81,7 +111,7 @@ export default class extends Controller {
.then(r => r.json())
.then(() => {
this.addMarker(latlng.lat, latlng.lng, name);
- this.closeModal();
+ this.popup.remove();
});
}
diff --git a/assets/styles/app.css b/assets/styles/app.css
index 2722a1d..a91872c 100644
--- a/assets/styles/app.css
+++ b/assets/styles/app.css
@@ -5,3 +5,52 @@
html, body { height: 100%; margin: 0; }
.map-container { height: calc(100vh - 4rem); width: 100%; }
+
+/* Leaflet popup chrome — match aqua theme */
+.leaflet-popup-content-wrapper {
+ background: oklch(37% 0.146 265.522) !important;
+ color: oklch(90% 0.058 230.902) !important;
+ border-radius: 0.75rem !important;
+ box-shadow: 0 4px 24px oklch(0% 0 0 / 0.4) !important;
+ padding: 0 !important;
+ border: none !important;
+}
+.leaflet-popup-content {
+ margin: 0 !important;
+ width: auto !important;
+}
+.leaflet-popup-tip {
+ background: oklch(37% 0.146 265.522) !important;
+}
+.leaflet-popup-close-button {
+ color: oklch(90% 0.058 230.902) !important;
+ top: 0.5rem !important;
+ right: 0.5rem !important;
+ font-size: 1.1rem !important;
+}
+.leaflet-popup-close-button:hover {
+ color: oklch(85.661% 0.144 198.645) !important;
+ background: transparent !important;
+}
+
+
+/* Add-place popup layout */
+.add-place-popup {
+ padding: 0.25rem 1rem 1rem;
+ width: 300px;
+}
+.add-place-heading {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-bottom: 0.75rem;
+ color: oklch(85.661% 0.144 198.645);
+}
+.add-place-popup .fieldset-legend {
+ padding-left: 2px !important;
+}
+.add-place-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.5rem;
+ margin-top: 1rem;
+}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index c7bb70d..6db0265 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -22,7 +22,7 @@
{% block navbar %}