Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 6 additions & 319 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@import "tailwindcss";
@import "mapbox-gl/dist/mapbox-gl.css";
/* Mapbox's stylesheet and our map-specific overrides (controls, popups, photo
markers) live in components/map-overrides.css, imported by MapView.tsx. They
ship in the lazy map chunk rather than this render-blocking critical CSS, and
only target DOM that exists once the map has mounted. */

@theme inline {
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
Expand Down Expand Up @@ -28,135 +31,9 @@ body {
letter-spacing: -0.01em;
}

.mapboxgl-popup-content,
.lofoten-popup .mapboxgl-popup-content {
border-radius: 14px !important;
background: rgba(255, 253, 246, 0.98) !important;
color: #1c1917 !important;
border: 1px solid rgba(214, 204, 188, 0.9);
box-shadow: 0 24px 60px rgba(46, 61, 54, 0.22) !important;
padding: 0 !important;
overflow: hidden;
}
.mapboxgl-popup-tip,
.lofoten-popup .mapboxgl-popup-tip { border-top-color: rgba(255, 253, 246, 0.98) !important; }
.mapboxgl-popup-close-button,
.lofoten-popup .mapboxgl-popup-close-button {
border-radius: 999px;
color: #57534e !important;
font-size: 20px;
line-height: 1;
margin: 8px;
padding: 5px 8px;
}
.mapboxgl-popup-close-button:hover,
.mapboxgl-popup-close-button:focus,
.lofoten-popup .mapboxgl-popup-close-button:hover,
.lofoten-popup .mapboxgl-popup-close-button:focus {
background: rgba(28, 25, 23, 0.06) !important;
color: #1c1917 !important;
outline: 2px solid rgba(15, 118, 110, 0.35);
outline-offset: 2px;
}
.mapboxgl-ctrl-group {
border-radius: 10px !important;
overflow: hidden;
background: rgba(255, 253, 246, 0.94) !important;
box-shadow: 0 12px 30px rgba(46, 61, 54, 0.18) !important;
}
.mapboxgl-ctrl button { color: #1c1917 !important; }

.lofoten-photo-marker {
width: 48px;
height: 48px;
cursor: pointer;
overflow: visible;
border: 3px solid #fffdf6;
border-radius: 12px;
background: #e7a13d;
padding: 0;
box-shadow:
0 2px 5px rgba(28, 25, 23, 0.22),
0 10px 22px rgba(46, 61, 54, 0.22);
transition: transform 160ms ease, box-shadow 160ms ease;
}

.lofoten-photo-marker::before,
.lofoten-photo-marker::after {
content: "";
position: absolute;
z-index: -1;
inset: 2px;
border: 2px solid #fffdf6;
border-radius: 11px;
background: #d6ccb8;
box-shadow: 0 4px 10px rgba(28, 25, 23, 0.16);
}

.lofoten-photo-marker:not(.lofoten-photo-marker-cluster)::before,
.lofoten-photo-marker:not(.lofoten-photo-marker-cluster)::after {
display: none;
}

.lofoten-photo-marker-cluster {
width: 56px;
height: 56px;
}

.lofoten-photo-marker-cluster::before {
transform: translate(5px, -4px) rotate(7deg);
}

.lofoten-photo-marker-cluster::after {
transform: translate(-5px, 4px) rotate(-5deg);
}

.lofoten-photo-marker img {
display: block;
width: 100%;
height: 100%;
border-radius: 9px;
object-fit: cover;
background: #dbe7df;
}

/* Pulsing ring around the photo currently open in the editor. The outer div is
positioned by Mapbox (inline transform), so the animation lives on a child. */
.lofoten-photo-highlight {
width: 76px;
height: 76px;
cursor: grab;
}

.lofoten-photo-highlight:active {
cursor: grabbing;
}

.lofoten-photo-highlight-ring {
position: absolute;
inset: 0;
border: 3px solid #e7a13d;
border-radius: 18px;
box-shadow:
0 0 0 7px rgba(231, 161, 61, 0.28),
0 0 34px rgba(231, 161, 61, 0.55);
animation: lofoten-highlight-pulse 1.5s ease-in-out infinite;
}

@keyframes lofoten-highlight-pulse {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.14);
opacity: 0.65;
}
}

