Initial commit
This commit is contained in:
+24
@@ -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?
|
||||||
@@ -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...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
@@ -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
@@ -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>
|
||||||
Generated
+3229
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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 på 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 på 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 på 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 få 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
|
}
|
||||||
@@ -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>,
|
||||||
|
)
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -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()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user