added jounal page

This commit is contained in:
haerikimmm
2026-06-12 15:55:43 +09:00
parent cb0e5602c8
commit 9be793e2dd

View File

@@ -1,156 +1,401 @@
<script>
import PhotoGallery from '../shared/PhotoGallery.svelte';
/** @type {{ entry: import('../stores/journalStore.js').JournalEntry, onBack: () => void }} */
let { entry, onBack } = $props();
function formatDate(/** @type {string} */ iso) {
const countryCodeMap = {
'Japan': 'JP', 'France': 'FR', 'Spain': 'ES', 'USA': 'US',
'Thailand': 'TH', 'Germany': 'DE', 'Italy': 'IT', 'UK': 'GB',
'Australia': 'AU', 'Canada': 'CA', 'China': 'CN', 'India': 'IN',
'Brazil': 'BR', 'Mexico': 'MX', 'Portugal': 'PT', 'Netherlands': 'NL',
'Greece': 'GR', 'Turkey': 'TR', 'Vietnam': 'VN', 'Indonesia': 'ID',
'South Korea': 'KR', 'Singapore': 'SG', 'Taiwan': 'TW', 'New Zealand': 'NZ',
};
function flagEmoji(country) {
const code = countryCodeMap[country];
if (!code) return '';
return [...code].map(c => String.fromCodePoint(0x1F1E6 - 65 + c.charCodeAt(0))).join('');
}
function formatDate(iso) {
return new Date(iso).toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric',
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
});
}
let lightboxSrc = $state(null);
</script>
<article class="detail-page">
<!-- 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}
<button class="back-btn" onclick={onBack} aria-label="Back to timeline">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.8"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Back
</button>
<div class="detail-layout">
<div class="hero-gallery-wrap">
<PhotoGallery photos={entry.photos} height="380px" thumbs counter />
<!-- ── Left: photo grid ── -->
<div class="photo-col">
<!-- Country overlay top-left -->
<div class="photo-country">
<span class="photo-flag">{flagEmoji(entry.location.country)}</span>
<div>
<p class="photo-city">{entry.location.city}</p>
<p class="photo-country-name">{entry.location.country}</p>
</div>
</div>
<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>
<div class="detail-content">
<div class="meta-row">
<span class="badge loc-badge">📍 {entry.location.city}, {entry.location.country}</span>
<span class="badge trip-badge trip-badge--{entry.tripType}">
{entry.tripType === 'solo' ? '🧍 Solo' : '👥 With Friends'}
</span>
</div>
<!-- ── Right: Q&A ── -->
<div class="info-col">
<div class="info-inner">
<h1 class="detail-title">{entry.title}</h1>
<div class="stats-row">
<div class="stat">
<span class="stat-label">Date</span>
<time class="stat-value" datetime={entry.date}>{formatDate(entry.date)}</time>
</div>
<div class="stat-divider"></div>
<div class="stat">
<span class="stat-label">Duration</span>
<span class="stat-value">{entry.days} {entry.days === 1 ? 'day' : 'days'}</span>
</div>
</div>
<div class="qa-list">
<hr class="section-divider" />
<p class="detail-memo">{entry.memo}</p>
<hr class="section-divider" />
<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}
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 class="qa-item">
<p class="question">Trip soundtrack</p>
<div class="song-answer">
<div class="song-icon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 18V5l12-2v13"/>
<circle cx="6" cy="18" r="3"/>
<circle cx="18" cy="16" r="3"/>
</svg>
</div>
<div>
<p class="song-title">{entry.song.title}</p>
<p class="song-artist">{entry.song.artist}</p>
</div>
</div>
</div>
<div class="song-row">
<div class="song-icon-wrap" aria-hidden="true">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M9 18V5l12-2v13" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="6" cy="18" r="3" stroke="currentColor" stroke-width="2"/>
<circle cx="18" cy="16" r="3" stroke="currentColor" stroke-width="2"/>
</svg>
</div>
<div class="song-text">
<span class="song-label">Soundtrack</span>
<span class="song-name">{entry.song.title}</span>
<span class="song-artist">{entry.song.artist}</span>
</div>
</div>
</div>
</article>
<!-- ── Floating action buttons ── -->
<div class="fab-group">
<button class="fab fab-delete" title="Delete entry">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/>
</svg>
</button>
<button class="fab fab-edit" title="Edit entry">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<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>
</div>
</div>
<style>
.detail-page {
max-width: 720px;
margin: 0 auto;
padding: 48px 40px 100px;
font-family: var(--sans, system-ui, sans-serif);
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 300;
color: var(--text, #6b6375);
background: none;
border: none;
cursor: pointer;
padding: 6px 0;
margin-bottom: 28px;
transition: color 0.15s;
}
.back-btn:hover { color: var(--accent); }
.hero-gallery-wrap {
border-radius: 16px;
overflow: hidden;
margin-bottom: 28px;
}
.detail-content { text-align: left; }
.meta-row {
/* ── Two-column layout ── */
.detail-layout {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 14px;
flex-direction: row;
height: 100%;
position: relative;
overflow: hidden;
}
.badge {
font-size: 12px;
/* ── Left: photos ── */
.photo-col {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: #f0f0f0;
}
/* Country bar at top of photo column */
.photo-country {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: var(--bg);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.photo-flag { font-size: 20px; line-height: 1; }
.photo-city {
font-size: 11px;
font-weight: 300;
padding: 4px 10px;
border-radius: 20px;
color: var(--text-sub);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.loc-badge { background: var(--accent-bg); color: var(--accent); }
.trip-badge--solo { background: rgba(245,158,11,0.1); color: #92400e; }
.trip-badge--friends { background: var(--accent-bg); color: var(--accent); }
.detail-title {
font-size: 28px;
.photo-country-name {
font-size: 15px;
font-weight: 400;
color: var(--text-h, #08060d);
margin: 0 0 20px;
letter-spacing: -0.6px;
color: var(--text-h);
letter-spacing: -0.2px;
line-height: 1.2;
}
.stats-row { display: flex; align-items: center; gap: 20px; margin-bottom: 24px; }
.stat { display: flex; flex-direction: column; gap: 2px; }
.stat-label { font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text, #6b6375); }
.stat-value { font-size: 15px; font-weight: 300; color: var(--text-h, #08060d); }
.stat-divider { width: 1px; height: 32px; background: var(--border, #e5e4e7); }
.photo-scroll {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.section-divider { border: none; border-top: 1px solid var(--border, #e5e4e7); margin: 24px 0; }
.photo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
grid-auto-rows: 200px;
}
.detail-memo { font-size: 16px; line-height: 1.75; color: var(--text, #6b6375); margin: 0; }
.photo-cell {
overflow: hidden;
background: var(--bg-subtle);
border-radius: 4px;
cursor: zoom-in;
}
/* First photo spans full width when there are multiple */
.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;
}
.song-row { display: flex; align-items: center; gap: 14px; }
.song-icon-wrap {
width: 44px; height: 44px; border-radius: 50%;
.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: 40px 32px 100px;
}
/* Heading */
.entry-heading {
margin-bottom: 36px;
padding-bottom: 24px;
border-bottom: 1px solid var(--border);
}
.entry-city {
font-size: 12px;
font-weight: 300;
color: var(--text-sub);
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 2px;
}
.entry-country {
font-size: 24px;
font-weight: 400;
color: var(--text-h);
letter-spacing: -0.5px;
line-height: 1.1;
}
/* Q&A list */
.qa-list {
display: flex;
flex-direction: column;
gap: 0;
}
.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;
}
/* Song answer */
.song-answer {
display: flex;
align-items: center;
gap: 10px;
}
.song-icon {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--accent-bg);
color: var(--accent);
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.song-title {
font-size: 14px;
font-weight: 400;
color: var(--text-h);
}
.song-artist {
font-size: 12px;
font-weight: 300;
color: var(--text-sub);
margin-top: 2px;
}
.song-text { display: flex; flex-direction: column; gap: 2px; }
.song-label { font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text, #6b6375); }
.song-name { font-size: 15px; font-weight: 300; color: var(--text-h, #08060d); }
.song-artist { font-size: 13px; color: var(--text, #6b6375); }
@media (max-width: 600px) {
.detail-page { padding: 24px 16px 60px; }
.detail-title { font-size: 22px; }
/* ── Floating action buttons ── */
.fab-group {
position: absolute;
bottom: 32px;
right: 28px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 10px;
z-index: 10;
}
.fab {
display: flex;
align-items: center;
gap: 7px;
font-family: var(--sans);
font-size: 13px;
font-weight: 300;
padding: 10px 18px 10px 14px;
border-radius: 40px;
border: 1px solid var(--border);
cursor: pointer;
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
transition: box-shadow 0.15s, transform 0.15s;
letter-spacing: 0.02em;
}
.fab:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.15); transform: translateY(-1px); }
.fab-edit {
background: var(--accent);
color: #fff;
border-color: transparent;
}
.fab-delete {
background: var(--bg);
color: var(--text);
padding: 10px 14px;
border-radius: 50%;
width: 40px;
height: 40px;
justify-content: center;
}
.fab-delete:hover { color: #dc2626; border-color: #fca5a5; }
/* ── 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-layout { 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); }
.fab-group { bottom: 20px; right: 16px; }
}
</style>