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
60 changes: 60 additions & 0 deletions public/hero.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
310 changes: 204 additions & 106 deletions server/index.mjs

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions server/sql/app/home_municipality.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Legger til kolonnen for «Mitt sted» (kommunen brukeren vil se turforslag fra på forsiden).
--
-- Må kjøres én gang av en databasebruker som eier tabellen norwegian_explorer_app.users.
-- Brukeren «gruppe» har ikke lov til å endre tabellen, så kjør den på VM-en, f.eks.:
-- sudo -u postgres psql -d norwegian_explorer -f server/sql/app/home_municipality.sql
-- Fila kan trygt kjøres flere ganger.
ALTER TABLE norwegian_explorer_app.users ADD COLUMN IF NOT EXISTS home_municipality text;
9 changes: 7 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'maplibre-gl/dist/maplibre-gl.css'
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'
import './App.css'
import './Filters.css'
import Home from './Home'
import Profile, { AuthGate } from './Profile'

maplibregl.setWorkerUrl(workerUrl)
Expand Down Expand Up @@ -118,8 +119,10 @@ function RouteMap({ route }: { route: Route }) {

export default function App() {
const [authenticated, setAuthenticated] = useState(false)
// Forsiden vises først. Innloggingssiden vises når man trykker «Logg inn».
const [showLogin, setShowLogin] = useState(false)
const handleAuthenticated = useCallback(() => setAuthenticated(true), [])
const handleLogout = useCallback(() => setAuthenticated(false), [])
const handleLogout = useCallback(() => { setAuthenticated(false); setShowLogin(false) }, [])
const detailsRef = useRef<HTMLHeadingElement>(null)
const [route, setRoute] = useState<Route | null>(null)
const [routeVariants, setRouteVariants] = useState<RouteSummary[]>([])
Expand Down Expand Up @@ -195,7 +198,9 @@ export default function App() {
void load()
return () => controller.abort()
}, [selected, attempt])
if (!authenticated) return <AuthGate onAuthenticated={handleAuthenticated} />
// Velger man en tur på forsiden, åpnes den etter innlogging
if (!authenticated && !showLogin) return <Home onLoginClick={() => setShowLogin(true)} onSelectRoute={item => { openRoute(item); setShowLogin(true) }} />
if (!authenticated) return <AuthGate onAuthenticated={handleAuthenticated} onHome={() => setShowLogin(false)} />

return <>
<a className="skip-link" href="#main">Hopp til ruteinformasjon</a>
Expand Down
241 changes: 241 additions & 0 deletions src/Home.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/* Forsiden. Header-stilen kommer fra App.css. */
.home-header .header-actions {
display: flex;
gap: 1rem;
align-items: center;
}

/* Forsiden skal gå kant i kant, så vi overstyrer max-width/padding fra main i App.css */
.home-main {
max-width: none;
padding: 0;
}

.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}

/* ---------- Hero med bakgrunnsbilde ---------- */
.home-hero {
position: relative;
min-height: 560px;
display: flex;
align-items: center;
justify-content: center;
padding: 80px 5%;
background-color: #2c5a49;
/* vises hvis bildet mangler eller ikke er lastet ennå */
background-size: cover;
background-position: 72% center; /* holder fotturisten i bildet også på smale skjermer */
}

/* Mørk toning over bildet så den hvite teksten får god kontrast (WCAG) */
.home-hero::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(rgb(0 0 0 / 0.35), rgb(0 0 0 / 0.45));
}

.home-hero-content {
position: relative;
width: min(100%, 960px);
text-align: center;
}

.home-hero h1 {
color: #fff;
margin: 0 0 40px;
text-shadow: 0 2px 12px rgb(0 0 0 / 0.35);
}

.home-search {
display: flex;
align-items: center;
gap: 14px;
background: #fff;
border-radius: 10px;
padding: 0 24px;
box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
color: #2c6e57;
}

.home-search input {
flex: 1;
min-width: 0;
border: 0;
padding: 24px 0;
font: inherit;
font-size: 20px;
color: #203b32;
background: transparent;
}

.home-search input::placeholder {
color: #2c6e57;
}

/* Skjuler den oransje fokusrammen (fra App.css) rundt søkefeltet */
.home-search input:focus-visible {
outline: none;
}

.home-search-results {
text-align: left;
margin-top: 10px;
}

.home-search-results p {
color: #fff;
margin: 8px 4px;
font-size: 14px;
}

/* Ett hvitt panel med gruppene «Steder» og «Turer» */
.home-search-panel {
background: #fff;
border-radius: 10px;
box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
padding: 6px;
}

.home-search-group {
margin: 10px 16px 4px;
font-size: 12px;
letter-spacing: 1.5px;
text-transform: uppercase;
color: #557365;
}

.home-search-results ul {
list-style: none;
margin: 0;
padding: 0;
}

.home-search-results button {
width: 100%;
display: flex;
justify-content: space-between;
gap: 12px;
text-align: left;
background: transparent;
color: #203b32;
padding: 12px 16px;
}

.home-search-results button:hover {
background: #edf2e9;
}

.home-search-results span {
color: #5a6c61;
font-size: 14px;
white-space: nowrap;
}

.home-hero-credit {
position: absolute;
right: 16px;
bottom: 8px;
margin: 0;
font-size: 13px;
color: rgb(255 255 255 / 0.85);
}

/* ---------- Turforslag i nærområdet ---------- */
.home-nearby {
max-width: 1200px;
margin: 0 auto;
padding: 72px 5%;
text-align: center;
}

.home-nearby h2 {
font-size: clamp(26px, 3.5vw, 38px);
margin: 0;
}





.home-suggestions {
list-style: none;
padding: 0;
margin: 24px 0 0;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
text-align: left;
}

.home-suggestions button {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 6px;
text-align: left;
background: #fff;
color: #203b32;
border: 1px solid #dce1d7;
border-radius: 14px;
padding: 20px;
overflow-wrap: anywhere;
}

.home-suggestions button:hover {
border-color: #126b51;
}

.home-suggestions strong {
font-size: 18px;
}

.home-suggestion-length {
font-size: 15px;
font-weight: 600;
color: #126b51;
}

.home-suggestion-meta {
font-size: 13px;
color: #5a6c61;
}

@media (max-width: 800px) {
.home-hero {
min-height: 440px;
}

.home-search input {
font-size: 17px;
padding: 18px 0;
}

.home-suggestions {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}

@media (max-width: 480px) {
.home-suggestions {
grid-template-columns: 1fr;
}
}
.home-nearby-subtitle {
color: #5a6c61;
margin: 8px 0 0;
}

.home-nearby:focus { outline: none; } /* får fokus fra koden når et sted velges, trenger ingen ramme */

.home-nearby-reset {
margin-top: 8px;
}
Loading