Initial static site implementation
- Node.js build script with gray-matter and marked - Self-hosted fonts (DM Serif Display, Karla) - Swedish badge system for origin transparency - Filtering by category, region, tags, and search - URL-based filter state for shareable links - Individual entry pages - About and badge info pages - Privacy-focused: no cookies, no tracking, no external requests - Hosted in Lerum, Sweden
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
101
README.md
101
README.md
@@ -1,2 +1,99 @@
|
|||||||
# ursprungsverige
|
# Ursprung Sverige
|
||||||
Ursprung Sverige gathers all Swedish manufacturers and highlights products that are created and produced within the country
|
|
||||||
|
Ursprung Sverige is a curated collection of Swedish products, services, experiences and manufacturers. The goal is to make it easier for people to support the Swedish economy and the Swedish culture.
|
||||||
|
|
||||||
|
## Privacy Principles
|
||||||
|
|
||||||
|
This site practices what it preaches:
|
||||||
|
|
||||||
|
- **No cookies** - We don't use any cookies
|
||||||
|
- **No tracking** - No Google Analytics, no external tracking scripts
|
||||||
|
- **No third-party services** - Only Google Fonts for typography (consider self-hosting)
|
||||||
|
- **Swedish hosting** - The site is hosted on servers in Lerum, Sweden
|
||||||
|
|
||||||
|
**Do not add:**
|
||||||
|
- Analytics scripts (Google Analytics, Plausible, etc.)
|
||||||
|
- Cookie consent banners
|
||||||
|
- External tracking pixels
|
||||||
|
- Third-party comment systems
|
||||||
|
- Social media embeds with tracking
|
||||||
|
|
||||||
|
If analytics are needed in the future, consider privacy-respecting, self-hosted alternatives like Umami or Matomo hosted in Sweden.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated site will be in the `dist/` folder.
|
||||||
|
|
||||||
|
## Adding Content
|
||||||
|
|
||||||
|
Create a Markdown file in the appropriate `content/` subfolder:
|
||||||
|
|
||||||
|
- `content/products/` - Swedish products
|
||||||
|
- `content/services/` - Swedish services
|
||||||
|
- `content/experiences/` - Swedish experiences
|
||||||
|
- `content/manufacturers/` - Swedish manufacturers
|
||||||
|
|
||||||
|
### Content Format
|
||||||
|
|
||||||
|
Each entry is a Markdown file with YAML frontmatter:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: Fjällräven
|
||||||
|
category: manufacturers
|
||||||
|
region: Norrbotten
|
||||||
|
tags: [friluftsliv, kläder, hållbarhet]
|
||||||
|
website: https://fjallraven.com
|
||||||
|
---
|
||||||
|
|
||||||
|
Description of the entry in Markdown format...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontmatter Fields
|
||||||
|
|
||||||
|
| Field | Required | Description |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| `title` | Yes | Name of the product/service/etc |
|
||||||
|
| `category` | No | Auto-detected from folder if not specified |
|
||||||
|
| `region` | No | Swedish region (län) |
|
||||||
|
| `tags` | No | Array of tags for filtering |
|
||||||
|
| `website` | No | Link to website |
|
||||||
|
| `image` | No | Featured image URL (displayed at top of card) |
|
||||||
|
| `logo` | No | Logo URL (displayed next to title) |
|
||||||
|
| `badge` | No | Swedish origin badge (see below) |
|
||||||
|
|
||||||
|
### Badges
|
||||||
|
|
||||||
|
Tiered badges indicate how Swedish a product/company is:
|
||||||
|
|
||||||
|
| Tier | Badge Value | Display | For | Description |
|
||||||
|
|------|-------------|---------|-----|-------------|
|
||||||
|
| 1 | `akta-svenskt` | Äkta Svenskt (gold) | All | 100% Swedish: company, production/hosting, suppliers |
|
||||||
|
| 2 | `tillverkat-i-sverige` | Tillverkat i Sverige (silver) | Physical | Swedish company, manufactured in Sweden |
|
||||||
|
| 2 | `svenskt-moln` | Svenskt Moln (silver) | Digital | Swedish company, Swedish servers/hosting |
|
||||||
|
| 3 | `designat-i-sverige` | Designat i Sverige (bronze) | Physical | Swedish design, production may be abroad |
|
||||||
|
| 3 | `utvecklat-i-sverige` | Utvecklat i Sverige (bronze) | Digital | Swedish development, hosting may be abroad |
|
||||||
|
| 4 | `svenskt-foretag` | Svenskt Företag (blue) | All | Swedish-founded/registered company |
|
||||||
|
|
||||||
|
### Images
|
||||||
|
|
||||||
|
Place images in the `static/` folder. They're symlinked to `dist/static/` (no copying), so reference them as `/static/images/example.png` in your content.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Watch mode rebuilds on file changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Deploy the `dist/` folder to a Swedish hosting provider. The production site is hosted in Lerum, Sweden.
|
||||||
|
|
||||||
|
**Important:** Do not deploy to non-Swedish hosting services (Netlify, Vercel, Cloudflare, etc.) as this would contradict the site's principles.
|
||||||
365
build.js
Normal file
365
build.js
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import matter from 'gray-matter';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
|
const CONTENT_DIR = './content';
|
||||||
|
const TEMPLATE_DIR = './templates';
|
||||||
|
const PUBLIC_DIR = './public';
|
||||||
|
const STATIC_DIR = './static';
|
||||||
|
const DIST_DIR = './dist';
|
||||||
|
|
||||||
|
// Swedish translations for categories
|
||||||
|
const CATEGORY_LABELS = {
|
||||||
|
manufacturers: 'Tillverkare',
|
||||||
|
products: 'Produkter',
|
||||||
|
services: 'Tjänster',
|
||||||
|
experiences: 'Upplevelser'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Badge tiers (highest to lowest)
|
||||||
|
const BADGE_TIERS = {
|
||||||
|
// Tier 1 - Gold (universal)
|
||||||
|
'akta-svenskt': { label: 'Äkta Svenskt', tier: 1, description: 'Helt svenskt: företag, produktion/drift och leverantörer' },
|
||||||
|
|
||||||
|
// Tier 2 - Silver (physical or digital)
|
||||||
|
'tillverkat-i-sverige': { label: 'Tillverkat i Sverige', tier: 2, description: 'Svenskt företag, tillverkat i Sverige' },
|
||||||
|
'svenskt-moln': { label: 'Svenskt Moln', tier: 2, description: 'Svenskt företag med svenska servrar och hosting' },
|
||||||
|
|
||||||
|
// Tier 3 - Bronze (physical or digital)
|
||||||
|
'designat-i-sverige': { label: 'Designat i Sverige', tier: 3, description: 'Svensk design, produktion kan ske utomlands' },
|
||||||
|
'utvecklat-i-sverige': { label: 'Utvecklat i Sverige', tier: 3, description: 'Svensk utveckling, hosting kan ske utomlands' },
|
||||||
|
|
||||||
|
// Tier 4 - Blue (universal)
|
||||||
|
'svenskt-foretag': { label: 'Svenskt Företag', tier: 4, description: 'Svenskregistrerat eller svenskgrundat företag' }
|
||||||
|
};
|
||||||
|
|
||||||
|
function translateCategory(category) {
|
||||||
|
return CATEGORY_LABELS[category] || category;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBadgeHTML(badgeKey) {
|
||||||
|
if (!badgeKey || !BADGE_TIERS[badgeKey]) return '';
|
||||||
|
const badge = BADGE_TIERS[badgeKey];
|
||||||
|
return `<span class="badge badge-tier-${badge.tier}" title="${badge.description}">${badge.label}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
function ensureDir(dir) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy directory recursively
|
||||||
|
function copyDirRecursive(src, dest) {
|
||||||
|
const items = fs.readdirSync(src, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const srcPath = path.join(src, item.name);
|
||||||
|
const destPath = path.join(dest, item.name);
|
||||||
|
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
ensureDir(destPath);
|
||||||
|
copyDirRecursive(srcPath, destPath);
|
||||||
|
} else {
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all markdown files from a directory recursively
|
||||||
|
function readContentFiles(dir) {
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const fullPath = path.join(dir, item.name);
|
||||||
|
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
entries.push(...readContentFiles(fullPath));
|
||||||
|
} else if (item.name.endsWith('.md')) {
|
||||||
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
|
const { data, content: body } = matter(content);
|
||||||
|
const category = path.basename(path.dirname(fullPath));
|
||||||
|
const slug = path.basename(item.name, '.md');
|
||||||
|
|
||||||
|
entries.push({
|
||||||
|
...data,
|
||||||
|
category: data.category || category,
|
||||||
|
body: marked(body),
|
||||||
|
bodyRaw: body,
|
||||||
|
slug,
|
||||||
|
url: `${category}/${slug}.html`,
|
||||||
|
sourcePath: fullPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple template replacement
|
||||||
|
function renderTemplate(template, data) {
|
||||||
|
let result = template;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
||||||
|
result = result.replace(regex, value ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate entry cards HTML
|
||||||
|
function generateEntryCards(entries) {
|
||||||
|
return entries.map(entry => {
|
||||||
|
const imageHtml = entry.image
|
||||||
|
? `<div class="entry-image"><img src="${entry.image}" alt="${entry.title}" loading="lazy"></div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const logoHtml = entry.logo
|
||||||
|
? `<img src="${entry.logo}" alt="${entry.title} logo" class="entry-logo">`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const badgeHtml = getBadgeHTML(entry.badge);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<article class="entry-card"
|
||||||
|
data-category="${entry.category || ''}"
|
||||||
|
data-region="${entry.region || ''}"
|
||||||
|
data-tags="${(entry.tags || []).join(',')}"
|
||||||
|
data-badge="${entry.badge || ''}">
|
||||||
|
${imageHtml}
|
||||||
|
<div class="entry-card-content">
|
||||||
|
<div class="entry-header">
|
||||||
|
${logoHtml}
|
||||||
|
<h2><a href="${entry.url}">${entry.title}</a></h2>
|
||||||
|
${badgeHtml}
|
||||||
|
</div>
|
||||||
|
<div class="entry-meta">
|
||||||
|
<button type="button" class="filter-btn category" data-filter-type="category" data-filter-value="${entry.category || ''}">${translateCategory(entry.category) || ''}</button>
|
||||||
|
${entry.region ? `<button type="button" class="filter-btn region" data-filter-type="region" data-filter-value="${entry.region}">${entry.region}</button>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="entry-body">${entry.body}</div>
|
||||||
|
${entry.tags?.length ? `
|
||||||
|
<div class="tags">
|
||||||
|
${entry.tags.map(tag => `<button type="button" class="tag filter-btn" data-filter-type="tag" data-filter-value="${tag}">${tag}</button>`).join('')}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
`}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate single entry page HTML
|
||||||
|
function generateEntryPage(entry, template) {
|
||||||
|
const imageHtml = entry.image
|
||||||
|
? `<div class="single-image"><img src="${entry.image}" alt="${entry.title}"></div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const logoHtml = entry.logo
|
||||||
|
? `<img src="${entry.logo}" alt="${entry.title} logo" class="single-logo">`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const badgeHtml = getBadgeHTML(entry.badge);
|
||||||
|
|
||||||
|
const entryHtml = `
|
||||||
|
<article class="single-entry">
|
||||||
|
${imageHtml}
|
||||||
|
<div class="single-header">
|
||||||
|
${logoHtml}
|
||||||
|
<div class="single-title-wrapper">
|
||||||
|
<h1>${entry.title}</h1>
|
||||||
|
${badgeHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="single-meta">
|
||||||
|
<a href="../index.html?category=${encodeURIComponent(entry.category || '')}" class="filter-link category">${translateCategory(entry.category) || ''}</a>
|
||||||
|
${entry.region ? `<a href="../index.html?region=${encodeURIComponent(entry.region)}" class="filter-link region">${entry.region}</a>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="single-body">${entry.body}</div>
|
||||||
|
${entry.website ? `<a href="${entry.website}" class="website-link" target="_blank" rel="noopener">Besök webbplats →</a>` : ''}
|
||||||
|
${entry.tags?.length ? `
|
||||||
|
<div class="tags">
|
||||||
|
${entry.tags.map(tag => `<a href="../index.html?tag=${encodeURIComponent(tag)}" class="tag">${tag}</a>`).join('')}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
<a href="../index.html" class="back-link">← Tillbaka till alla poster</a>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return renderTemplate(template, {
|
||||||
|
title: `${entry.title} | Ursprung Sverige`,
|
||||||
|
subtitle: entry.title,
|
||||||
|
content: entryHtml,
|
||||||
|
year: new Date().getFullYear()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract unique values for filters
|
||||||
|
function extractFilters(entries) {
|
||||||
|
const categories = [...new Set(entries.map(e => e.category).filter(Boolean))].sort();
|
||||||
|
const regions = [...new Set(entries.map(e => e.region).filter(Boolean))].sort();
|
||||||
|
const tags = [...new Set(entries.flatMap(e => e.tags || []))].sort();
|
||||||
|
|
||||||
|
return { categories, regions, tags };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate filter HTML
|
||||||
|
function generateFilterHTML(filters) {
|
||||||
|
const categoryOptions = filters.categories.map(c =>
|
||||||
|
`<option value="${c}">${translateCategory(c)}</option>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
const regionOptions = filters.regions.map(r =>
|
||||||
|
`<option value="${r}">${r}</option>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
const tagOptions = filters.tags.map(t =>
|
||||||
|
`<option value="${t}">${t}</option>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="filters">
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="category-filter">Kategori</label>
|
||||||
|
<select id="category-filter">
|
||||||
|
<option value="">Alla kategorier</option>
|
||||||
|
${categoryOptions}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="region-filter">Region</label>
|
||||||
|
<select id="region-filter">
|
||||||
|
<option value="">Alla regioner</option>
|
||||||
|
${regionOptions}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="tag-filter">Tagg</label>
|
||||||
|
<select id="tag-filter">
|
||||||
|
<option value="">Alla taggar</option>
|
||||||
|
${tagOptions}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="search-input">Sök</label>
|
||||||
|
<input type="text" id="search-input" placeholder="Sök...">
|
||||||
|
</div>
|
||||||
|
<button type="button" id="clear-filters" class="clear-filters-btn">Rensa filter</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main build function
|
||||||
|
function build() {
|
||||||
|
console.log('Building site...');
|
||||||
|
|
||||||
|
// Clean and create dist directory
|
||||||
|
if (fs.existsSync(DIST_DIR)) {
|
||||||
|
fs.rmSync(DIST_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
ensureDir(DIST_DIR);
|
||||||
|
|
||||||
|
// Read content
|
||||||
|
const entries = readContentFiles(CONTENT_DIR);
|
||||||
|
console.log(`Found ${entries.length} entries`);
|
||||||
|
|
||||||
|
// Extract filters
|
||||||
|
const filters = extractFilters(entries);
|
||||||
|
|
||||||
|
// Read templates
|
||||||
|
const indexTemplatePath = path.join(TEMPLATE_DIR, 'template.html');
|
||||||
|
const singleTemplatePath = path.join(TEMPLATE_DIR, 'single.html');
|
||||||
|
|
||||||
|
if (!fs.existsSync(indexTemplatePath)) {
|
||||||
|
console.error('Template not found:', indexTemplatePath);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexTemplate = fs.readFileSync(indexTemplatePath, 'utf-8');
|
||||||
|
const singleTemplate = fs.existsSync(singleTemplatePath)
|
||||||
|
? fs.readFileSync(singleTemplatePath, 'utf-8')
|
||||||
|
: indexTemplate; // Fallback to index template
|
||||||
|
|
||||||
|
// Generate index HTML
|
||||||
|
const indexHtml = renderTemplate(indexTemplate, {
|
||||||
|
title: 'Ursprung Sverige',
|
||||||
|
subtitle: 'En kurerad samling av svenska produkter, tjänster, upplevelser och tillverkare',
|
||||||
|
filters: generateFilterHTML(filters),
|
||||||
|
entries: generateEntryCards(entries),
|
||||||
|
entryCount: entries.length,
|
||||||
|
year: new Date().getFullYear()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write index.html
|
||||||
|
fs.writeFileSync(path.join(DIST_DIR, 'index.html'), indexHtml);
|
||||||
|
|
||||||
|
// Generate badges info page
|
||||||
|
const badgesTemplatePath = path.join(TEMPLATE_DIR, 'badges.html');
|
||||||
|
if (fs.existsSync(badgesTemplatePath)) {
|
||||||
|
const badgesTemplate = fs.readFileSync(badgesTemplatePath, 'utf-8');
|
||||||
|
const badgesHtml = renderTemplate(badgesTemplate, {
|
||||||
|
year: new Date().getFullYear()
|
||||||
|
});
|
||||||
|
fs.writeFileSync(path.join(DIST_DIR, 'om-markning.html'), badgesHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate about page
|
||||||
|
const aboutTemplatePath = path.join(TEMPLATE_DIR, 'about.html');
|
||||||
|
if (fs.existsSync(aboutTemplatePath)) {
|
||||||
|
const aboutTemplate = fs.readFileSync(aboutTemplatePath, 'utf-8');
|
||||||
|
const aboutHtml = renderTemplate(aboutTemplate, {
|
||||||
|
year: new Date().getFullYear()
|
||||||
|
});
|
||||||
|
fs.writeFileSync(path.join(DIST_DIR, 'om-oss.html'), aboutHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate individual entry pages
|
||||||
|
for (const entry of entries) {
|
||||||
|
const categoryDir = path.join(DIST_DIR, entry.category);
|
||||||
|
ensureDir(categoryDir);
|
||||||
|
|
||||||
|
const entryHtml = generateEntryPage(entry, singleTemplate);
|
||||||
|
fs.writeFileSync(path.join(categoryDir, `${entry.slug}.html`), entryHtml);
|
||||||
|
}
|
||||||
|
console.log(`Generated ${entries.length} entry pages`);
|
||||||
|
|
||||||
|
// Copy public assets (CSS, JS - small files)
|
||||||
|
if (fs.existsSync(PUBLIC_DIR)) {
|
||||||
|
copyDirRecursive(PUBLIC_DIR, DIST_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symlink static folder (images, large assets - avoids copying)
|
||||||
|
const staticDest = path.join(DIST_DIR, 'static');
|
||||||
|
if (fs.existsSync(STATIC_DIR) && !fs.existsSync(staticDest)) {
|
||||||
|
const absoluteStatic = path.resolve(STATIC_DIR);
|
||||||
|
fs.symlinkSync(absoluteStatic, staticDest);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Build complete! Output in', DIST_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch mode
|
||||||
|
if (process.argv.includes('--watch')) {
|
||||||
|
console.log('Watching for changes...');
|
||||||
|
build();
|
||||||
|
|
||||||
|
const watchDirs = [CONTENT_DIR, TEMPLATE_DIR, PUBLIC_DIR];
|
||||||
|
for (const dir of watchDirs) {
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
fs.watch(dir, { recursive: true }, (event, filename) => {
|
||||||
|
console.log(`\nChange detected: ${filename}`);
|
||||||
|
build();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
build();
|
||||||
|
}
|
||||||
10
content/experiences/icehotel.md
Normal file
10
content/experiences/icehotel.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: Icehotel
|
||||||
|
category: experiences
|
||||||
|
region: Norrbotten
|
||||||
|
tags: [vinter, is, konst, boende, norrsken]
|
||||||
|
website: https://icehotel.com
|
||||||
|
image: https://images.unsplash.com/photo-1520769945061-0a448c463865?w=800&q=80
|
||||||
|
---
|
||||||
|
|
||||||
|
Icehotel i Jukkasjärvi är världens första ishotell, byggt varje vinter sedan 1989. Hotellet återskapas varje år av konstnärer från hela världen och erbjuder unika upplevelser med isskulpturer, norrskenssafari och samisk kultur. En genuint svensk vinterupplevelse.
|
||||||
8
content/experiences/midsommar.md
Normal file
8
content/experiences/midsommar.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Midsommarfirande
|
||||||
|
category: experiences
|
||||||
|
region: Dalarna
|
||||||
|
tags: [tradition, sommar, dans, kultur, mat]
|
||||||
|
---
|
||||||
|
|
||||||
|
Midsommar är en av Sveriges viktigaste högtider och firas traditionellt kring sommarsolståndet. Med midsommarstång, blommor i håret, sill och jordgubbar samlas svenskar för dans och festligheter. Dalarna är särskilt känt för sitt autentiska midsommarfirande i pittoreska byar.
|
||||||
11
content/manufacturers/fjallraven.md
Normal file
11
content/manufacturers/fjallraven.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
title: Fjällräven
|
||||||
|
region: Norrbotten
|
||||||
|
tags: [friluftsliv, kläder, hållbarhet, ryggsäckar]
|
||||||
|
website: https://fjallraven.com
|
||||||
|
image: https://images.unsplash.com/photo-1551632811-561732d1e306?w=800&q=80
|
||||||
|
logo: /static/images/fjallraven_logo.png
|
||||||
|
badge: designat-i-sverige
|
||||||
|
---
|
||||||
|
|
||||||
|
Fjällräven är ett svenskt friluftsföretag grundat 1960 i Örnsköldsvik av Åke Nordin. Företaget är mest känt för sin ikoniska Kånken-ryggsäck och sina hållbara friluftskläder. Med fokus på kvalitet och miljömedvetenhet har Fjällräven blivit en symbol för svensk friluftskultur.
|
||||||
9
content/manufacturers/hasselblad.md
Normal file
9
content/manufacturers/hasselblad.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Hasselblad
|
||||||
|
category: manufacturers
|
||||||
|
region: Västra Götaland
|
||||||
|
tags: [kameror, fotografi, premium, teknik]
|
||||||
|
website: https://hasselblad.com
|
||||||
|
---
|
||||||
|
|
||||||
|
Hasselblad är en svensk tillverkare av mellanformatskameror, grundat 1841 i Göteborg. Företaget är legendariskt inom professionell fotografi och var kameran som dokumenterade månlandningen 1969. Hasselblad står för kompromisslös bildkvalitet och svensk ingenjörskonst.
|
||||||
9
content/products/dalahast.md
Normal file
9
content/products/dalahast.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Dalahäst
|
||||||
|
region: Dalarna
|
||||||
|
tags: [hantverk, tradition, trä, present]
|
||||||
|
website: https://www.grannas.com
|
||||||
|
badge: akta-svenskt
|
||||||
|
---
|
||||||
|
|
||||||
|
Dalahästen är en handsnidad trähäst som har tillverkats i Dalarna sedan 1600-talet. Varje häst snittas för hand och målas i traditionella mönster, ofta i rött med vit och grön dekor. Dalahästen har blivit en av Sveriges mest kända symboler och ett uppskattat hantverk världen över.
|
||||||
9
content/products/orrefors-glas.md
Normal file
9
content/products/orrefors-glas.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Orrefors Glas
|
||||||
|
region: Småland
|
||||||
|
tags: [glas, design, konst, kristall]
|
||||||
|
website: https://orrefors.se
|
||||||
|
badge: tillverkat-i-sverige
|
||||||
|
---
|
||||||
|
|
||||||
|
Orrefors är ett svenskt glasbruk grundat 1898 i Småland, hjärtat av det svenska glasriket. Bruket är känt för sitt konstnärliga glas och sin kristall av högsta kvalitet. Orrefors har samarbetat med några av Sveriges främsta designers och konstnärer genom åren.
|
||||||
10
content/services/mediaflow.md
Normal file
10
content/services/mediaflow.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: Mediaflow
|
||||||
|
region: Uppsala
|
||||||
|
tags: [digital, media, video, varumärke, dam, saas]
|
||||||
|
website: https://www.mediaflow.com
|
||||||
|
badge: svenskt-moln
|
||||||
|
image: /static/images/mediaflow-cover.webp
|
||||||
|
---
|
||||||
|
|
||||||
|
Mediaflow är en svensk plattform för digital asset management, videohantering och varumärkeshantering. Företaget hjälper organisationer att samla, organisera och dela digitala filer på ett säkert sätt. Med svenska servrar och starkt fokus på GDPR används Mediaflow av tusentals marknadsförare, kommunikatörer och kreatörer inom både privat och offentlig sektor.
|
||||||
9
content/services/postnord.md
Normal file
9
content/services/postnord.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: PostNord
|
||||||
|
category: services
|
||||||
|
region: Stockholm
|
||||||
|
tags: [post, leverans, logistik, paket]
|
||||||
|
website: https://postnord.se
|
||||||
|
---
|
||||||
|
|
||||||
|
PostNord är Nordens ledande leverantör av kommunikations- och logistiklösningar. Med rötter i det svenska postväsendet sedan 1636 har företaget utvecklats till en modern logistikpartner för både privatpersoner och företag i hela Norden.
|
||||||
9
content/services/sj.md
Normal file
9
content/services/sj.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: SJ
|
||||||
|
category: services
|
||||||
|
region: Stockholm
|
||||||
|
tags: [tåg, resor, hållbart, transport]
|
||||||
|
website: https://sj.se
|
||||||
|
---
|
||||||
|
|
||||||
|
SJ är Sveriges största tågoperatör och har fraktat passagerare sedan 1856. Med snabbtåg som kopplar samman Sveriges städer erbjuder SJ ett hållbart alternativ till flyg och bil. Att resa med tåg är en del av den svenska livsstilen och ett miljövänligt val.
|
||||||
136
package-lock.json
generated
Normal file
136
package-lock.json
generated
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"name": "ursprungsverige",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "ursprungsverige",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"marked": "^12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/argparse": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sprintf-js": "~1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/extend-shallow": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-extendable": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gray-matter": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"kind-of": "^6.0.2",
|
||||||
|
"section-matter": "^1.0.0",
|
||||||
|
"strip-bom-string": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-extendable": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-yaml": {
|
||||||
|
"version": "3.14.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||||
|
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^1.0.7",
|
||||||
|
"esprima": "^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"js-yaml": "bin/js-yaml.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/kind-of": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "12.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
|
||||||
|
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/section-matter": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"extend-shallow": "^2.0.1",
|
||||||
|
"kind-of": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sprintf-js": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/strip-bom-string": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
package.json
Normal file
15
package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "ursprungsverige",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A curated collection of Swedish products, services, experiences and manufacturers",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.js",
|
||||||
|
"dev": "node build.js --watch",
|
||||||
|
"serve": "npx --yes serve dist -p 3000"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"marked": "^12.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
194
public/filter.js
Normal file
194
public/filter.js
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// Ursprung Sverige - Filtering functionality
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const categoryFilter = document.getElementById('category-filter');
|
||||||
|
const regionFilter = document.getElementById('region-filter');
|
||||||
|
const tagFilter = document.getElementById('tag-filter');
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||||
|
const entriesGrid = document.getElementById('entries-grid');
|
||||||
|
const visibleCount = document.getElementById('visible-count');
|
||||||
|
const noResults = document.getElementById('no-results');
|
||||||
|
|
||||||
|
if (!entriesGrid) return;
|
||||||
|
|
||||||
|
const entries = Array.from(entriesGrid.querySelectorAll('.entry-card'));
|
||||||
|
|
||||||
|
// Update URL with current filter state
|
||||||
|
function updateURL() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (categoryFilter?.value) {
|
||||||
|
params.set('category', categoryFilter.value);
|
||||||
|
}
|
||||||
|
if (regionFilter?.value) {
|
||||||
|
params.set('region', regionFilter.value);
|
||||||
|
}
|
||||||
|
if (tagFilter?.value) {
|
||||||
|
params.set('tag', tagFilter.value);
|
||||||
|
}
|
||||||
|
if (searchInput?.value.trim()) {
|
||||||
|
params.set('search', searchInput.value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = params.toString();
|
||||||
|
const newURL = queryString
|
||||||
|
? `${window.location.pathname}?${queryString}`
|
||||||
|
: window.location.pathname;
|
||||||
|
|
||||||
|
window.history.replaceState({}, '', newURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply filters from URL on page load
|
||||||
|
function applyFiltersFromURL() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
if (params.has('category') && categoryFilter) {
|
||||||
|
categoryFilter.value = params.get('category');
|
||||||
|
}
|
||||||
|
if (params.has('region') && regionFilter) {
|
||||||
|
regionFilter.value = params.get('region');
|
||||||
|
}
|
||||||
|
if (params.has('tag') && tagFilter) {
|
||||||
|
tagFilter.value = params.get('tag');
|
||||||
|
}
|
||||||
|
if (params.has('search') && searchInput) {
|
||||||
|
searchInput.value = params.get('search');
|
||||||
|
}
|
||||||
|
|
||||||
|
filterEntries(false); // Don't update URL on initial load
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterEntries(shouldUpdateURL = true) {
|
||||||
|
const categoryValue = categoryFilter?.value.toLowerCase() || '';
|
||||||
|
const regionValue = regionFilter?.value.toLowerCase() || '';
|
||||||
|
const tagValue = tagFilter?.value.toLowerCase() || '';
|
||||||
|
const searchValue = searchInput?.value.toLowerCase().trim() || '';
|
||||||
|
|
||||||
|
let visibleEntries = 0;
|
||||||
|
|
||||||
|
entries.forEach(entry => {
|
||||||
|
const entryCategory = (entry.dataset.category || '').toLowerCase();
|
||||||
|
const entryRegion = (entry.dataset.region || '').toLowerCase();
|
||||||
|
const entryTags = (entry.dataset.tags || '').toLowerCase().split(',');
|
||||||
|
const entryText = entry.textContent.toLowerCase();
|
||||||
|
|
||||||
|
const matchesCategory = !categoryValue || entryCategory === categoryValue;
|
||||||
|
const matchesRegion = !regionValue || entryRegion === regionValue;
|
||||||
|
const matchesTag = !tagValue || entryTags.includes(tagValue);
|
||||||
|
const matchesSearch = !searchValue || entryText.includes(searchValue);
|
||||||
|
|
||||||
|
const isVisible = matchesCategory && matchesRegion && matchesTag && matchesSearch;
|
||||||
|
|
||||||
|
entry.hidden = !isVisible;
|
||||||
|
if (isVisible) visibleEntries++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (visibleCount) {
|
||||||
|
visibleCount.textContent = visibleEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noResults) {
|
||||||
|
noResults.hidden = visibleEntries > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entriesGrid) {
|
||||||
|
entriesGrid.hidden = visibleEntries === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateClearButtonVisibility();
|
||||||
|
|
||||||
|
if (shouldUpdateURL) {
|
||||||
|
updateURL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateClearButtonVisibility() {
|
||||||
|
if (!clearFiltersBtn) return;
|
||||||
|
|
||||||
|
const hasFilters =
|
||||||
|
(categoryFilter?.value || '') !== '' ||
|
||||||
|
(regionFilter?.value || '') !== '' ||
|
||||||
|
(tagFilter?.value || '') !== '' ||
|
||||||
|
(searchInput?.value || '') !== '';
|
||||||
|
|
||||||
|
clearFiltersBtn.hidden = !hasFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilters() {
|
||||||
|
if (categoryFilter) categoryFilter.value = '';
|
||||||
|
if (regionFilter) regionFilter.value = '';
|
||||||
|
if (tagFilter) tagFilter.value = '';
|
||||||
|
if (searchInput) searchInput.value = '';
|
||||||
|
|
||||||
|
filterEntries(); // This will also clear the URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle clicking on filter buttons in cards
|
||||||
|
function handleFilterClick(e) {
|
||||||
|
const btn = e.target.closest('.filter-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
const filterType = btn.dataset.filterType;
|
||||||
|
const filterValue = btn.dataset.filterValue;
|
||||||
|
|
||||||
|
if (filterType === 'category' && categoryFilter) {
|
||||||
|
categoryFilter.value = filterValue;
|
||||||
|
} else if (filterType === 'region' && regionFilter) {
|
||||||
|
regionFilter.value = filterValue;
|
||||||
|
} else if (filterType === 'tag' && tagFilter) {
|
||||||
|
tagFilter.value = filterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterEntries();
|
||||||
|
|
||||||
|
// Scroll to top to see filtered results
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle browser back/forward
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
applyFiltersFromURL();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debounce for search input
|
||||||
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function(...args) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
if (categoryFilter) {
|
||||||
|
categoryFilter.addEventListener('change', () => filterEntries());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regionFilter) {
|
||||||
|
regionFilter.addEventListener('change', () => filterEntries());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagFilter) {
|
||||||
|
tagFilter.addEventListener('change', () => filterEntries());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.addEventListener('input', debounce(() => filterEntries(), 300));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearFiltersBtn) {
|
||||||
|
clearFiltersBtn.addEventListener('click', clearFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegate click events for filter buttons in cards
|
||||||
|
if (entriesGrid) {
|
||||||
|
entriesGrid.addEventListener('click', handleFilterClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply URL filters on load
|
||||||
|
applyFiltersFromURL();
|
||||||
|
updateClearButtonVisibility();
|
||||||
|
})();
|
||||||
BIN
public/fonts/DMSerifDisplay-Regular.ttf
Normal file
BIN
public/fonts/DMSerifDisplay-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/Karla-Variable.ttf
Normal file
BIN
public/fonts/Karla-Variable.ttf
Normal file
Binary file not shown.
840
public/styles.css
Normal file
840
public/styles.css
Normal file
@@ -0,0 +1,840 @@
|
|||||||
|
/* Ursprung Sverige - Scandinavian Minimal Design */
|
||||||
|
|
||||||
|
/* Self-hosted fonts - DM Serif Display */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DM Serif Display';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('fonts/DMSerifDisplay-Regular.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Self-hosted fonts - Karla (variable font) */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Karla';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 200 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('fonts/Karla-Variable.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Swedish-inspired palette */
|
||||||
|
--color-bg: #faf9f7;
|
||||||
|
--color-bg-alt: #f0eeea;
|
||||||
|
--color-text: #1a1a1a;
|
||||||
|
--color-text-muted: #5a5a5a;
|
||||||
|
--color-accent: #005baa;
|
||||||
|
--color-accent-gold: #fecc00;
|
||||||
|
--color-border: #d8d4cc;
|
||||||
|
--color-card-bg: #ffffff;
|
||||||
|
|
||||||
|
/* Badge tier colors */
|
||||||
|
--badge-tier-1-bg: linear-gradient(135deg, #c9a227 0%, #f4d03f 50%, #c9a227 100%);
|
||||||
|
--badge-tier-1-text: #1a1a1a;
|
||||||
|
--badge-tier-2-bg: linear-gradient(135deg, #7b8a8b 0%, #bdc3c7 50%, #7b8a8b 100%);
|
||||||
|
--badge-tier-2-text: #1a1a1a;
|
||||||
|
--badge-tier-3-bg: linear-gradient(135deg, #a0522d 0%, #cd853f 50%, #a0522d 100%);
|
||||||
|
--badge-tier-3-text: #ffffff;
|
||||||
|
--badge-tier-4-bg: var(--color-accent);
|
||||||
|
--badge-tier-4-text: #ffffff;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-display: 'DM Serif Display', Georgia, serif;
|
||||||
|
--font-body: 'Karla', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--space-xs: 0.25rem;
|
||||||
|
--space-sm: 0.5rem;
|
||||||
|
--space-md: 1rem;
|
||||||
|
--space-lg: 2rem;
|
||||||
|
--space-xl: 4rem;
|
||||||
|
|
||||||
|
/* Sizes */
|
||||||
|
--max-width: 1200px;
|
||||||
|
--border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base */
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--max-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container--narrow {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.site-header {
|
||||||
|
background: linear-gradient(135deg, var(--color-bg) 0%, var(--color-bg-alt) 100%);
|
||||||
|
padding: var(--space-xl) 0;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header--single {
|
||||||
|
padding: var(--space-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-text);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title--small {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title-link:hover .site-title {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-subtitle {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-decoration {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sweden-motif {
|
||||||
|
width: 80px;
|
||||||
|
height: 48px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filters */
|
||||||
|
.filter-section {
|
||||||
|
padding: var(--space-lg) 0;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group select,
|
||||||
|
.filter-group input {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--color-card-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
min-width: 160px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group select:hover,
|
||||||
|
.filter-group input:hover {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group select:focus,
|
||||||
|
.filter-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 91, 170, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-filters-btn {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--color-bg-alt);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-filters-btn:hover {
|
||||||
|
background-color: var(--color-text);
|
||||||
|
color: var(--color-bg);
|
||||||
|
border-color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-count {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges-info-link {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges-info-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main content */
|
||||||
|
.site-main {
|
||||||
|
flex: 1;
|
||||||
|
padding-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-main--single {
|
||||||
|
padding-top: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Entry cards grid */
|
||||||
|
.entries-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card {
|
||||||
|
background: var(--color-card-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-image {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card-content {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-logo {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card h2 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-text);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card h2 a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card h2 a:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-1 {
|
||||||
|
background: var(--badge-tier-1-bg);
|
||||||
|
color: var(--badge-tier-1-text);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-2 {
|
||||||
|
background: var(--badge-tier-2-bg);
|
||||||
|
color: var(--badge-tier-2-text);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-3 {
|
||||||
|
background: var(--badge-tier-3-bg);
|
||||||
|
color: var(--badge-tier-3-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-4 {
|
||||||
|
background: var(--badge-tier-4-bg);
|
||||||
|
color: var(--badge-tier-4-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-meta .filter-btn,
|
||||||
|
.filter-link {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s ease, transform 0.1s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-meta .filter-btn:hover,
|
||||||
|
.filter-link:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-meta .category,
|
||||||
|
.filter-link.category {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-meta .region,
|
||||||
|
.filter-link.region {
|
||||||
|
background: var(--color-accent-gold);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-body {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-body p {
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-body p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.website-link {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.website-link:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag:hover {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No results */
|
||||||
|
.no-results {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-xl);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hidden utility */
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single entry page */
|
||||||
|
.single-entry {
|
||||||
|
background: var(--color-card-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-image {
|
||||||
|
aspect-ratio: 21/9;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
padding-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-logo {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-title-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-header h1 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(1.75rem, 4vw, 2.5rem);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-header .badge {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
padding: 0 var(--space-lg);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-body {
|
||||||
|
padding: 0 var(--space-lg);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-body p {
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-entry .website-link {
|
||||||
|
display: block;
|
||||||
|
padding: 0 var(--space-lg);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-entry .tags {
|
||||||
|
padding: 0 var(--space-lg);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
display: block;
|
||||||
|
padding: var(--space-md) var(--space-lg);
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.site-footer {
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
padding: var(--space-lg) 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-tagline {
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-nav {
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-nav a {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-nav a:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.site-header .container {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-decoration {
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-subtitle {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group select,
|
||||||
|
.filter-group input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-filters-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entries-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-header {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* About page */
|
||||||
|
.about-page {
|
||||||
|
background: var(--color-card-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-page h1 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(1.75rem, 4vw, 2.5rem);
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section {
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
padding-bottom: var(--space-lg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section h2 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section p {
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: var(--space-md) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section li {
|
||||||
|
padding: var(--space-sm) 0;
|
||||||
|
padding-left: var(--space-lg);
|
||||||
|
position: relative;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section li::before {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-page .back-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: var(--space-lg);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-page .back-link:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges info page */
|
||||||
|
.badges-page {
|
||||||
|
background: var(--color-card-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges-page h1 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(1.75rem, 4vw, 2.5rem);
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges-page .intro {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-section {
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
padding-bottom: var(--space-lg);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-section:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tier-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-section p {
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-tier-section .criteria {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.why-section {
|
||||||
|
margin-top: var(--space-xl);
|
||||||
|
padding-top: var(--space-lg);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.why-section h2 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.why-section ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: var(--space-md) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.why-section li {
|
||||||
|
padding: var(--space-sm) 0;
|
||||||
|
padding-left: var(--space-lg);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.why-section li::before {
|
||||||
|
content: '→';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges-page .back-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: var(--space-lg);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges-page .back-link:hover {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print styles */
|
||||||
|
@media print {
|
||||||
|
.filter-section,
|
||||||
|
.header-decoration,
|
||||||
|
.back-link {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card {
|
||||||
|
break-inside: avoid;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/images/fjallraven_logo.png
Normal file
BIN
static/images/fjallraven_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
BIN
static/images/mediaflow-cover.webp
Normal file
BIN
static/images/mediaflow-cover.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 456 KiB |
78
templates/about.html
Normal file
78
templates/about.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sv">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Om Oss | Ursprung Sverige</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header site-header--single">
|
||||||
|
<div class="container">
|
||||||
|
<a href="index.html" class="site-title-link">
|
||||||
|
<h1 class="site-title site-title--small">Ursprung Sverige</h1>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="site-main site-main--single">
|
||||||
|
<div class="container container--narrow">
|
||||||
|
<article class="about-page">
|
||||||
|
<h1>Om Ursprung Sverige</h1>
|
||||||
|
|
||||||
|
<section class="about-section">
|
||||||
|
<h2>Vårt Syfte</h2>
|
||||||
|
<p>Ursprung Sverige är en kurerad samling av svenska produkter, tjänster, upplevelser och tillverkare. Målet är att göra det enklare för dig att stödja svensk ekonomi och kultur genom medvetna val.</p>
|
||||||
|
<p>Jag tror på att synliggöra företag och produkter som bidrar till det svenska samhället – från traditionellt hantverk till moderna digitala tjänster.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about-section">
|
||||||
|
<h2>Vem Står Bakom?</h2>
|
||||||
|
<p>Ursprung Sverige skapas och underhålls av <strong>Jonas Raneryd Imaizumi</strong>. Det här är ett personligt projekt drivet av en önskan att göra det enklare att hitta och stödja svenska alternativ.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about-section">
|
||||||
|
<h2>Hosting i Sverige</h2>
|
||||||
|
<p>Vi lever som vi lär. Denna webbplats hostas på servrar i <strong>Lerum, Sverige</strong>. Dina besök stannar inom Sveriges gränser.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about-section">
|
||||||
|
<h2>Din Integritet</h2>
|
||||||
|
<p>Vi respekterar din integritet fullt ut:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Inga cookies</strong> – Vi använder inga cookies överhuvudtaget</li>
|
||||||
|
<li><strong>Ingen spårning</strong> – Inget Google Analytics eller liknande tjänster</li>
|
||||||
|
<li><strong>Ingen dataförsäljning</strong> – Vi samlar inte in eller säljer personuppgifter</li>
|
||||||
|
<li><strong>Inga tredjepartstjänster</strong> – Allt laddas från våra egna servrar</li>
|
||||||
|
</ul>
|
||||||
|
<p>Du kan surfa här utan att lämna digitala spår.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about-section">
|
||||||
|
<h2>Öppen Källkod</h2>
|
||||||
|
<p>Webbplatsen är byggd med enkel teknik – ren HTML, CSS och minimal JavaScript. Ingen spårningskod, inga tunga ramverk, inga dolda skript.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about-section">
|
||||||
|
<h2>Kontakt</h2>
|
||||||
|
<p>Har du förslag på svenska företag, produkter eller tjänster som borde finnas med? Eller har du upptäckt att en märkning eller annan information är felaktig? Hör av dig!</p>
|
||||||
|
<p><a href="mailto:jonas@ursprungsverige.se">jonas@ursprungsverige.se</a></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<a href="index.html" class="back-link">← Tillbaka till alla poster</a>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>Ursprung Sverige © {{ year }}</p>
|
||||||
|
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
|
||||||
|
<nav class="footer-nav">
|
||||||
|
<a href="om-oss.html">Om oss</a>
|
||||||
|
<a href="om-markning.html">Om märkning</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
89
templates/badges.html
Normal file
89
templates/badges.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sv">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Om Märkning | Ursprung Sverige</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header site-header--single">
|
||||||
|
<div class="container">
|
||||||
|
<a href="index.html" class="site-title-link">
|
||||||
|
<h1 class="site-title site-title--small">Ursprung Sverige</h1>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="site-main site-main--single">
|
||||||
|
<div class="container container--narrow">
|
||||||
|
<article class="badges-page">
|
||||||
|
<h1>Om Märkning</h1>
|
||||||
|
<p class="intro">Vi använder ett märkningssystem för att visa hur svenskt ett företag, en produkt eller tjänst verkligen är. Märkningarna hjälper dig att göra medvetna val när du vill stödja svensk ekonomi och kultur.</p>
|
||||||
|
|
||||||
|
<section class="badge-tier-section">
|
||||||
|
<div class="badge-tier-header">
|
||||||
|
<span class="badge badge-tier-1">Äkta Svenskt</span>
|
||||||
|
<span class="tier-label">Högsta nivån</span>
|
||||||
|
</div>
|
||||||
|
<p>Den finaste märkningen. Företaget är svenskt, produktionen eller driften sker i Sverige, och de använder svenska underleverantörer och tjänster. För digitala tjänster innebär detta svenska servrar och svensk hosting genomgående.</p>
|
||||||
|
<p class="criteria"><strong>Kriterier:</strong> Svenskt företag + svensk produktion/drift + svenska leverantörer</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="badge-tier-section">
|
||||||
|
<div class="badge-tier-header">
|
||||||
|
<span class="badge badge-tier-2">Tillverkat i Sverige</span>
|
||||||
|
<span class="badge badge-tier-2">Svenskt Moln</span>
|
||||||
|
</div>
|
||||||
|
<p><strong>Tillverkat i Sverige</strong> gäller fysiska produkter som tillverkas i Sverige av ett svenskt företag.</p>
|
||||||
|
<p><strong>Svenskt Moln</strong> gäller digitala tjänster där ett svenskt företag har sina servrar och hosting i Sverige.</p>
|
||||||
|
<p class="criteria"><strong>Kriterier:</strong> Svenskt företag + svensk produktion (fysisk) eller svensk hosting (digital)</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="badge-tier-section">
|
||||||
|
<div class="badge-tier-header">
|
||||||
|
<span class="badge badge-tier-3">Designat i Sverige</span>
|
||||||
|
<span class="badge badge-tier-3">Utvecklat i Sverige</span>
|
||||||
|
</div>
|
||||||
|
<p><strong>Designat i Sverige</strong> gäller produkter som designas i Sverige men kan tillverkas utomlands.</p>
|
||||||
|
<p><strong>Utvecklat i Sverige</strong> gäller digitala tjänster som utvecklas i Sverige men kan hostas utomlands.</p>
|
||||||
|
<p class="criteria"><strong>Kriterier:</strong> Svenskt företag + svensk design/utveckling</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="badge-tier-section">
|
||||||
|
<div class="badge-tier-header">
|
||||||
|
<span class="badge badge-tier-4">Svenskt Företag</span>
|
||||||
|
</div>
|
||||||
|
<p>Grundnivån. Företaget är registrerat eller grundat i Sverige. Produktion, utveckling eller drift kan ske var som helst.</p>
|
||||||
|
<p class="criteria"><strong>Kriterier:</strong> Svenskregistrerat eller svenskgrundat företag</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="why-section">
|
||||||
|
<h2>Varför spelar det roll?</h2>
|
||||||
|
<p>Genom att välja svenska alternativ bidrar du till:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Svenska jobb</strong> – Produktion och utveckling i Sverige skapar arbetstillfällen</li>
|
||||||
|
<li><strong>Kortare transporter</strong> – Mindre miljöpåverkan när varor inte fraktas långt</li>
|
||||||
|
<li><strong>Dataskydd</strong> – Svenska servrar innebär att dina data skyddas av svensk och europeisk lag</li>
|
||||||
|
<li><strong>Kvalitet och tradition</strong> – Svenskt hantverk och ingenjörskonst har lång tradition</li>
|
||||||
|
<li><strong>Lokal ekonomi</strong> – Pengarna stannar i Sverige och stärker samhället</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<a href="index.html" class="back-link">← Tillbaka till alla poster</a>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>Ursprung Sverige © {{ year }}</p>
|
||||||
|
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
|
||||||
|
<nav class="footer-nav">
|
||||||
|
<a href="om-oss.html">Om oss</a>
|
||||||
|
<a href="om-markning.html">Om märkning</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
templates/partials/footer.html
Normal file
10
templates/partials/footer.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>Ursprung Sverige © {{ year }}</p>
|
||||||
|
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
|
||||||
|
<nav class="footer-nav">
|
||||||
|
<a href="{{ rootPath }}om-oss.html">Om oss</a>
|
||||||
|
<a href="{{ rootPath }}om-markning.html">Om märkning</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
35
templates/single.html
Normal file
35
templates/single.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sv">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<link rel="stylesheet" href="../styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header site-header--single">
|
||||||
|
<div class="container">
|
||||||
|
<a href="../index.html" class="site-title-link">
|
||||||
|
<h1 class="site-title site-title--small">Ursprung Sverige</h1>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="site-main site-main--single">
|
||||||
|
<div class="container container--narrow">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>Ursprung Sverige © {{ year }}</p>
|
||||||
|
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
|
||||||
|
<nav class="footer-nav">
|
||||||
|
<a href="../om-oss.html">Om oss</a>
|
||||||
|
<a href="../om-markning.html">Om märkning</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
59
templates/template.html
Normal file
59
templates/template.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sv">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<meta name="description" content="{{ subtitle }}">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1 class="site-title">{{ title }}</h1>
|
||||||
|
<p class="site-subtitle">{{ subtitle }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-decoration">
|
||||||
|
<svg viewBox="0 0 100 60" class="sweden-motif" aria-hidden="true">
|
||||||
|
<rect x="0" y="0" width="100" height="60" fill="var(--color-accent)"/>
|
||||||
|
<rect x="0" y="24" width="100" height="12" fill="var(--color-accent-gold)"/>
|
||||||
|
<rect x="30" y="0" width="12" height="60" fill="var(--color-accent-gold)"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="site-main">
|
||||||
|
<div class="container">
|
||||||
|
<section class="filter-section">
|
||||||
|
{{ filters }}
|
||||||
|
<div class="filter-footer">
|
||||||
|
<p class="entry-count"><span id="visible-count">{{ entryCount }}</span> av {{ entryCount }} poster</p>
|
||||||
|
<a href="om-markning.html" class="badges-info-link">Vad betyder märkningarna?</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="entries-section">
|
||||||
|
<div class="entries-grid" id="entries-grid">
|
||||||
|
{{ entries }}
|
||||||
|
</div>
|
||||||
|
<p class="no-results" id="no-results" hidden>Inga resultat hittades.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>Ursprung Sverige © {{ year }}</p>
|
||||||
|
<p class="footer-tagline">Stöd svensk ekonomi och kultur</p>
|
||||||
|
<nav class="footer-nav">
|
||||||
|
<a href="om-oss.html">Om oss</a>
|
||||||
|
<a href="om-markning.html">Om märkning</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="filter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
tmp/cache/bootsnap/compile-cache-iseq/00/65e8d47f40c7db
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/00/65e8d47f40c7db
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/00/761cee489a18a2
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/00/761cee489a18a2
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/00/f905a4fe22322f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/00/f905a4fe22322f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/1d10e406052c09
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/1d10e406052c09
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/39cc39746148a3
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/39cc39746148a3
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/9a3efc41171fd9
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/9a3efc41171fd9
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/9bbf8857b6f014
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/9bbf8857b6f014
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/c15cd392bc8546
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/c15cd392bc8546
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/d129d24295d871
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/d129d24295d871
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/ef1ae8137611b5
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/ef1ae8137611b5
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/fdf2aff6a67d75
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/01/fdf2aff6a67d75
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/196bd10c960439
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/196bd10c960439
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/4b7957b4bb7874
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/4b7957b4bb7874
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/527123e7312f6f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/527123e7312f6f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/6fc9a7222a30ac
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/6fc9a7222a30ac
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/8adeed35dc2327
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/8adeed35dc2327
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/d32460ae7b5026
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/02/d32460ae7b5026
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/02a1a941ac14ec
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/02a1a941ac14ec
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/1cde7bf8ff59d8
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/1cde7bf8ff59d8
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/4c907886137e78
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/4c907886137e78
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/627b67c079c68f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/627b67c079c68f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/691af526fc4d58
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/691af526fc4d58
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/7cf55bd249161f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/7cf55bd249161f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/a8c5c2aa62ade8
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/a8c5c2aa62ade8
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/b5c92e5cdb9481
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/b5c92e5cdb9481
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/b8bf0d1dc263a7
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/b8bf0d1dc263a7
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/d0d9e877cebdeb
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/d0d9e877cebdeb
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/e116f881bb4bed
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/e116f881bb4bed
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/ee7a23353023d9
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/ee7a23353023d9
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/ef842c0ff613c3
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/03/ef842c0ff613c3
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/0903927b24c3ee
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/0903927b24c3ee
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/0f7d4b408f6a54
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/0f7d4b408f6a54
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/1d75a738b04726
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/1d75a738b04726
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/848a8a44e032c3
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/848a8a44e032c3
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/8afd3c57aaf578
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/8afd3c57aaf578
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/bbfb2c9b5af1bc
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/bbfb2c9b5af1bc
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/d46c0be0033f3f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/d46c0be0033f3f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/e37d5918eb3c28
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/e37d5918eb3c28
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/f9fa0e9985b2f6
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/04/f9fa0e9985b2f6
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/05/30dd2896d7cf6e
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/05/30dd2896d7cf6e
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/05/d7bf091796b10f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/05/d7bf091796b10f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/05/f804949c51f481
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/05/f804949c51f481
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/3cdc909f848f42
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/3cdc909f848f42
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/65df346d400737
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/65df346d400737
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/c96c2e7e004323
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/c96c2e7e004323
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/e243d29569a65f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/06/e243d29569a65f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/4ca9b90b8a9524
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/4ca9b90b8a9524
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/9c8c1f7656f2c5
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/9c8c1f7656f2c5
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/cecaa92c9c131e
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/cecaa92c9c131e
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/d832fa3950c611
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/d832fa3950c611
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/dd26a9553784dd
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/dd26a9553784dd
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/fa89b9d56ff893
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/07/fa89b9d56ff893
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/3e93a9035b94ac
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/3e93a9035b94ac
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/4cd79debd07717
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/4cd79debd07717
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/765f03ab3804aa
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/765f03ab3804aa
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/ac8e6acd920825
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/ac8e6acd920825
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/b26b68319ce635
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/b26b68319ce635
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/ca9135f6f40a29
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/ca9135f6f40a29
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/d10c51ad963cf4
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/08/d10c51ad963cf4
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/3cdcae1d067a8c
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/3cdcae1d067a8c
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/3d04f6face5419
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/3d04f6face5419
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/4bdfc67ce7b1c9
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/4bdfc67ce7b1c9
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/6c0055e04a2671
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/6c0055e04a2671
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/a2501b7229780f
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/09/a2501b7229780f
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/2252240956f745
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/2252240956f745
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/2fc0c2d20c2765
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/2fc0c2d20c2765
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/3b17ba6f76b266
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/3b17ba6f76b266
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/5de7fe90648b4a
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/5de7fe90648b4a
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/8a8a5aed34bda6
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/8a8a5aed34bda6
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/8cc9a02e8d3295
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/8cc9a02e8d3295
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/d2a828cc7537ae
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/d2a828cc7537ae
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/d6877ce2c5b570
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/d6877ce2c5b570
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/fa2c01354ebc22
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0a/fa2c01354ebc22
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0b/1ff04b66e171a6
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0b/1ff04b66e171a6
vendored
Normal file
Binary file not shown.
BIN
tmp/cache/bootsnap/compile-cache-iseq/0b/69fd1a95617aad
vendored
Normal file
BIN
tmp/cache/bootsnap/compile-cache-iseq/0b/69fd1a95617aad
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user