diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index fcb0d35..25ae903 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -8,13 +8,13 @@ import { tool, experimental_createProviderRegistry, } from "ai"; -import { BlobRequestAbortedError, put } from '@vercel/blob'; +import { BlobRequestAbortedError, put, list } from '@vercel/blob'; import CodeInterpreter from "@e2b/code-interpreter"; import FirecrawlApp from '@mendable/firecrawl-js'; import { tavily } from '@tavily/core' // Allow streaming responses up to 60 seconds -export const maxDuration = 60; +export const maxDuration = 120; // Azure setup const azure = createAzure({ @@ -108,7 +108,7 @@ DON'Ts and IMPORTANT GUIDELINES: - Show plots from the programming tool using plt.show() function. The tool will automatically capture the plot and display it in the response. - If asked for multiple plots, make it happen in one run of the tool. The tool will automatically capture the plots and display them in the response. - the web search may return an incorrect latex format, please correct it before using it in the response. Check the Latex in Markdown rules for more information. -- The location search tools return images in the response, please do not include them in the response at all costs. +- The location search tools return images in the response, please DO NOT include them in the response at all costs!!!!!!!! This is extremely important to follow!! - Do not use the $ symbol in the stock chart queries at all costs. Use the word USD instead of the $ symbol in the stock chart queries. - Never run web_search tool for stock chart queries at all costs. @@ -122,6 +122,7 @@ Follow the format and guidelines for each tool and provide the response accordin - For queries related to trips, always use the find_place tool for map location and then run the web_search tool to find information about places, directions, or reviews. - Calling web and find place tools in the same response is allowed, but do not call the same tool in a response at all costs!! - For nearby search queries, use the nearby_search tool to find places around a location. Provide the location and radius in the parameters, then compose your response based on the information gathered. +- Never call find_place tool before or after the nearby_search tool in the same response at all costs!! THIS IS NOT ALLOWED AT ALL COSTS!!! ## Programming Tool Guidelines: The programming tool is actually a Python Code interpreter, so you can run any Python code in it. @@ -487,58 +488,204 @@ When asked a "What is" question, maintain the same format as the question and an }, }), nearby_search: tool({ - description: "Search for nearby places, such as restaurants or hotels.", + description: "Search for nearby places, such as restaurants or hotels based on the details given.", parameters: z.object({ + location: z.string().describe("The location name given by user."), latitude: z.number().describe("The latitude of the location."), longitude: z.number().describe("The longitude of the location."), - type: z.string().describe("The type of place to search for (e.g., restaurant, hotel, attraction)."), - radius: z.number().default(3000).describe("The radius of the search area in meters (max 50000, default 3000)."), + type: z.string().describe("The type of place to search for (restaurants, hotels, attractions, geos)."), + radius: z.number().default(6000).describe("The radius in meters (max 50000, default 6000)."), }), - execute: async ({ latitude, longitude, type, radius }: { + execute: async ({ location, latitude, longitude, type, radius }: { latitude: number; longitude: number; + location: string; type: string; radius: number; }) => { const apiKey = process.env.TRIPADVISOR_API_KEY; - console.log("Latitude:", latitude); - console.log("Longitude:", longitude); - console.log("Type:", type); - console.log("Radius:", radius); - const response = await fetch( - `https://api.content.tripadvisor.com/api/v1/location/nearby_search?latLong=${latitude},${longitude}&category=${type}&radius=${radius}&language=en&key=${apiKey}` - ); + let finalLat = latitude; + let finalLng = longitude; - // check for error - if (!response.ok) { - console.log(response); - throw new Error(`HTTP error! status: ${response.status} ${response}`); - } + try { + // Try geocoding first + const geocodingData = await fetch( + `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=${process.env.GOOGLE_MAPS_API_KEY}` + ); - const data = await response.json(); + const geocoding = await geocodingData.json(); - return { - results: data.data.map((place: any) => ({ - name: place.name, - location: { - lat: parseFloat(place.latitude), - lng: parseFloat(place.longitude) - }, - place_id: place.location_id, - vicinity: place.address_obj.address_string, - distance: place.distance, - bearing: place.bearing, - type: type - })), - center: { lat: latitude, lng: longitude } - }; + if (geocoding.results?.[0]?.geometry?.location) { + let trimmedLat = geocoding.results[0].geometry.location.lat.toString().split('.'); + finalLat = parseFloat(trimmedLat[0] + '.' + trimmedLat[1].slice(0, 6)); + let trimmedLng = geocoding.results[0].geometry.location.lng.toString().split('.'); + finalLng = parseFloat(trimmedLng[0] + '.' + trimmedLng[1].slice(0, 6)); + console.log('Using geocoded coordinates:', finalLat, finalLng); + } else { + console.log('Using provided coordinates:', finalLat, finalLng); + } + + // Get nearby places + const nearbyResponse = await fetch( + `https://api.content.tripadvisor.com/api/v1/location/nearby_search?latLong=${finalLat},${finalLng}&category=${type}&radius=${radius}&language=en&key=${apiKey}`, + { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'origin': 'https://mplx.local', + 'referer': 'https://mplx.local', + }, + } + ); + + if (!nearbyResponse.ok) { + throw new Error(`Nearby search failed: ${nearbyResponse.status}`); + } + + const nearbyData = await nearbyResponse.json(); + + if (!nearbyData.data || nearbyData.data.length === 0) { + console.log('No nearby places found'); + return { + results: [], + center: { lat: finalLat, lng: finalLng } + }; + } + + // Process each place + const detailedPlaces = await Promise.all( + nearbyData.data.map(async (place: any) => { + try { + if (!place.location_id) { + console.log(`Skipping place "${place.name}": No location_id`); + return null; + } + + // Fetch place details + const detailsResponse = await fetch( + `https://api.content.tripadvisor.com/api/v1/location/${place.location_id}/details?language=en¤cy=USD&key=${apiKey}`, + { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'origin': 'https://mplx.local', + 'referer': 'https://mplx.local', + }, + } + ); + + if (!detailsResponse.ok) { + console.log(`Failed to fetch details for "${place.name}"`); + return null; + } + + const details = await detailsResponse.json(); + + // Fetch place photos + let photos = []; + try { + const photosResponse = await fetch( + `https://api.content.tripadvisor.com/api/v1/location/${place.location_id}/photos?language=en&key=${apiKey}`, + { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'origin': 'https://mplx.local', + 'referer': 'https://mplx.local', + }, + } + ); + + if (photosResponse.ok) { + const photosData = await photosResponse.json(); + photos = photosData.data?.map((photo: any) => ({ + thumbnail: photo.images?.thumbnail?.url, + small: photo.images?.small?.url, + medium: photo.images?.medium?.url, + large: photo.images?.large?.url, + original: photo.images?.original?.url, + caption: photo.caption + })).filter((photo: any) => photo.medium) || []; + } + } catch (error) { + console.log(`Photo fetch failed for "${place.name}":`, error); + } + + // Process hours and status + const now = new Date(); + const currentDay = now.getDay(); + const currentTime = now.getHours() * 100 + now.getMinutes(); + + let is_closed = true; + let next_open_close = ''; + + if (details.hours?.periods) { + const todayPeriod = details.hours.periods.find((period: any) => + period.open?.day === currentDay + ); + + if (todayPeriod) { + const openTime = parseInt(todayPeriod.open.time); + const closeTime = todayPeriod.close ? parseInt(todayPeriod.close.time) : 2359; + is_closed = currentTime < openTime || currentTime > closeTime; + next_open_close = is_closed ? todayPeriod.open.time : todayPeriod.close?.time; + } + } + + // Return processed place data + return { + name: place.name || 'Unnamed Place', + location: { + lat: parseFloat(details.latitude || place.latitude || finalLat), + lng: parseFloat(details.longitude || place.longitude || finalLng) + }, + place_id: place.location_id, + vicinity: place.address_obj?.address_string || '', + distance: parseFloat(place.distance || '0'), + bearing: place.bearing || '', + type: type, + rating: parseFloat(details.rating || '0'), + price_level: details.price_level || '', + cuisine: details.cuisine?.[0]?.name || '', + description: details.description || '', + phone: details.phone || '', + website: details.website || '', + reviews_count: parseInt(details.num_reviews || '0'), + is_closed, + hours: details.hours?.weekday_text || [], + next_open_close, + photos, + source: details.source?.name || 'TripAdvisor' + }; + } catch (error) { + console.log(`Failed to process place "${place.name}":`, error); + return null; + } + }) + ); + + // Filter and sort results + const validPlaces = detailedPlaces + .filter(place => place !== null) + .sort((a, b) => (a?.distance || 0) - (b?.distance || 0)); + + return { + results: validPlaces, + center: { lat: finalLat, lng: finalLng } + }; + + } catch (error) { + console.error('Nearby search error:', error); + throw error; + } }, - }), - + }) }, toolChoice: "auto", onChunk(event) { - console.log("Call Type: ", event.chunk.type); + if (event.chunk.type === "tool-call") { + console.log("Called Tool: ", event.chunk.toolName); + } }, }); diff --git a/app/globals.css b/app/globals.css index df2d29e..57ab585 100644 --- a/app/globals.css +++ b/app/globals.css @@ -32,16 +32,16 @@ body { margin-bottom: 1em; } -.markdown-body .katex-display > .katex { +.markdown-body .katex-display>.katex { font-size: 1.21em; } -.markdown-body .katex-display > .katex > .katex-html { +.markdown-body .katex-display>.katex>.katex-html { display: block; position: relative; } -.markdown-body .katex-display > .katex > .katex-html > .tag { +.markdown-body .katex-display>.katex>.katex-html>.tag { position: absolute; right: 0; } @@ -67,7 +67,7 @@ body { flex-direction: column; } -.tweet-container > div { +.tweet-container>div { flex: 1; } @@ -80,6 +80,7 @@ h1 { text-wrap: balance; } } + @layer base { :root { --background: 0 0% 100%; @@ -141,6 +142,7 @@ h1 { * { @apply border-border; } + body { @apply bg-background text-foreground; } diff --git a/app/search/page.tsx b/app/search/page.tsx index c51e23e..3215796 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -103,8 +103,9 @@ import { import Autoplay from 'embla-carousel-autoplay'; import FormComponent from '@/components/ui/form-component'; import WeatherChart from '@/components/weather-chart'; -import { MapComponent, MapContainer, MapSkeleton, MapView, Place, PlaceDetails } from '@/components/map-components'; import InteractiveChart from '@/components/interactive-charts'; +import NearbySearchMapView from '@/components/nearby-search-map-view'; +import { MapComponent, MapContainer, MapSkeleton } from '@/components/map-components'; export const maxDuration = 60; @@ -271,7 +272,6 @@ const HomeContent = () => { const [attachments, setAttachments] = useState([]); const fileInputRef = useRef(null); const inputRef = useRef(null); - const [viewMode, setViewMode] = useState<'map' | 'list'>('map'); const { theme } = useTheme(); @@ -701,54 +701,15 @@ GPT-4o has been re-enabled! You can use it by selecting the model from the dropd ); } + console.log(result); return ( -
-
-

