353 lines
8.6 KiB
Svelte
353 lines
8.6 KiB
Svelte
<script>
|
|
import { removeEntry } from '../stores/entriesStore.svelte.js';
|
|
import { flagEmoji } from '../shared/countries.js';
|
|
import DeleteConfirm from './DeleteConfirm.svelte';
|
|
|
|
/** @type {{ entry: import('../shared/types.js').JournalEntry, onBack: () => void, onEdit: () => void }} */
|
|
let { entry, onBack, onEdit } = $props();
|
|
|
|
let showDeleteConfirm = $state(false);
|
|
|
|
function handleDelete() {
|
|
removeEntry(entry.id);
|
|
onBack();
|
|
}
|
|
|
|
function formatDate(iso) {
|
|
return new Date(iso).toLocaleDateString('en-US', {
|
|
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
|
|
});
|
|
}
|
|
|
|
let lightboxSrc = $state(null);
|
|
</script>
|
|
|
|
{#if showDeleteConfirm}
|
|
<DeleteConfirm {entry} onConfirm={handleDelete} onCancel={() => showDeleteConfirm = false} />
|
|
{/if}
|
|
|
|
<!-- Lightbox -->
|
|
{#if lightboxSrc}
|
|
<div class="lightbox" onclick={() => lightboxSrc = null} role="button" tabindex="0"
|
|
onkeydown={(e) => e.key === 'Escape' && (lightboxSrc = null)}>
|
|
<img src={lightboxSrc} alt="" />
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="detail-layout">
|
|
|
|
<!-- ── Full-width top bar ── -->
|
|
<header class="detail-topbar">
|
|
<div class="topbar-left">
|
|
<button class="topbar-btn" onclick={onBack}>
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M19 12H5M12 5l-7 7 7 7"/>
|
|
</svg>
|
|
Back
|
|
</button>
|
|
|
|
<div class="topbar-divider"></div>
|
|
|
|
<span class="topbar-flag">{flagEmoji(entry.location.country)}</span>
|
|
<div class="topbar-place">
|
|
<span class="topbar-city">{entry.location.cities.join(', ')}</span>
|
|
<span class="topbar-country">{entry.location.country}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="topbar-right">
|
|
<button class="topbar-btn" title="Edit entry" onclick={onEdit}>
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
</svg>
|
|
Edit
|
|
</button>
|
|
<button class="topbar-btn topbar-btn--danger" title="Delete entry" onclick={() => showDeleteConfirm = true}>
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/>
|
|
</svg>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- ── Body: photo left + Q&A right ── -->
|
|
<div class="detail-body">
|
|
|
|
<!-- Left: photos -->
|
|
<div class="photo-col">
|
|
<div class="photo-scroll">
|
|
{#if entry.photos.length === 0}
|
|
<div class="no-photos">No photos</div>
|
|
{:else}
|
|
<div class="photo-grid">
|
|
{#each entry.photos as photo, i}
|
|
<div class="photo-cell" class:cell-wide={i === 0 && entry.photos.length > 1}>
|
|
<img src={photo} alt=""
|
|
onclick={() => lightboxSrc = photo}
|
|
onerror={(e) => e.currentTarget.parentElement.classList.add('cell-broken')} />
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right: Q&A -->
|
|
<div class="info-col">
|
|
<div class="info-inner">
|
|
<div class="qa-list">
|
|
|
|
<div class="qa-item">
|
|
<p class="question">When did you go?</p>
|
|
<p class="answer">{formatDate(entry.date)}</p>
|
|
</div>
|
|
|
|
<div class="qa-item">
|
|
<p class="question">How long did you stay?</p>
|
|
<p class="answer">{entry.days} {entry.days === 1 ? 'day' : 'days'}</p>
|
|
</div>
|
|
|
|
<div class="qa-item">
|
|
<p class="question">Who did you go with?</p>
|
|
<p class="answer">
|
|
{#if entry.tripType === 'solo'}
|
|
Just me — solo trip
|
|
{:else if entry.tripType === 'family'}
|
|
With family
|
|
{:else}
|
|
With friends
|
|
{/if}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="qa-item">
|
|
<p class="question">How was it?</p>
|
|
<p class="answer memo">{entry.memo}</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* ── Outer layout: column (topbar + body) ── */
|
|
.detail-layout {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ── Full-width top bar ── */
|
|
.detail-topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 20px;
|
|
height: 60px;
|
|
flex-shrink: 0;
|
|
background: var(--bg);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.topbar-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.topbar-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.topbar-divider {
|
|
width: 1px;
|
|
height: 20px;
|
|
background: var(--border);
|
|
}
|
|
|
|
.topbar-flag { font-size: 20px; line-height: 1; }
|
|
|
|
.topbar-place {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 6px;
|
|
}
|
|
.topbar-city {
|
|
font-size: 13px;
|
|
font-weight: 300;
|
|
color: var(--text-sub);
|
|
}
|
|
.topbar-country {
|
|
font-size: 17px;
|
|
font-weight: 400;
|
|
color: var(--text-h);
|
|
letter-spacing: -0.3px;
|
|
}
|
|
|
|
.topbar-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-family: var(--sans);
|
|
font-size: 15px;
|
|
font-weight: 400;
|
|
color: var(--text);
|
|
background: none;
|
|
border: 1px solid transparent;
|
|
border-radius: 10px;
|
|
padding: 8px 14px;
|
|
cursor: pointer;
|
|
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
.topbar-btn:hover {
|
|
background: var(--bg-subtle);
|
|
border-color: var(--border);
|
|
color: var(--text-h);
|
|
}
|
|
.topbar-btn--danger:hover { color: #dc2626; background: #fff1f1; border-color: #fca5a5; }
|
|
|
|
/* ── Body row ── */
|
|
.detail-body {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: row;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ── Left: photos ── */
|
|
.photo-col {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
background: #f0f0f0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.photo-scroll {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 8px;
|
|
}
|
|
|
|
.photo-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 4px;
|
|
grid-auto-rows: 200px;
|
|
}
|
|
|
|
.photo-cell {
|
|
overflow: hidden;
|
|
background: var(--bg-subtle);
|
|
border-radius: 4px;
|
|
cursor: zoom-in;
|
|
}
|
|
.photo-cell.cell-wide {
|
|
grid-column: 1 / -1;
|
|
grid-row: span 2;
|
|
}
|
|
.photo-cell img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
transition: transform 0.2s ease;
|
|
}
|
|
.photo-cell:hover img { transform: scale(1.03); }
|
|
.photo-cell.cell-broken {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--text-sub);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.no-photos {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
color: var(--text-sub);
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* ── Right: Q&A ── */
|
|
.info-col {
|
|
width: 440px;
|
|
flex-shrink: 0;
|
|
overflow-y: auto;
|
|
border-left: 1px solid var(--border);
|
|
background: var(--bg);
|
|
}
|
|
|
|
.info-inner {
|
|
padding: 36px 32px 80px;
|
|
}
|
|
|
|
.qa-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.qa-item {
|
|
padding: 20px 0;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.qa-item:first-child { padding-top: 0; }
|
|
.qa-item:last-child { border-bottom: none; }
|
|
|
|
.question {
|
|
font-size: 11px;
|
|
font-weight: 400;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
color: var(--accent);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.answer {
|
|
font-size: 15px;
|
|
font-weight: 300;
|
|
color: var(--text-h);
|
|
line-height: 1.5;
|
|
}
|
|
.answer.memo {
|
|
font-size: 14px;
|
|
color: var(--text);
|
|
line-height: 1.75;
|
|
}
|
|
|
|
/* ── Lightbox ── */
|
|
.lightbox {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0,0,0,0.9);
|
|
z-index: 100;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: zoom-out;
|
|
}
|
|
.lightbox img {
|
|
max-width: 90vw;
|
|
max-height: 90vh;
|
|
object-fit: contain;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* ── Responsive ── */
|
|
@media (max-width: 700px) {
|
|
.detail-body { flex-direction: column; overflow-y: auto; }
|
|
.photo-col { height: 260px; flex: none; }
|
|
.info-col { width: 100%; border-left: none; border-top: 1px solid var(--border); }
|
|
}
|
|
</style>
|