feat: redesign itinerary flow and add catppuccin mocha theme

This commit is contained in:
2026-03-07 10:20:06 +00:00
parent 0b514a99ea
commit 246d836459
25 changed files with 503 additions and 283 deletions

View File

@@ -56,7 +56,7 @@ Voyage aims to be simple, beautiful, and open to everyone — inheriting Adventu
<img src="./brand/screenshots/dashboard.png" alt="Dashboard" />
<p>Displays a summary of your locations, including your world travel stats.</p>
<img src="./brand/screenshots/itinerary.png" alt="Itinerary" />
<p>Plan your adventures and travel itinerary with a list of activities and a map view. View your trip in a variety of ways, including an itinerary list, a map view, and a calendar view. Order your plans and details to create the perfect trip.</p>
<p>Plan your adventures with a timeline-style itinerary planner. Each day shows numbered stops, compact transportation connectors between locations, and inline controls for adding places. Drag-and-drop reordering, day-level actions, and multiple views help you build the perfect trip.</p>
<img src="./brand/screenshots/countries.png" alt="Countries" />
<p>Lists all the countries you have visited and plan to visit, with the ability to filter by visit status.</p>
<img src="./brand/screenshots/regions.png" alt="Regions" />
@@ -98,11 +98,14 @@ Voyage aims to be simple, beautiful, and open to everyone — inheriting Adventu
- Upload trails and activities to your locations to remember your experiences with detailed maps and stats.
- **Plan Your Next Trip** 📃: Take the guesswork out of planning your next adventure with an easy-to-use itinerary planner.
- Itineraries can be created for any number of days and can include multiple destinations.
- A timeline-style day view shows ordered stops with numbered markers, compact transportation connector rows (mode, duration, distance), and inline add-place rows per day.
- Day-level quick actions include Auto-fill (to populate an empty itinerary from dated records) and an Optimize placeholder for future route optimization.
- Itineraries include many planning features like flight information, notes, checklists, and links to external resources.
- Itineraries can be shared with friends and family for collaborative planning.
- **Share Your Experiences** 📸: Share your adventures with friends and family and collaborate on trips together.
- Locations and itineraries can be shared via a public link or directly with other Voyage users.
- Collaborators can view and edit shared itineraries (collections), making planning a breeze.
- **Customizable Themes** 🎨: Choose from 10 built-in themes including Light, Dark, Dim, Night, Forest, Aqua, Catppuccin Mocha, Aesthetic Light, Aesthetic Dark, and Northern Lights. Theme selection persists across sessions.
<!-- Roadmap -->

View File

@@ -14,11 +14,14 @@ Voyage is a full-fledged travel companion. With Voyage, you can log your adventu
- Upload trails and activities to your locations to remember your experiences with detailed maps and stats.
- **Plan Your Next Trip** 📃: Take the guesswork out of planning your next adventure with an easy-to-use itinerary planner.
- Itineraries can be created for any number of days and can include multiple destinations.
- A timeline-style day view shows ordered stops with numbered markers, compact transportation connector rows (mode, duration, distance), and inline add-place rows per day.
- Day-level quick actions include Auto-fill (to populate an empty itinerary from dated records) and an Optimize placeholder for future route optimization.
- Itineraries include many planning features like flight information, notes, checklists, and links to external resources.
- Itineraries can be shared with friends and family for collaborative planning.
- **Share Your Experiences** 📸: Share your adventures with friends and family and collaborate on trips together.
- Locations and itineraries can be shared via a public link or directly with other Voyage users.
- Collaborators can view and edit shared itineraries (collections), making planning a breeze.
- **Customizable Themes** 🎨: Choose from 10 built-in themes including Light, Dark, Dim, Night, Forest, Aqua, Catppuccin Mocha, Aesthetic Light, Aesthetic Dark, and Northern Lights. Theme selection persists across sessions.
## Why Voyage?

View File

