Compare commits
4 Commits
47fc81bc72
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 28f232571d | |||
| cfb35a3c38 | |||
| 2519520e1b | |||
| 6d43b3c844 |
11
README.md
11
README.md
@@ -33,10 +33,13 @@ The generated site will be in the `dist/` folder.
|
|||||||
|
|
||||||
Create a Markdown file in the appropriate `content/` subfolder:
|
Create a Markdown file in the appropriate `content/` subfolder:
|
||||||
|
|
||||||
- `content/products/` - Swedish products
|
| Folder | Category | Description |
|
||||||
- `content/services/` - Swedish services
|
|--------|----------|-------------|
|
||||||
- `content/experiences/` - Swedish experiences
|
| `content/products/` | Produkter | Physical Swedish products (e.g., Dalahäst, Orrefors glass) |
|
||||||
- `content/manufacturers/` - Swedish manufacturers
|
| `content/manufacturers/` | Tillverkare | Companies that design/manufacture products (e.g., Fjällräven, Hasselblad) |
|
||||||
|
| `content/shops/` | Butiker | Retail stores selling products (e.g., Göteborg Manufaktur) |
|
||||||
|
| `content/services/` | Tjänster | Service providers, both physical and digital (e.g., SJ, Mediaflow) |
|
||||||
|
| `content/experiences/` | Upplevelser | Experiences, events, and cultural activities (e.g., Icehotel, Midsommar) |
|
||||||
|
|
||||||
### Content Format
|
### Content Format
|
||||||
|
|
||||||
|
|||||||
51
build.js
51
build.js
@@ -14,7 +14,8 @@ const CATEGORY_LABELS = {
|
|||||||
manufacturers: 'Tillverkare',
|
manufacturers: 'Tillverkare',
|
||||||
products: 'Produkter',
|
products: 'Produkter',
|
||||||
services: 'Tjänster',
|
services: 'Tjänster',
|
||||||
experiences: 'Upplevelser'
|
experiences: 'Upplevelser',
|
||||||
|
shops: 'Butiker'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Badge tiers (highest to lowest)
|
// Badge tiers (highest to lowest)
|
||||||
@@ -198,7 +199,8 @@ function generateEntryPage(entry, template) {
|
|||||||
title: `${entry.title} | Ursprung Sverige`,
|
title: `${entry.title} | Ursprung Sverige`,
|
||||||
subtitle: entry.title,
|
subtitle: entry.title,
|
||||||
content: entryHtml,
|
content: entryHtml,
|
||||||
year: new Date().getFullYear()
|
year: new Date().getFullYear(),
|
||||||
|
footer: getFooter('../')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +209,14 @@ function extractFilters(entries) {
|
|||||||
const categories = [...new Set(entries.map(e => e.category).filter(Boolean))].sort();
|
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 regions = [...new Set(entries.map(e => e.region).filter(Boolean))].sort();
|
||||||
const tags = [...new Set(entries.flatMap(e => e.tags || []))].sort();
|
const tags = [...new Set(entries.flatMap(e => e.tags || []))].sort();
|
||||||
|
const badges = [...new Set(entries.map(e => e.badge).filter(Boolean))].sort((a, b) => {
|
||||||
|
// Sort by tier (highest first)
|
||||||
|
const tierA = BADGE_TIERS[a]?.tier || 99;
|
||||||
|
const tierB = BADGE_TIERS[b]?.tier || 99;
|
||||||
|
return tierA - tierB;
|
||||||
|
});
|
||||||
|
|
||||||
return { categories, regions, tags };
|
return { categories, regions, tags, badges };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate filter HTML
|
// Generate filter HTML
|
||||||
@@ -224,6 +232,10 @@ function generateFilterHTML(filters) {
|
|||||||
const tagOptions = filters.tags.map(t =>
|
const tagOptions = filters.tags.map(t =>
|
||||||
`<option value="${t}">${t}</option>`
|
`<option value="${t}">${t}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
|
const badgeOptions = filters.badges.map(b =>
|
||||||
|
`<option value="${b}">${BADGE_TIERS[b]?.label || b}</option>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
@@ -241,6 +253,13 @@ function generateFilterHTML(filters) {
|
|||||||
${regionOptions}
|
${regionOptions}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="badge-filter">Märkning</label>
|
||||||
|
<select id="badge-filter">
|
||||||
|
<option value="">Alla märkningar</option>
|
||||||
|
${badgeOptions}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<label for="tag-filter">Tagg</label>
|
<label for="tag-filter">Tagg</label>
|
||||||
<select id="tag-filter">
|
<select id="tag-filter">
|
||||||
@@ -257,6 +276,19 @@ function generateFilterHTML(filters) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load footer partial
|
||||||
|
function getFooter(rootPath = '') {
|
||||||
|
const footerPath = path.join(TEMPLATE_DIR, 'partials', 'footer.html');
|
||||||
|
if (!fs.existsSync(footerPath)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const footerTemplate = fs.readFileSync(footerPath, 'utf-8');
|
||||||
|
return renderTemplate(footerTemplate, {
|
||||||
|
year: new Date().getFullYear(),
|
||||||
|
rootPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Main build function
|
// Main build function
|
||||||
function build() {
|
function build() {
|
||||||
console.log('Building site...');
|
console.log('Building site...');
|
||||||
@@ -295,28 +327,31 @@ function build() {
|
|||||||
filters: generateFilterHTML(filters),
|
filters: generateFilterHTML(filters),
|
||||||
entries: generateEntryCards(entries),
|
entries: generateEntryCards(entries),
|
||||||
entryCount: entries.length,
|
entryCount: entries.length,
|
||||||
year: new Date().getFullYear()
|
year: new Date().getFullYear(),
|
||||||
|
footer: getFooter('')
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write index.html
|
// Write index.html
|
||||||
fs.writeFileSync(path.join(DIST_DIR, 'index.html'), indexHtml);
|
fs.writeFileSync(path.join(DIST_DIR, 'index.html'), indexHtml);
|
||||||
|
|
||||||
// Generate badges info page
|
// Generate badges info page
|
||||||
const badgesTemplatePath = path.join(TEMPLATE_DIR, 'badges.html');
|
const badgesTemplatePath = path.join(TEMPLATE_DIR, 'om-markning.html');
|
||||||
if (fs.existsSync(badgesTemplatePath)) {
|
if (fs.existsSync(badgesTemplatePath)) {
|
||||||
const badgesTemplate = fs.readFileSync(badgesTemplatePath, 'utf-8');
|
const badgesTemplate = fs.readFileSync(badgesTemplatePath, 'utf-8');
|
||||||
const badgesHtml = renderTemplate(badgesTemplate, {
|
const badgesHtml = renderTemplate(badgesTemplate, {
|
||||||
year: new Date().getFullYear()
|
year: new Date().getFullYear(),
|
||||||
|
footer: getFooter('')
|
||||||
});
|
});
|
||||||
fs.writeFileSync(path.join(DIST_DIR, 'om-markning.html'), badgesHtml);
|
fs.writeFileSync(path.join(DIST_DIR, 'om-markning.html'), badgesHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate about page
|
// Generate about page
|
||||||
const aboutTemplatePath = path.join(TEMPLATE_DIR, 'about.html');
|
const aboutTemplatePath = path.join(TEMPLATE_DIR, 'om-oss.html');
|
||||||
if (fs.existsSync(aboutTemplatePath)) {
|
if (fs.existsSync(aboutTemplatePath)) {
|
||||||
const aboutTemplate = fs.readFileSync(aboutTemplatePath, 'utf-8');
|
const aboutTemplate = fs.readFileSync(aboutTemplatePath, 'utf-8');
|
||||||
const aboutHtml = renderTemplate(aboutTemplate, {
|
const aboutHtml = renderTemplate(aboutTemplate, {
|
||||||
year: new Date().getFullYear()
|
year: new Date().getFullYear(),
|
||||||
|
footer: getFooter('')
|
||||||
});
|
});
|
||||||
fs.writeFileSync(path.join(DIST_DIR, 'om-oss.html'), aboutHtml);
|
fs.writeFileSync(path.join(DIST_DIR, 'om-oss.html'), aboutHtml);
|
||||||
}
|
}
|
||||||
|
|||||||
9
content/manufacturers/jeansverket.md
Normal file
9
content/manufacturers/jeansverket.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Jeansverket
|
||||||
|
region: Borås
|
||||||
|
tags: [jeans, kläder, denim, hantverk]
|
||||||
|
website: https://www.jeansverket.se
|
||||||
|
badge: tillverkat-i-sverige
|
||||||
|
---
|
||||||
|
|
||||||
|
Jeansverket är ett svenskt jeansföretag som tillverkar jeans i Borås. Med fokus på kvalitet och hållbarhet erbjuder de jeans som är skapade för att hålla länge.
|
||||||
13
content/shops/goteborg-manufaktur.md
Normal file
13
content/shops/goteborg-manufaktur.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: Göteborg Manufaktur
|
||||||
|
region: Västra Götaland
|
||||||
|
tags: [jeans, denim, kläder, reparation, second-hand, hållbarhet]
|
||||||
|
website: https://goteborgmanufaktur.se
|
||||||
|
badge: svenskt-foretag
|
||||||
|
image: /static/images/goteborg-manufaktur-cover.jpeg
|
||||||
|
logo: /static/images/goteborg-manufaktur.avif
|
||||||
|
---
|
||||||
|
|
||||||
|
Göteborg Manufaktur är en denimbutik på Tredje Långgatan i Göteborg, grundad 2016 av Jonas Melin, Olof Norrman och Erik Davis. Butiken erbjuder ett noggrant utvalt sortiment av premiumjeans och kläder med fokus på japansk denim och workwear heritage.
|
||||||
|
|
||||||
|
Det som började som en liten jeansreparationsverkstad 2014 har vuxit till en fullskalig butik med etisk produktion i centrum. Alla jeansreparationer görs på plats och är gratis för produkter köpta i butiken. Genom "Worn Not Wasted" erbjuds även second-hand på kommission – en cirkulär modell för hållbart mode.
|
||||||
1883
package-lock.json
generated
1883
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,15 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node build.js",
|
"build": "node build.js",
|
||||||
"dev": "node build.js --watch",
|
"dev": "concurrently \"node build.js --watch\" \"browser-sync start --server dist --files dist --no-open\"",
|
||||||
"serve": "npx --yes serve dist -p 3000"
|
"serve": "npx --yes serve dist -p 3000"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"marked": "^12.0.0"
|
"marked": "^12.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"browser-sync": "^3.0.2",
|
||||||
|
"concurrently": "^8.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
const categoryFilter = document.getElementById('category-filter');
|
const categoryFilter = document.getElementById('category-filter');
|
||||||
const regionFilter = document.getElementById('region-filter');
|
const regionFilter = document.getElementById('region-filter');
|
||||||
|
const badgeFilter = document.getElementById('badge-filter');
|
||||||
const tagFilter = document.getElementById('tag-filter');
|
const tagFilter = document.getElementById('tag-filter');
|
||||||
const searchInput = document.getElementById('search-input');
|
const searchInput = document.getElementById('search-input');
|
||||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||||
@@ -26,6 +27,9 @@
|
|||||||
if (regionFilter?.value) {
|
if (regionFilter?.value) {
|
||||||
params.set('region', regionFilter.value);
|
params.set('region', regionFilter.value);
|
||||||
}
|
}
|
||||||
|
if (badgeFilter?.value) {
|
||||||
|
params.set('badge', badgeFilter.value);
|
||||||
|
}
|
||||||
if (tagFilter?.value) {
|
if (tagFilter?.value) {
|
||||||
params.set('tag', tagFilter.value);
|
params.set('tag', tagFilter.value);
|
||||||
}
|
}
|
||||||
@@ -51,6 +55,9 @@
|
|||||||
if (params.has('region') && regionFilter) {
|
if (params.has('region') && regionFilter) {
|
||||||
regionFilter.value = params.get('region');
|
regionFilter.value = params.get('region');
|
||||||
}
|
}
|
||||||
|
if (params.has('badge') && badgeFilter) {
|
||||||
|
badgeFilter.value = params.get('badge');
|
||||||
|
}
|
||||||
if (params.has('tag') && tagFilter) {
|
if (params.has('tag') && tagFilter) {
|
||||||
tagFilter.value = params.get('tag');
|
tagFilter.value = params.get('tag');
|
||||||
}
|
}
|
||||||
@@ -64,6 +71,7 @@
|
|||||||
function filterEntries(shouldUpdateURL = true) {
|
function filterEntries(shouldUpdateURL = true) {
|
||||||
const categoryValue = categoryFilter?.value.toLowerCase() || '';
|
const categoryValue = categoryFilter?.value.toLowerCase() || '';
|
||||||
const regionValue = regionFilter?.value.toLowerCase() || '';
|
const regionValue = regionFilter?.value.toLowerCase() || '';
|
||||||
|
const badgeValue = badgeFilter?.value.toLowerCase() || '';
|
||||||
const tagValue = tagFilter?.value.toLowerCase() || '';
|
const tagValue = tagFilter?.value.toLowerCase() || '';
|
||||||
const searchValue = searchInput?.value.toLowerCase().trim() || '';
|
const searchValue = searchInput?.value.toLowerCase().trim() || '';
|
||||||
|
|
||||||
@@ -72,15 +80,17 @@
|
|||||||
entries.forEach(entry => {
|
entries.forEach(entry => {
|
||||||
const entryCategory = (entry.dataset.category || '').toLowerCase();
|
const entryCategory = (entry.dataset.category || '').toLowerCase();
|
||||||
const entryRegion = (entry.dataset.region || '').toLowerCase();
|
const entryRegion = (entry.dataset.region || '').toLowerCase();
|
||||||
|
const entryBadge = (entry.dataset.badge || '').toLowerCase();
|
||||||
const entryTags = (entry.dataset.tags || '').toLowerCase().split(',');
|
const entryTags = (entry.dataset.tags || '').toLowerCase().split(',');
|
||||||
const entryText = entry.textContent.toLowerCase();
|
const entryText = entry.textContent.toLowerCase();
|
||||||
|
|
||||||
const matchesCategory = !categoryValue || entryCategory === categoryValue;
|
const matchesCategory = !categoryValue || entryCategory === categoryValue;
|
||||||
const matchesRegion = !regionValue || entryRegion === regionValue;
|
const matchesRegion = !regionValue || entryRegion === regionValue;
|
||||||
|
const matchesBadge = !badgeValue || entryBadge === badgeValue;
|
||||||
const matchesTag = !tagValue || entryTags.includes(tagValue);
|
const matchesTag = !tagValue || entryTags.includes(tagValue);
|
||||||
const matchesSearch = !searchValue || entryText.includes(searchValue);
|
const matchesSearch = !searchValue || entryText.includes(searchValue);
|
||||||
|
|
||||||
const isVisible = matchesCategory && matchesRegion && matchesTag && matchesSearch;
|
const isVisible = matchesCategory && matchesRegion && matchesBadge && matchesTag && matchesSearch;
|
||||||
|
|
||||||
entry.hidden = !isVisible;
|
entry.hidden = !isVisible;
|
||||||
if (isVisible) visibleEntries++;
|
if (isVisible) visibleEntries++;
|
||||||
@@ -111,6 +121,7 @@
|
|||||||
const hasFilters =
|
const hasFilters =
|
||||||
(categoryFilter?.value || '') !== '' ||
|
(categoryFilter?.value || '') !== '' ||
|
||||||
(regionFilter?.value || '') !== '' ||
|
(regionFilter?.value || '') !== '' ||
|
||||||
|
(badgeFilter?.value || '') !== '' ||
|
||||||
(tagFilter?.value || '') !== '' ||
|
(tagFilter?.value || '') !== '' ||
|
||||||
(searchInput?.value || '') !== '';
|
(searchInput?.value || '') !== '';
|
||||||
|
|
||||||
@@ -120,6 +131,7 @@
|
|||||||
function clearFilters() {
|
function clearFilters() {
|
||||||
if (categoryFilter) categoryFilter.value = '';
|
if (categoryFilter) categoryFilter.value = '';
|
||||||
if (regionFilter) regionFilter.value = '';
|
if (regionFilter) regionFilter.value = '';
|
||||||
|
if (badgeFilter) badgeFilter.value = '';
|
||||||
if (tagFilter) tagFilter.value = '';
|
if (tagFilter) tagFilter.value = '';
|
||||||
if (searchInput) searchInput.value = '';
|
if (searchInput) searchInput.value = '';
|
||||||
|
|
||||||
@@ -138,6 +150,8 @@
|
|||||||
categoryFilter.value = filterValue;
|
categoryFilter.value = filterValue;
|
||||||
} else if (filterType === 'region' && regionFilter) {
|
} else if (filterType === 'region' && regionFilter) {
|
||||||
regionFilter.value = filterValue;
|
regionFilter.value = filterValue;
|
||||||
|
} else if (filterType === 'badge' && badgeFilter) {
|
||||||
|
badgeFilter.value = filterValue;
|
||||||
} else if (filterType === 'tag' && tagFilter) {
|
} else if (filterType === 'tag' && tagFilter) {
|
||||||
tagFilter.value = filterValue;
|
tagFilter.value = filterValue;
|
||||||
}
|
}
|
||||||
@@ -171,6 +185,10 @@
|
|||||||
regionFilter.addEventListener('change', () => filterEntries());
|
regionFilter.addEventListener('change', () => filterEntries());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (badgeFilter) {
|
||||||
|
badgeFilter.addEventListener('change', () => filterEntries());
|
||||||
|
}
|
||||||
|
|
||||||
if (tagFilter) {
|
if (tagFilter) {
|
||||||
tagFilter.addEventListener('change', () => filterEntries());
|
tagFilter.addEventListener('change', () => filterEntries());
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
static/images/goteborg-manufaktur-cover.jpeg
Normal file
BIN
static/images/goteborg-manufaktur-cover.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
BIN
static/images/goteborg-manufaktur.avif
Normal file
BIN
static/images/goteborg-manufaktur.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -75,15 +75,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="site-footer">
|
{{ 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
<section class="about-section">
|
<section class="about-section">
|
||||||
<h2>Öppen Källkod</h2>
|
<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>
|
<p>Webbplatsen är byggd med enkel teknik – ren HTML, CSS och minimal JavaScript. Ingen spårningskod, inga tunga ramverk, inga dolda skript.</p>
|
||||||
|
<p>All källkod finns tillgänglig på <a href="https://git.4z.nu/jonas/ursprungsverige" target="_blank" rel="noopener">git.4z.nu</a> – en egenhostad Gitea-instans som också körs på servrar i Lerum.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="about-section">
|
<section class="about-section">
|
||||||
@@ -64,15 +65,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="site-footer">
|
{{ 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -21,15 +21,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="site-footer">
|
{{ 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -43,16 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="site-footer">
|
{{ 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>
|
<script src="filter.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user