- Nearby {args.type}s -

- - Found {result.results.length} places - -
- - + setViewMode(newView)} + type={args.type} /> - - {viewMode === 'list' && ( -
- {result.results.map((place: Place, placeIndex: number) => ( - { - const url = `https://www.google.com/maps/dir/?api=1&destination=${place.location.lat},${place.location.lng}`; - window.open(url, '_blank'); - }} - onWebsiteClick={() => { - if (place.place_id) { - window.open(`https://www.tripadvisor.com/Attraction_Review-g-d${place.place_id}`, '_blank'); - } else { - toast.info("Website not available"); - } - }} - onCallClick={() => { - if (place.phone) { - window.open(`tel:${place.phone}`); - } else { - toast.info("Phone number not available"); - } - }} - /> - ))} -
- )}
); } @@ -1388,7 +1349,7 @@ GPT-4o has been re-enabled! You can use it by selecting the model from the dropd const Navbar: React.FC = () => { return ( -
+
+ {place.website && ( + + )} + {place.phone && ( + + )} + {place.place_id && ( + + )} +
+
+ + + ); +}; + +export default PlaceCard; \ No newline at end of file diff --git a/components/map-components.tsx b/components/map-components.tsx index ea90d41..995de5b 100644 --- a/components/map-components.tsx +++ b/components/map-components.tsx @@ -1,12 +1,8 @@ -import React, { useEffect, useRef, useState } from 'react'; +// /app/components/map-components.tsx +import React, { useEffect, useRef } from 'react'; import mapboxgl from 'mapbox-gl'; import 'mapbox-gl/dist/mapbox-gl.css'; -import { Badge } from "@/components/ui/badge"; -import { Star, MapPin, Globe, Phone } from 'lucide-react'; import { Skeleton } from "@/components/ui/skeleton"; -import { Card, CardContent } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || ''; @@ -19,15 +15,6 @@ export interface Place { name: string; location: Location; vicinity?: string; - rating?: number; - user_ratings_total?: number; - place_id?: string; // TripAdvisor location_id - distance?: string; // Distance from search center - bearing?: string; // Direction from search center (e.g., "north", "southeast") - type?: string; // Type of place (e.g., "restaurant", "hotel") - phone?: string; // Phone number if available - website?: string; // Website URL if available - photos?: string[]; // Array of photo URLs } interface MapProps { @@ -129,63 +116,6 @@ interface PlaceDetailsProps extends Place { onCallClick?: () => void; } -const PlaceDetails = ({ - name, - vicinity, - rating, - user_ratings_total, - onDirectionsClick, - onWebsiteClick, - onCallClick -}: PlaceDetailsProps) => ( - - -
-
-

