Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ed9ac6941 | |||
| b4fff04ee1 | |||
| 7e01106801 | |||
| caf6e395cd |
@@ -905,6 +905,36 @@ html.scheme-dark .themed-select-option.is-selected {
|
||||
color: var(--app-text-heading);
|
||||
}
|
||||
|
||||
.section-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.copy-link-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.copy-link-row .input-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-actions--start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.btn-refresh {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -1645,6 +1675,224 @@ html.scheme-dark .themed-select-option.is-selected {
|
||||
.hide-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.dashboard-header,
|
||||
.app-header {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.app-header-left {
|
||||
flex: 1 1 100%;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.app-title-area {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.app-title-area h2 {
|
||||
font-size: 17px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.app-title-row {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.app-subtitle {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-brand {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-brand h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex: 1 1 100%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.conn-status > span:not(.pulse-dot) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.skipper-badge__name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
padding: 8px 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-title-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.section-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section-toolbar .btn {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.section-toolbar .btn.primary {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.section-title-left {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-title-left .form-header h2 {
|
||||
font-size: 16px;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.logbooks-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.logbook-card {
|
||||
flex-wrap: wrap;
|
||||
padding: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card-info h3 {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.crew-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.copy-link-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.copy-link-row .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-actions--start {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.form-actions--start .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.table-responsive table {
|
||||
min-width: 480px;
|
||||
}
|
||||
|
||||
.custom-dialog-overlay {
|
||||
padding: max(16px, env(safe-area-inset-left)) max(16px, env(safe-area-inset-right));
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.custom-dialog-card {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
padding: 22px 18px;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0px);
|
||||
}
|
||||
|
||||
.custom-dialog-actions {
|
||||
flex-direction: column-reverse;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.custom-dialog-actions .btn {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.disclaimer-modal-overlay {
|
||||
padding: max(12px, env(safe-area-inset-left)) max(12px, env(safe-area-inset-right));
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.disclaimer-modal-panel,
|
||||
.registration-disclaimer--modal {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
padding: 28px 20px;
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
|
||||
.app-layout,
|
||||
.dashboard-container {
|
||||
padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
.track-info-left {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.track-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.track-actions .btn {
|
||||
flex: 1 1 calc(50% - 4px);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#openseamap-container,
|
||||
.track-map-container {
|
||||
height: min(360px, 45svh);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================== */
|
||||
@@ -2267,6 +2515,12 @@ html.theme-cupertino .events-scroll-container {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.track-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.track-error-msg {
|
||||
color: #ef4444;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
@@ -3352,7 +3606,9 @@ body.app-tour-active .app-tour-target-active {
|
||||
.app-tour-tooltip {
|
||||
position: fixed;
|
||||
z-index: 10002;
|
||||
box-sizing: border-box;
|
||||
width: min(420px, calc(100vw - 32px));
|
||||
max-width: calc(100vw - 32px);
|
||||
padding: 20px 20px 16px;
|
||||
border-radius: 16px;
|
||||
background: #1e293b;
|
||||
@@ -3361,10 +3617,19 @@ body.app-tour-active .app-tour-target-active {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.app-tour-tooltip:not(.centered) {
|
||||
left: max(16px, env(safe-area-inset-left, 0px));
|
||||
right: max(16px, env(safe-area-inset-right, 0px));
|
||||
width: auto;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.app-tour-tooltip.centered {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: min(420px, calc(100vw - 32px - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px)));
|
||||
max-width: calc(100vw - 32px - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
|
||||
}
|
||||
|
||||
.app-tour-close {
|
||||
@@ -3441,6 +3706,34 @@ body.app-tour-active .app-tour-target-active {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.app-tour-tooltip {
|
||||
padding: 18px 16px 14px;
|
||||
}
|
||||
|
||||
.app-tour-actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.app-tour-nav {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-tour-nav-btn {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
body.app-tour-active .pwa-install-banner {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.app-tour-active .disclaimer-modal-overlay.feedback-modal-overlay--tour {
|
||||
|
||||
+3
-3
@@ -421,7 +421,7 @@ function App() {
|
||||
)
|
||||
}
|
||||
|
||||
const pwaInstallBanner = <PwaInstallPrompt variant="banner" />
|
||||
const pwaInstallBanner = !isActive ? <PwaInstallPrompt variant="banner" /> : null
|
||||
|
||||
const logbookReadOnly =
|
||||
activeLogbookRecord?.isShared === 1 && activeAccessRole === 'READ'
|
||||
@@ -446,9 +446,9 @@ function App() {
|
||||
{/* Active Logbook Header */}
|
||||
<header className="app-header">
|
||||
<div className="app-header-left">
|
||||
<button className="btn-back" onClick={handleBackToDashboard}>
|
||||
<button className="btn-back" onClick={handleBackToDashboard} title={t('nav.dashboard')}>
|
||||
<ChevronLeft size={16} />
|
||||
{t('nav.dashboard')}
|
||||
<span className="hide-mobile">{t('nav.dashboard')}</span>
|
||||
</button>
|
||||
<div className="app-title-area">
|
||||
<div className="app-title-row">
|
||||
|
||||
@@ -15,12 +15,33 @@ interface SpotlightRect {
|
||||
height: number
|
||||
}
|
||||
|
||||
const TOOLTIP_EDGE_MARGIN = 16
|
||||
const TOOLTIP_ESTIMATED_HEIGHT = 240
|
||||
|
||||
function buildCutoutClipPath(rect: SpotlightRect): string {
|
||||
const right = rect.left + rect.width
|
||||
const bottom = rect.top + rect.height
|
||||
return `polygon(evenodd, 0 0, 100vw 0, 100vw 100vh, 0 100vh, 0 0, ${rect.left}px ${rect.top}px, ${right}px ${rect.top}px, ${right}px ${bottom}px, ${rect.left}px ${bottom}px, ${rect.left}px ${rect.top}px)`
|
||||
}
|
||||
|
||||
function computeTooltipTop(spotlight: SpotlightRect): number {
|
||||
const viewportBottom = window.innerHeight - TOOLTIP_EDGE_MARGIN
|
||||
const below = spotlight.top + spotlight.height + 12
|
||||
if (below + TOOLTIP_ESTIMATED_HEIGHT <= viewportBottom) {
|
||||
return below
|
||||
}
|
||||
|
||||
const above = spotlight.top - 12 - TOOLTIP_ESTIMATED_HEIGHT
|
||||
if (above >= TOOLTIP_EDGE_MARGIN) {
|
||||
return above
|
||||
}
|
||||
|
||||
return Math.max(
|
||||
TOOLTIP_EDGE_MARGIN,
|
||||
Math.min(below, viewportBottom - TOOLTIP_ESTIMATED_HEIGHT)
|
||||
)
|
||||
}
|
||||
|
||||
export default function AppTourOverlay() {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
@@ -111,12 +132,8 @@ export default function AppTourOverlay() {
|
||||
const tooltipStyle = centered
|
||||
? undefined
|
||||
: spotlight
|
||||
? {
|
||||
top: Math.min(window.innerHeight - 220, spotlight.top + spotlight.height + 12),
|
||||
left: Math.min(window.innerWidth - 340, Math.max(16, spotlight.left)),
|
||||
maxWidth: '420px'
|
||||
}
|
||||
: { top: '20%', left: '50%', transform: 'translateX(-50%)', maxWidth: '420px' }
|
||||
? { top: computeTooltipTop(spotlight) }
|
||||
: { top: '20%' }
|
||||
|
||||
const backdropStyle = spotlight && !centered
|
||||
? { clipPath: buildCutoutClipPath(spotlight) }
|
||||
|
||||
@@ -372,7 +372,7 @@ export default function LogEntriesList({
|
||||
<Calendar size={24} className="form-icon" />
|
||||
<h2>{t('logs.title')}</h2>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<div className="section-toolbar">
|
||||
<button className="btn secondary" onClick={handleDownloadCsv} disabled={loading || exporting || entries.length === 0} style={{ width: 'auto', padding: '8px 16px' }} title={t('logs.export_csv')}>
|
||||
<Download size={16} />
|
||||
<span className="hide-mobile">{exporting ? t('logs.exporting') : t('logs.export_csv')}</span>
|
||||
@@ -384,9 +384,9 @@ export default function LogEntriesList({
|
||||
</button>
|
||||
|
||||
{!readOnly && (
|
||||
<button className="btn primary" onClick={handleCreate} disabled={loading || exporting} style={{ width: 'auto', padding: '8px 16px' }}>
|
||||
<button className="btn primary" onClick={handleCreate} disabled={loading || exporting} style={{ width: 'auto', padding: '8px 16px' }} title={t('logs.new_entry')}>
|
||||
<Plus size={16} />
|
||||
{t('logs.new_entry')}
|
||||
<span className="hide-mobile">{t('logs.new_entry')}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -992,7 +992,7 @@ export default function LogEntryEditor({
|
||||
style={{ width: 'auto', padding: '8px 16px' }}
|
||||
>
|
||||
<Download size={16} />
|
||||
<span>{exporting ? t('logs.exporting_pdf') : t('logs.export_pdf')}</span>
|
||||
<span className="hide-mobile">{exporting ? t('logs.exporting_pdf') : t('logs.export_pdf')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1567,15 +1567,16 @@ export default function LogEntryEditor({
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
<div className="track-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn secondary"
|
||||
onClick={() => downloadTrackFile(savedTrack)}
|
||||
style={{ width: 'auto', padding: '6px 12px', fontSize: '13px', display: 'flex', alignItems: 'center', gap: '4px' }}
|
||||
title={t('logs.gps_tracking_btn_gpx')}
|
||||
>
|
||||
<Download size={14} />
|
||||
{t('logs.gps_tracking_btn_gpx')}
|
||||
<span className="hide-mobile">{t('logs.gps_tracking_btn_gpx')}</span>
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button
|
||||
@@ -1583,9 +1584,10 @@ export default function LogEntryEditor({
|
||||
className="btn secondary"
|
||||
onClick={handleDeleteTrack}
|
||||
style={{ width: 'auto', padding: '6px 12px', fontSize: '13px', display: 'flex', alignItems: 'center', gap: '4px', background: 'rgba(239, 68, 68, 0.1)', color: '#ef4444', borderColor: 'rgba(239, 68, 68, 0.2)' }}
|
||||
title={t('logs.gps_track_delete')}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
{t('logs.gps_track_delete')}
|
||||
<span className="hide-mobile">{t('logs.gps_track_delete')}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -414,7 +414,7 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF
|
||||
</div>
|
||||
|
||||
{shareEnabled && shareLink && (
|
||||
<div className="input-group mb-4" style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<div className="input-group mb-4 copy-link-row">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
@@ -455,7 +455,7 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF
|
||||
{t('logs.invite_link_desc')}
|
||||
</p>
|
||||
|
||||
<div className="form-actions" style={{ justifyContent: 'flex-start', gap: '12px', marginBottom: '20px' }}>
|
||||
<div className="form-actions form-actions--start" style={{ gap: '12px', marginBottom: '20px' }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn primary"
|
||||
@@ -469,7 +469,7 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF
|
||||
</div>
|
||||
|
||||
{inviteLink && (
|
||||
<div className="input-group mb-6" style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<div className="input-group mb-6 copy-link-row">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
|
||||
@@ -250,9 +250,12 @@
|
||||
<section class="features" aria-label="Funktionen">
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Reisetage im nautischen Logbuch-Format (Hafen, Wetter, Besegelung, Crew, Tankstände, etc.)</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Offline-fähige PWA — läuft auf jedem Smartphone & Tablet</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Passwortlose Passkey-Anmeldung & Ende-zu-Ende Verschlüsselung</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>GPS-Track Upload (GPX/KML), Karte & Streckenstatistik</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Einfache passwortlose Passkey-Anmeldung</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Ende-zu-Ende Verschlüsselung</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>GPS-Track Upload (GPX/KML) mit Kartendarstellung</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Streckenstatistik</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Foto-Anhänge pro Reisetag</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Foto-Anhänge für Skipper und Crew</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Crew einladen — gemeinsam am Logbuch arbeiten</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>PDF- & CSV-Export</span></div>
|
||||
<div class="feature"><span class="feature-icon">✦</span><span>Verschlüsseltes Backup & Wiederherstellung</span></div>
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user