Merge pull request 'Improve app menu sidebar, add separate subsections, more content' (#31) from feature/app_menu into master
Reviewed-on: #31
This commit was merged in pull request #31.
This commit is contained in:
@@ -2,6 +2,7 @@ import { on } from '@ember/modifier';
|
|||||||
import Icon from '#components/icon';
|
import Icon from '#components/icon';
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
{{! template-lint-disable no-nested-interactive }}
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<button type="button" class="back-btn" {{on "click" @onBack}}>
|
<button type="button" class="back-btn" {{on "click" @onBack}}>
|
||||||
<Icon @name="arrow-left" @size={{20}} @color="#333" />
|
<Icon @name="arrow-left" @size={{20}} @color="#333" />
|
||||||
@@ -13,7 +14,7 @@ import Icon from '#components/icon';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<section class="settings-section">
|
<section class="about-section">
|
||||||
<p>
|
<p>
|
||||||
<strong>Marco</strong>
|
<strong>Marco</strong>
|
||||||
(as in
|
(as in
|
||||||
@@ -33,31 +34,119 @@ import Icon from '#components/icon';
|
|||||||
>remote storage</a>
|
>remote storage</a>
|
||||||
to sync place bookmarks across apps and devices.
|
to sync place bookmarks across apps and devices.
|
||||||
</p>
|
</p>
|
||||||
<ul class="link-list">
|
|
||||||
<li>
|
<details>
|
||||||
|
<summary>
|
||||||
|
<Icon @name="gift" @size={{20}} />
|
||||||
|
<span>Open Source</span>
|
||||||
|
</summary>
|
||||||
|
<div class="details-content">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>License</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<a
|
<a
|
||||||
href="https://gitea.kosmos.org/raucao/marco"
|
href="https://gitea.kosmos.org/raucao/marco"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
Source Code
|
Marco App
|
||||||
</a>
|
</a>
|
||||||
(<a
|
</td>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
href="https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License"
|
href="https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>AGPL</a>)
|
>
|
||||||
</li>
|
<abbr title="GNU Affero General Public License">AGPL</abbr>
|
||||||
<li>
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<a
|
<a
|
||||||
href="https://openstreetmap.org/copyright"
|
href="https://openstreetmap.org/copyright"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
Map Data © OpenStreetMap
|
Map Data
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</td>
|
||||||
</ul>
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://opendatacommons.org/licenses/odbl/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<abbr
|
||||||
|
title="Open Data Commons Open Database License"
|
||||||
|
>ODbL</abbr>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://github.com/feathericons/feather"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
Feather Icons
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
href="https://en.wikipedia.org/wiki/MIT_License"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<abbr title="MIT License">MIT</abbr>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
<Icon @name="heart" @size={{20}} @color="#e5533d" />
|
||||||
|
<span>Contribute</span>
|
||||||
|
</summary>
|
||||||
|
<div class="details-content">
|
||||||
|
<p>
|
||||||
|
<strong>Most impactful:</strong>
|
||||||
|
Add and improve data for points of interest in
|
||||||
|
<a
|
||||||
|
href="https://www.openstreetmap.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>OpenStreetMap</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Most appreciated:</strong>
|
||||||
|
Use this app as much as you can and
|
||||||
|
<a
|
||||||
|
href="https://community.remotestorage.io/t/marco-an-unhosted-maps-app/941"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>submit feedback</a>
|
||||||
|
about your experience, problems, feature wishes, etc.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Most supportive:</strong>
|
||||||
|
Tell others about this app, on social media, in blog posts,
|
||||||
|
educational videos, etc.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export default class AppMenuSettings extends Component {
|
|||||||
this.settings.updateMapKinetic(event.target.value === 'true');
|
this.settings.updateMapKinetic(event.target.value === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
updatePhotonApi(event) {
|
||||||
|
this.settings.updatePhotonApi(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<button type="button" class="back-btn" {{on "click" @onBack}}>
|
<button type="button" class="back-btn" {{on "click" @onBack}}>
|
||||||
@@ -72,6 +77,23 @@ export default class AppMenuSettings extends Component {
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="photon-api">Photon API Provider</label>
|
||||||
|
<select
|
||||||
|
id="photon-api"
|
||||||
|
class="form-control"
|
||||||
|
{{on "change" this.updatePhotonApi}}
|
||||||
|
>
|
||||||
|
{{#each this.settings.photonApis as |api|}}
|
||||||
|
<option
|
||||||
|
value={{api.url}}
|
||||||
|
selected={{if (eq api.url this.settings.photonApi) "selected"}}
|
||||||
|
>
|
||||||
|
{{api.name}}
|
||||||
|
</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -499,10 +499,9 @@ export default class MapComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.preventZoom) {
|
if (options.preventZoom) {
|
||||||
// If we are preventing zoom (e.g. user clicked a bookmark), we still need to center
|
// If we are preventing zoom (e.g. user clicked a bookmark), we rely on visibility check.
|
||||||
// but without changing the zoom level.
|
// This avoids unnecessary panning if the place is already visible.
|
||||||
// We use animateToSmartCenter without a second argument (zoom=null).
|
this.handlePinVisibility(coords, { maintainZoom: true });
|
||||||
this.animateToSmartCenter(coords);
|
|
||||||
} else if (selected.bbox) {
|
} else if (selected.bbox) {
|
||||||
this.zoomToBbox(selected.bbox);
|
this.zoomToBbox(selected.bbox);
|
||||||
} else {
|
} else {
|
||||||
@@ -547,7 +546,10 @@ export default class MapComponent extends Component {
|
|||||||
}
|
}
|
||||||
// Desktop: Sidebar covers left side (approx 400px)
|
// Desktop: Sidebar covers left side (approx 400px)
|
||||||
else if (this.args.isSidebarOpen) {
|
else if (this.args.isSidebarOpen) {
|
||||||
const sidebarWidth = 400;
|
const sidebarWidthVar = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('--sidebar-width')
|
||||||
|
.trim();
|
||||||
|
const sidebarWidth = parseInt(sidebarWidthVar, 10) || 360;
|
||||||
const visibleWidth = size[0] - sidebarWidth;
|
const visibleWidth = size[0] - sidebarWidth;
|
||||||
|
|
||||||
// Left padding: Sidebar + 15% of visible width
|
// Left padding: Sidebar + 15% of visible width
|
||||||
@@ -566,14 +568,15 @@ export default class MapComponent extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePinVisibility(coords) {
|
handlePinVisibility(coords, options = {}) {
|
||||||
if (!this.mapInstance) return;
|
if (!this.mapInstance) return;
|
||||||
|
|
||||||
const view = this.mapInstance.getView();
|
const view = this.mapInstance.getView();
|
||||||
const currentZoom = view.getZoom();
|
const currentZoom = view.getZoom();
|
||||||
|
|
||||||
// If too far out (e.g. world view), zoom in to neighborhood level (16)
|
// If too far out (e.g. world view), zoom in to neighborhood level (16)
|
||||||
if (currentZoom < 16) {
|
// UNLESS we want to maintain the current zoom
|
||||||
|
if (!options.maintainZoom && currentZoom < 16) {
|
||||||
this.animateToSmartCenter(coords, 16);
|
this.animateToSmartCenter(coords, 16);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -590,8 +593,12 @@ export default class MapComponent extends Component {
|
|||||||
pixel[1] > size[1];
|
pixel[1] > size[1];
|
||||||
|
|
||||||
if (isOffScreen) {
|
if (isOffScreen) {
|
||||||
this.animateToSmartCenter(coords);
|
// If off-screen, center it smartly (considering sidebar/bottom sheet)
|
||||||
|
// Pass maintainZoom to prevent zoom reset if desired
|
||||||
|
const zoom = options.maintainZoom ? null : 16;
|
||||||
|
this.animateToSmartCenter(coords, zoom);
|
||||||
} else {
|
} else {
|
||||||
|
// If on-screen, only pan if obscured by UI
|
||||||
this.panIfObscured(coords);
|
this.panIfObscured(coords);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,6 +634,28 @@ export default class MapComponent extends Component {
|
|||||||
// To move the camera South (Lower Y), we subtract.
|
// To move the camera South (Lower Y), we subtract.
|
||||||
targetCenter = [coords[0], coords[1] - offsetMapUnits];
|
targetCenter = [coords[0], coords[1] - offsetMapUnits];
|
||||||
}
|
}
|
||||||
|
// Desktop: Check if sidebar is open
|
||||||
|
else if (this.args.isSidebarOpen) {
|
||||||
|
const sidebarWidthVar = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('--sidebar-width')
|
||||||
|
.trim();
|
||||||
|
const sidebarWidth = parseInt(sidebarWidthVar, 10) || 360;
|
||||||
|
|
||||||
|
// We want the pin to be in the center of the remaining space.
|
||||||
|
// Remaining space starts at x = sidebarWidth.
|
||||||
|
// Center of remaining space = sidebarWidth + (totalWidth - sidebarWidth) / 2
|
||||||
|
// = sidebarWidth/2 + totalWidth/2
|
||||||
|
// Map Center is totalWidth/2
|
||||||
|
// Offset = sidebarWidth/2 (to the right)
|
||||||
|
|
||||||
|
const offsetPixels = sidebarWidth / 2;
|
||||||
|
const offsetMapUnits = offsetPixels * resolution;
|
||||||
|
|
||||||
|
// We want pin at center + offset.
|
||||||
|
// So map center must be pin - offset.
|
||||||
|
// X increases to the right.
|
||||||
|
targetCenter = [coords[0] - offsetMapUnits, coords[1]];
|
||||||
|
}
|
||||||
|
|
||||||
const animationOptions = {
|
const animationOptions = {
|
||||||
center: targetCenter,
|
center: targetCenter,
|
||||||
@@ -645,33 +674,73 @@ export default class MapComponent extends Component {
|
|||||||
if (!this.mapInstance) return;
|
if (!this.mapInstance) return;
|
||||||
|
|
||||||
const size = this.mapInstance.getSize();
|
const size = this.mapInstance.getSize();
|
||||||
// Check if mobile (width <= 768px matches CSS)
|
|
||||||
if (size[0] > 768) return;
|
|
||||||
|
|
||||||
const pixel = this.mapInstance.getPixelFromCoordinate(coords);
|
const pixel = this.mapInstance.getPixelFromCoordinate(coords);
|
||||||
if (!pixel) return;
|
if (!pixel) return;
|
||||||
|
|
||||||
const height = size[1];
|
|
||||||
|
|
||||||
// Sidebar covers the bottom 50%
|
|
||||||
const splitPoint = height / 2;
|
|
||||||
|
|
||||||
// If the pin is in the bottom half (y > splitPoint), it is obscured
|
|
||||||
if (pixel[1] > splitPoint) {
|
|
||||||
// Target position: Center of top half = height * 0.25
|
|
||||||
const targetY = height * 0.25;
|
|
||||||
const deltaY = pixel[1] - targetY;
|
|
||||||
|
|
||||||
const view = this.mapInstance.getView();
|
const view = this.mapInstance.getView();
|
||||||
const center = view.getCenter();
|
const center = view.getCenter();
|
||||||
const resolution = view.getResolution();
|
const resolution = view.getResolution();
|
||||||
|
|
||||||
// Move the map center SOUTH (decrease Y) to move the pin UP (decrease pixel Y)
|
// Default targets (current position)
|
||||||
const deltaMapUnits = deltaY * resolution;
|
let targetPixelX = pixel[0];
|
||||||
const newCenter = [center[0], center[1] - deltaMapUnits];
|
let targetPixelY = pixel[1];
|
||||||
|
let needsPan = false;
|
||||||
|
|
||||||
|
// 1. Mobile Bottom Sheet Logic (Screen <= 768px)
|
||||||
|
if (size[0] <= 768) {
|
||||||
|
const height = size[1];
|
||||||
|
const splitPoint = height / 2;
|
||||||
|
|
||||||
|
// If in bottom half
|
||||||
|
if (pixel[1] > splitPoint) {
|
||||||
|
targetPixelY = height * 0.25; // Target: Center of top half
|
||||||
|
needsPan = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. Desktop Sidebar Logic (Screen > 768px + Sidebar Open)
|
||||||
|
else if (this.args.isSidebarOpen) {
|
||||||
|
const sidebarWidthVar = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('--sidebar-width')
|
||||||
|
.trim();
|
||||||
|
const sidebarWidth = parseInt(sidebarWidthVar, 10) || 360;
|
||||||
|
|
||||||
|
// If under sidebar
|
||||||
|
if (pixel[0] < sidebarWidth) {
|
||||||
|
const visibleWidth = size[0] - sidebarWidth;
|
||||||
|
targetPixelX = sidebarWidth + visibleWidth / 2; // Target: Center of visible area
|
||||||
|
needsPan = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Header Logic (Any screen size)
|
||||||
|
// Check if the (potentially new) target Y is under the header
|
||||||
|
const headerHeight = 60;
|
||||||
|
const minTopDistance = headerHeight + 20; // 80px
|
||||||
|
|
||||||
|
if (targetPixelY < minTopDistance) {
|
||||||
|
targetPixelY = minTopDistance + 30; // Move it to ~110px, clear of header
|
||||||
|
needsPan = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsPan) {
|
||||||
|
const deltaPixelX = pixel[0] - targetPixelX;
|
||||||
|
const deltaPixelY = pixel[1] - targetPixelY;
|
||||||
|
|
||||||
|
// X: Camera moves same direction as we want the world to move? No.
|
||||||
|
// If we want pin to move RIGHT (pixel increases), Camera must move LEFT (X decreases).
|
||||||
|
// deltaPixelX = current - target. If current < target (want move right), delta is negative.
|
||||||
|
// center + negative = decrease. Correct.
|
||||||
|
const newCenterX = center[0] + deltaPixelX * resolution;
|
||||||
|
|
||||||
|
// Y: Camera moves opposite direction to world relative to pixel coords.
|
||||||
|
// Pixel Y increases DOWN. Map Y increases UP.
|
||||||
|
// If we want pin to move DOWN (pixel increases), Camera must move UP (Y increases).
|
||||||
|
// deltaPixelY = current - target. If current < target (want move down), delta is negative.
|
||||||
|
// center - negative = increase. Correct.
|
||||||
|
const newCenterY = center[1] - deltaPixelY * resolution;
|
||||||
|
|
||||||
view.animate({
|
view.animate({
|
||||||
center: newCenter,
|
center: [newCenterX, newCenterY],
|
||||||
duration: 500,
|
duration: 500,
|
||||||
easing: (t) => t * (2 - t), // Ease-out
|
easing: (t) => t * (2 - t), // Ease-out
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ export default class PlaceDetails extends Component {
|
|||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
@name="bookmark"
|
@name="bookmark"
|
||||||
@color={{if this.isSaved "currentColor" "#007bff"}}
|
@color={{if this.isSaved "currentColor" "var(--link-color)"}}
|
||||||
/>
|
/>
|
||||||
{{if this.isSaved "Saved" "Save"}}
|
{{if this.isSaved "Saved" "Save"}}
|
||||||
</button>
|
</button>
|
||||||
@@ -307,7 +307,7 @@ export default class PlaceDetails extends Component {
|
|||||||
title="Edit"
|
title="Edit"
|
||||||
{{on "click" this.startEditing}}
|
{{on "click" this.startEditing}}
|
||||||
>
|
>
|
||||||
<Icon @name="edit" @color="#007bff" />
|
<Icon @name="edit" @color="var(--link-color)" />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ export default class PlacesSidebar extends Component {
|
|||||||
class="btn btn-outline create-place"
|
class="btn btn-outline create-place"
|
||||||
{{on "click" this.createNewPlace}}
|
{{on "click" this.createNewPlace}}
|
||||||
>
|
>
|
||||||
<Icon @name="plus" @size={{18}} @color="#007bff" />
|
<Icon @name="plus" @size={{18}} @color="var(--link-color)" />
|
||||||
Create new place
|
Create new place
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import Service from '@ember/service';
|
import Service, { service } from '@ember/service';
|
||||||
import { getPlaceType } from '../utils/osm';
|
import { getPlaceType } from '../utils/osm';
|
||||||
import { humanizeOsmTag } from '../utils/format-text';
|
import { humanizeOsmTag } from '../utils/format-text';
|
||||||
|
|
||||||
export default class PhotonService extends Service {
|
export default class PhotonService extends Service {
|
||||||
baseUrl = 'https://photon.komoot.io/api/';
|
@service settings;
|
||||||
|
|
||||||
|
get baseUrl() {
|
||||||
|
return this.settings.photonApi;
|
||||||
|
}
|
||||||
|
|
||||||
async search(query, lat, lon, limit = 10) {
|
async search(query, lat, lon, limit = 10) {
|
||||||
if (!query || query.length < 2) return [];
|
if (!query || query.length < 2) return [];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { tracked } from '@glimmer/tracking';
|
|||||||
export default class SettingsService extends Service {
|
export default class SettingsService extends Service {
|
||||||
@tracked overpassApi = 'https://overpass-api.de/api/interpreter';
|
@tracked overpassApi = 'https://overpass-api.de/api/interpreter';
|
||||||
@tracked mapKinetic = true;
|
@tracked mapKinetic = true;
|
||||||
|
@tracked photonApi = 'https://photon.komoot.io/api/';
|
||||||
|
|
||||||
overpassApis = [
|
overpassApis = [
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,13 @@ export default class SettingsService extends Service {
|
|||||||
// },
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
photonApis = [
|
||||||
|
{
|
||||||
|
name: 'photon.komoot.io',
|
||||||
|
url: 'https://photon.komoot.io/api/',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.loadSettings();
|
this.loadSettings();
|
||||||
@@ -59,4 +67,8 @@ export default class SettingsService extends Service {
|
|||||||
this.mapKinetic = enabled;
|
this.mapKinetic = enabled;
|
||||||
localStorage.setItem('marco:map-kinetic', String(enabled));
|
localStorage.setItem('marco:map-kinetic', String(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePhotonApi(url) {
|
||||||
|
this.photonApi = url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
:root {
|
:root {
|
||||||
--default-list-color: #fc3;
|
--default-list-color: #fc3;
|
||||||
--hover-bg: #f8f9fa;
|
--hover-bg: #f8f9fa;
|
||||||
|
--sidebar-width: 360px;
|
||||||
|
--link-color: #2a7fff;
|
||||||
|
--link-color-visited: #6a4fbf;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
@@ -185,7 +188,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: #007bff;
|
color: var(--link-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-danger {
|
.text-danger {
|
||||||
@@ -202,7 +205,7 @@ body {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 300px;
|
width: var(--sidebar-width);
|
||||||
background: white;
|
background: white;
|
||||||
z-index: 3100; /* Higher than Header (3000) */
|
z-index: 3100; /* Higher than Header (3000) */
|
||||||
box-shadow: 2px 0 5px rgb(0 0 0 / 10%);
|
box-shadow: 2px 0 5px rgb(0 0 0 / 10%);
|
||||||
@@ -285,6 +288,82 @@ body {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-content details {
|
||||||
|
margin: 0 -1rem; /* Top margin, negative side margins to span full width */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details summary {
|
||||||
|
list-style: none; /* Hide default triangle */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
padding: 1rem;
|
||||||
|
padding-left: 1.4rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #333;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details summary::-webkit-details-marker {
|
||||||
|
display: none; /* Hide default triangle in WebKit */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details summary:hover {
|
||||||
|
background-color: var(--hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details summary .icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details summary::after {
|
||||||
|
content: '';
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 18 15 12 9 6'/%3E%3C/svg%3E");
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
margin-left: auto;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details[open] summary::after {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details .details-content {
|
||||||
|
padding: 0 1.4rem 1rem;
|
||||||
|
animation: details-slide-down 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details .link-list {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes details-slide-down {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details .link-list li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content details .link-list li:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-form {
|
.edit-form {
|
||||||
margin: -1rem;
|
margin: -1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
@@ -312,12 +391,25 @@ body {
|
|||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
box-sizing: border-box; /* Ensure padding doesn't overflow width */
|
box-sizing: border-box; /* Ensure padding doesn't overflow width */
|
||||||
|
color: #333;
|
||||||
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus {
|
.form-control:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #007bff;
|
border-color: var(--link-color);
|
||||||
box-shadow: 0 0 0 2px rgb(0 123 255 / 10%);
|
box-shadow: 0 0 0 2px rgb(42 127 255 / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
select.form-control {
|
||||||
|
appearance: none;
|
||||||
|
background-color: #fff;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 0.75rem center;
|
||||||
|
background-size: 16px 16px;
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-actions {
|
.edit-actions {
|
||||||
@@ -335,12 +427,21 @@ body {
|
|||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-section p a {
|
.about-section {
|
||||||
color: #007bff;
|
margin-bottom: 2rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section a {
|
||||||
|
color: var(--link-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-section p a:hover {
|
.about-section a:visited {
|
||||||
|
color: var(--link-color-visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +450,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: #007bff;
|
background: var(--link-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
@@ -378,7 +479,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.meta-info a {
|
.meta-info a {
|
||||||
color: #007bff;
|
color: var(--link-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,6 +487,37 @@ body {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content table th,
|
||||||
|
.sidebar-content table td {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content table th {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #898989;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content table td {
|
||||||
|
border-bottom: 1px solid #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content table tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
.link-list {
|
.link-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -397,7 +529,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link-list a {
|
.link-list a {
|
||||||
color: #007bff;
|
color: var(--link-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
@@ -522,7 +654,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-blue {
|
.btn-blue {
|
||||||
background: #007bff;
|
background: var(--link-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@@ -767,15 +899,14 @@ span.icon {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar is open (Desktop: Left 300px) */
|
/* Sidebar is open (Desktop: Left var(--sidebar-width)) */
|
||||||
|
|
||||||
/* We want to center in the remaining space (width - 300px) */
|
/* We want to center in the remaining space (width - var(--sidebar-width)) */
|
||||||
|
|
||||||
/* Center X = 300 + (width - 300) / 2 = 300 + width/2 - 150 = width/2 + 150 */
|
/* Center X = var(--sidebar-width) + (width - var(--sidebar-width)) / 2 = var(--sidebar-width)/2 + 50% */
|
||||||
|
|
||||||
/* So shift left by 150px from center */
|
|
||||||
.map-container.sidebar-open .map-crosshair {
|
.map-container.sidebar-open .map-crosshair {
|
||||||
left: calc(50% + 150px);
|
left: calc(50% + var(--sidebar-width) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 768px) {
|
@media (width <= 768px) {
|
||||||
@@ -1063,7 +1194,7 @@ button.create-place {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.place-lists-manager input[type='checkbox'] {
|
.place-lists-manager input[type='checkbox'] {
|
||||||
accent-color: #007bff;
|
accent-color: var(--link-color);
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import checkSquare from 'feather-icons/dist/icons/check-square.svg?raw';
|
|||||||
import clock from 'feather-icons/dist/icons/clock.svg?raw';
|
import clock from 'feather-icons/dist/icons/clock.svg?raw';
|
||||||
import edit from 'feather-icons/dist/icons/edit.svg?raw';
|
import edit from 'feather-icons/dist/icons/edit.svg?raw';
|
||||||
import facebook from 'feather-icons/dist/icons/facebook.svg?raw';
|
import facebook from 'feather-icons/dist/icons/facebook.svg?raw';
|
||||||
|
import gift from 'feather-icons/dist/icons/gift.svg?raw';
|
||||||
import globe from 'feather-icons/dist/icons/globe.svg?raw';
|
import globe from 'feather-icons/dist/icons/globe.svg?raw';
|
||||||
|
import heart from 'feather-icons/dist/icons/heart.svg?raw';
|
||||||
import home from 'feather-icons/dist/icons/home.svg?raw';
|
import home from 'feather-icons/dist/icons/home.svg?raw';
|
||||||
import info from 'feather-icons/dist/icons/info.svg?raw';
|
import info from 'feather-icons/dist/icons/info.svg?raw';
|
||||||
import instagram from 'feather-icons/dist/icons/instagram.svg?raw';
|
import instagram from 'feather-icons/dist/icons/instagram.svg?raw';
|
||||||
@@ -35,7 +37,9 @@ const ICONS = {
|
|||||||
clock,
|
clock,
|
||||||
edit,
|
edit,
|
||||||
facebook,
|
facebook,
|
||||||
|
gift,
|
||||||
globe,
|
globe,
|
||||||
|
heart,
|
||||||
home,
|
home,
|
||||||
info,
|
info,
|
||||||
instagram,
|
instagram,
|
||||||
|
|||||||
Reference in New Issue
Block a user