{name}

- {vicinity &&

{vicinity}

} -
- {rating && ( - - - {rating} - {user_ratings_total && ({user_ratings_total})} - - )} -
-
- - - -
-
-
-); - interface MapContainerProps { title: string; center: Location; @@ -212,64 +142,8 @@ const MapContainer: React.FC = ({

{title}

- {places.map((place, index) => ( - - ))}
); }; -interface MapViewProps extends MapProps { - view: 'map' | 'list'; - onViewChange: (view: 'map' | 'list') => void; -} - -const MapView: React.FC = ({ - center, - places = [], - zoom = 14, - view, - onViewChange -}) => { - const [selectedPlace, setSelectedPlace] = useState(null); - - return ( -
-
-

- Nearby Places -

- onViewChange(v as 'map' | 'list')}> - - Map - List - - -
- - {view === 'map' ? ( -
- - {selectedPlace && ( -
- -
- )} -
- ) : ( -
- {places.map((place, index) => ( - - ))} -
- )} -
- ); -}; - -export { MapComponent, MapSkeleton, MapContainer, PlaceDetails, MapView }; \ No newline at end of file +export { MapComponent, MapSkeleton, MapContainer }; \ No newline at end of file diff --git a/components/nearby-search-map-view.tsx b/components/nearby-search-map-view.tsx new file mode 100644 index 0000000..0ed548f --- /dev/null +++ b/components/nearby-search-map-view.tsx @@ -0,0 +1,146 @@ +/* eslint-disable @next/next/no-img-element */ +import React, { useState } from 'react'; +import { cn } from "@/lib/utils"; +import dynamic from 'next/dynamic'; +import PlaceCard from './place-card'; +import { Badge } from './ui/badge'; + + +interface Location { + lat: number; + lng: number; +} + +interface Photo { + thumbnail: string; + small: string; + medium: string; + large: string; + original: string; + caption?: string; +} + +interface Place { + name: string; + location: Location; + place_id: string; + vicinity: string; + rating?: number; + reviews_count?: number; + price_level?: string; + description?: string; + photos?: Photo[]; + is_closed?: boolean; + next_open_close?: string; + type?: string; + cuisine?: string; + source?: string; + phone?: string; + website?: string; + hours?: string[]; + distance?: string; + bearing?: string; +} + +// Dynamic import for the map component +const InteractiveMap = dynamic(() => import('./interactive-maps'), { ssr: false }); + +interface NearbySearchMapViewProps { + center: { + lat: number; + lng: number; + }; + places: Place[]; + type: string; +} + +const NearbySearchMapView: React.FC = ({ + center, + places, + type, +}) => { + const [viewMode, setViewMode] = useState<'map' | 'list'>('map'); + const [selectedPlace, setSelectedPlace] = useState(null); + + return ( +
+ Beta + {/* View Toggle */} +
+ + +
+ +
+ {/* Map Container */} +
+ + + {/* Selected Place Overlay - Only show in map view */} + {selectedPlace && viewMode === 'map' && ( +
+ {}} + isSelected={true} + variant="overlay" + /> +
+ )} +
+ + {/* List Container */} + {viewMode === 'list' && ( +
+
+
+ {places.map((place, index) => ( + setSelectedPlace(place)} + isSelected={selectedPlace?.place_id === place.place_id} + variant="list" + /> + ))} +
+
+
+ )} +
+
+ ); +}; + +export default NearbySearchMapView; \ No newline at end of file diff --git a/components/place-card.tsx b/components/place-card.tsx new file mode 100644 index 0000000..68fa32b --- /dev/null +++ b/components/place-card.tsx @@ -0,0 +1,234 @@ +/* eslint-disable @next/next/no-img-element */ +import React from 'react'; +import { cn } from "@/lib/utils"; +import { Button } from '@/components/ui/button'; +import PlaceholderImage from './placeholder-image'; + + +interface Location { + lat: number; + lng: number; +} + +interface Photo { + thumbnail: string; + small: string; + medium: string; + large: string; + original: string; + caption?: string; +} + +interface Place { + name: string; + location: Location; + place_id: string; + vicinity: string; + rating?: number; + reviews_count?: number; + price_level?: string; + description?: string; + photos?: Photo[]; + is_closed?: boolean; + next_open_close?: string; + type?: string; + cuisine?: string; + source?: string; + phone?: string; + website?: string; + hours?: string[]; + distance?: string; + bearing?: string; +} + +interface PlaceCardProps { + place: Place; + onClick: () => void; + isSelected?: boolean; + variant?: 'overlay' | 'list'; +} + +const PlaceCard: React.FC = ({ + place, + onClick, + isSelected = false, + variant = 'list' +}) => { + const isOverlay = variant === 'overlay'; + + // Validation helpers from before... + const isValidString = (str: any): boolean => { + return str !== undefined && + str !== null && + String(str).trim() !== '' && + String(str).toLowerCase() !== 'undefined' && + String(str).toLowerCase() !== 'null'; + }; + + const isValidNumber = (num: any): boolean => { + if (num === undefined || num === null) return false; + const parsed = Number(num); + return !isNaN(parsed) && isFinite(parsed) && parsed !== 0; + }; + + const formatRating = (rating: any): string => { + if (!isValidNumber(rating)) return ''; + const parsed = Number(rating); + return parsed.toFixed(1); + }; + + return ( +
+
+ {/* Image Container */} +
+ {place.photos?.[0]?.medium ? ( + {place.name} + ) : ( + + )} +
+ +
+ {/* Title Section */} +
+

+ {place.name} +

+ + {isValidNumber(place.rating) && ( +
+ + {formatRating(place.rating)} + + {isValidNumber(place.reviews_count) && ( + + ({place.reviews_count} reviews) + + )} +
+ )} +
+ + {/* Status & Info */} +
+ {place.is_closed !== undefined && ( + + {place.is_closed ? "Closed" : "Open now"} + + )} + {isValidString(place.next_open_close) && ( + <> + · + + until {place.next_open_close} + + + )} + {isValidString(place.type) && ( + <> + · + + {place.type} + + + )} + {isValidString(place.price_level) && ( + <> + · + + {place.price_level} + + + )} +
+ + {/* Description */} + {isValidString(place.description) && ( +

+ {place.description} +

+ )} + + {/* Action Buttons */} +
+ + {isValidString(place.website) && ( + + )} + {isValidString(place.phone) && ( + + )} + {isValidString(place.place_id) && ( + + )} +
+
+
+
+ ); +}; + +export default PlaceCard; \ No newline at end of file diff --git a/components/placeholder-image.tsx b/components/placeholder-image.tsx new file mode 100644 index 0000000..de16a76 --- /dev/null +++ b/components/placeholder-image.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { ImageIcon } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface PlaceholderImageProps { + className?: string; +} + +const PlaceholderImage: React.FC = ({ className }) => { + return ( +
+ +
+ ); +}; + +export default PlaceholderImage; \ No newline at end of file diff --git a/components/ui/form-component.tsx b/components/ui/form-component.tsx index 3492a0d..b5e21c0 100644 --- a/components/ui/form-component.tsx +++ b/components/ui/form-component.tsx @@ -194,6 +194,7 @@ interface FormComponentProps { selectedModel: string; setSelectedModel: (value: string) => void; resetSuggestedQuestions: () => void; + lastSubmittedQueryRef: React.MutableRefObject; } const AttachmentPreview: React.FC<{ attachment: Attachment | UploadingAttachment, onRemove: () => void, isUploading: boolean }> = ({ attachment, onRemove, isUploading }) => { @@ -214,7 +215,7 @@ const AttachmentPreview: React.FC<{ attachment: Attachment | UploadingAttachment animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.8 }} transition={{ duration: 0.2 }} - className="relative flex items-center bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg p-2 pr-8 gap-2 shadow-sm flex-shrink-0" + className="relative flex items-center bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg p-2 pr-8 gap-2 shadow-sm flex-shrink-0 z-0" > {isUploading ? (
@@ -305,6 +306,7 @@ const FormComponent: React.FC = ({ selectedModel, setSelectedModel, resetSuggestedQuestions, + lastSubmittedQueryRef, }) => { const [uploadQueue, setUploadQueue] = useState>([]); const { width } = useWindowSize(); @@ -388,6 +390,7 @@ const FormComponent: React.FC = ({ if (input.trim() || attachments.length > 0) { setHasSubmitted(true); + lastSubmittedQueryRef.current = input.trim(); track("search input", {query: input.trim()}) handleSubmit(event, { @@ -401,7 +404,7 @@ const FormComponent: React.FC = ({ } else { toast.error("Please enter a search query or attach an image."); } - }, [input, attachments, setHasSubmitted, handleSubmit, setAttachments, fileInputRef]); + }, [input, attachments, setHasSubmitted, handleSubmit, setAttachments, fileInputRef, lastSubmittedQueryRef]); const submitForm = useCallback(() => { onSubmit({ preventDefault: () => { }, stopPropagation: () => { } } as React.FormEvent); @@ -428,7 +431,7 @@ const FormComponent: React.FC = ({ return (
0 || uploadQueue.length > 0 ? "bg-gray-100/70 dark:bg-neutral-800 p-1" : "bg-transparent" diff --git a/package.json b/package.json index 87d76db..faaa034 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "lucide-react": "^0.424.0", "mapbox-gl": "^3.7.0", "marked-react": "^2.0.0", - "next": "^14.2.10", + "next": "^14.2.17", "next-themes": "^0.3.0", "openai": "^4.56.0", "react": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d93fb9b..f9fd673 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,7 +94,7 @@ dependencies: version: 1.34.2 '@vercel/analytics': specifier: ^1.3.1 - version: 1.3.1(next@14.2.14)(react@18.3.1) + version: 1.3.1(next@14.2.17)(react@18.3.1) '@vercel/blob': specifier: ^0.23.4 version: 0.23.4 @@ -153,8 +153,8 @@ dependencies: specifier: ^2.0.0 version: 2.0.0(react@18.3.1) next: - specifier: ^14.2.10 - version: 14.2.14(react-dom@18.3.1)(react@18.3.1) + specifier: ^14.2.17 + version: 14.2.17(react-dom@18.3.1)(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1)(react@18.3.1) @@ -782,8 +782,8 @@ packages: - ws dev: false - /@next/env@14.2.14: - resolution: {integrity: sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg==} + /@next/env@14.2.17: + resolution: {integrity: sha512-MCgO7VHxXo8sYR/0z+sk9fGyJJU636JyRmkjc7ZJY8Hurl8df35qG5hoAh5KMs75FLjhlEo9bb2LGe89Y/scDA==} dev: false /@next/eslint-plugin-next@14.2.5: @@ -792,8 +792,8 @@ packages: glob: 10.3.10 dev: true - /@next/swc-darwin-arm64@14.2.14: - resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} + /@next/swc-darwin-arm64@14.2.17: + resolution: {integrity: sha512-WiOf5nElPknrhRMTipXYTJcUz7+8IAjOYw3vXzj3BYRcVY0hRHKWgTgQ5439EvzQyHEko77XK+yN9x9OJ0oOog==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -801,8 +801,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.2.14: - resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} + /@next/swc-darwin-x64@14.2.17: + resolution: {integrity: sha512-29y425wYnL17cvtxrDQWC3CkXe/oRrdt8ie61S03VrpwpPRI0XsnTvtKO06XCisK4alaMnZlf8riwZIbJTaSHQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -810,8 +810,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.2.14: - resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} + /@next/swc-linux-arm64-gnu@14.2.17: + resolution: {integrity: sha512-SSHLZls3ZwNEHsc+d0ynKS+7Af0Nr8+KTUBAy9pm6xz9SHkJ/TeuEg6W3cbbcMSh6j4ITvrjv3Oi8n27VR+IPw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -819,8 +819,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.2.14: - resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} + /@next/swc-linux-arm64-musl@14.2.17: + resolution: {integrity: sha512-VFge37us5LNPatB4F7iYeuGs9Dprqe4ZkW7lOEJM91r+Wf8EIdViWHLpIwfdDXinvCdLl6b4VyLpEBwpkctJHA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -828,8 +828,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.2.14: - resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} + /@next/swc-linux-x64-gnu@14.2.17: + resolution: {integrity: sha512-aaQlpxUVb9RZ41adlTYVQ3xvYEfBPUC8+6rDgmQ/0l7SvK8S1YNJzPmDPX6a4t0jLtIoNk7j+nroS/pB4nx7vQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -837,8 +837,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.2.14: - resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} + /@next/swc-linux-x64-musl@14.2.17: + resolution: {integrity: sha512-HSyEiFaEY3ay5iATDqEup5WAfrhMATNJm8dYx3ZxL+e9eKv10XKZCwtZByDoLST7CyBmyDz+OFJL1wigyXeaoA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -846,8 +846,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.2.14: - resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} + /@next/swc-win32-arm64-msvc@14.2.17: + resolution: {integrity: sha512-h5qM9Btqv87eYH8ArrnLoAHLyi79oPTP2vlGNSg4CDvUiXgi7l0+5KuEGp5pJoMhjuv9ChRdm7mRlUUACeBt4w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -855,8 +855,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.2.14: - resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} + /@next/swc-win32-ia32-msvc@14.2.17: + resolution: {integrity: sha512-BD/G++GKSLexQjdyoEUgyo5nClU7er5rK0sE+HlEqnldJSm96CIr/+YOTT063LVTT/dUOeQsNgp5DXr86/K7/A==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -864,8 +864,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.2.14: - resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==} + /@next/swc-win32-x64-msvc@14.2.17: + resolution: {integrity: sha512-vkQfN1+4V4KqDibkW2q0sJ6CxQuXq5l2ma3z0BRcfIqkAMZiiW67T9yCpwqJKP68QghBtPEFjPAlaqe38O6frw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2013,7 +2013,7 @@ packages: crypto-js: 4.2.0 dev: false - /@vercel/analytics@1.3.1(next@14.2.14)(react@18.3.1): + /@vercel/analytics@1.3.1(next@14.2.17)(react@18.3.1): resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==} peerDependencies: next: '>= 13' @@ -2024,7 +2024,7 @@ packages: react: optional: true dependencies: - next: 14.2.14(react-dom@18.3.1)(react@18.3.1) + next: 14.2.17(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 server-only: 0.0.1 dev: false @@ -5219,8 +5219,8 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false - /next@14.2.14(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-Q1coZG17MW0Ly5x76shJ4dkC23woLAhhnDnw+DfTc7EpZSGuWrlsZ3bZaO8t6u1Yu8FVfhkqJE+U8GC7E0GLPQ==} + /next@14.2.17(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-hNo/Zy701DDO3nzKkPmsLRlDfNCtb1OJxFUvjGEl04u7SFa3zwC6hqsOUzMajcaEOEV8ey1GjvByvrg0Qr5AiQ==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -5237,7 +5237,7 @@ packages: sass: optional: true dependencies: - '@next/env': 14.2.14 + '@next/env': 14.2.17 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001667 @@ -5247,15 +5247,15 @@ packages: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.14 - '@next/swc-darwin-x64': 14.2.14 - '@next/swc-linux-arm64-gnu': 14.2.14 - '@next/swc-linux-arm64-musl': 14.2.14 - '@next/swc-linux-x64-gnu': 14.2.14 - '@next/swc-linux-x64-musl': 14.2.14 - '@next/swc-win32-arm64-msvc': 14.2.14 - '@next/swc-win32-ia32-msvc': 14.2.14 - '@next/swc-win32-x64-msvc': 14.2.14 + '@next/swc-darwin-arm64': 14.2.17 + '@next/swc-darwin-x64': 14.2.17 + '@next/swc-linux-arm64-gnu': 14.2.17 + '@next/swc-linux-arm64-musl': 14.2.17 + '@next/swc-linux-x64-gnu': 14.2.17 + '@next/swc-linux-x64-musl': 14.2.17 + '@next/swc-win32-arm64-msvc': 14.2.17 + '@next/swc-win32-ia32-msvc': 14.2.17 + '@next/swc-win32-x64-msvc': 14.2.17 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros diff --git a/tailwind.config.ts b/tailwind.config.ts index 7f34b22..d694d9f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -18,6 +18,10 @@ const config = { }, }, extend: { + height: { + screen: '100vh', + 'screen-small': '100svh', + }, fontFamily: { sans: ['Inter', 'sans-serif'], serif: ['var(--font-serif)', 'serif'],