diff --git a/client/package.json b/client/package.json index de5e0283a..ea8046eb3 100644 --- a/client/package.json +++ b/client/package.json @@ -47,6 +47,7 @@ }, "dependencies": { "@fontsource/bebas-neue": "^5.3.0", + "@fontsource/creepster": "^5.3.0", "@fontsource/eb-garamond": "^5.3.0", "@fontsource/geist-sans": "^5.3.0", "@fontsource/inter": "^5.3.0", diff --git a/client/public/theme-boot.js b/client/public/theme-boot.js index 05a9a963a..9c0e51a90 100644 --- a/client/public/theme-boot.js +++ b/client/public/theme-boot.js @@ -24,6 +24,8 @@ if (isShared) dark = false; else if (s.darkMode === 'auto') dark = window.matchMedia('(prefers-color-scheme: dark)').matches; else dark = s.darkMode === true || s.darkMode === 'dark'; + // Special themes (APPEARANCE_SPECIAL_SCHEMES in @trek/shared) are dark-only. + if (!isShared && s.scheme === 'halloween') dark = true; root.classList.toggle('dark', dark); var scheme = isShared ? 'default' : s.scheme; diff --git a/client/src/App.tsx b/client/src/App.tsx index d1de86eba..402c3fa4a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -33,6 +33,7 @@ import OfflineBanner from './components/Layout/OfflineBanner' import NewVersionNotice from './components/Layout/NewVersionNotice' import { SystemNoticeHost } from './components/SystemNotices/SystemNoticeHost.js' import HelpPanel from './components/Help/HelpPanel' +import SpecialThemeLayer from './theme/special/SpecialThemeLayer' // Notice action registrations (side-effect imports): import './pages/Trips/noticeActions.js' import { managedRoutes } from './managed' @@ -405,6 +406,7 @@ export default function App() { {!hideAuthedWidgets && (isPhone ? : )} + {/* One boundary for all route chunks, above so it stays mounted across navigations. react-router runs location updates inside a transition, so a mounted boundary keeps the current page on screen instead of flashing diff --git a/client/src/components/Layout/BottomNav.tsx b/client/src/components/Layout/BottomNav.tsx index bb5290c2b..d6b9443fc 100644 --- a/client/src/components/Layout/BottomNav.tsx +++ b/client/src/components/Layout/BottomNav.tsx @@ -9,6 +9,7 @@ import { resolvePluginIcon } from '../shared/PluginIcon' import { useAuthStore } from '../../store/authStore' import { visibleManagedNavItems } from '../../managed' import { isNavItemActive, useCreateAction } from './useCreateAction' +import { useIsDark } from '../../hooks/useIsDark' const ADDON_NAV: Record = { vacay: { icon: CalendarDays, labelKey: 'admin.addons.catalog.vacay.name' }, @@ -22,8 +23,8 @@ interface NavItem { to: string; label: string; icon: LucideIcon } export default function BottomNav() { const { t } = useTranslation() const navigate = useNavigate() - const darkMode = useSettingsStore(s => s.settings.dark_mode) - const dark = darkMode === true || darkMode === 'dark' || (darkMode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) + // Off the .dark class: a special theme is dark whatever the stored mode says. + const dark = useIsDark() const addons = useAddonStore(s => s.addons) const globalAddons = addons.filter(a => a.type === 'global' && a.enabled) // Page plugins are reachable from the mobile tab bar too, mirroring the desktop diff --git a/client/src/components/Layout/InAppNotificationBell.tsx b/client/src/components/Layout/InAppNotificationBell.tsx index a824c5077..40988b354 100644 --- a/client/src/components/Layout/InAppNotificationBell.tsx +++ b/client/src/components/Layout/InAppNotificationBell.tsx @@ -7,13 +7,14 @@ import { useInAppNotificationStore } from '../../store/inAppNotificationStore.ts import { useSettingsStore } from '../../store/settingsStore' import { useAuthStore } from '../../store/authStore' import InAppNotificationItem from '../Notifications/InAppNotificationItem.tsx' +import { useIsDark } from '../../hooks/useIsDark' export default function InAppNotificationBell(): React.ReactElement { const { t } = useTranslation() const navigate = useNavigate() const { settings } = useSettingsStore() - const darkMode = settings.dark_mode - const dark = darkMode === true || darkMode === 'dark' || (darkMode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) + // Off the .dark class: a special theme is dark whatever the stored mode says. + const dark = useIsDark() const isAuthenticated = useAuthStore(s => s.isAuthenticated) const { notifications, unreadCount, isLoading, fetchNotifications, fetchUnreadCount, markAllRead, deleteAll } = useInAppNotificationStore() diff --git a/client/src/components/Layout/Navbar.tsx b/client/src/components/Layout/Navbar.tsx index 87f13786c..3e915d13a 100644 --- a/client/src/components/Layout/Navbar.tsx +++ b/client/src/components/Layout/Navbar.tsx @@ -13,6 +13,7 @@ import HelpButton from '../Help/HelpButton' import { resolvePluginIcon } from '../shared/PluginIcon' import { visibleManagedNavItems } from '../../managed' import { useCollapseOnOverflow } from '../../hooks/useCollapseOnOverflow' +import { useIsDark } from '../../hooks/useIsDark' const ADDON_ICONS: Record = { CalendarDays, Briefcase, Globe, Compass, Bookmark } @@ -41,8 +42,8 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }: const location = useLocation() const [userMenuOpen, setUserMenuOpen] = useState(false) const [scrolled, setScrolled] = useState(false) - const darkMode = settings.dark_mode - const dark = darkMode === true || darkMode === 'dark' || (darkMode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) + // Off the .dark class: a special theme is dark whatever the stored mode says. + const dark = useIsDark() useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 8 || (document.body.scrollTop || 0) > 8) @@ -121,7 +122,7 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }: paddingTop: 'env(safe-area-inset-top, 0px)', height: 'var(--nav-h)', transition: 'background 240ms cubic-bezier(0.23,1,0.32,1), backdrop-filter 240ms cubic-bezier(0.23,1,0.32,1), box-shadow 240ms cubic-bezier(0.23,1,0.32,1)', - }} className="hidden md:flex items-center px-4 gap-4 fixed top-0 inset-x-0 z-[200]"> + }} className="trek-navbar hidden md:flex items-center px-4 gap-4 fixed top-0 inset-x-0 z-[200]"> {/* Left side. flex-1 basis-0, matching the action cluster on the right, so the tab pill between them sits in the middle of the bar rather than being centred on top of both (#1983). Both columns keep at least their diff --git a/client/src/components/Notifications/InAppNotificationItem.tsx b/client/src/components/Notifications/InAppNotificationItem.tsx index 4d68ecf25..00f728364 100644 --- a/client/src/components/Notifications/InAppNotificationItem.tsx +++ b/client/src/components/Notifications/InAppNotificationItem.tsx @@ -4,6 +4,7 @@ import { useTranslation } from '../../i18n' import type { InAppNotification } from '../../store/inAppNotificationStore' import { useSettingsStore } from '../../store/settingsStore' import { compactTime, useNotificationItemActions } from './useNotificationItemActions' +import { useIsDark } from '../../hooks/useIsDark' function relativeTime(dateStr: string, locale: string): string { return compactTime(dateStr, locale === 'ar' ? 'الآن' : 'just now') @@ -17,8 +18,8 @@ interface NotificationItemProps { export default function InAppNotificationItem({ notification, onClose }: NotificationItemProps): React.ReactElement { const { t, locale } = useTranslation() const { settings } = useSettingsStore() - const darkMode = settings.dark_mode - const dark = darkMode === true || darkMode === 'dark' || (darkMode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) + // Off the .dark class: a special theme is dark whatever the stored mode says. + const dark = useIsDark() const { responding, handleRespond, handleNavigate, markRead, deleteNotification } = useNotificationItemActions(notification, onClose) diff --git a/client/src/components/Settings/AppearanceSettingsTab.tsx b/client/src/components/Settings/AppearanceSettingsTab.tsx index f12b0eeb1..b4fcde667 100644 --- a/client/src/components/Settings/AppearanceSettingsTab.tsx +++ b/client/src/components/Settings/AppearanceSettingsTab.tsx @@ -6,7 +6,8 @@ import { Segmented } from '../shared/dialogParts' import Section from './Section' import ToggleSwitch from './ToggleSwitch' import { SETTINGS_BUTTON, SettingRow, SettingRows, StatusPill } from './settingsKit' -import { APPEARANCE_SCHEMES, CUSTOM_ACCENT_PRESETS } from '../../theme/schemes' +import { APPEARANCE_SCHEMES, CUSTOM_ACCENT_PRESETS, SPECIAL_THEMES } from '../../theme/schemes' +import SpecialThemeCard from '../../theme/special/SpecialThemeCard' import { APPEARANCE_SCALE_MIN, APPEARANCE_SCALE_MAX } from '@trek/shared' import { DESKTOP_GROUPS, MOBILE_GROUPS, isHex } from './appearanceModel' import { useAppearanceEditor } from './useAppearanceEditor' @@ -35,7 +36,7 @@ const DOT = 'h-4 w-4 flex-none rounded-full shadow-[inset_0_0_0_1px_var(--border export default function AppearanceSettingsTab(): React.ReactElement { const { t } = useTranslation() const tr = (key: string, fallback: string) => t(key) || fallback - const { cfg, darkMode, isDark, update, setMode, setWidget, resetAll, accentLight, accentDark, customRatio } = + const { cfg, darkMode, isDark, modeLocked, update, setMode, setWidget, resetAll, accentLight, accentDark, customRatio } = useAppearanceEditor() // The stored mode, read the way the old boolean values meant it. @@ -50,7 +51,9 @@ export default function AppearanceSettingsTab(): React.ReactElement { {/* Color mode */} + } /> + {/* Special themes: a whole look, not just an accent */} + +
+ {SPECIAL_THEMES.map((s) => ( + update({ schemeId: s.id })} /> + ))} +
+
+ {/* Color scheme swatches */}
@@ -290,6 +304,7 @@ function schemeFallback(id: string): string { rose: 'Rose', amber: 'Amber', violet: 'Violet', + halloween: 'Halloween', } return map[id] || id } diff --git a/client/src/components/Settings/useAppearanceEditor.ts b/client/src/components/Settings/useAppearanceEditor.ts index f144625fd..36082d659 100644 --- a/client/src/components/Settings/useAppearanceEditor.ts +++ b/client/src/components/Settings/useAppearanceEditor.ts @@ -1,6 +1,7 @@ -import { DEFAULT_APPEARANCE, normalizeAppearance, type AppearanceConfig } from '@trek/shared'; +import { DEFAULT_APPEARANCE, isSpecialScheme, normalizeAppearance, type AppearanceConfig } from '@trek/shared'; import { useEffect, useRef, useState } from 'react'; +import { useIsDark } from '../../hooks/useIsDark'; import { useTranslation } from '../../i18n'; import { useSettingsStore } from '../../store/settingsStore'; import { applyAppearance } from '../../theme/applyAppearance'; @@ -42,10 +43,10 @@ export function useAppearanceEditor() { [updateSetting] ); - const isDark = - settings.dark_mode === true || - settings.dark_mode === 'dark' || - (settings.dark_mode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches); + // Off the `.dark` class: a special theme forces dark whatever the stored mode says. + const isDark = useIsDark(); + // While a special theme is on, the color mode has nothing to switch. + const modeLocked = isSpecialScheme(cfg.schemeId); // Live preview now (DOM), persist after a short debounce (API). const update = (patch: Partial) => { @@ -83,6 +84,7 @@ export function useAppearanceEditor() { cfg, darkMode: settings.dark_mode, isDark, + modeLocked, update, setMode, setWidget, diff --git a/client/src/hooks/useActiveScheme.ts b/client/src/hooks/useActiveScheme.ts new file mode 100644 index 000000000..302fd1f8f --- /dev/null +++ b/client/src/hooks/useActiveScheme.ts @@ -0,0 +1,26 @@ +import { useEffect, useState } from 'react'; + +const read = (): string => + (typeof document !== 'undefined' && document.documentElement.getAttribute('data-scheme')) || 'default'; + +/** + * The scheme currently on ``, as `applyAppearance()` and the + * pre-paint boot script wrote it. Same contract as `useIsDark`: components read + * the DOM, never the appearance config, so shared pages (which drop the + * attribute) and the live preview in settings are covered for free. + * + * Only for decor that has to exist in the DOM, like a special theme's overlay + * or the mascot's hat. Colours belong on tokens. + */ +export function useActiveScheme(): string { + const [scheme, setScheme] = useState(read); + + useEffect(() => { + if (typeof document === 'undefined') return; + const mo = new MutationObserver(() => setScheme(read())); + mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-scheme'] }); + return () => mo.disconnect(); + }, []); + + return scheme; +} diff --git a/client/src/main.tsx b/client/src/main.tsx index 7b2e41322..24ad1faa2 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -25,6 +25,9 @@ import '@fontsource/geist-sans/600.css' // several .leaflet-* styles. import 'leaflet/dist/leaflet.css' import './index.css' +// Special themes: the palette is eager (first paint), the decor loads with its layer. +import '@fontsource/creepster/400.css' +import './theme/special/halloween.tokens.css' import './styles/ferratas.css' import './styles/paddle.css' // Native HTML5 drag-and-drop never fires on touch input, so the planner's place / diff --git a/client/src/mobile/components/MDancingTrek.tsx b/client/src/mobile/components/MDancingTrek.tsx index ab50c2ec4..66814d366 100644 --- a/client/src/mobile/components/MDancingTrek.tsx +++ b/client/src/mobile/components/MDancingTrek.tsx @@ -1,4 +1,6 @@ import { useState } from 'react'; +import { useActiveScheme } from '../../hooks/useActiveScheme'; +import { MascotHat, MascotPumpkin } from '../../theme/special/halloween/MascotCostume'; import MascotClip from './trek3d/MascotClip'; import { findClip, mascot3dEnabled } from './trek3d/mascotClips'; @@ -66,6 +68,8 @@ type MascotProps = { export default function MDancingTrek(props: MascotProps) { const { size = 96, scene = 'idle', mood, className = '' } = props; const sceneMood = SCENE_MOOD[scene] ?? 'default'; + // Special themes dress the mascot: the 3D clips come in costume, and so does the SVG. + const costume = useActiveScheme() === 'halloween'; const clip = import.meta.env.MODE !== 'test' && mascot3dEnabled() ? findClip(scene, mood ?? sceneMood, sceneMood) : null; if (clip) @@ -75,13 +79,20 @@ export default function MDancingTrek(props: MascotProps) { scene={scene} size={size} className={className} - fallback={} + costume={costume} + fallback={} /> ); - return ; + return ; } -function MDancingTrekSvg({ size = 96, scene = 'idle', mood, className = '' }: MascotProps) { +function MDancingTrekSvg({ + size = 96, + scene = 'idle', + mood, + className = '', + costume = false, +}: MascotProps & { costume?: boolean }) { const face = mood ?? SCENE_MOOD[scene] ?? 'default'; // Ground shadow sits under whatever the mascot stands on this scene. const shadowCy = scene === 'transport' ? 86 : 73; @@ -111,8 +122,10 @@ function MDancingTrekSvg({ size = 96, scene = 'idle', mood, className = '' }: Ma + {costume && } + {costume && } {/* scene prop in front of the body — animates on its own */} diff --git a/client/src/mobile/components/trek3d/MascotClip.tsx b/client/src/mobile/components/trek3d/MascotClip.tsx index d84e4c54e..80912163c 100644 --- a/client/src/mobile/components/trek3d/MascotClip.tsx +++ b/client/src/mobile/components/trek3d/MascotClip.tsx @@ -14,7 +14,8 @@ import { CLIPS } from './mascotClips'; * stay 2 px stand-ins: browsers hang their own controls on visible videos. * * Clips live next to this file as `[--]--.mp4`, - * cropped to the area the scene uses; `crop` in clips.meta.json places them back + * plus a `halloween` set in costume (hat, pumpkin) whose crops sit under + * `@halloween`. They are cropped to the area the scene uses; `crop` in clips.meta.json places them back * inside the full 88:96 box so every scene keeps the same scale. Only URLs are * bundled; the files load when a mascot actually renders. */ @@ -116,6 +117,7 @@ export default function MascotClip({ size, className = '', fallback, + costume = false, }: { clip: string; scene: string; @@ -123,12 +125,17 @@ export default function MascotClip({ className?: string; /** shown when the device refuses to autoplay (e.g. iOS low power mode) */ fallback: ReactNode; + /** The Halloween theme is on: play the clips in costume where they exist. */ + costume?: boolean; }) { - const theme = useIsDark() ? 'dark' : 'light'; + const dark = useIsDark(); + const dressed = costume && !!CLIPS[`./clips/${clip}-halloween-loop.mp4`]; + const theme = dressed ? 'halloween' : dark ? 'dark' : 'light'; const loopUrl = CLIPS[`./clips/${clip}-${theme}-loop.mp4`]; const pokeUrl = CLIPS[`./clips/${scene}-${theme}-poke.mp4`]; - const pokeMs = (META[scene]?.poke ?? 2) * 1000; - const [x, y, w, h] = META[clip]?.crop ?? [0, 0, 1, 1]; + const metaKey = (key: string) => (dressed ? `${key}@halloween` : key); + const pokeMs = (META[metaKey(scene)]?.poke ?? 2) * 1000; + const [x, y, w, h] = META[metaKey(clip)]?.crop ?? [0, 0, 1, 1]; const boxW = size * CLIP_SCALE, boxH = (boxW * 96) / 88; diff --git a/client/src/mobile/components/trek3d/clips.meta.json b/client/src/mobile/components/trek3d/clips.meta.json index 137ab1d52..b21a0208d 100644 --- a/client/src/mobile/components/trek3d/clips.meta.json +++ b/client/src/mobile/components/trek3d/clips.meta.json @@ -1,122 +1,542 @@ { - "idle": { - "poke": 2.3, - "crop": [0.14545, 0.11667, 0.72273, 0.74167] - }, - "idle--sleepy": { - "poke": 2.3, - "crop": [0.14545, 0.11667, 0.72273, 0.74167] - }, - "idle--happy": { - "poke": 2.3, - "crop": [0.14545, 0.11667, 0.72273, 0.74167] - }, - "idle--sad": { - "poke": 2.3, - "crop": [0.14545, 0.11667, 0.72273, 0.74167] - }, - "idle--confused": { - "poke": 2.3, - "crop": [0.14545, 0.11667, 0.72273, 0.74167] - }, - "idle--error": { - "poke": 2.3, - "crop": [0.14545, 0.11667, 0.72273, 0.74167] - }, - "dashboard": { - "poke": 2, - "crop": [0.07273, 0.14583, 0.88182, 0.625] - }, - "dashboard--happy": { - "poke": 2, - "crop": [0.07273, 0.14583, 0.88182, 0.625] - }, - "transport": { - "poke": 1.5, - "crop": [0.19545, 0.0, 0.64545, 0.75] - }, - "transport--sad": { - "poke": 1.5, - "crop": [0.19545, 0.0, 0.64545, 0.75] - }, - "transport--happy": { - "poke": 1.5, - "crop": [0.19545, 0.0, 0.64545, 0.75] - }, - "bookings": { - "poke": 2.1, - "crop": [0.16364, 0.19167, 0.83636, 0.575] - }, - "tours": { - "poke": 2.2, - "crop": [0.08636, 0.11667, 0.81364, 0.65417] - }, - "packing": { - "poke": 2.45, - "crop": [0.09091, 0.025, 0.82273, 0.7125] - }, - "packing--happy": { - "poke": 2.45, - "crop": [0.09091, 0.025, 0.82273, 0.7125] - }, - "tasks": { - "poke": 2, - "crop": [0.13182, 0.1125, 0.85909, 0.80833] - }, - "costs": { - "poke": 2.3, - "crop": [0.14091, 0.0, 0.85909, 0.7625] - }, - "files": { - "poke": 2.1, - "crop": [0.16364, 0.21667, 0.76818, 0.52917] - }, - "notes": { - "poke": 2.6, - "crop": [0.13182, 0.0875, 0.86818, 0.67083] - }, - "links": { - "poke": 2.2, - "crop": [0.03636, 0.08333, 0.91364, 0.66667] - }, - "polls": { - "poke": 2, - "crop": [0.08636, 0.12917, 0.76364, 0.6125] - }, - "chat": { - "poke": 2.2, - "crop": [0.12727, 0.18333, 0.80455, 0.55417] - }, - "guide": { - "poke": 2.2, - "crop": [0.10909, 0.1875, 0.85909, 0.55] - }, - "journey": { - "poke": 2.2, - "crop": [0.09545, 0.2, 0.86818, 0.57083] - }, - "collections": { - "poke": 2.25, - "crop": [0.0, 0.0, 0.95455, 0.75417] - }, - "collections--happy": { - "poke": 2.25, - "crop": [0.0, 0.0, 0.95455, 0.75417] - }, - "atlas": { - "poke": 2.4, - "crop": [0.12273, 0.20833, 0.70909, 0.52917] - }, - "notifications": { - "poke": 2.4, - "crop": [0.00909, 0.05417, 0.86364, 0.6875] - }, - "search": { - "poke": 2.2, - "crop": [0.14545, 0.0125, 0.75909, 0.75833] - }, - "search--sad": { - "poke": 2.2, - "crop": [0.14545, 0.0125, 0.75909, 0.75833] - } -} + "idle": { + "poke": 2.3, + "crop": [ + 0.14545, + 0.11667, + 0.72273, + 0.74167 + ] + }, + "idle--sleepy": { + "poke": 2.3, + "crop": [ + 0.14545, + 0.11667, + 0.72273, + 0.74167 + ] + }, + "idle--happy": { + "poke": 2.3, + "crop": [ + 0.14545, + 0.11667, + 0.72273, + 0.74167 + ] + }, + "idle--sad": { + "poke": 2.3, + "crop": [ + 0.14545, + 0.11667, + 0.72273, + 0.74167 + ] + }, + "idle--confused": { + "poke": 2.3, + "crop": [ + 0.14545, + 0.11667, + 0.72273, + 0.74167 + ] + }, + "idle--error": { + "poke": 2.3, + "crop": [ + 0.14545, + 0.11667, + 0.72273, + 0.74167 + ] + }, + "dashboard": { + "poke": 2, + "crop": [ + 0.07273, + 0.14583, + 0.88182, + 0.625 + ] + }, + "dashboard--happy": { + "poke": 2, + "crop": [ + 0.07273, + 0.14583, + 0.88182, + 0.625 + ] + }, + "transport": { + "poke": 1.5, + "crop": [ + 0.19545, + 0.0, + 0.64545, + 0.75 + ] + }, + "transport--sad": { + "poke": 1.5, + "crop": [ + 0.19545, + 0.0, + 0.64545, + 0.75 + ] + }, + "transport--happy": { + "poke": 1.5, + "crop": [ + 0.19545, + 0.0, + 0.64545, + 0.75 + ] + }, + "bookings": { + "poke": 2.1, + "crop": [ + 0.16364, + 0.19167, + 0.83636, + 0.575 + ] + }, + "tours": { + "poke": 2.2, + "crop": [ + 0.08636, + 0.11667, + 0.81364, + 0.65417 + ] + }, + "packing": { + "poke": 2.45, + "crop": [ + 0.09091, + 0.025, + 0.82273, + 0.7125 + ] + }, + "packing--happy": { + "poke": 2.45, + "crop": [ + 0.09091, + 0.025, + 0.82273, + 0.7125 + ] + }, + "tasks": { + "poke": 2, + "crop": [ + 0.13182, + 0.1125, + 0.85909, + 0.80833 + ] + }, + "costs": { + "poke": 2.3, + "crop": [ + 0.14091, + 0.0, + 0.85909, + 0.7625 + ] + }, + "files": { + "poke": 2.1, + "crop": [ + 0.16364, + 0.21667, + 0.76818, + 0.52917 + ] + }, + "notes": { + "poke": 2.6, + "crop": [ + 0.13182, + 0.0875, + 0.86818, + 0.67083 + ] + }, + "links": { + "poke": 2.2, + "crop": [ + 0.03636, + 0.08333, + 0.91364, + 0.66667 + ] + }, + "polls": { + "poke": 2, + "crop": [ + 0.08636, + 0.12917, + 0.76364, + 0.6125 + ] + }, + "chat": { + "poke": 2.2, + "crop": [ + 0.12727, + 0.18333, + 0.80455, + 0.55417 + ] + }, + "guide": { + "poke": 2.2, + "crop": [ + 0.10909, + 0.1875, + 0.85909, + 0.55 + ] + }, + "journey": { + "poke": 2.2, + "crop": [ + 0.09545, + 0.2, + 0.86818, + 0.57083 + ] + }, + "collections": { + "poke": 2.25, + "crop": [ + 0.0, + 0.0, + 0.95455, + 0.75417 + ] + }, + "collections--happy": { + "poke": 2.25, + "crop": [ + 0.0, + 0.0, + 0.95455, + 0.75417 + ] + }, + "atlas": { + "poke": 2.4, + "crop": [ + 0.12273, + 0.20833, + 0.70909, + 0.52917 + ] + }, + "notifications": { + "poke": 2.4, + "crop": [ + 0.00909, + 0.05417, + 0.86364, + 0.6875 + ] + }, + "search": { + "poke": 2.2, + "crop": [ + 0.14545, + 0.0125, + 0.75909, + 0.75833 + ] + }, + "search--sad": { + "poke": 2.2, + "crop": [ + 0.14545, + 0.0125, + 0.75909, + 0.75833 + ] + }, + "costs@halloween": { + "poke": 2.3, + "crop": [ + 0.08182, + -0.02917, + 0.92273, + 0.79583 + ] + }, + "idle@halloween": { + "poke": 2.3, + "crop": [ + 0.11364, + -0.07083, + 0.75455, + 0.92917 + ] + }, + "idle--happy@halloween": { + "poke": 2.3, + "crop": [ + 0.11364, + -0.07083, + 0.75455, + 0.92917 + ] + }, + "idle--sad@halloween": { + "poke": 2.3, + "crop": [ + 0.11364, + -0.07083, + 0.75455, + 0.92917 + ] + }, + "idle--sleepy@halloween": { + "poke": 2.3, + "crop": [ + 0.11364, + -0.07083, + 0.75455, + 0.92917 + ] + }, + "idle--confused@halloween": { + "poke": 2.3, + "crop": [ + 0.11364, + -0.07083, + 0.75455, + 0.92917 + ] + }, + "idle--error@halloween": { + "poke": 2.3, + "crop": [ + 0.11364, + -0.07083, + 0.75455, + 0.92917 + ] + }, + "dashboard@halloween": { + "poke": 2, + "crop": [ + 0.07273, + -0.04167, + 0.88182, + 0.8125 + ] + }, + "dashboard--happy@halloween": { + "poke": 2, + "crop": [ + 0.07273, + -0.04167, + 0.88182, + 0.8125 + ] + }, + "transport@halloween": { + "poke": 1.5, + "crop": [ + 0.11364, + -0.17917, + 0.72727, + 0.94583 + ] + }, + "transport--happy@halloween": { + "poke": 1.5, + "crop": [ + 0.11364, + -0.17917, + 0.72727, + 0.94583 + ] + }, + "transport--sad@halloween": { + "poke": 1.5, + "crop": [ + 0.11364, + -0.17917, + 0.72727, + 0.94583 + ] + }, + "bookings@halloween": { + "poke": 2.1, + "crop": [ + 0.09091, + 0.0125, + 0.96818, + 0.75417 + ] + }, + "tours@halloween": { + "poke": 2.2, + "crop": [ + 0.08636, + -0.06667, + 0.81364, + 0.8375 + ] + }, + "packing@halloween": { + "poke": 2.45, + "crop": [ + -0.06364, + -0.12083, + 0.97727, + 0.8875 + ] + }, + "packing--happy@halloween": { + "poke": 2.45, + "crop": [ + -0.06364, + -0.12083, + 0.97727, + 0.8875 + ] + }, + "tasks@halloween": { + "poke": 2, + "crop": [ + 0.07727, + -0.05833, + 0.91364, + 0.97917 + ] + }, + "files@halloween": { + "poke": 2.1, + "crop": [ + 0.09091, + 0.04583, + 0.84091, + 0.72083 + ] + }, + "notes@halloween": { + "poke": 2.6, + "crop": [ + 0.07727, + 0.04583, + 0.95, + 0.72083 + ] + }, + "links@halloween": { + "poke": 2.2, + "crop": [ + 0.03636, + 0.04583, + 0.91364, + 0.725 + ] + }, + "polls@halloween": { + "poke": 2, + "crop": [ + 0.05909, + -0.0625, + 0.79091, + 0.83333 + ] + }, + "chat@halloween": { + "poke": 2.2, + "crop": [ + 0.07727, + 0.00417, + 0.85455, + 0.7625 + ] + }, + "guide@halloween": { + "poke": 2.2, + "crop": [ + 0.06818, + 0.02083, + 0.9, + 0.75 + ] + }, + "journey@halloween": { + "poke": 2.2, + "crop": [ + 0.06364, + 0.03333, + 0.9, + 0.7375 + ] + }, + "collections@halloween": { + "poke": 2.25, + "crop": [ + -0.04545, + -0.29167, + 1.0, + 1.0625 + ] + }, + "collections--happy@halloween": { + "poke": 2.25, + "crop": [ + -0.04545, + -0.29167, + 1.0, + 1.0625 + ] + }, + "atlas@halloween": { + "poke": 2.4, + "crop": [ + 0.07273, + 0.03333, + 0.75909, + 0.73333 + ] + }, + "notifications@halloween": { + "poke": 2.4, + "crop": [ + 0.00909, + -0.05417, + 0.86364, + 0.825 + ] + }, + "search@halloween": { + "poke": 2.2, + "crop": [ + 0.08182, + 0.0125, + 0.82273, + 0.75833 + ] + }, + "search--sad@halloween": { + "poke": 2.2, + "crop": [ + 0.08182, + 0.0125, + 0.82273, + 0.75833 + ] + } +} \ No newline at end of file diff --git a/client/src/mobile/components/trek3d/clips/atlas-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/atlas-halloween-loop.mp4 new file mode 100644 index 000000000..d6617d9f5 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/atlas-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/atlas-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/atlas-halloween-poke.mp4 new file mode 100644 index 000000000..6d39183f9 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/atlas-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/bookings-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/bookings-halloween-loop.mp4 new file mode 100644 index 000000000..19431d09e Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/bookings-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/bookings-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/bookings-halloween-poke.mp4 new file mode 100644 index 000000000..da36797ae Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/bookings-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/chat-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/chat-halloween-loop.mp4 new file mode 100644 index 000000000..dcdacca65 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/chat-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/chat-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/chat-halloween-poke.mp4 new file mode 100644 index 000000000..3449958d0 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/chat-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/collections--happy-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/collections--happy-halloween-loop.mp4 new file mode 100644 index 000000000..6104329fd Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/collections--happy-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/collections-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/collections-halloween-loop.mp4 new file mode 100644 index 000000000..1e6121159 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/collections-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/collections-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/collections-halloween-poke.mp4 new file mode 100644 index 000000000..5a76b1a87 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/collections-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/costs-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/costs-halloween-loop.mp4 new file mode 100644 index 000000000..53f65f96a Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/costs-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/costs-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/costs-halloween-poke.mp4 new file mode 100644 index 000000000..05b92df6e Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/costs-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/dashboard--happy-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/dashboard--happy-halloween-loop.mp4 new file mode 100644 index 000000000..d82e9662d Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/dashboard--happy-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/dashboard-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/dashboard-halloween-loop.mp4 new file mode 100644 index 000000000..d900f5b4d Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/dashboard-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/dashboard-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/dashboard-halloween-poke.mp4 new file mode 100644 index 000000000..325fc0425 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/dashboard-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/files-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/files-halloween-loop.mp4 new file mode 100644 index 000000000..b242fd659 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/files-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/files-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/files-halloween-poke.mp4 new file mode 100644 index 000000000..b07d708fb Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/files-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/guide-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/guide-halloween-loop.mp4 new file mode 100644 index 000000000..cac6c0694 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/guide-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/guide-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/guide-halloween-poke.mp4 new file mode 100644 index 000000000..b2b7afc69 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/guide-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/idle--confused-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/idle--confused-halloween-loop.mp4 new file mode 100644 index 000000000..a2c6c4ccf Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/idle--confused-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/idle--error-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/idle--error-halloween-loop.mp4 new file mode 100644 index 000000000..a1c22e15c Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/idle--error-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/idle--happy-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/idle--happy-halloween-loop.mp4 new file mode 100644 index 000000000..116c4e9f6 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/idle--happy-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/idle--sad-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/idle--sad-halloween-loop.mp4 new file mode 100644 index 000000000..5ef773675 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/idle--sad-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/idle--sleepy-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/idle--sleepy-halloween-loop.mp4 new file mode 100644 index 000000000..417158f13 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/idle--sleepy-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/idle-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/idle-halloween-loop.mp4 new file mode 100644 index 000000000..985148c9f Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/idle-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/idle-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/idle-halloween-poke.mp4 new file mode 100644 index 000000000..7d31389a3 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/idle-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/journey-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/journey-halloween-loop.mp4 new file mode 100644 index 000000000..edb1b33ac Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/journey-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/journey-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/journey-halloween-poke.mp4 new file mode 100644 index 000000000..83f23378e Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/journey-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/links-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/links-halloween-loop.mp4 new file mode 100644 index 000000000..7b8641a45 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/links-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/links-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/links-halloween-poke.mp4 new file mode 100644 index 000000000..b60957a3a Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/links-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/notes-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/notes-halloween-loop.mp4 new file mode 100644 index 000000000..ff8dd2959 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/notes-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/notes-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/notes-halloween-poke.mp4 new file mode 100644 index 000000000..b7fd0ce56 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/notes-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/notifications-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/notifications-halloween-loop.mp4 new file mode 100644 index 000000000..022dc6891 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/notifications-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/notifications-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/notifications-halloween-poke.mp4 new file mode 100644 index 000000000..c418e2234 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/notifications-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/packing--happy-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/packing--happy-halloween-loop.mp4 new file mode 100644 index 000000000..b6001e0cc Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/packing--happy-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/packing-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/packing-halloween-loop.mp4 new file mode 100644 index 000000000..482a10f31 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/packing-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/packing-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/packing-halloween-poke.mp4 new file mode 100644 index 000000000..5cd7296f4 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/packing-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/polls-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/polls-halloween-loop.mp4 new file mode 100644 index 000000000..4408fd073 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/polls-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/polls-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/polls-halloween-poke.mp4 new file mode 100644 index 000000000..53199595c Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/polls-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/search--sad-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/search--sad-halloween-loop.mp4 new file mode 100644 index 000000000..bf4a36686 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/search--sad-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/search-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/search-halloween-loop.mp4 new file mode 100644 index 000000000..83133a11c Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/search-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/search-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/search-halloween-poke.mp4 new file mode 100644 index 000000000..0ab9b8ea5 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/search-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/tasks-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/tasks-halloween-loop.mp4 new file mode 100644 index 000000000..8ebe5cedb Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/tasks-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/tasks-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/tasks-halloween-poke.mp4 new file mode 100644 index 000000000..7b9d3e55c Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/tasks-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/tours-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/tours-halloween-loop.mp4 new file mode 100644 index 000000000..89ae0bf58 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/tours-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/tours-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/tours-halloween-poke.mp4 new file mode 100644 index 000000000..6ef1631c8 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/tours-halloween-poke.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/transport--happy-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/transport--happy-halloween-loop.mp4 new file mode 100644 index 000000000..eda674316 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/transport--happy-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/transport--sad-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/transport--sad-halloween-loop.mp4 new file mode 100644 index 000000000..c443c8b85 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/transport--sad-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/transport-halloween-loop.mp4 b/client/src/mobile/components/trek3d/clips/transport-halloween-loop.mp4 new file mode 100644 index 000000000..37a4732e8 Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/transport-halloween-loop.mp4 differ diff --git a/client/src/mobile/components/trek3d/clips/transport-halloween-poke.mp4 b/client/src/mobile/components/trek3d/clips/transport-halloween-poke.mp4 new file mode 100644 index 000000000..1062e725b Binary files /dev/null and b/client/src/mobile/components/trek3d/clips/transport-halloween-poke.mp4 differ diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index 9df6a9e7a..8ecacff3c 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -295,7 +295,7 @@ function TripPlannerPageDesktop(): React.ReactElement | null {