/* Slow drift on the active Journey hero slide — ambient motion that hints the
trip can be played. Disabled for reduced-motion users. */
trip can be played. Disabled for reduced-motion users. Lives here (not in the
map overrides) because the hero card renders independently of the map. */
.journey-hero-kenburns {
animation: journey-hero-kenburns 7s ease-out forwards;
}
Expand All @@ -169,193 +46,3 @@ body {
@media (prefers-reduced-motion: reduce) {
.journey-hero-kenburns { animation: none; }
}

/* Location-check preview: the flagged photo's time-neighbors and the
suggested corrected spot. */
.lofoten-outlier-neighbor {
width: 14px;
height: 14px;
border-radius: 9999px;
border: 2.5px solid #fffdf6;
background: #0f766e;
box-shadow: 0 2px 8px rgba(28, 25, 23, 0.35);
pointer-events: none;
}

.lofoten-outlier-center {
width: 36px;
height: 36px;
border-radius: 9999px;
border: 3px dashed #0f766e;
background: rgba(15, 118, 110, 0.14);
pointer-events: none;
}

.lofoten-photo-marker-fallback {
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
border-radius: 9px;
color: #fffdf6;
font-size: 0.62rem;
font-weight: 800;
text-transform: uppercase;
}

.lofoten-photo-marker-count {
position: absolute;
top: -10px;
right: -10px;
min-width: 25px;
height: 25px;
padding: 0 7px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #fffdf6;
border-radius: 999px;
background: #0f766e;
color: white;
font-size: 0.7rem;
font-weight: 800;
line-height: 1;
box-shadow: 0 4px 10px rgba(28, 25, 23, 0.24);
}

.lofoten-photo-marker:hover,
.lofoten-photo-marker:focus-visible {
z-index: 2;
box-shadow:
0 3px 7px rgba(28, 25, 23, 0.24),
0 14px 28px rgba(46, 61, 54, 0.28);
}

.lofoten-photo-marker:focus-visible {
outline: 3px solid rgba(15, 118, 110, 0.55);
outline-offset: 4px;
}

.map-unavailable .mapboxgl-ctrl,
.map-unavailable .mapboxgl-ctrl-bottom-left,
.map-unavailable .mapboxgl-ctrl-bottom-right,
.map-unavailable-active .mapboxgl-control-container,
.map-unavailable-active .mapboxgl-ctrl,
.map-unavailable-active .mapboxgl-ctrl-bottom-left,
.map-unavailable-active .mapboxgl-ctrl-bottom-right {
display: none !important;
}

@media (max-width: 767px) {
.mapboxgl-ctrl-bottom-left,
.mapboxgl-ctrl-bottom-right {
bottom: calc(11.75rem + env(safe-area-inset-bottom)) !important;
}

.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl {
margin-left: 0.75rem !important;
}

.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl {
margin-right: 0.75rem !important;
}
}

.lofoten-popup-card {
width: 16rem;
background: rgba(255, 253, 246, 0.98);
color: #1c1917;
}

.lofoten-popup-image {
display: block;
height: 9rem;
width: 100%;
object-fit: cover;
}

.lofoten-popup-video-fallback {
align-items: center;
background: #f5f5f4;
color: #78716c;
display: flex;
font-size: 0.85rem;
font-weight: 800;
justify-content: center;
letter-spacing: 0.08em;
text-transform: uppercase;
}

.lofoten-popup-body {
padding: 0.85rem;
}

.lofoten-popup-title {
color: #1c1917;
font-size: 0.92rem;
font-weight: 800;
line-height: 1.35;
padding-right: 1.5rem;
}

.lofoten-popup-meta {
color: #78716c;
font-size: 0.78rem;
line-height: 1.4;
margin-top: 0.35rem;
}

.lofoten-popup-by { font-weight: 600; color: #57534e; }

.lofoten-popup-actions {
display: flex;
gap: 0.4rem;
margin-top: 0.7rem;
padding-top: 0.6rem;
border-top: 1px solid rgba(214, 204, 188, 0.7);
}
.lofoten-popup-action {
flex: 1;
cursor: pointer;
border-radius: 8px;
border: 1px solid rgba(214, 204, 188, 0.9);
background: #fffdf6;
padding: 0.4rem 0.5rem;
font-size: 0.78rem;
font-weight: 800;
color: #1c1917;
transition: background 0.15s ease;
}
.lofoten-popup-action:hover { background: rgba(28, 25, 23, 0.05); }
.lofoten-popup-action:focus-visible {
outline: 2px solid rgba(15, 118, 110, 0.4);
outline-offset: 1px;
}
.lofoten-popup-action-danger {
border-color: #f6c9cf;
background: #fef2f3;
color: #b4232f;
}
.lofoten-popup-action-danger:hover { background: #fde4e6; }
.lofoten-popup-action-journey {
border-color: rgba(231, 161, 61, 0.6);
background: #fdf1dc;
color: #8a5414;
}
.lofoten-popup-action-journey:hover { background: #fbe6c2; }

.lofoten-popup-tag {
display: inline-block;
margin-bottom: 0.4rem;
border-radius: 999px;
padding: 0.12rem 0.5rem;
font-size: 0.62rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.lofoten-popup-tag-photo { background: #fbe7c6; color: #8a5212; }
.lofoten-popup-tag-note { background: #f6ead0; color: #7c4a14; }
.lofoten-popup-tag-place { background: #d7ecdf; color: #0f5f55; }
.lofoten-popup-tag-route { background: #d7ecdf; color: #0f5f55; }
25 changes: 25 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,34 @@ export const viewport: Viewport = {
themeColor: "#e7efe8",
};

// Origin of the Supabase project (storage + realtime). Derived from the public
// env var so a preconnect can warm the TLS handshake before the first photo or
// data request fires.
const supabaseOrigin = (() => {
try {
return process.env.NEXT_PUBLIC_SUPABASE_URL ? new URL(process.env.NEXT_PUBLIC_SUPABASE_URL).origin : null;
} catch {
return null;
}
})();

export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
return (
<html lang="en">
<head>
{/* Warm the connections the map and gallery need on first paint: Mapbox
style/tiles/telemetry and Supabase storage. preconnect opens the
TCP+TLS early; dns-prefetch is the cheaper fallback for older browsers. */}
<link rel="preconnect" href="https://api.mapbox.com" crossOrigin="" />
<link rel="preconnect" href="https://events.mapbox.com" crossOrigin="" />
<link rel="dns-prefetch" href="https://api.mapbox.com" />
{supabaseOrigin ? (
<>
<link rel="preconnect" href={supabaseOrigin} crossOrigin="" />
<link rel="dns-prefetch" href={supabaseOrigin} />
</>
) : null}
</head>
<body>{children}</body>
</html>
);
Expand Down
1 change: 1 addition & 0 deletions components/JourneyHeroCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export function JourneyHeroCard({ photos, momentCount, dayCount, onPlay, disable
src={previewUrl(photo) ?? undefined}
alt=""
aria-hidden
decoding="async"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full object-cover transition-opacity duration-[1200ms] ease-in-out motion-reduce:transition-none",
index === active ? "opacity-100" : "opacity-0",
Expand Down
6 changes: 6 additions & 0 deletions components/MapView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"use client";

import mapboxgl from "mapbox-gl";
// Loaded here rather than in globals.css so the vendored stylesheet and our map
// overrides ship with this lazily-imported (ssr:false) map chunk, keeping them
// off the render-blocking critical path. The overrides import must follow the
// vendor sheet so its cascade wins.
import "mapbox-gl/dist/mapbox-gl.css";
import "./map-overrides.css";
import { MapPin } from "lucide-react";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { LOFOTEN_CENTER } from "@/lib/geo";
Expand Down
Loading
Loading