Erstes Commit
1
hugo/static/css/main.min.css
vendored
Normal file
BIN
hugo/static/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
hugo/static/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
hugo/static/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
hugo/static/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 825 B |
BIN
hugo/static/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
hugo/static/icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
hugo/static/icons/site.webmanifest
Normal file
@@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/static/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/static/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#6d28d9","background_color":"#ffffff","display":"standalone"}
|
||||
1
hugo/static/img/datenspeicher.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M5 21h14c1.1 0 2-.9 2-2V8c0-.27-.11-.52-.29-.71l-4-4A1 1 0 0 0 16 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2m10-2H9v-5h6zM11 5h2v2h-2zM5 5h2v4h8V5h.59L19 8.41V19h-2v-5c0-1.1-.9-2-2-2H9c-1.1 0-2 .9-2 2v5H5z"></path></svg>
|
||||
|
After Width: | Height: | Size: 382 B |
1
hugo/static/img/druecke.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m20,10h-4v-3c0-.55-.45-1-1-1h-6c-.55,0-1,.45-1,1v3H4c-.38,0-.73.22-.9.57-.17.35-.12.76.12,1.06l8,10c.19.24.48.38.78.38s.59-.14.78-.38l8-10c.24-.3.29-.71.12-1.06-.17-.35-.52-.57-.9-.57Zm-8,9.4l-5.92-7.4h2.92c.55,0,1-.45,1-1v-3h4v3c0,.55.45,1,1,1h2.92l-5.92,7.4Z"></path><path d="M8 2H16V4H8z"></path></svg>
|
||||
|
After Width: | Height: | Size: 473 B |
1
hugo/static/img/energie.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m5,15h5v6c0,.43.28.82.69.95.1.03.21.05.31.05.31,0,.62-.15.81-.41l8-11c.22-.3.25-.71.08-1.04-.17-.34-.52-.55-.89-.55h-5V3c0-.43-.28-.82-.69-.95-.41-.13-.86.01-1.12.36L4.19,13.41c-.22.3-.25.71-.08,1.04.17.34.52.55.89.55Zm7-8.92v3.92c0,.55.45,1,1,1h4.04l-5.04,6.92v-3.92c0-.55-.45-1-1-1h-4.04l5.04-6.92Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 483 B |
1
hugo/static/img/flaechen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2M5 19V5h14v14z"></path><path d="M12 9h3v3h2V7h-5zM9 12H7v5h5v-2H9z"></path></svg>
|
||||
|
After Width: | Height: | Size: 324 B |
1
hugo/static/img/geschwindigkeiten.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m19.58 11.19-7-5A.997.997 0 0 0 11 7v3.06L5.58 6.19A.997.997 0 0 0 4 7v10c0 .37.21.72.54.89.14.07.3.11.46.11.2 0 .41-.06.58-.19L11 13.94V17c0 .37.21.72.54.89.14.07.3.11.46.11.2 0 .41-.06.58-.19l7-5c.26-.19.42-.49.42-.81s-.16-.63-.42-.81M6 15.06V8.95l4.28 3.06L6 15.07Zm7 0V8.95l4.28 3.06L13 15.07Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 480 B |
1
hugo/static/img/gewichte.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m22,8c0-.55-.45-1-1-1h-2v-1c0-.55-.45-1-1-1h-3c-.55,0-1,.45-1,1v5h-4v-5c0-.55-.45-1-1-1h-3c-.55,0-1,.45-1,1v1h-2c-.55,0-1,.45-1,1v3h-1v2h1v3c0,.55.45,1,1,1h2v1c0,.55.45,1,1,1h3c.55,0,1-.45,1-1v-5h4v5c0,.55.45,1,1,1h3c.55,0,1-.45,1-1v-1h2c.55,0,1-.45,1-1v-3h1v-2h-1v-3ZM4,15v-6h1v6h-1Zm4,2h-1V7h1v10Zm9,0h-1V7h1v10Zm3-2h-1v-6h1v6Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 512 B |
1
hugo/static/img/laengen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M22 7H2c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h20c.55 0 1-.45 1-1V8c0-.55-.45-1-1-1m-1 8H3V9h2v3h2V9h2v4h2V9h2v3h2V9h2v4h2V9h2z"></path></svg>
|
||||
|
After Width: | Height: | Size: 305 B |
1
hugo/static/img/mehrwertsteuer.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M7.51 18.35c1.63 1.71 3.8 2.65 6.11 2.65s4.48-.94 6.11-2.65l-1.45-1.38c-1.25 1.31-2.9 2.03-4.66 2.03s-3.41-.72-4.66-2.03c-.55-.58-.99-1.25-1.31-1.97h4.36v-2H7.08c-.04-.33-.07-.66-.07-1s.03-.67.07-1h4.93V9H7.65c.32-.72.76-1.39 1.31-1.97C10.21 5.72 11.86 5 13.62 5s3.41.72 4.66 2.03l1.45-1.38C18.1 3.94 15.93 3 13.62 3s-4.48.94-6.11 2.65C6.59 6.61 5.92 7.75 5.5 9H3v2h2.06c-.03.33-.06.66-.06 1s.02.67.06 1H3v2h2.5a9 9 0 0 0 2.01 3.35"></path></svg>
|
||||
|
After Width: | Height: | Size: 614 B |
1
hugo/static/img/temperaturen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m15,11.81v-7.81c0-1.65-1.35-3-3-3s-3,1.35-3,3v7.81c-2.16,1.25-3.34,3.73-2.91,6.25.41,2.38,2.27,4.32,4.63,4.81.42.09.85.13,1.27.13,1.38,0,2.71-.46,3.79-1.34,1.41-1.15,2.22-2.84,2.22-4.66,0-2.16-1.16-4.13-3-5.19Zm-.48,8.3c-.95.77-2.16,1.06-3.39.8-1.54-.32-2.8-1.63-3.06-3.19-.32-1.84.65-3.64,2.34-4.38.36-.16.6-.52.6-.92V4c0-.55.45-1,1-1s1,.45,1,1v8.42c0,.4.24.76.6.92,1.46.64,2.4,2.08,2.4,3.66,0,1.21-.54,2.34-1.48,3.11Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 602 B |
1
hugo/static/img/volumen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M3 16c0 .34.18.67.47.85l8 5a1.01 1.01 0 0 0 1.06 0l8-5c.29-.18.47-.5.47-.85V8c0-.34-.18-.67-.47-.85l-8-5c-.32-.2-.74-.2-1.06 0l-8 5c-.29.18-.47.5-.47.85zm2-6.53 6 3.6v6.13l-6-3.75zm8 9.73v-6.13l6-3.6v5.98zM12 4.18l5.84 3.65-5.84 3.5-5.84-3.5z"></path></svg>
|
||||
|
After Width: | Height: | Size: 425 B |
1
hugo/static/img/waehrungen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M21 4H3c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1m-1 11c-1.66 0-3 1.34-3 3H7c0-1.66-1.34-3-3-3V9c1.66 0 3-1.34 3-3h10c0 1.66 1.34 3 3 3z"></path><path d="M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2"></path></svg>
|
||||
|
After Width: | Height: | Size: 471 B |
1
hugo/static/img/winkel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m13.75,11.71l-1.31,1.59c1.22,1.65,2.06,3.59,2.4,5.71H5.12L17.77,3.64l-1.54-1.27-5.39,6.55c-2.48-1.82-5.53-2.91-8.83-2.91v2c2.82,0,5.43.92,7.56,2.46l-7.33,8.9c-.25.3-.3.71-.13,1.06s.52.57.9.57h19v-2h-5.15c-.37-2.73-1.47-5.23-3.1-7.29Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 416 B |
1
hugo/static/img/zeiten.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" transform="" id="injected-svg"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8"></path><path d="M13 6h-2v6c0 .55.45 1 1 1h6v-2h-5z"></path></svg>
|
||||
|
After Width: | Height: | Size: 357 B |
5
hugo/static/js/alpine-3.14.9.min.js
vendored
Normal file
1
hugo/static/js/alpine-ajax-0.12.2.min.js
vendored
Normal file
280
hugo/static/js/converter.js
Normal file
@@ -0,0 +1,280 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Factory function for Alpine.js converter components.
|
||||
* @param {string} engine - 'linear', 'intermediate' or 'runtime'
|
||||
* @param {object} config - Engine-specific configuration
|
||||
* @returns {object} Alpine component data
|
||||
*/
|
||||
function createConverter(engine, config) {
|
||||
if (engine === 'runtime') {
|
||||
return {
|
||||
inputValue: '1',
|
||||
result: '',
|
||||
rates: {},
|
||||
ratesError: '',
|
||||
async init() {
|
||||
await this.loadRates();
|
||||
const params = new URLSearchParams(
|
||||
window.location.search);
|
||||
const valueParam = params.get('v');
|
||||
if (valueParam) {
|
||||
this.inputValue = valueParam.replace(/,/g, '.');
|
||||
}
|
||||
this.$watch('inputValue', val => {
|
||||
if (val && val.indexOf(',') !== -1) {
|
||||
this.inputValue = val.replace(/,/g, '.');
|
||||
}
|
||||
});
|
||||
this.calculate();
|
||||
},
|
||||
async loadRates() {
|
||||
const pbUrl = document.querySelector('meta[name="pocketbase-url"]')?.content ||
|
||||
'https://www.alphabreed.com';
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${pbUrl}/api/collections/currencies/records`);
|
||||
if (!response.ok) {
|
||||
this.ratesError =
|
||||
'Wechselkurse konnten nicht geladen werden.';
|
||||
return;
|
||||
}
|
||||
const data = await response.json();
|
||||
for (const item of data.items || []) {
|
||||
this.rates[item.id] = item.rate;
|
||||
}
|
||||
} catch (e) {
|
||||
this.ratesError =
|
||||
'Wechselkurse konnten nicht geladen werden.';
|
||||
}
|
||||
},
|
||||
calculate() {
|
||||
try {
|
||||
this.result = '';
|
||||
if (Object.keys(this.rates).length === 0) {
|
||||
return;
|
||||
}
|
||||
const normalized = parseNumber(
|
||||
this.inputValue);
|
||||
if (!normalized || isNaN(normalized)) {
|
||||
return;
|
||||
}
|
||||
const fromCode = (config.fromCurrency || '')
|
||||
.toUpperCase();
|
||||
const toCode = (config.toCurrency || '')
|
||||
.toUpperCase();
|
||||
const fromRate = fromCode === 'EUR'
|
||||
? 1 : this.rates[fromCode];
|
||||
const toRate = toCode === 'EUR'
|
||||
? 1 : this.rates[toCode];
|
||||
if (!fromRate || !toRate) {
|
||||
return;
|
||||
}
|
||||
const value = new Decimal(normalized);
|
||||
const rate = new Decimal(toRate)
|
||||
.dividedBy(new Decimal(fromRate));
|
||||
const rawResult = value.times(rate);
|
||||
this.result = prettyNumber(rawResult);
|
||||
const normalizedInputValue = this.inputValue
|
||||
.replace(/,/g, '.');
|
||||
if (normalizedInputValue && normalizedInputValue !== '0') {
|
||||
history.replaceState(
|
||||
null, '', '?v=' + normalizedInputValue);
|
||||
}
|
||||
} catch (e) {
|
||||
this.result = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
inputValue: '1',
|
||||
result: '',
|
||||
ratesError: '',
|
||||
init() {
|
||||
const params = new URLSearchParams(
|
||||
window.location.search);
|
||||
const valueParam = params.get('v');
|
||||
if (valueParam) {
|
||||
this.inputValue = valueParam.replace(/,/g, '.');
|
||||
}
|
||||
this.$watch('inputValue', val => {
|
||||
if (val && val.indexOf(',') !== -1) {
|
||||
this.inputValue = val.replace(/,/g, '.');
|
||||
}
|
||||
});
|
||||
this.calculate();
|
||||
},
|
||||
calculate() {
|
||||
try {
|
||||
const normalized = parseNumber(this.inputValue);
|
||||
if (!normalized || isNaN(normalized)) {
|
||||
this.result = '';
|
||||
return;
|
||||
}
|
||||
const value = new Decimal(normalized);
|
||||
let rawResult;
|
||||
if (engine === 'linear') {
|
||||
const fromFactor = new Decimal(
|
||||
config.fromFactor);
|
||||
const toFactor = new Decimal(
|
||||
config.toFactor);
|
||||
rawResult = value.times(fromFactor)
|
||||
.dividedBy(toFactor);
|
||||
} else if (engine === 'intermediate') {
|
||||
rawResult = convertTemperature(
|
||||
value,
|
||||
config.fromUnit,
|
||||
config.toUnit
|
||||
);
|
||||
} else {
|
||||
this.result = '';
|
||||
return;
|
||||
}
|
||||
this.result = prettyNumber(rawResult);
|
||||
const normalizedInputValue = this.inputValue.replace(/,/g, '.');
|
||||
if (normalizedInputValue && normalizedInputValue !== '0') {
|
||||
history.replaceState(
|
||||
null, '', '?v=' + normalizedInputValue);
|
||||
}
|
||||
} catch (e) {
|
||||
this.result = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Alpine.js component for the conversion form.
|
||||
* Handles category/from/to dropdowns and swap button.
|
||||
* @param {object} defaults - Initial values
|
||||
* @returns {object} Alpine component data
|
||||
*/
|
||||
function conversionForm(defaults) {
|
||||
return {
|
||||
category: defaults.category,
|
||||
from: defaults.from,
|
||||
to: defaults.to,
|
||||
|
||||
get actionUrl() {
|
||||
const search = window.location.search;
|
||||
return '/' + this.from + '-in-' + this.to + '/'
|
||||
+ (search || '');
|
||||
},
|
||||
|
||||
swapWithResult() {
|
||||
const resultEl = document.getElementById('result');
|
||||
const resultVal = resultEl?.value || '';
|
||||
if (resultVal) {
|
||||
history.replaceState(
|
||||
null, '', '?v=' + resultVal.replace(/,/g, '.'));
|
||||
}
|
||||
const temp = this.from;
|
||||
this.from = this.to;
|
||||
this.to = temp;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.navLink.click();
|
||||
});
|
||||
},
|
||||
|
||||
onCategoryChange() {
|
||||
const cat = UNITS_DATA[this.category];
|
||||
if (!cat || !cat.units) return;
|
||||
const units = Object.keys(cat.units);
|
||||
this.from = units[0];
|
||||
this.to = units[1] || units[0];
|
||||
this.$nextTick(() => {
|
||||
this.$refs.navLink.click();
|
||||
});
|
||||
},
|
||||
|
||||
init() {
|
||||
this.$watch('category', (val, old) => {
|
||||
if (val && old && val !== old) {
|
||||
this.onCategoryChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Temperature conversion via Celsius intermediate.
|
||||
* @param {Decimal} value
|
||||
* @param {string} fromUnit
|
||||
* @param {string} toUnit
|
||||
* @returns {number}
|
||||
*/
|
||||
function convertTemperature(value, fromUnit, toUnit) {
|
||||
let v = value.toNumber();
|
||||
if (fromUnit === 'fahrenheit') {
|
||||
v = (v - 32) * 5 / 9;
|
||||
} else if (fromUnit === 'kelvin') {
|
||||
v = v - 273.15;
|
||||
} else if (fromUnit === 'rankine') {
|
||||
v = (v - 491.67) * 5 / 9;
|
||||
} else if (fromUnit === 'reaumur') {
|
||||
v = v * 5 / 4;
|
||||
}
|
||||
if (toUnit === 'fahrenheit') {
|
||||
v = v * 9 / 5 + 32;
|
||||
} else if (toUnit === 'kelvin') {
|
||||
v = v + 273.15;
|
||||
} else if (toUnit === 'rankine') {
|
||||
v = (v + 273.15) * 9 / 5;
|
||||
} else if (toUnit === 'reaumur') {
|
||||
v = v * 4 / 5;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number as plain digits with dot as decimal separator.
|
||||
* @param {Decimal|string|number} num
|
||||
* @param {number} minPrecision - Minimum decimal places
|
||||
* @param {number} maxPrecision - Maximum decimal places
|
||||
* @returns {string} Formatted number string
|
||||
*/
|
||||
function prettyNumber(num, minPrecision, maxPrecision) {
|
||||
minPrecision = minPrecision || 4;
|
||||
maxPrecision = maxPrecision || 10;
|
||||
const d = new Decimal(num);
|
||||
const absVal = d.abs();
|
||||
// Larger numbers need fewer decimal places for readable output;
|
||||
// smaller numbers get more to stay meaningful.
|
||||
let precision = maxPrecision;
|
||||
if (absVal.gte(1000000)) {
|
||||
precision = 2;
|
||||
} else if (absVal.gte(1000)) {
|
||||
precision = 4;
|
||||
} else if (absVal.gte(1)) {
|
||||
precision = 6;
|
||||
} else if (absVal.gte(0.001)) {
|
||||
precision = 8;
|
||||
}
|
||||
precision = Math.max(minPrecision,
|
||||
Math.min(precision, maxPrecision));
|
||||
let str = d.toFixed(precision);
|
||||
if (str.indexOf('.') !== -1) {
|
||||
str = str.replace(/0+$/, '');
|
||||
str = str.replace(/\.$/, '');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a German number string.
|
||||
* Accepts both comma and dot as decimal separator.
|
||||
* @param {string} input
|
||||
* @returns {string} Normalized string with dot as decimal
|
||||
*/
|
||||
function parseNumber(input) {
|
||||
if (!input) return '0';
|
||||
const hasComma = input.indexOf(',') !== -1;
|
||||
const hasDot = input.indexOf('.') !== -1;
|
||||
if (hasComma && hasDot) {
|
||||
return input.replace(/\./g, '').replace(/,/g, '.') || '0';
|
||||
}
|
||||
return input.replace(/,/g, '.') || '0';
|
||||
}
|
||||