@@ -22,8 +22,8 @@ The term "Location" is now used instead of "Adventure" - the usage remains the s
#### Collections
- **Collection**: a collection is a way to group locations together. Collections are flexible and can be used in many ways. When no start or end date is added to a collection, it acts like a folder to group locations together. When a start and end date is added to a collection, it acts like a trip to group locations together that were visited during that time period. With start and end dates, the collection is transformed into a full itinerary with a map showing the route taken between locations. For example, you could have a collection for a trip to Europe with dates so you can plan where you want to visit, a collection of local hiking trails, or a collection for a list of restaurants you want to try.
- **Transportation**: a transportation is a collection exclusive feature that allows you to add transportation information to your trip. This can be used to show the route taken between locations and the mode of transportation used. It can also be used to track flight information, such as flight number and departure time.
- **Collection**: a collection is a way to group locations together. Collections are flexible and can be used in many ways. When no start or end date is added to a collection, it acts like a folder to group locations together. When a start and end date is added to a collection, it acts like a trip to group locations together that were visited during that time period. With start and end dates, the collection is transformed into a full itinerary with a timeline-style day view — each day displays numbered stops, compact transportation connector rows, and an inline add-place row. Day-level quick actions include Auto-fill (populates an empty itinerary from dated records) and an Optimize placeholder for future route optimization. The itinerary also includes a map showing the route taken between locations. For example, you could have a collection for a trip to Europe with dates so you can plan where you want to visit, a collection of local hiking trails, or a collection for a list of restaurants you want to try.
- **Transportation**: a transportation is a collection exclusive feature that allows you to add transportation information to your trip. In the itinerary timeline view, transportation items appear as compact connector rows between stops — showing the travel mode, duration, and distance. This can be used to show the route taken between locations and the mode of transportation used. It can also be used to track flight information, such as flight number and departure time.
- **Lodging**: a lodging is a collection exclusive feature that allows you to add lodging information to your trip. This can be used to plan where you will stay during your trip and add notes about the lodging location. It can also be used to track reservation information, such as reservation number and check-in time.
- **Note**: a note is a collection exclusive feature that allows you to add notes to your trip. This can be used to add additional information about your trip, such as a summary of the trip or a list of things to do. Notes can be assigned to a specific day of the trip to help organize the information.
- **Checklist**: a checklist is a collection exclusive feature that allows you to add a checklist to your trip. This can be used to create a list of things to do during your trip or for planning purposes like packing lists. Checklists can be assigned to a specific day of the trip to help organize the information.

View File

