Initial commit

This commit is contained in:
2026-03-20 02:46:55 +01:00 Verified
commit f0a903d073
13 changed files with 3657 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+73
View File
@@ -0,0 +1,73 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
+23
View File
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>naturvardarna</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+3229
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "naturvardarna",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwindcss": "^4.2.2"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.57.0",
"vite": "^8.0.1"
}
}
+167
View File
@@ -0,0 +1,167 @@
// src/App.tsx
import { useState } from 'react';
// --- TYPER ---
// Detta säkerställer att vi bara kan skicka in dessa exakta strängar
type Tab = 'home' | 'about' | 'contact';
interface NavbarProps {
activeTab: Tab;
setActiveTab: (tab: Tab) => void;
}
// --- KOMPONENTER ---
const Navbar = ({ activeTab, setActiveTab }: NavbarProps) => (
<nav className="bg-nature-dark text-white shadow-lg sticky top-0 z-50">
<div className="max-w-6xl mx-auto px-4">
<div className="flex justify-between items-center py-4">
<div
className="text-2xl font-bold cursor-pointer"
onClick={() => setActiveTab('home')}
>
🌲 Naturvärdarna
</div>
<div className="space-x-6 hidden md:flex">
<button
onClick={() => setActiveTab('home')}
className={`hover:text-nature-light cursor-pointer ${activeTab === 'home' ? 'border-b-2 border-white' : ''}`}
>
Hem
</button>
<button
onClick={() => setActiveTab('about')}
className={`hover:text-nature-light cursor-pointer ${activeTab === 'about' ? 'border-b-2 border-white' : ''}`}
>
Om oss
</button>
<button
onClick={() => setActiveTab('contact')}
className={`hover:text-nature-light cursor-pointer ${activeTab === 'contact' ? 'border-b-2 border-white' : ''}`}
>
Kontakt
</button>
</div>
</div>
</div>
</nav>
);
const Home = () => (
<div className="animate-fade-in">
{/* Hero-sektion */}
<div
className="h-96 bg-cover bg-center flex items-center justify-center relative"
style={{ backgroundImage: "url('https://images.unsplash.com/photo-1473448912268-2022ce9509d8?auto=format&fit=crop&q=80&w=2000')" }}
>
<div className="absolute inset-0 bg-black/40"></div>
<div className="relative z-10 text-center text-white px-4">
<h1 className="text-5xl font-bold mb-4 drop-shadow-md">Vi värnar om Kullaberg</h1>
<p className="text-xl max-w-2xl mx-auto drop-shadow-md">
Vi är sommarjobbarna som håller naturreservatet rent, säkert och vackert under årets mest hektiska veckor.
</p>
</div>
</div>
{/* Info-sektion */}
<div className="max-w-6xl mx-auto py-16 px-4 text-center">
<h2 className="text-3xl font-bold text-nature-dark mb-8">Vad gör vi?</h2>
<div className="grid md:grid-cols-3 gap-8">
<div className="bg-white p-6 rounded-lg shadow-md border-t-4 border-nature">
<h3 className="text-xl font-bold text-nature-dark mb-2">Håller rent</h3>
<p className="text-gray-600">Vi städar rastplatser, vandringsleder och stränder för att skydda djurlivet och naturen.</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md border-t-4 border-nature">
<h3 className="text-xl font-bold text-nature-dark mb-2">Guidar besökare</h3>
<p className="text-gray-600">Vi hjälper turister att hitta rätt och informerar om reservatets unika regler och miljö.</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-md border-t-4 border-nature">
<h3 className="text-xl font-bold text-nature-dark mb-2">Skyddar miljön</h3>
<p className="text-gray-600">Genom vår närvaro ser vi till att naturen Kullaberg bevaras för framtida generationer.</p>
</div>
</div>
</div>
</div>
);
const About = () => (
<div className="max-w-4xl mx-auto py-16 px-4 animate-fade-in">
<h1 className="text-4xl font-bold text-nature-dark mb-6 border-b-2 border-nature pb-2">Om Naturvärdarna</h1>
<img
src="https://images.unsplash.com/photo-1551632811-561732d1e306?auto=format&fit=crop&q=80&w=1000"
alt="Ungdomar som arbetar i naturen"
className="w-full h-80 object-cover rounded-xl shadow-lg mb-8"
/>
<div className="prose prose-lg text-gray-700 max-w-none">
<p className="mb-4">
Vi är ett gäng engagerade ungdomar som fått det ärofyllda uppdraget att arbeta som Naturvärdar vackra Kullaberg under sommarmånaderna.
</p>
<p className="mb-4">
När trycket från turister och besökare är som allra störst, är det vi som rycker ut. Våra dagar består av allt från att plocka skräp och underhålla eldstäder, till att svara frågor från nyfikna vandrare och se till att allemansrätten respekteras.
</p>
<p>
Kullaberg är en fantastisk plats, och vi är otroligt stolta över att hjälpa till att hålla reservatet i toppskick!
</p>
</div>
</div>
);
const Contact = () => (
<div className="max-w-4xl mx-auto py-16 px-4 animate-fade-in">
<h1 className="text-4xl font-bold text-nature-dark mb-6 border-b-2 border-nature pb-2">Kontakta oss</h1>
<div className="bg-white rounded-xl shadow-lg p-8">
<p className="text-gray-600 mb-6 text-lg">
Har du sett något i reservatet som behöver åtgärdas, eller vill du bara säga hej? Tveka inte att höra av dig!
</p>
<form className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Namn</label>
<input type="text" className="w-full border border-gray-300 rounded-md p-2 focus:ring-2 focus:ring-nature focus:border-nature outline-none transition-all" placeholder="Ditt namn" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">E-post</label>
<input type="email" className="w-full border border-gray-300 rounded-md p-2 focus:ring-2 focus:ring-nature focus:border-nature outline-none transition-all" placeholder="din@epost.se" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Meddelande</label>
<textarea rows={4} className="w-full border border-gray-300 rounded-md p-2 focus:ring-2 focus:ring-nature focus:border-nature outline-none transition-all" placeholder="Vad har du på hjärtat?"></textarea>
</div>
<button type="button" className="bg-nature-dark hover:bg-nature text-white font-bold py-2 px-6 rounded transition-colors duration-300 cursor-pointer">
Skicka meddelande
</button>
</form>
</div>
</div>
);
const Footer = () => (
<footer className="bg-stone-800 text-stone-300 py-8 text-center mt-auto">
<p>© {new Date().getFullYear()} Naturvärdarna Kullaberg. Ta hand om naturen!</p>
</footer>
);
// --- HUVUDKOMPONENT ---
export default function App() {
const [activeTab, setActiveTab] = useState<Tab>('home');
const renderContent = () => {
switch (activeTab) {
case 'home': return <Home />;
case 'about': return <About />;
case 'contact': return <Contact />;
default: return <Home />;
}
};
return (
<div className="min-h-screen flex flex-col font-sans">
<Navbar activeTab={activeTab} setActiveTab={setActiveTab} />
<main className="flex-grow">
{renderContent()}
</main>
<Footer />
</div>
);
}
+13
View File
@@ -0,0 +1,13 @@
/* src/index.css */
@import "tailwindcss";
@theme {
--color-nature-light: #ecfdf5;
--color-nature: #10b981;
--color-nature-dark: #064e3b;
}
body {
background-color: #f5f5f4; /* Mjuk jordnära stenfärg */
}
+12
View File
@@ -0,0 +1,12 @@
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
+28
View File
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2023",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
+10
View File
@@ -0,0 +1,10 @@
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
})