@@ -35,6 +35,7 @@
import { t } from 'svelte-i18n';
import { addToast } from '$lib/toasts';
import Globe from '~icons/mdi/globe';
import { TRANSPORTATION_TYPES_ICONS } from '$lib';
export let collection: Collection;
export let user: any;
@@ -396,6 +397,74 @@
return value.includes('T') ? value.split('T')[0] : value;
}
function getTransportationIcon(type: string | null | undefined) {
if (type && type in TRANSPORTATION_TYPES_ICONS) {
return TRANSPORTATION_TYPES_ICONS[type as keyof typeof TRANSPORTATION_TYPES_ICONS];
}
return '🚗';
}
function formatTransportationDuration(minutes: number | null | undefined): string | null {
if (minutes === null || minutes === undefined || Number.isNaN(minutes)) return null;
const safeMinutes = Math.max(0, Math.floor(minutes));
const hours = Math.floor(safeMinutes / 60);
const mins = safeMinutes % 60;
const parts = [] as string[];
if (hours) parts.push(`${hours}h`);
parts.push(`${mins}m`);
return parts.join(' ');
}
function formatTransportationDistance(distanceKm: number | null | undefined): string | null {
if (distanceKm === null || distanceKm === undefined || Number.isNaN(distanceKm)) return null;
if (distanceKm < 10) return `${distanceKm.toFixed(1)} km`;
return `${Math.round(distanceKm)} km`;
}
function editTransportationInline(transportation: Transportation) {
handleEditTransportation({ detail: transportation } as CustomEvent<Transportation>);
}
async function removeItineraryEntry(item: CollectionItineraryItem) {
if (!item?.id) return;
try {
const res = await fetch(`/api/itineraries/${item.id}`, {
method: 'DELETE'
});
if (!res.ok) throw new Error('Failed to remove itinerary item');
handleRemoveItineraryItem(new CustomEvent('removeFromItinerary', { detail: item }) as any);
addToast('info', $t('itinerary.item_remove_success'));
} catch (error) {
console.error('Error removing itinerary item:', error);
addToast('error', $t('itinerary.item_remove_error'));
}
}
async function deleteTransportationFromItinerary(
item: CollectionItineraryItem,
transportation: Transportation
) {
const confirmed = window.confirm($t('adventures.transportation_delete_confirm'));
if (!confirmed) return;
try {
const res = await fetch(`/api/transportations/${transportation.id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (!res.ok) throw new Error('Failed to delete transportation');
addToast('info', $t('transportation.transportation_deleted'));
handleItemDelete(new CustomEvent('delete', { detail: transportation.id }) as any);
} catch (error) {
console.error('Failed to delete transportation:', error);
addToast('error', $t('transportation.transportation_delete_error'));
}
}
function upsertNote(note: Note) {
const notes = collection.notes ? [...collection.notes] : [];
const idx = notes.findIndex((n) => n.id === note.id);
@@ -1944,7 +2013,7 @@
{/if}
</div>
<!-- Actions: saving indicator + Add dropdown -->
<!-- Actions: saving indicator + day quick actions -->
<div class="flex-none ml-3 flex items-start gap-2">
{#if savingDay === day.date}
<div>
@@ -1956,111 +2025,19 @@
{/if}
{#if canModify}
<div class="dropdown z-30">
<button
type="button"
class="btn btn-square btn-sm btn-outline p-1"
aria-haspopup="menu"
aria-expanded="false"
title={$t('adventures.add')}
>
<Plus class="w-5 h-5" />
</button>
<ul
class="dropdown-content menu p-2 shadow bg-base-300 rounded-box w-56"
role="menu"
>
<li>
<button
type="button"
role="menuitem"
class="w-full text-left"
on:click={() => {
linkModalTargetDate = day.date;
linkModalDisplayDate = day.displayDate;
isItineraryLinkModalOpen = true;
}}
>
{$t('itinerary.link_existing_item')}
</button>
</li>
<li class="menu-title">{$t('adventures.create_new')}</li>
<li>
<button
type="button"
role="menuitem"
class="w-full text-left"
on:click={() => {
pendingAddDate = day.date;
locationToEdit = null;
locationBeingUpdated = null;
isLocationModalOpen = true;
}}
>
{$t('locations.location')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
class="w-full text-left"
on:click={() => {
pendingAddDate = day.date;
lodgingToEdit = null;
lodgingBeingUpdated = null;
isLodgingModalOpen = true;
}}
>
{$t('adventures.lodging')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
class="w-full text-left"
on:click={() => {
pendingAddDate = day.date;
isTransportationModalOpen = true;
}}
>
{$t('adventures.transportation')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
class="w-full text-left"
on:click={() => {
pendingAddDate = day.date;
isNoteModalOpen = true;
}}
>
{$t('adventures.note')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
class="w-full text-left"
on:click={() => {
pendingAddDate = day.date;
isChecklistModalOpen = true;
}}
>
{$t('adventures.checklist')}
</button>
</li>
</ul>
</div>
<button
type="button"
class="btn btn-sm btn-outline"
disabled={true}
title={$t('itinerary.optimize')}
>
{$t('itinerary.optimize')}
</button>
{/if}
</div>
</div>
<!-- Day Items -->
<!-- Day Items (vertical timeline with ordered stops) -->
<div>
{#if day.items.length === 0}
<div
@@ -2082,7 +2059,7 @@
}}
on:consider={(e) => handleDndConsider(dayIndex, e)}
on:finalize={(e) => handleDndFinalize(dayIndex, e)}
class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3"
class="space-y-3"
>
{#each day.items as item, index (item.id)}
{@const objectType = item.item?.type || ''}
@@ -2091,173 +2068,223 @@
{@const isDraggingShadow = item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
<div
class="group relative transition-all duration-200 pointer-events-auto h-full {isDraggingShadow
class="group relative transition-all duration-200 pointer-events-auto {isDraggingShadow
? 'opacity-40 scale-95'
: ''}"
animate:flip={{ duration: flipDurationMs }}
>
{#if resolvedObj}
<!-- Drag Handle Container -->
{#if canModify}
<div
class="absolute left-2 top-2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
title={$t('itinerary.drag_to_reorder')}
>
<div class="flex gap-3">
<div class="relative flex flex-col items-center shrink-0 pt-1">
<div
class="itinerary-drag-handle btn btn-circle btn-xs btn-ghost bg-base-100/80 backdrop-blur-sm shadow-sm hover:bg-base-200 cursor-grab active:cursor-grabbing"
aria-label={$t('itinerary.drag_to_reorder')}
role="button"
tabindex="0"
class="w-7 h-7 rounded-full bg-primary text-primary-content text-xs font-bold flex items-center justify-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-3 w-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 8h16M4 16h16"
/>
</svg>
{index + 1}
</div>
{#if index < day.items.length - 1}
<div class="w-px bg-base-300 flex-1 min-h-10 mt-1"></div>
{/if}
</div>
{/if}
<!-- Order Badge
<div class="absolute right-2 top-2 z-10">
<div
class="badge badge-primary badge-sm font-bold shadow-md"
title="Item order"
>
#{index + 1}
</div>
</div> -->
<!-- Multi-day indicator for lodging -->
{#if multiDay && objectType === 'lodging'}
<div class="absolute left-2 bottom-2 z-10">
<div class="badge badge-info badge-xs gap-1 shadow-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-3 w-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
<div class="relative flex-1 min-w-0">
{#if canModify}
<div
class="absolute left-0 top-0 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
title={$t('itinerary.drag_to_reorder')}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span class="text-xs">{$t('itinerary.multi_day')}</span>
</div>
</div>
{/if}
<div
class="itinerary-drag-handle btn btn-circle btn-xs btn-ghost bg-base-100/80 backdrop-blur-sm shadow-sm hover:bg-base-200 cursor-grab active:cursor-grabbing"
aria-label={$t('itinerary.drag_to_reorder')}
role="button"
tabindex="0"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-3 w-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 8h16M4 16h16"
/>
</svg>
</div>
</div>
{/if}
<!-- Card with smooth transition and proper sizing for grid -->
<div class="transition-all duration-200 h-full">
<!-- Display the appropriate card based on type -->
{#if objectType === 'location'}
<LocationCard
adventure={resolvedObj}
on:edit={handleEditLocation}
on:delete={handleItemDelete}
on:duplicate={handleDuplicateLocation}
itineraryItem={item}
on:removeFromItinerary={handleRemoveItineraryItem}
on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
{user}
{collection}
compact={true}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{:else if objectType === 'transportation'}
<TransportationCard
transportation={resolvedObj}
{user}
{collection}
on:delete={handleItemDelete}
itineraryItem={item}
on:removeFromItinerary={handleRemoveItineraryItem}
on:edit={handleEditTransportation}
on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{:else if objectType === 'lodging'}
<LodgingCard
lodging={resolvedObj}
{user}
{collection}
itineraryItem={item}
on:delete={handleItemDelete}
on:removeFromItinerary={handleRemoveItineraryItem}
on:edit={handleEditLodging}
on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{:else if objectType === 'note'}
<!-- @ts-ignore - TypeScript can't narrow union type properly -->
<NoteCard
note={resolvedObj}
{user}
{collection}
on:delete={handleItemDelete}
itineraryItem={item}
on:removeFromItinerary={handleRemoveItineraryItem}
on:edit={handleEditNote}
on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{:else if objectType === 'checklist'}
<!-- @ts-ignore - TypeScript can't narrow union type properly -->
<ChecklistCard
checklist={resolvedObj}
{user}
{collection}
on:delete={handleItemDelete}
itineraryItem={item}
on:removeFromItinerary={handleRemoveItineraryItem}
on:edit={handleEditChecklist}
on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{/if}
{#if objectType === 'transportation'}
<div class="rounded-xl border border-base-300 bg-base-100 px-4 py-3">
<div class="flex items-center justify-between gap-3 mb-2">
<div class="flex items-center gap-2 min-w-0">
<span class="text-lg"
>{getTransportationIcon(resolvedObj.type)}</span
>
<p class="font-semibold truncate">{resolvedObj.name}</p>
<span class="badge badge-outline badge-sm truncate">
{$t(`transportation.modes.${resolvedObj.type}`) ||
resolvedObj.type}
</span>
</div>
<div class="text-xs opacity-70 flex items-center gap-2 shrink-0">
{#if formatTransportationDuration(resolvedObj.travel_duration_minutes)}
<span
>{formatTransportationDuration(
resolvedObj.travel_duration_minutes
)}</span
>
{/if}
{#if formatTransportationDistance(resolvedObj.distance)}
<span>{formatTransportationDistance(resolvedObj.distance)}</span
>
{/if}
</div>
</div>
<div class="text-sm opacity-80 truncate">
{resolvedObj.from_location || '—'} → {resolvedObj.to_location ||
'—'}
</div>
{#if canModify}
<div class="mt-2 flex flex-wrap gap-2">
<button
type="button"
class="btn btn-xs btn-ghost"
on:click={() => editTransportationInline(resolvedObj)}
>
{$t('transportation.edit')}
</button>
<button
type="button"
class="btn btn-xs btn-ghost"
on:click={() =>
handleOpenDayPickerForItem(
'transportation',
resolvedObj,
true,
day.date
)}
>
{$t('itinerary.change_day')}
</button>
<button
type="button"
class="btn btn-xs btn-ghost"
on:click={() =>
moveItemToGlobal('transportation', resolvedObj.id)}
>
{$t('itinerary.move_to_trip_context') || 'Move to Trip Context'}
</button>
<button
type="button"
class="btn btn-xs btn-ghost"
on:click={() => removeItineraryEntry(item)}
>
{$t('itinerary.remove_from_itinerary')}
</button>
<button
type="button"
class="btn btn-xs btn-error btn-outline"
on:click={() =>
deleteTransportationFromItinerary(item, resolvedObj)}
>
{$t('adventures.delete')}
</button>
</div>
{/if}
</div>
{:else}
{#if multiDay && objectType === 'lodging'}
<div class="mb-2">
<div class="badge badge-info badge-xs gap-1 shadow-sm">
<span class="text-xs">{$t('itinerary.multi_day')}</span>
</div>
</div>
{/if}
{#if objectType === 'location'}
<LocationCard
adventure={resolvedObj}
on:edit={handleEditLocation}
on:delete={handleItemDelete}
on:duplicate={handleDuplicateLocation}
itineraryItem={item}
on:removeFromItinerary={handleRemoveItineraryItem}
on:moveToGlobal={(e) =>
moveItemToGlobal(e.detail.type, e.detail.id)}
{user}
{collection}
compact={true}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{:else if objectType === 'lodging'}
<LodgingCard
lodging={resolvedObj}
{user}
{collection}
itineraryItem={item}
on:delete={handleItemDelete}
on:removeFromItinerary={handleRemoveItineraryItem}
on:edit={handleEditLodging}
on:moveToGlobal={(e) =>
moveItemToGlobal(e.detail.type, e.detail.id)}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{:else if objectType === 'note'}
<NoteCard
note={resolvedObj}
{user}
{collection}
on:delete={handleItemDelete}
itineraryItem={item}
on:removeFromItinerary={handleRemoveItineraryItem}
on:edit={handleEditNote}
on:moveToGlobal={(e) =>
moveItemToGlobal(e.detail.type, e.detail.id)}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{:else if objectType === 'checklist'}
<ChecklistCard
checklist={resolvedObj}
{user}
{collection}
on:delete={handleItemDelete}
itineraryItem={item}
on:removeFromItinerary={handleRemoveItineraryItem}
on:edit={handleEditChecklist}
on:moveToGlobal={(e) =>
moveItemToGlobal(e.detail.type, e.detail.id)}
on:changeDay={(e) =>
handleOpenDayPickerForItem(
e.detail.type,
e.detail.item,
e.detail.forcePicker,
day.date
)}
/>
{/if}
{/if}
</div>
</div>
{:else}
<!-- Fallback for unresolved items -->
@@ -2269,6 +2296,108 @@
{/each}
</div>
{/if}
{#if canModify}
<div class="mt-4 pt-4 border-t border-base-300 border-dashed">
<div class="flex items-center justify-between gap-3 flex-wrap">
<p class="text-sm opacity-70">{$t('itinerary.add_place')}</p>
<div class="dropdown dropdown-end z-30">
<button
type="button"
class="btn btn-sm btn-outline"
aria-haspopup="menu"
aria-expanded="false"
>
<Plus class="w-4 h-4" />
{$t('adventures.add')}
</button>
<ul
class="dropdown-content menu p-2 shadow bg-base-300 rounded-box w-56"
role="menu"
>
<li>
<button
type="button"
role="menuitem"
on:click={() => {
linkModalTargetDate = day.date;
linkModalDisplayDate = day.displayDate;
isItineraryLinkModalOpen = true;
}}
>
{$t('itinerary.link_existing_item')}
</button>
</li>
<li class="menu-title">{$t('adventures.create_new')}</li>
<li>
<button
type="button"
role="menuitem"
on:click={() => {
pendingAddDate = day.date;
locationToEdit = null;
locationBeingUpdated = null;
isLocationModalOpen = true;
}}
>
{$t('locations.location')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
on:click={() => {
pendingAddDate = day.date;
lodgingToEdit = null;
lodgingBeingUpdated = null;
isLodgingModalOpen = true;
}}
>
{$t('adventures.lodging')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
on:click={() => {
pendingAddDate = day.date;
isTransportationModalOpen = true;
}}
>
{$t('adventures.transportation')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
on:click={() => {
pendingAddDate = day.date;
isNoteModalOpen = true;
}}
>
{$t('adventures.note')}
</button>
</li>
<li>
<button
type="button"
role="menuitem"
on:click={() => {
pendingAddDate = day.date;
isChecklistModalOpen = true;
}}
>
{$t('adventures.checklist')}
</button>
</li>
</ul>
</div>
</div>
</div>
{/if}
</div>
<!-- Overnight Lodging + Dated Trip-wide Indicators (share row to save space) -->

View File

@@ -462,6 +462,7 @@ export let themes = [
{ name: 'night', label: 'Night' },
{ name: 'forest', label: 'Forest' },
{ name: 'aqua', label: 'Aqua' },
{ name: 'catppuccinMocha', label: 'Catppuccin Mocha' },
{ name: 'aestheticLight', label: 'Aesthetic Light' },
{ name: 'aestheticDark', label: 'Aesthetic Dark' },
{ name: 'northernLights', label: 'Northern Lights' }
@@ -601,6 +602,7 @@ export function getIsDarkMode() {
const isDark =
theme === 'dark' ||
theme === 'night' ||
theme === 'catppuccinMocha' ||
theme === 'aestheticDark' ||
theme === 'northernLights' ||
theme === 'forest' ||

View File

@@ -704,6 +704,7 @@
"aqua": "أكوا",
"dark": "مظلم",
"dim": "خافت",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "غابة",
"light": "ضوء",
"night": "ليلة",
@@ -1138,6 +1139,8 @@
"trip_context": "سياق الرحلة",
"trip_context_info": "تنطبق عناصر سياق الرحلة على الرحلة بأكملها — على سبيل المثال المواقع التي تمثل الوجهة نفسها، أو الملاحظات العامة، أو قوائم التعبئة المهمة للرحلة بأكملها.",
"unscheduled_items": "العناصر غير المجدولة",
"unscheduled_items_desc": "ترتبط هذه العناصر بهذه الرحلة ولكن لم تتم إضافتها إلى يوم محدد بعد."
"unscheduled_items_desc": "ترتبط هذه العناصر بهذه الرحلة ولكن لم تتم إضافتها إلى يوم محدد بعد.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -481,6 +481,7 @@
"aqua": "Aqua",
"dark": "Dunkel",
"dim": "Düster",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Wald",
"light": "Hell",
"night": "Nacht",
@@ -1138,6 +1139,8 @@
"trip_context": "Reisekontext",
"trip_context_info": "Reisekontextelemente gelten für die gesamte Reise zum Beispiel Orte, die das Ziel selbst darstellen, allgemeine Notizen oder Packlisten, die für die gesamte Reise wichtig sind.",
"unscheduled_items": "Ungeplante Einträge",
"unscheduled_items_desc": "Diese Einträge sind mit dieser Reise verknüpft, wurden aber noch keinem bestimmten Tag hinzugefügt."
"unscheduled_items_desc": "Diese Einträge sind mit dieser Reise verknüpft, wurden aber noch keinem bestimmten Tag hinzugefügt.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -1096,7 +1096,9 @@
"failed_to_add_to_trip_context": "Failed to add item to trip context",
"remove_from_trip_context": "Remove from Context",
"drag_to_reorder": "Drag to reorder",
"add_to_day": "Add to day"
"add_to_day": "Add to day",
"optimize": "Optimize",
"add_place": "+ Add place"
},
"common": {
"show_less": "Hide details",

View File

@@ -18,6 +18,7 @@
"aqua": "Agua",
"dark": "Oscuro",
"dim": "Oscuro",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Bosque",
"light": "Luz",
"night": "Noche",
@@ -1138,6 +1139,8 @@
"trip_context": "Contexto del viaje",
"trip_context_info": "Los elementos del contexto del viaje se aplican a todo el viaje; por ejemplo, ubicaciones que son el destino en sí, notas generales o listas de equipaje que son importantes para todo el viaje.",
"unscheduled_items": "Artículos no programados",
"unscheduled_items_desc": "Estos elementos están vinculados a este viaje pero aún no se han agregado a un día específico."
"unscheduled_items_desc": "Estos elementos están vinculados a este viaje pero aún no se han agregado a un día específico.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -481,6 +481,7 @@
"aqua": "Aqua",
"dark": "Sombre",
"dim": "Faible",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Forêt",
"light": "Lumière",
"night": "Nuit",
@@ -1138,6 +1139,8 @@
"trip_context_info": "Les éléments de contexte du voyage s'appliquent à l'ensemble du voyage, par exemple les lieux qui constituent la destination elle-même, les notes générales ou les listes de colisage importantes pour l'ensemble du voyage.",
"unscheduled_items": "Éléments non planifiés",
"unscheduled_items_desc": "Ces éléments sont liés à ce voyage mais n'ont pas encore été ajoutés à un jour spécifique.",
"link_existing_item": "Lier un élément existant"
"link_existing_item": "Lier un élément existant",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -25,7 +25,8 @@
"aestheticDark": "Esztétikus sötét",
"aqua": "Akvamarin",
"northernLights": "Sarki fény",
"dim": "Halvány"
"dim": "Halvány",
"catppuccinMocha": "Catppuccin Mocha"
},
"navigation": "Navigáció",
"worldtravel": "Világutazás"
@@ -1138,6 +1139,8 @@
"trip_context": "Utazási kontextus",
"trip_context_info": "Az utazási kontextus elemei az egész utazásra vonatkoznak például olyan helyek, amelyek maga az úti cél, általános megjegyzések vagy csomaglisták, amelyek az egész utazás szempontjából fontosak.",
"unscheduled_items": "Nem ütemezett tételek",
"unscheduled_items_desc": "Ezek az elemek ehhez az utazáshoz kapcsolódnak, de még nem adták hozzá egy adott naphoz."
"unscheduled_items_desc": "Ezek az elemek ehhez az utazáshoz kapcsolódnak, de még nem adták hozzá egy adott naphoz.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -481,6 +481,7 @@
"aqua": "Aqua",
"dark": "Buio",
"dim": "Fioco",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Foresta",
"light": "Leggero",
"night": "Notte",
@@ -1138,6 +1139,8 @@
"trip_context": "Contesto del viaggio",
"trip_context_info": "Gli elementi contestuali del viaggio si applicano all'intero viaggio, ad esempio luoghi che rappresentano la destinazione stessa, note generali o liste di cose da portare importanti per l'intero viaggio.",
"unscheduled_items": "Elementi non pianificati",
"unscheduled_items_desc": "Questi elementi sono collegati a questo viaggio ma non sono ancora stati aggiunti a un giorno specifico."
"unscheduled_items_desc": "Questi elementi sono collegati a questo viaggio ma non sono ancora stati aggiunti a un giorno specifico.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -704,6 +704,7 @@
"aqua": "アクア",
"dark": "暗い",
"dim": "薄暗い",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "森",
"light": "ライト",
"night": "夜",
@@ -1138,6 +1139,8 @@
"trip_context": "旅行のコンテキスト",
"trip_context_info": "旅行コンテキスト項目は、旅行全体に適用されます。たとえば、目的地そのものである場所、一般的なメモ、旅行全体にとって重要な持ち物リストなどです。",
"unscheduled_items": "予定外の項目",
"unscheduled_items_desc": "これらのアイテムはこの旅行にリンクされていますが、まだ特定の日に追加されていません。"
"unscheduled_items_desc": "これらのアイテムはこの旅行にリンクされていますが、まだ特定の日に追加されていません。",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -644,6 +644,7 @@
"aqua": "아쿠아",
"dark": "어두운",
"dim": "어둑한",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "숲",
"light": "빛",
"night": "밤",
@@ -1138,6 +1139,8 @@
"trip_context": "여행 상황",
"trip_context_info": "여행 컨텍스트 항목은 전체 여행에 적용됩니다. 예를 들어 목적지 자체인 위치, 일반 참고 사항, 전체 여행에 중요한 짐 목록 등이 있습니다.",
"unscheduled_items": "예정되지 않은 품목",
"unscheduled_items_desc": "이 항목은 이 여행에 연결되어 있지만 아직 특정 날짜에 추가되지 않았습니다."
"unscheduled_items_desc": "이 항목은 이 여행에 연결되어 있지만 아직 특정 날짜에 추가되지 않았습니다.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -481,6 +481,7 @@
"aqua": "Aqua",
"dark": "Donker",
"dim": "Duister",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Woud",
"light": "Licht",
"night": "Nacht",
@@ -1138,6 +1139,8 @@
"trip_context": "Reiscontext",
"trip_context_info": "Reiscontextitems zijn van toepassing op de hele reis, bijvoorbeeld locaties die de bestemming zelf vormen, algemene opmerkingen of paklijsten die belangrijk zijn voor de hele reis.",
"unscheduled_items": "Niet-geplande items",
"unscheduled_items_desc": "Deze items zijn gekoppeld aan deze reis, maar nog niet toegevoegd aan een specifieke dag."
"unscheduled_items_desc": "Deze items zijn gekoppeld aan deze reis, maar nog niet toegevoegd aan een specifieke dag.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -22,6 +22,7 @@
"aqua": "Aqua",
"dark": "Mørk",
"dim": "Svak",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Skog",
"light": "Lys",
"night": "Natt",
@@ -1138,6 +1139,8 @@
"trip_context": "Turkontekst",
"trip_context_info": "Turkontekstelementer gjelder for hele turen for eksempel steder som er selve destinasjonen, generelle notater eller pakkelister som er viktige for hele turen.",
"unscheduled_items": "Ikke-planlagte elementer",
"unscheduled_items_desc": "Disse elementene er knyttet til denne turen, men har ikke blitt lagt til en bestemt dag ennå."
"unscheduled_items_desc": "Disse elementene er knyttet til denne turen, men har ikke blitt lagt til en bestemt dag ennå.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -20,6 +20,7 @@
"aqua": "Aqua",
"dark": "Ciemny",
"dim": "Ciemny",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Las",
"light": "Światło",
"night": "Noc",
@@ -1138,6 +1139,8 @@
"trip_context": "Kontekst podróży",
"trip_context_info": "Elementy kontekstu podróży dotyczą całej podróży — na przykład lokalizacje będące samym celem podróży, uwagi ogólne lub listy rzeczy do spakowania ważne dla całej podróży.",
"unscheduled_items": "Niezaplanowane pozycje",
"unscheduled_items_desc": "Te elementy są powiązane z tą podróżą, ale nie zostały jeszcze dodane do konkretnego dnia."
"unscheduled_items_desc": "Te elementy są powiązane z tą podróżą, ale nie zostały jeszcze dodane do konkretnego dnia.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -704,6 +704,7 @@
"aqua": "Aqua",
"dark": "Escuro",
"dim": "Escurecido",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Floresta",
"light": "Claro",
"night": "Noite",
@@ -1138,6 +1139,8 @@
"trip_context": "Contexto da viagem",
"trip_context_info": "Os itens de contexto da viagem aplicam-se a toda a viagem — por exemplo, locais que são o próprio destino, notas gerais ou listas de embalagem que são importantes para toda a viagem.",
"unscheduled_items": "Itens não programados",
"unscheduled_items_desc": "Esses itens estão vinculados a esta viagem, mas ainda não foram adicionados a um dia específico."
"unscheduled_items_desc": "Esses itens estão vinculados a esta viagem, mas ainda não foram adicionados a um dia específico.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -22,6 +22,7 @@
"aqua": "Аква",
"dark": "Темный",
"dim": "Тусклый",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Лес",
"light": "Свет",
"night": "Ночь",
@@ -1138,6 +1139,8 @@
"trip_context_info": "Элементы контекста поездки применяются ко всей поездке — например, места, которые являются пунктом назначения, общие заметки или упаковочные листы, важные для всей поездки.",
"unscheduled_items": "Незапланированные предметы",
"unscheduled_items_desc": "Эти элементы связаны с этой поездкой, но еще не добавлены к определенному дню.",
"link_existing_item": "Связать существующий элемент"
"link_existing_item": "Связать существующий элемент",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -25,7 +25,8 @@
"aestheticDark": "Estetická tmavá",
"aqua": "Aqua",
"northernLights": "Polárna žiara",
"dim": "Tlmená"
"dim": "Tlmená",
"catppuccinMocha": "Catppuccin Mocha"
},
"navigation": "Navigácia",
"worldtravel": "Svetové cestovanie"
@@ -1138,6 +1139,8 @@
"trip_context": "Kontext cesty",
"trip_context_info": "Kontextové položky cesty sa vzťahujú na celú cestu napríklad miesta, ktoré sú samotným cieľom, všeobecné poznámky alebo zoznamy balíkov, ktoré sú dôležité pre celú cestu.",
"unscheduled_items": "Neplánované položky",
"unscheduled_items_desc": "Tieto položky sú spojené s touto cestou, ale zatiaľ neboli pridané ku konkrétnemu dňu."
"unscheduled_items_desc": "Tieto položky sú spojené s touto cestou, ale zatiaľ neboli pridané ku konkrétnemu dňu.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -481,6 +481,7 @@
"aqua": "Vatten",
"dark": "Mörk",
"dim": "Dämpad",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Skog",
"light": "Ljus",
"night": "Natt",
@@ -1138,6 +1139,8 @@
"trip_context": "Resans sammanhang",
"trip_context_info": "Resans kontextobjekt gäller för hela resan till exempel platser som är själva destinationen, allmänna anteckningar eller packlistor som är viktiga för hela resan.",
"unscheduled_items": "Oschemalagda objekt",
"unscheduled_items_desc": "Dessa objekt är länkade till denna resa men har inte lagts till en specifik dag än."
"unscheduled_items_desc": "Dessa objekt är länkade till denna resa men har inte lagts till en specifik dag än.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -25,7 +25,8 @@
"aestheticDark": "Estetik Karanlık",
"aqua": "Su",
"northernLights": "Kuzey Işıkları",
"dim": "Loş"
"dim": "Loş",
"catppuccinMocha": "Catppuccin Mocha"
},
"navigation": "Navigasyon",
"worldtravel": "Dünya Seyahati"
@@ -1138,6 +1139,8 @@
"trip_context": "Seyahat İçeriği",
"trip_context_info": "Seyahat bağlamı öğeleri seyahatin tamamı için geçerlidir; örneğin varış noktasının kendisi olan konumlar, genel notlar veya seyahatin tamamı için önemli olan paket listeleri.",
"unscheduled_items": "Planlanmamış Öğeler",
"unscheduled_items_desc": "Bu öğeler bu geziye bağlı ancak henüz belirli bir güne eklenmedi."
"unscheduled_items_desc": "Bu öğeler bu geziye bağlı ancak henüz belirli bir güne eklenmedi.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -705,6 +705,7 @@
"aqua": "Аква",
"dark": "Темний",
"dim": "тьмяний",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "Ліс",
"light": "світло",
"night": "ніч",
@@ -1138,6 +1139,8 @@
"trip_context": "Контекст поїздки",
"trip_context_info": "Елементи контексту подорожі застосовуються до всієї подорожі — наприклад, місця, які є самим пунктом призначення, загальні примітки або пакувальні листи, важливі для всієї подорожі.",
"unscheduled_items": "Позапланові пункти",
"unscheduled_items_desc": "Ці елементи пов’язані з цією поїздкою, але ще не додані до певного дня."
"unscheduled_items_desc": "Ці елементи пов’язані з цією поїздкою, але ще не додані до певного дня.",
"optimize": "Optimize",
"add_place": "+ Add place"
}
}

View File

@@ -17,6 +17,7 @@
"aqua": "水色",
"dark": "深色",
"dim": "昏暗",
"catppuccinMocha": "Catppuccin Mocha",
"forest": "森林",
"light": "浅色",
"night": "夜色",
@@ -1098,7 +1099,9 @@
"remove_from_trip_context": "从上下文中删除",
"drag_to_reorder": "拖动以重新排序",
"add_to_day": "添加到日期",
"add_to_trip_context": "添加旅行背景"
"add_to_trip_context": "添加旅行背景",
"optimize": "Optimize",
"add_place": "+ Add place"
},
"collections": {
"all_items": "所有项目",

View File

@@ -61,6 +61,33 @@ export default {
fontFamily:
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
},
catppuccinMocha: {
primary: '#89b4fa',
'primary-focus': '#74c7ec',
'primary-content': '#1e1e2e',
secondary: '#f5c2e7',
'secondary-focus': '#cba6f7',
'secondary-content': '#1e1e2e',
accent: '#a6e3a1',
'accent-focus': '#94e2d5',
'accent-content': '#1e1e2e',
neutral: '#313244',
'neutral-focus': '#45475a',
'neutral-content': '#cdd6f4',
'base-100': '#1e1e2e',
'base-200': '#181825',
'base-300': '#11111b',
'base-content': '#cdd6f4',
info: '#89dceb',
success: '#a6e3a1',
warning: '#f9e2af',
error: '#f38ba8'
},
aestheticLight: {
primary: '#5a7c65',
'primary-focus': '#48604f',