This commit is contained in:
Daniel Ledda
2024-11-01 15:42:09 +01:00
parent f60e975765
commit 7539e6ed48
50 changed files with 2004 additions and 750 deletions

16
app/DJDonate.tsx Normal file
View File

@@ -0,0 +1,16 @@
import { defineComponent } from "vue";
export default defineComponent({
name: "dj-donate",
setup: () => {
const imgsrc =
"https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=djledda&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff";
return () => (
<div class="dj-donate">
<a href="https://www.buymeacoffee.com/djledda">
<img src={imgsrc} />
</a>
</div>
);
},
});

View File

@@ -14,7 +14,7 @@ export default defineComponent({
} }
return () => ( return () => (
<a href="#" onClick={clickMail}> <a href="#" onClick={clickMail}>
{ slots.default ? slots.default() : 'dan.j.ledda [at] gmail [dot] com' } {slots.default ? slots.default() : "dan.j.ledda [at] gmail [dot] com"}
</a> </a>
); );
}, },

80
app/DJTooltip.tsx Normal file
View File

@@ -0,0 +1,80 @@
import { watchEffect, watch, onMounted, type CSSProperties, defineComponent, ref } from "vue";
const carrierStyle = {
opacity: "0",
display: "block",
pointerEvents: "none",
backgroundColor: "black",
border: "white solid 1px",
color: "white",
padding: "10px",
position: "absolute",
zIndex: "1",
overflow: "hidden",
height: "0",
width: "0",
transition: "opacity 200ms, height 200ms, width 200ms",
} satisfies CSSProperties;
const textCarrierStyle = {
fontSize: '16px',
fontFamily: "Roboto, serif",
display: "block",
overflow: "hidden",
} satisfies CSSProperties;
const defaultWidth = 350;
export default defineComponent({
name: "dj-sexy-tooltip",
props: {
tooltip: {
type: String,
required: true,
},
},
setup(props, { slots, attrs }) {
const active = ref(false);
const carrier = ref<HTMLElement | null>(null);
const textCarrier = ref<HTMLElement | null>(null);
onMounted(() => {
document.addEventListener("mousemove", (event) => {
const pos = { x: event.pageX, y: event.pageY };
if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") {
if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) {
carrier.value.style.left = (pos.x + 15) + "px";
} else {
carrier.value.style.left = (document.body.scrollWidth - carrier.value.clientWidth - 5) + "px";
}
if (pos.y + carrier.value.clientHeight <= document.body.scrollHeight) {
carrier.value.style.top = pos.y + "px";
} else {
carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px";
}
}
});
});
watchEffect(() => {
if (carrier.value) {
carrier.value.style.height = active.value ? '16px' : '0';
carrier.value.style.opacity = active.value ? '1' : '0';
carrier.value.style.width = active.value ? '350px' : '0';
}
});
return () => <>
<div class="tooltip-container" {...attrs}
onMouseenter={() => { active.value = true; }}
onMouseleave={() => { active.value = false; }}
>
{slots.default && <slots.default />}
</div>
<div style={carrierStyle} ref={carrier}>
<span style={textCarrierStyle}>{props.tooltip}</span>
</div>
</>;
},
});

14
app/api.ts Normal file
View File

@@ -0,0 +1,14 @@
export type DJAPIEndpoint = "/rp-articles";
export interface DJAPIResultMap extends Record<DJAPIEndpoint, unknown> {
"/rp-articles": { slug: string; name: string }[];
}
export type DJAPIResult = DJAPIResultMap[DJAPIEndpoint];
export default async function getDJAPI<T extends DJAPIEndpoint>(
hostUrl: string,
endpoint: T,
): Promise<DJAPIResultMap[typeof endpoint]> {
return await (await fetch(`${hostUrl}/api${endpoint}`)).json();
}

View File

@@ -1,194 +1,7 @@
import { defineComponent, computed, ref } from 'vue'; import { computed, defineComponent, ref } from "vue";
/*
class GrainInput {
static inputs = [];
name;
unit;
mpg;
inputEl;
reference;
* @param {{
* name: string,
* mpg: number,
* reference: GrainInput | 'self',
* unit: string,
* step?: number,
* }} opts
constructor(opts) {
this.name = opts.name;
this.mpg = opts.mpg;
this.unit = opts.unit;
this.reference = opts.reference === "self" ? this : opts.reference;
this.inputEl = h("input", { type: "number", min: 0, step: opts.step ?? 1 });
eventBus.addListener(this);
}
attachInput(insertionPoint) {
insertionPoint.appendChild(this.inputEl);
this.inputEl.valueAsNumber = this.mpg;
this.inputEl.addEventListener("input", (e) => {
const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
if (this !== this.reference) {
this.reference.inputEl.valueAsNumber = newVal / this.mpg;
}
eventBus.sendChange(this);
});
}
onChange() {
if (!this.reference || this === this.reference) return;
this.inputEl.valueAsNumber = this.mpg * this.reference.inputEl.valueAsNumber;
}
getCurrentValue() {
return this.inputEl.valueAsNumber;
}
}
class RatiosController {
t3Ratio = h("input", { type: "number", min: 0, step: 1 });
t4Ratio = h("input", { type: "number", min: 0, step: 1 });
t3Syn = h("input", { type: "number", min: 0 });
t4Syn = h("input", { type: "number", min: 0 });
t3 = 1;
t4 = 4;
reference;
constructor(referenceInput) {
this.reference = referenceInput;
}
attachInputs(inputs) {
inputs.t3Ratio.replaceWith(this.t3Ratio);
inputs.t4Ratio.replaceWith(this.t4Ratio);
inputs.t3Syn.replaceWith(this.t3Syn);
inputs.t4Syn.replaceWith(this.t4Syn);
this.t3Ratio.addEventListener("input", (e) => {
const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
this.t3 = newVal;
this.onChange();
});
this.t4Ratio.addEventListener("input", (e) => {
const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
this.t4 = newVal;
this.onChange();
});
this.t3Syn.addEventListener("input", (e) => {
const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
this.reference.inputEl.valueAsNumber = newVal / MPG_T3_SYN + this.t4Syn.valueAsNumber / MPG_T4_SYN;
this.t3 = newVal;
this.t4 = this.t4Syn.valueAsNumber;
this.updateRatio();
this.updateSyn();
eventBus.sendChange(this);
});
this.t4Syn.addEventListener("input", (e) => {
const newVal = (e.currentTarget)?.valueAsNumber ?? 0;
this.reference.inputEl.valueAsNumber = newVal / MPG_T4_SYN + this.t3Syn.valueAsNumber / MPG_T3_SYN;
this.t3 = this.t3Syn.valueAsNumber;
this.t4 = newVal;
this.updateRatio();
this.updateSyn();
eventBus.sendChange(this);
});
eventBus.addListener(this);
this.onChange();
}
onChange() {
this.updateRatio();
this.updateSyn();
}
updateRatio() {
this.t3Ratio.valueAsNumber = this.t3;
this.t4Ratio.valueAsNumber = this.t4;
}
updateSyn() {
const total = this.t3 + this.t4;
const t3Proportion = this.t3 / total;
const t4Proportion = this.t4 / total;
const grainsSyn = t3Proportion / MPG_T3_SYN + t4Proportion / MPG_T4_SYN;
const multiplierSyn = this.reference.getCurrentValue() / grainsSyn;
this.t3Syn.valueAsNumber = t3Proportion * multiplierSyn;
this.t4Syn.valueAsNumber = t4Proportion * multiplierSyn;
}
}
class ThyroidConverter extends DJElement {
static styles = css``;
static template = html;
constructor() {
super();
const mainGrainInput = new GrainInput({
name: "Grains",
mpg: 1,
reference: "self",
unit: "",
step: 0.1,
});
const ratiosController = new RatiosController(mainGrainInput);
const inputs = [
mainGrainInput,
new GrainInput({
name: "Armour, Natural Dessicated Thyroid",
mpg: 60,
unit: "mg",
reference: mainGrainInput,
}),
new GrainInput({
name: 'Liothyronine (Triiodothyronine, "Cytomel", T3)',
mpg: MPG_T3_SYN,
unit: "mcg",
reference: mainGrainInput,
}),
new GrainInput({
name: 'Levothyroxine (Thyroxine, "Cynoplus", T4)',
mpg: MPG_T4_SYN,
unit: "mcg",
reference: mainGrainInput,
}),
];
const tableBody = qs("#table-body", this.root);
const compoundedStart = qs("#compounded-start", this.root);
for (const field of inputs) {
const newRow = h("tr", {}, [
h("td", { textContent: field.name }),
h("td", {}, [
h("div", { className: "input", style: "display: inline-block;" }),
h("span", { className: "breathe", textContent: field.unit }),
]),
]);
field.attachInput(qs("div.input", newRow));
tableBody.insertBefore(newRow, compoundedStart);
}
ratiosController.attachInputs({
t3Ratio: qs(".ratios .t3", tableBody),
t4Ratio: qs(".ratios .t4", tableBody),
t3Syn: qs(".synthetic .t3", tableBody),
t4Syn: qs(".synthetic .t4", tableBody),
});
}
}
*/
export default defineComponent({ export default defineComponent({
name: 'ge-calculator', name: "ge-calculator",
setup() { setup() {
const t3Ratio = ref(1); const t3Ratio = ref(1);
const t4Ratio = ref(4); const t4Ratio = ref(4);
@@ -209,12 +22,12 @@ export default defineComponent({
unit: "mg", unit: "mg",
}, },
{ {
name: 'Liothyronine (Triiodothyronine, "Cytomel", T3)', name: 'Liothyronine (Triiodothyronine, "Cytomel/Cynomel", T3)',
mpg: MPG_T3_SYN, mpg: MPG_T3_SYN,
unit: "mcg", unit: "mcg",
}, },
{ {
name: 'Levothyroxine (Thyroxine, "Cynoplus", T4)', name: "Levothyroxine (Thyroxine, T4)",
mpg: MPG_T4_SYN, mpg: MPG_T4_SYN,
unit: "mcg", unit: "mcg",
}, },
@@ -222,7 +35,6 @@ export default defineComponent({
const numGrains = ref(2); const numGrains = ref(2);
const ratio = computed(() => t3Ratio.value + t4Ratio.value);
const compounded = computed(() => { const compounded = computed(() => {
const total = t3Ratio.value + t4Ratio.value; const total = t3Ratio.value + t4Ratio.value;
const t3Proportion = t3Ratio.value / total; const t3Proportion = t3Ratio.value / total;
@@ -235,121 +47,138 @@ export default defineComponent({
}; };
}); });
return () => <> return () => (
<header> <>
<h1>Thyroid Calculator</h1> <header>
</header> <h1>Thyroid Calculator</h1>
<div class="text-slab"> </header>
<p> <div class="text-slab">
Use this calculator to convert between different forms and units of thyroid hormone. Common <p>
synthetic, pure and natural dessicated forms are listed. The last section of the table provides Use this calculator to convert between different forms and units of thyroid hormone. Common
input fields for a specified ratio of T3 to T4, and will give the dosages required for both the synthetic, pure and natural dessicated forms are listed. The last section of the table provides
synthetic and pure forms. All dosages given are based on the "Grains" field at the beginning of the input fields for a specified ratio of T3 to T4, and will give the dosages required for both the
table, but editing any field will automatically update the rest to keep them in sync. synthetic and pure forms. All dosages given are based on the "Grains" field at the beginning of
</p> the table, but editing any field will automatically update the rest to keep them in sync.
</div> </p>
<div class="text-slab ge-calculator"> </div>
<table> <div class="text-slab ge-calculator">
<tbody> <table>
{ inputDefs.map((_, i) => ( <tbody>
<tr key={_.name}> {inputDefs.map((_) => (
<td> <tr key={_.name}>
{ _.name } <td>
</td> {_.name}
<td class="right"> </td>
<div style="display: inline-block;"> <td class="right">
<input <div style="display: inline-block;">
value={_.name === 'Grains' ? numGrains.value : _.mpg * numGrains.value} <input
onInput={(e) => { value={_.name === "Grains" ? numGrains.value : _.mpg * numGrains.value}
const val = (e.target as HTMLInputElement).valueAsNumber; onInput={(e) => {
if (_.name === 'Grains') { const val = (e.target as HTMLInputElement).valueAsNumber;
numGrains.value = val; if (_.name === "Grains") {
} else { numGrains.value = val;
numGrains.value = _.mpg / val; } else {
} numGrains.value = _.mpg / val;
}} }
min="0" }}
step={ _.step ?? 1 } min="0"
type="number" /> step={_.step ?? 1}
</div> type="number"
<span class="breathe">{ _.unit }</span> />
</div>
<span class="breathe">{_.unit}</span>
</td>
</tr>
))}
<tr>
<td colspan="2">
<strong>Compounded (T3 and T4, "Cynoplus")</strong>
</td> </td>
</tr> </tr>
))} <tr class="ratios">
<tr><td colspan="2"><strong>Compounded (T3 and T4, "Cynpolus")</strong></td></tr> <td>
<tr class="ratios"> Desired Ratio (T3:T4)
<td> </td>
Desired Ratio (T3:T4) <td class="right">
</td> <div>
<td class="right"> <input
<div> value={t3Ratio.value}
<input onInput={(e) => {
value={t3Ratio.value} t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
onInput={(e) => { }}
t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; min="0"
}} step="1"
min="0" type="number"
step="1" />
type="number" /> </div>{" "}
</div> : <div> :{" "}
<input <div>
value={t4Ratio.value} <input
onInput={(e) => { value={t4Ratio.value}
t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; onInput={(e) => {
}} t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
min="0" }}
step="1" min="0"
type="number" /> step="1"
</div> type="number"
</td> />
</tr> </div>
<tr class="synthetic"> </td>
<td> </tr>
Synthetic T3/T4 Combo <tr class="synthetic">
</td> <td>
<td class="right"> Synthetic T3/T4 Combo
<div> </td>
<input <td class="right">
value={compounded.value.t3Syn} <div>
onInput={(e) => { <input
t4Ratio.value = compounded.value.t4Syn; value={compounded.value.t3Syn}
t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; onInput={(e) => {
numGrains.value = t3Ratio.value / MPG_T3_SYN + t4Ratio.value / MPG_T4_SYN; t4Ratio.value = compounded.value.t4Syn;
}} t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
min="0" numGrains.value = t3Ratio.value / MPG_T3_SYN +
step="1" t4Ratio.value / MPG_T4_SYN;
type="number" /> }}
</div> : <div> min="0"
<input step="1"
value={compounded.value.t4Syn} type="number"
onInput={(e) => { />
t3Ratio.value = compounded.value.t3Syn; </div>{" "}
t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; :{" "}
numGrains.value = t3Ratio.value / MPG_T3_SYN + t4Ratio.value / MPG_T4_SYN; <div>
}} <input
min="0" value={compounded.value.t4Syn}
step="1" onInput={(e) => {
type="number" /> t3Ratio.value = compounded.value.t3Syn;
</div> t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber;
</td> numGrains.value = t3Ratio.value / MPG_T3_SYN +
</tr> t4Ratio.value / MPG_T4_SYN;
</tbody> }}
</table> min="0"
</div> step="1"
<div class="text-slab"> type="number"
<strong>Changelog:</strong> />
<p> </div>
<ul> </td>
<li> </tr>
November 2024: Migrated to new web framework and fixed some buggy input. </tbody>
</li> </table>
<li> </div>
13th March 2024: Removed the synthetic/pure distinction as it was confusing and unnecessary <div class="text-slab">
bloat. <strong>Changelog:</strong>
</li> <p>
</ul> <ul>
</p> <li>
</div> November 2024: Migrated to new web framework and fixed some buggy input.
</>; </li>
} <li>
13th March 2024: Removed the synthetic/pure distinction as it was confusing and
unnecessary bloat.
</li>
</ul>
</p>
</div>
</>
);
},
}); });

View File

@@ -1,61 +1,76 @@
import { defineComponent } from 'vue'; import { defineComponent } from "vue";
import { RouterLink } from 'vue-router'; import { RouterLink } from "vue-router";
import DJEmail from "@/DJEmail.tsx"; import DJEmail from "@/DJEmail.tsx";
import useHead from "@/useHead.ts";
import useAsyncState from "@/useAsyncState.ts";
import getDJAPI from "@/api.ts";
export default defineComponent({ export default defineComponent({
name: 'ge-deutsch', name: "ge-deutsch",
setup() { async setup() {
return () => <> useHead({ title: "Ray Peat Artikel auf Deutsch" });
<header>
<h1>Ray Peat Deutsche Übersetzungen</h1> const {
</header> result: rpArticles,
<div class="text-slab"> stateIsReady,
<p> } = useAsyncState("rp-articles", ({ hostUrl }) => getDJAPI(hostUrl, "/rp-articles"));
Auf dieser Seite befindet sich eine Auswahl der Artikel von Dr. Raymond Peat, die ich in meiner
Freizeit ins Deutsche übersetzt habe. await stateIsReady;
</p> return () => (
<p> <>
Ray Peat war ein US-Amerikaner aus Eugene, Oregon, der neben seinem Studium von Linguistik, <header>
Literatur, und Malerei eine Promotion in der Biologie und Physiologie des Alterns und degenerativer <h1>Ray Peat Deutsche Übersetzungen</h1>
Krankheiten absolvierte, mit besonderem Fokus auf Zellen-Stoffwechsel, Hormone, und </header>
Ernährungsphysiologie. <div class="text-slab">
</p> <p>
<p> Auf dieser Seite befindet sich eine Auswahl der Artikel von Dr. Raymond Peat, die ich in meiner
Nach mehreren Jahren als Professor an diversen Universitäten in den USA und Mexiko begann er Artikel Freizeit ins Deutsche übersetzt habe.
in Form eines Newsletters herauszugeben, von denen mehrere auf <a href="http://raypeat.com/" </p>
>seiner Website</a> zu finden sind. <p>
</p> Ray Peat war ein US-Amerikaner aus Eugene, Oregon, der neben seinem Studium von Linguistik,
<p> Literatur, und Malerei eine Promotion in der Biologie und Physiologie des Alterns und
Da er in meinem Leben zu einem enormen Verständnis meines Körpers und einem unglaublichen degenerativer Krankheiten absolvierte, mit besonderem Fokus auf Zellen-Stoffwechsel, Hormone,
Zurückerlangen meiner Gesundheit und meines Wohlbefindens trotz dem Scheitern mehrerer Ärzte und Ernährungsphysiologie.
beigetragen hat, und sein Nachlass in Deutschland kaum Publikum aufgrund schwieriger physiologischer </p>
Texte genießt, habe ich entschieden hauptsächlich für Freunde und Bekannte einige seiner Artikel ins <p>
Deutsche zu übersetzen, damit vielleicht auch ein breiteres Publikum von seinen Ideen profitieren Nach mehreren Jahren als Professor an diversen Universitäten in den USA und Mexiko begann er
könnte. Artikel in Form eines Newsletters herauszugeben, von denen mehrere auf{" "}
</p> <a href="http://raypeat.com/">seiner Website</a> zu finden sind.
<p> </p>
Falls was bei der Übersetzung auffällt oder besonders unidiomatisch klingt, bzw. der deutschen <p>
Fachsprache der Medizin nicht gerecht sein sollte, kannst du mir unter <DJEmail /> eine Mail senden. Da er in meinem Leben zu einem enormen Verständnis meines Körpers und einem unglaublichen
Meine Muttersprache ist schließlich Englisch und ich bin kein professioneller Übersetzer! Zurückerlangen meiner Gesundheit und meines Wohlbefindens trotz dem Scheitern mehrerer Ärzte
</p> beigetragen hat, und sein Nachlass in Deutschland kaum Publikum aufgrund schwieriger
<p> physiologischer Texte genießt, habe ich entschieden hauptsächlich für Freunde und Bekannte
Zusätzlich zu der Funktion, den Artikel mit dem Button oben in der Originalversion anzusehen, hat einige seiner Artikel ins Deutsche zu übersetzen, damit vielleicht auch ein breiteres Publikum
jeder Absatz oben rechts beim drüberhovern mit der Maus einen zusätzlichen Button über den man den von seinen Ideen profitieren könnte.
jeweiligen Absatz in die andere Sprache wechseln kann, um die Versionen beim Lesen schnell zu </p>
vergleichen zu können. <p>
</p> Falls was bei der Übersetzung auffällt oder besonders unidiomatisch klingt, bzw. der deutschen
</div> Fachsprache der Medizin nicht gerecht sein sollte, kannst du mir unter <DJEmail />{" "}
<div class="text-slab"> eine Mail senden. Meine Muttersprache ist schließlich Englisch und ich bin kein professioneller
<h3>Artikel auswählen:</h3> Übersetzer!
<ul id="article"> </p>
<li> <p>
<RouterLink to={{ name: 'GEDeutschArticle', params: { articleName: 'hypothyroidism'}}}>Indikatoren Schilddrüsenunterfunktion</RouterLink> Zusätzlich zu der Funktion, den Artikel mit dem Button oben in der Originalversion anzusehen,
</li> hat jeder Absatz oben rechts beim drüberhovern mit der Maus einen zusätzlichen Button über den
<li> man den jeweiligen Absatz in die andere Sprache wechseln kann, um die Versionen beim Lesen
<RouterLink to={{ name: 'GEDeutschArticle', params: { articleName: 'caffeine'}}}>Koffein</RouterLink> schnell zu vergleichen zu können.
</li> </p>
</ul> </div>
</div> <div class="text-slab">
</>; <h3>Artikel auswählen:</h3>
} <ul id="article">
{rpArticles.value && rpArticles.value.map((_) => (
<li>
<RouterLink to={{ name: "GEDeutschArticle", params: { articleName: _.slug } }}>
{_.name}
</RouterLink>
</li>
))}
</ul>
</div>
</>
);
},
}); });

View File

@@ -1,9 +1,10 @@
import { h, inject, defineComponent, ref, createTextVNode, type VNode } from 'vue'; import { createTextVNode, defineComponent, h, inject, onServerPrefetch, ref, type VNode, watchEffect } from "vue";
import { RouterLink } from 'vue-router'; import { RouterLink } from "vue-router";
import useAsyncState from "@/useAsyncState.ts"; import useAsyncState from "@/useAsyncState.ts";
import useHead from "@/useHead.ts";
export default defineComponent({ export default defineComponent({
name: 'ge-deutsch-article', name: "ge-deutsch-article",
props: { props: {
articleName: { articleName: {
type: String, type: String,
@@ -17,24 +18,50 @@ export default defineComponent({
currentLang.value = currentLang.value === "en" ? "de" : "en"; currentLang.value = currentLang.value === "en" ? "de" : "en";
} }
const parseDom = inject('dom-parse', (innerHTML: string) => Object.assign(document.createElement('div'), { innerHTML })); const parseDom = inject(
"dom-parse",
(innerHTML: string) => Object.assign(document.createElement("div"), { innerHTML }),
);
const { result: articleContent, stateIsReady } = useAsyncState('ge-deutsch-article-data', async ({ hostUrl }) => { const title = ref("");
const articleResponse = await fetch(`${ hostUrl }/generative-energy/static/content/${ props.articleName }.html`);
return await articleResponse.text(); const { result: articleContent, stateIsReady } = useAsyncState(
}); "ge-deutsch-article-data",
async ({ hostUrl }) => {
const articleResponse = await fetch(`${hostUrl}/generative-energy/content/${props.articleName}.html`);
const result = await articleResponse.text();
title.value = result.split('<h1 lang="de">')[1].split("</h1>")[0];
return result;
},
);
useHead({ title });
onServerPrefetch(() =>
new Promise<void>((res) => {
watchEffect(() => {
if (title.value !== "") {
console.log("resolve", title.value);
res();
}
});
})
);
function transformArticleNode(node: Node): VNode | string { function transformArticleNode(node: Node): VNode | string {
if (node.nodeType === node.ELEMENT_NODE) { if (node.nodeType === node.ELEMENT_NODE) {
const el = node as Element; const el = node as Element;
const attrs: Record<string, string> = {}; const attrs: Record<string, string> = {};
const children = [...node.childNodes].map(_ => transformArticleNode(_)); const children = [...node.childNodes].map((_) => transformArticleNode(_));
if (el.tagName === 'P') { if (el.tagName === "P") {
el.classList.add('text-slab'); el.classList.add("text-slab");
children.unshift(h('button', { class: 'swap', onClick: (e) => { children.unshift(h("button", {
e.target.parentElement.classList.toggle('swap'); class: "swap",
} }, '↻')); onClick: (e) => {
e.target.parentElement.classList.toggle("swap");
},
}, "↻"));
} }
for (let i = 0; i < el.attributes.length; i++) { for (let i = 0; i < el.attributes.length; i++) {
@@ -46,30 +73,32 @@ export default defineComponent({
return h((node as Element).tagName, attrs, children); return h((node as Element).tagName, attrs, children);
} else { } else {
return createTextVNode(node.textContent ?? ''); return createTextVNode(node.textContent ?? "");
} }
} }
function ArticleContentTransformed() { function ArticleContentTransformed() {
if (articleContent.value) { if (articleContent.value) {
const dom = parseDom(articleContent.value); const dom = parseDom(articleContent.value);
return h('div', {}, [...dom.children].map(_ => transformArticleNode(_))); return h("div", {}, [...dom.children].map((_) => transformArticleNode(_)));
} }
return <div>Artikel lädt...</div>; return <div>Artikel lädt...</div>;
} }
await stateIsReady; await stateIsReady;
return () => <div class="ge-article"> return () => (
<div class="header"> <div class="ge-article">
<RouterLink to={{ name: 'GEDeutsch' }}>Zur Artikelübersicht</RouterLink> <div class="header">
<button onClick={clickBtn}> <RouterLink to={{ name: "GEDeutsch" }}>Zur Artikelübersicht</RouterLink>
Sprache auf <span>{currentLang.value === "en" ? "Deutsch" : "Englisch"}</span> umschalten <button onClick={clickBtn}>
</button> Sprache auf <span>{currentLang.value === "en" ? "Deutsch" : "Englisch"}</span> umschalten
</button>
</div>
<article class={`lang-${currentLang.value}`}>
<ArticleContentTransformed />
</article>
</div> </div>
<article class={`lang-${ currentLang.value }`}> );
<ArticleContentTransformed /> },
</article>
</div>;
}
}); });

View File

@@ -1,33 +1,39 @@
import { RouterLink } from 'vue-router'; import { RouterLink } from "vue-router";
import useHead from "@/useHead.ts";
export default { export default {
name: 'ge-main', name: "ge-main",
setup() { setup() {
return () => <> useHead({ title: "Generative Energy" });
<header> return () => (
<h1>Generative Energy</h1> <>
</header> <header>
<div class="text-slab"> <h1>Generative Energy</h1>
<p> </header>
This page is dedicated to Dr. Raymond Peat ( 2022), who has had a profound impact on my health and <div class="text-slab">
my life in general. Hover over the links below for more detail. <p>
</p> This page is dedicated to Dr. Raymond Peat ( 2022), who has had a profound impact on my health
</div> and my life in general. Hover over the links below for more detail.
<div class="text-slab"> </p>
<h2>Links</h2> </div>
<ul> <div class="text-slab">
<li> <h2>Links</h2>
<RouterLink to={{ name: 'GECalculator' }}>Thyroid Calculator</RouterLink> <ul>
<span style="display: none" class="tooltip">Convert to and from grains, set ratios, etc.</span> <li>
</li> <RouterLink to={{ name: "GECalculator" }}>Thyroid Calculator</RouterLink>
<li> <span style="display: none" class="tooltip">
<RouterLink to={{ name: 'GEDeutsch' }}>Ray Peat Articles in German</RouterLink> Convert to and from grains, set ratios, etc.
<span style="display: none" class="tooltip"> </span>
A selection of articles by Ray that I have translated in my spare time into German. </li>
</span> <li>
</li> <RouterLink to={{ name: "GEDeutsch" }}>Ray Peat Articles in German</RouterLink>
</ul> <span style="display: none" class="tooltip">
</div> A selection of articles by Ray that I have translated in my spare time into German.
</> </span>
</li>
</ul>
</div>
</>
);
}, },
}; };

View File

@@ -1,21 +0,0 @@
import { defineComponent } from "vue";
export default defineComponent({
name: "dj-paypal-donate",
setup: () => {
return () => (
<form action="https://www.paypal.com/donate" method="post" target="_top">
<input type="hidden" name="hosted_button_id" value="9NXC6V5HDPGFL" />
<input
id="ppbutton"
type="image"
src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif"
name="submit"
title="Thanks for your support!"
alt="Donate with PayPal button"
/>
<img alt="" src="https://www.paypal.com/en_DE/i/scr/pixel.gif" width="1" height="1" />
</form>
);
},
});

View File

@@ -1,34 +1,34 @@
import { defineComponent, type VNode, Suspense } from "vue"; import { defineComponent, Suspense, type VNode } from "vue";
import { useRoute, RouterLink, RouterView, type RouteRecordRaw } from 'vue-router'; import { type RouteRecordRaw, RouterLink, RouterView, useRoute } from "vue-router";
import GEMain from "@/generative-energy/GEMain.tsx"; import GEMain from "@/generative-energy/GEMain.tsx";
import DJEmail from "@/DJEmail.tsx"; import DJEmail from "@/DJEmail.tsx";
import GEPaypal from "@/generative-energy/GEPaypal.tsx";
import GEDeutsch from "@/generative-energy/GEDeutsch.tsx"; import GEDeutsch from "@/generative-energy/GEDeutsch.tsx";
import GEDeutschArticle from "@/generative-energy/GEDeutschArticle.tsx"; import GEDeutschArticle from "@/generative-energy/GEDeutschArticle.tsx";
import GECalculator from "@/generative-energy/GECalculator.tsx"; import GECalculator from "@/generative-energy/GECalculator.tsx";
import DJDonate from "@/DJDonate.tsx";
export const routes: RouteRecordRaw[] = [ export const routes: RouteRecordRaw[] = [
{ {
path: '/', path: "/",
name: 'GEMain', name: "GEMain",
component: GEMain, component: GEMain,
}, },
{ {
path: '/calculator', path: "/calculator",
name: 'GECalculator', name: "GECalculator",
component: GECalculator, component: GECalculator,
}, },
{ {
path: '/raypeat-deutsch', path: "/raypeat-deutsch",
name: 'GEDeutsch', name: "GEDeutsch",
component: GEDeutsch, component: GEDeutsch,
}, },
{ {
path: '/raypeat-deutsch/article/:articleName', path: "/raypeat-deutsch/article/:articleName",
name: 'GEDeutschArticle', name: "GEDeutschArticle",
component: GEDeutschArticle, component: GEDeutschArticle,
props: ({ params }) => { props: ({ params }) => {
if ('articleName' in params) { if ("articleName" in params) {
return { articleName: params.articleName }; return { articleName: params.articleName };
} else { } else {
return false; return false;
@@ -41,27 +41,35 @@ export default defineComponent({
name: "ge-root", name: "ge-root",
setup() { setup() {
const route = useRoute(); const route = useRoute();
return () => <> return () => (
<main> <>
<RouterLink class={"home-btn" + (route.name === 'GEMain' ? ' hide' : '') } <main>
to={{ name: 'GEMain' }}> <RouterLink class={"home-btn" + (route.name === "GEMain" ? " hide" : "")} to={{ name: "GEMain" }}>
Generative Energy Home Generative Energy Home
</RouterLink> </RouterLink>
<RouterView>{{ default: ({ Component }: { Component: VNode }) => (Component && <RouterView>
<Suspense>{{ {{
default: () => Component, default: ({ Component }: { Component: VNode }) => (Component &&
fallback: () => <div>Page loading...</div>, (
}}</Suspense> <Suspense>
)}}</RouterView> {{
<footer> default: () => Component,
<div class="bottom"> fallback: () => <div>Page loading...</div>,
<div> }}
<a href="https://djledda.de/">djledda.de</a> 2023 - <DJEmail>{ () => 'Contact' }</DJEmail> </Suspense>
)),
}}
</RouterView>
<footer>
<div class="bottom">
<div>
<a href="/">djledda.de</a> 2023 - <DJEmail>{() => "Contact"}</DJEmail>
</div>
<DJDonate />
</div> </div>
<GEPaypal /> </footer>
</div> </main>
</footer> </>
</main> );
</>;
}, },
}); });

View File

@@ -5,6 +5,6 @@ import GERoot, { routes } from "@/generative-energy/GERoot.tsx";
createSSRApp(GERoot) createSSRApp(GERoot)
.use(createRouter({ .use(createRouter({
routes, routes,
history: createWebHistory('/generative-energy') history: createWebHistory("/generative-energy"),
})) }))
.mount('#app-root'); .mount("#app-root");

View File

@@ -0,0 +1,12 @@
import { createSSRApp } from "vue";
import { createMemoryHistory, createRouter } from "vue-router";
import GERoot, { routes } from "@/generative-energy/GERoot.tsx";
export default function createApp() {
const router = createRouter({
routes,
history: createMemoryHistory("/generative-energy"),
});
const app = createSSRApp(GERoot).use(router);
return { app, router };
}

View File

@@ -1,19 +1,75 @@
import { computed, defineComponent, ref } from "vue"; import { defineComponent } from "vue";
import useHead from "@/useHead.ts";
import DJTooltip from "@/DJTooltip.tsx";
import DJEmail from "@/DJEmail.tsx";
export default defineComponent({ export default defineComponent({
name: "app-root", name: "app-root",
setup() { setup() {
const count = ref(0); useHead({ title: "DJ Ledda's Homepage" });
const countDouble = computed(() => count.value * 2);
count.value++;
return () => ( return () => (
<div class="app-main"> <div class="supercontainer">
<button class="test" onClick={() => count.value++}>Click me!</button> <div class="shakeable">
<div>Count: {count.value}</div> <div class="title_name">
<div>Count Double: {countDouble.value}</div> <DJTooltip tooltip="I wonder what he's listening to?">
<p> <img src="/home/img/dj.gif" alt="dj legt krasse Mucke auf" class="dude" />
<a href="/generative-energy/">Go to this other site hosted here but a different Vue app!</a> </DJTooltip>
</p> <DJTooltip tooltip="Easily the coolest guy out there.">
<span>DJ Ledda</span>
</DJTooltip>
<DJTooltip tooltip="I once heard this guy played at revs.">
<img src="/home/img/dj.gif" alt="dj laying down some sick beats" class="dude" />
</DJTooltip>
</div>
<div class="main">
<div class="subject">
<div class="resourcelist">
<a href="https://drum-slayer.com">
<DJTooltip class="resource" tooltip="Small app for designing multitrack looped rhythms with local save and multiple files. Originally built using just vanilla TypeScript and CSS, now with Vue.">
Drum Slayer
</DJTooltip>
</a>
<a href="/somaesque">
<DJTooltip class="resource" tooltip="Puzzle solver app for puzzle cubes resembling the original Soma Cube puzzle. Save and edit your own puzzles! Built with Svelte, THREE.js and AssemblyScript.">
Somaesque
</DJTooltip>
</a>
<a href="/generative-energy">
<DJTooltip class="resource" tooltip="Thyroid calculator, German translations, and more...">
Generative Energy - Ray Peat Resources
</DJTooltip>
</a>
<a href="/home/muenchen-auf-englisch.html">
<DJTooltip class="resource" tooltip="
Authentic historically accurate translations of all of Munich's S-Bahn and U-Bahn
stations, as well as the main municipalities, into English. You live in Allach? It's
Axleigh now. Giesing? Nope! Kyesing! This is a WIP.
">
München auf Englisch - Munich in English
</DJTooltip>
</a>
<a href="/kadi/">
<DJTooltip class="resource" tooltip="Make an account and start saving paper and tracking your Yatzy stats with your
friends! Make your own rulesets, and more. Built with React, express.js, and
MongoDB. Currently inactive.">
K A D I: Online Yatzy Scoresheets
</DJTooltip>
</a>
<a href="http://git.djledda.de/Ledda">
<DJTooltip class="resource" tooltip="Check out what I'm coding!">
My git projects
</DJTooltip>
</a>
<DJEmail>
<DJTooltip class="resource" tooltip="You'll see my address when you click here.">
Click here to get in touch
</DJTooltip>
</DJEmail>
</div>
</div>
</div>
<div id="tooltipCarrier"></div>
</div>
</div> </div>
); );
}, },

4
app/home/client.ts Normal file
View File

@@ -0,0 +1,4 @@
import { createSSRApp } from "vue";
import App from "@/home/App.tsx";
createSSRApp(App).mount("#app-root");

7
app/home/server.ts Normal file
View File

@@ -0,0 +1,7 @@
import { createSSRApp } from "vue";
import App from "@/home/App.tsx";
export default function createApp() {
const app = createSSRApp(App);
return { app, router: null };
}

View File

@@ -1,13 +0,0 @@
import { useSSRContext, toValue, type MaybeRefOrGetter } from 'vue';
export default function useHead(params: {
title: MaybeRefOrGetter<string>,
}) {
const context = useSSRContext();
if (context) {
context.meta ??= {
title: toValue(params.title),
meta: {},
};
}
}

View File

@@ -1,122 +0,0 @@
import { h, qsa } from "@/util.ts";
import { type CSSProperties, defineComponent, onBeforeUnmount, onMounted, ref } from "vue";
const carrierStyle = {
opacity: "0",
display: "block",
pointerEvents: "none",
backgroundColor: "black",
border: "white solid 1px",
color: "white",
padding: "10px",
position: "absolute",
zIndex: "1",
overflow: "hidden",
height: "0",
width: "0",
transition: "opacity 200ms, height 200ms, width 200ms",
} satisfies CSSProperties;
const textCarrierStyle = {
width: "350px",
display: "block",
overflow: "hidden",
} satisfies CSSProperties;
const defaultWidth = 350;
export default defineComponent({
name: "dj-sexy-tooltip",
setup() {
const active = ref(false);
const carrier = ref<HTMLElement | null>(null);
const textCarrier = ref<HTMLElement | null>(null);
onMounted(() => {
document.body.appendChild(h("style", { textContent: `.tooltip { display: none; }` }));
document.body.style.position = "relative";
document.addEventListener("mousemove", (event) => this.rerenderTooltip({ x: event.pageX, y: event.pageY }));
});
function rerenderTooltip(mousePos: { x: number; y: number }) {
const newText = this.getTooltipTextForHoveredElement(mousePos);
if (newText !== this.textCarrier) {
this.transitionTooltipSize(newText);
const tooltipHasText = newText !== "";
if (tooltipHasText !== this.active) {
this.toggleActive();
}
}
if (this.isStillVisible()) {
this.updatePosition(mousePos);
}
}
function isStillVisible() {
return getComputedStyle(carrier.value).opacity !== "0";
}
function updatePosition(pos: { x: number; y: number }) {
if (pos.x + 15 + this.carrier.clientWidth <= document.body.scrollWidth) {
this.carrier.style.left = (pos.x + 15) + "px";
} else {
this.carrier.style.left = (document.body.scrollWidth - this.carrier.clientWidth - 5) + "px";
}
if (pos.y + this.carrier.clientHeight <= document.body.scrollHeight) {
this.carrier.style.top = pos.y + "px";
} else {
this.carrier.style.top = (document.body.scrollHeight - this.carrier.clientHeight - 5) + "px";
}
}
function toggleActive() {
if (this.active) {
this.active = false;
this.carrier.style.opacity = "0";
this.carrier.style.padding = "0";
} else {
this.active = true;
this.carrier.style.opacity = "1";
this.carrier.style.padding = SexyTooltip.carrierStyle.padding;
}
}
function transitionTooltipSize(text: string) {
const testDiv = h("div");
testDiv.style.height = "auto";
testDiv.style.width = SexyTooltip.defaultWidth + "px";
testDiv.innerHTML = text;
document.body.appendChild(testDiv);
const calculatedHeight = testDiv.scrollHeight;
testDiv.style.display = "inline";
testDiv.style.width = "auto";
document.body.removeChild(testDiv);
const size = { height: calculatedHeight + "px", width: "350px" };
this.carrier.style.height = size.height;
this.carrier.style.width = text === "" ? "0" : size.width;
this.textCarrier.innerHTML = text;
}
function getTooltipTextForHoveredElement(mousePos: { x: number; y: number }) {
for (const elem of this.elementsWithTooltips) {
const boundingRect = elem.getBoundingClientRect();
const inYRange = mousePos.y >= boundingRect.top + window.pageYOffset - 1 &&
mousePos.y <= boundingRect.bottom + window.pageYOffset + 1;
const inXRange = mousePos.x >= boundingRect.left + window.pageXOffset - 1 &&
mousePos.x <= boundingRect.right + window.pageXOffset + 1;
if (inYRange && inXRange) {
return elem.nextElementSibling?.innerText ?? "";
}
}
return "";
}
return () => (
<div ref={carrier}>
<span ref={textCarrier} />
</div>
);
},
});

View File

@@ -1,17 +1,22 @@
import { onMounted, onServerPrefetch, useSSRContext, shallowRef, type ShallowRef } from 'vue'; import { onMounted, onServerPrefetch, type ShallowRef, shallowRef } from "vue";
import useDJSSRContext from "@/useDJSSRContext.ts";
declare global { declare global {
// deno-lint-ignore no-var // deno-lint-ignore no-var
var appstate: Partial<Record<string, unknown>>; var appstate: Partial<Record<string, unknown>>;
} }
export default function useAsyncState<T>(key: string, getter: (context: { hostUrl: string }) => Promise<T | null>, options?: { suspensible: boolean }): { result: ShallowRef<T | null>, stateIsReady: Promise<unknown> } { export default function useAsyncState<T>(
const ssrContext = useSSRContext<{ registry: Record<string, unknown> }>(); key: string,
const isClient = typeof ssrContext === 'undefined'; getter: (context: { hostUrl: string }) => Promise<T | null>,
options?: { suspensible: boolean },
): { result: ShallowRef<T | null>; stateIsReady: Promise<unknown> } {
const ssrContext = useDJSSRContext();
const isClient = typeof ssrContext === "undefined";
const registry = ssrContext?.registry ?? globalThis?.appstate; const registry = ssrContext?.registry ?? globalThis?.appstate;
const hostUrl = isClient ? globalThis.location.origin : 'http://localhost:8080'; const hostUrl = isClient ? globalThis.location.origin : "http://localhost:8080";
const state = shallowRef<T | null>(null); const state = shallowRef<T | null>(null);

12
app/useDJSSRContext.ts Normal file
View File

@@ -0,0 +1,12 @@
import { type MaybeRefOrGetter, useSSRContext } from "vue";
export type DJSSRContext = {
head: {
title: MaybeRefOrGetter<string>;
};
registry: Record<string, unknown>;
};
export default function useDJSSRContext() {
return useSSRContext<DJSSRContext>();
}

19
app/useHead.ts Normal file
View File

@@ -0,0 +1,19 @@
import { watchEffect, toValue, type MaybeRefOrGetter } from 'vue';
import useDJSSRContext from "@/useDJSSRContext.ts";
export default function useHead(params: {
title: MaybeRefOrGetter<string>,
}) {
const context = useDJSSRContext();
if (context) {
context.head.title = params.title;
} else {
watchEffect(() => {
const newTitle = toValue(params.title);
if (newTitle) {
document.title = newTitle;
}
});
}
}

10
deno.lock generated
View File

@@ -11,11 +11,13 @@
"jsr:@std/encoding@^1.0.5": "1.0.5", "jsr:@std/encoding@^1.0.5": "1.0.5",
"jsr:@std/fmt@0.223": "0.223.0", "jsr:@std/fmt@0.223": "0.223.0",
"jsr:@std/fmt@^1.0.3": "1.0.3", "jsr:@std/fmt@^1.0.3": "1.0.3",
"jsr:@std/fs@*": "0.223.0",
"jsr:@std/fs@0.223": "0.223.0", "jsr:@std/fs@0.223": "0.223.0",
"jsr:@std/http@*": "1.0.9", "jsr:@std/http@*": "1.0.9",
"jsr:@std/io@0.223": "0.223.0", "jsr:@std/io@0.223": "0.223.0",
"jsr:@std/media-types@^1.0.3": "1.0.3", "jsr:@std/media-types@^1.0.3": "1.0.3",
"jsr:@std/net@^1.0.4": "1.0.4", "jsr:@std/net@^1.0.4": "1.0.4",
"jsr:@std/path@*": "1.0.7",
"jsr:@std/path@0.223": "0.223.0", "jsr:@std/path@0.223": "0.223.0",
"jsr:@std/path@^1.0.7": "1.0.7", "jsr:@std/path@^1.0.7": "1.0.7",
"jsr:@std/streams@^1.0.7": "1.0.7", "jsr:@std/streams@^1.0.7": "1.0.7",
@@ -34,7 +36,7 @@
"dependencies": [ "dependencies": [
"jsr:@deno/graph", "jsr:@deno/graph",
"jsr:@std/fmt@0.223", "jsr:@std/fmt@0.223",
"jsr:@std/fs", "jsr:@std/fs@0.223",
"jsr:@std/io", "jsr:@std/io",
"jsr:@std/path@0.223" "jsr:@std/path@0.223"
] ]
@@ -68,7 +70,11 @@
"integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f"
}, },
"@std/fs@0.223.0": { "@std/fs@0.223.0": {
"integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c",
"dependencies": [
"jsr:@std/assert",
"jsr:@std/path@0.223"
]
}, },
"@std/http@1.0.9": { "@std/http@1.0.9": {
"integrity": "d409fc319a5e8d4a154e576c758752e9700282d74f31357a12fec6420f9ecb6c", "integrity": "d409fc319a5e8d4a154e576c758752e9700282d74f31357a12fec6420f9ecb6c",

View File

@@ -1,4 +1,6 @@
import "jsr:@deno/emit"; import "jsr:@deno/emit";
import "jsr:@std/http"; import "jsr:@std/http";
import "vue"; import "vue";
import 'jsr:@b-fuze/deno-dom'; import "jsr:@b-fuze/deno-dom";
import "jsr:@std/fs";
import "jsr:@std/path";

198
main.ts
View File

@@ -1,16 +1,85 @@
import { serveFile } from "jsr:@std/http/file-server"; import { serveFile } from "jsr:@std/http/file-server";
import { createSSRApp } from "vue"; import { type App, toValue } from "vue";
import { type Router } from "vue-router";
import { renderToString } from "vue/server-renderer"; import { renderToString } from "vue/server-renderer";
import { createRouter, createMemoryHistory } from 'vue-router';
import Home from "@/home/App.tsx";
import GERoot, { routes as geRoutes } from "@/generative-energy/GERoot.tsx";
import transpileResponse from "./transpile.ts"; import transpileResponse from "./transpile.ts";
import { DOMParser } from 'jsr:@b-fuze/deno-dom' import { DOMParser } from "jsr:@b-fuze/deno-dom";
import { join } from "jsr:@std/path/join";
import { exists } from "jsr:@std/fs";
import { type DJSSRContext } from "@/useDJSSRContext.ts";
import { type DJAPIResult, type DJAPIResultMap } from "@/api.ts";
const utf8Decoder = new TextDecoder("utf-8"); const utf8Decoder = new TextDecoder("utf-8");
const parser = new DOMParser(); const parser = new DOMParser();
function appHeaderScript(params: { appstate: Record<string, unknown>; entryPath: string }) {
return `<script type="importmap">
{
"imports": {
"vue": "/deps/vue/dist/vue.esm-browser.prod.js",
"vue-router": "/deps/vue-router/dist/vue-router.esm-browser.js",
"vue/jsx-runtime": "/deps/vue/jsx-runtime/index.mjs",
"@vue/devtools-api": "/deps/@vue/devtools-api/lib/esm/index.js",
"@/": "/app/"
}
}
</script>
<script type="module">
window.appstate = ${JSON.stringify(params.appstate)};
import('${params.entryPath}');
</script>`;
}
async function* siteEntries(path: string): AsyncGenerator<string> {
for await (const dirEnt of Deno.readDir(path)) {
if (dirEnt.isDirectory) {
yield* siteEntries(join(path, dirEnt.name));
} else if (dirEnt.name === "index_template.html") {
yield path.split("/")[1] ?? "";
}
}
}
const publicFiles = siteEntries("public");
const sites: string[] = [];
for await (const path of publicFiles) {
sites.push(path);
}
function getAPIResponse(apiReq: Request): Response {
let jsonResponse: DJAPIResult | { error: string } | null = null;
let status = 200;
const pathname = URL.parse(apiReq.url)?.pathname;
if (!pathname) {
jsonResponse = { error: "Invalid Route" };
status = 400;
}
if (!jsonResponse && pathname) {
const apiPath = pathname.split("/api")[1];
if (apiPath === "/rp-articles") {
jsonResponse = [
{ name: "Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen", slug: "caffeine" },
{
name: "TSH, Temperatur, Puls, und andere Indikatoren bei einer Schilddrüsenunterfunktion",
slug: "hypothyroidism",
},
] satisfies DJAPIResultMap["/rp-articles"];
}
}
const headers = new Headers();
headers.set("Content-Type", "application/json");
return new Response(JSON.stringify(jsonResponse), {
status,
headers,
});
}
Deno.serve({ Deno.serve({
port: 8080, port: 8080,
hostname: "0.0.0.0", hostname: "0.0.0.0",
@@ -18,63 +87,78 @@ Deno.serve({
console.log(`Listening on port http://${hostname}:${port}/`); console.log(`Listening on port http://${hostname}:${port}/`);
}, },
}, async (req, _conn) => { }, async (req, _conn) => {
let response: Response | null = null;
if (req.method === "GET") { if (req.method === "GET") {
const pathname = URL.parse(req.url)?.pathname ?? "/"; const pathname = URL.parse(req.url)?.pathname ?? "/";
if (pathname.startsWith('/static/') ||
pathname.startsWith('/generative-energy/static/')) { if (pathname.startsWith("/api/")) {
if (pathname.startsWith('/static/')) { response = getAPIResponse(req);
return serveFile(req, `public/home/${ pathname }`); }
} else {
return serveFile(req, `public${pathname}`); // Public/static files
} if (!response) {
} else if (pathname === "/") { let filepath = join(".", "public", pathname);
const rendered = await renderToString(createSSRApp(Home)); if (filepath.endsWith("/")) {
const content = utf8Decoder.decode(await Deno.readFile("./public/home/index.html")) filepath = join(filepath, "index.html");
.replace(`<!-- SSR OUTLET -->`, rendered) }
.replace(`<!-- SSR HEAD OUTLET -->`, ""); if (await exists(filepath, { isFile: true })) {
return new Response(content, { headers: { "Content-Type": "text/html" } }); response = await serveFile(req, filepath);
} else if (pathname.startsWith('/generative-energy')) { }
const app = createSSRApp(GERoot); }
const router = createRouter({
routes: geRoutes, // NPM Vendor deps
history: createMemoryHistory('/generative-energy'), if (response === null && pathname.startsWith("/deps")) {
}); response = await serveFile(req, `node_modules/${pathname.split("/deps")[1]}`);
app.use(router); }
app.provide('dom-parse', (innerHTML: string) => {
return parser.parseFromString(innerHTML, 'text/html').documentElement; // Transpile Code
}); if (
const ssrContext = { registry: {}}; response === null &&
await router.replace(pathname.split('/generative-energy')[1]); pathname.startsWith("/app") &&
await router.isReady(); (pathname.endsWith(".ts") || pathname.endsWith(".tsx")) &&
const rendered = await renderToString(app, ssrContext); !pathname.endsWith("server.ts")
const content = utf8Decoder.decode(await Deno.readFile("./public/generative-energy/index.html")) ) {
.replace('%TITLE%', 'Generative Energy') response = await serveFile(req, "./" + pathname);
.replace('<!-- SSR HEAD OUTLET -->', ` response = await transpileResponse(response, req.url, pathname);
<script type="importmap"> }
{
"imports": { // SSR
"vue": "/deps/vue/dist/vue.esm-browser.prod.js", if (response === null) {
"vue-router": "/deps/vue-router/dist/vue-router.esm-browser.js", const baseDirectoryName = pathname.split("/")[1] ?? "";
"vue/jsx-runtime": "/deps/vue/jsx-runtime/index.mjs", if (sites.includes(baseDirectoryName)) {
"@vue/devtools-api": "/deps/@vue/devtools-api/lib/esm/index.js", const appLocation = baseDirectoryName === "" ? "home" : baseDirectoryName;
"@/": "/app/" const siteTemplate = join("public", baseDirectoryName, "index_template.html");
} const siteEntry = join("app", appLocation, "server.ts");
} const clientEntry = join("@", appLocation, "client.ts");
</script> const { app, router } = (await import("./" + siteEntry)).default() as {
<script> window.appstate = ${ JSON.stringify(ssrContext.registry) }; </script> app: App;
`) router: Router | null;
.replace(`<!-- SSR OUTLET -->`, rendered); };
return new Response(content, { headers: { "Content-Type": "text/html" } }); app.provide("dom-parse", (innerHTML: string) => {
} else if (pathname.startsWith("/app") && (pathname.endsWith(".ts") || pathname.endsWith(".tsx"))) { return parser.parseFromString(innerHTML, "text/html").documentElement;
const response = await serveFile(req, "./" + pathname); });
return await transpileResponse(response, req.url, pathname); const ssrContext: DJSSRContext = { registry: {}, head: { title: "" } };
} else if (pathname.startsWith("/deps")) { if (router) {
return serveFile(req, `node_modules/${pathname.split("/deps")[1]}`); await router.replace(pathname.split("/generative-energy")[1]);
await router.isReady();
}
const rendered = await renderToString(app, ssrContext);
const content = utf8Decoder.decode(await Deno.readFile(siteTemplate))
.replace(`<!-- SSR OUTLET -->`, rendered)
.replaceAll("%TITLE%", toValue(ssrContext.head?.title) ?? "Site")
.replace(
`<!-- SSR HEAD OUTLET -->`,
appHeaderScript({ appstate: ssrContext.registry, entryPath: clientEntry }),
);
response = new Response(content, { headers: { "Content-Type": "text/html" } });
}
} }
return new Response("Not found.", { status: 404 });
} else { } else {
return new Response("Only GET allowed.", { status: 500 }); response = new Response("Only GET allowed.", { status: 500 });
} }
return response ?? new Response("Not found.", { status: 404 });
}); });
Deno.addSignalListener("SIGINT", () => { Deno.addSignalListener("SIGINT", () => {

View File

@@ -1,7 +1,5 @@
<h1> <h1 lang="en">Caffeine: A vitamin-like nutrient, or adaptogen</h1>
<span lang="en">Caffeine: A vitamin-like nutrient, or adaptogen</span> <h1 lang="de">Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen</h1>
<span lang="de">Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen</span>
</h1>
<p> <p>
<span lang="en"><strong>Questions about tea and coffee, cancer and other degenerative diseases, and the hormones.</strong></span> <span lang="en"><strong>Questions about tea and coffee, cancer and other degenerative diseases, and the hormones.</strong></span>

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>%TITLE%</title> <title>%TITLE%</title>
<link rel="stylesheet" href="/generative-energy/static/styles.css"> <link rel="stylesheet" href="/generative-energy/styles.css">
<link <link
href="https://fonts.googleapis.com/css2?family=Roboto&amp;family=Roboto+Slab:wght@600&amp;display=swap" href="https://fonts.googleapis.com/css2?family=Roboto&amp;family=Roboto+Slab:wght@600&amp;display=swap"
rel="stylesheet"> rel="stylesheet">

View File

@@ -139,18 +139,27 @@ hr {
background: #999; background: #999;
} }
footer .bottom {
display: flex;
flex-direction: row;
justify-content: space-between;
}
footer { footer {
margin-bottom: 20px; margin-bottom: 20px;
.bottom {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
> * {
flex: 1;
}
.dj-donate {
text-align: right;
}
img {
display: inline-block;
}
}
} }
.ge-calculator { .ge-calculator {
table { table {
border-collapse: separate; border-collapse: separate;
@@ -186,13 +195,3 @@ footer {
} }
} }
} }
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

0
public/home/img/.gitignore vendored Normal file
View File

BIN
public/home/img/daniel.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
public/home/img/dj.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
public/home/img/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
public/home/img/preis.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/home/img/tile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
public/home/img/tradies.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>G'day</title>
<link rel="icon" href="icon.webp" />
<link rel="stylesheet" href="/static/styles.css" />
<!-- SSR HEAD OUTLET -->
<script type="module" src="/static/app.js"></script>
</head>
<body>
<main class="container">
<h1>G'day, mate!</h1>
<h2>YOUR SITE GOES HERE</h2>
<img src="/static/image.jpeg" alt="KANGAROO" />
<div id="app-root"><!-- SSR OUTLET --></div>
</main>
</body>
</html>

View File

@@ -0,0 +1,130 @@
interface Point {
x: number;
y: number;
}
interface Size {
height: string;
width: string;
}
class SexyTooltip {
private static readonly carrierStyle: Partial<CSSStyleDeclaration> = {
opacity: "0",
display: "block",
pointerEvents: "none",
backgroundColor: "black",
border: "white solid 1px",
color: "white",
padding: "10px",
position: "absolute",
zIndex: "1",
overflow: "hidden",
height: "0",
width: "0",
transition: "opacity 200ms, height 200ms, width 200ms",
};
private static readonly textCarrierStyle: Partial<CSSStyleDeclaration> = {
width: "350px",
display: "block",
overflow: "hidden",
};
private static readonly defaultWidth = 350;
private readonly carrier: HTMLDivElement;
private readonly textCarrier: HTMLSpanElement;
private readonly elementsWithTooltips: Element[] = [];
private active: boolean = false;
constructor(carrier: HTMLDivElement) {
if (carrier.childNodes.length > 0) {
throw new Error("Incorrect setup for tooltip! Remove the child nodes from the tooltip div.");
}
this.carrier = carrier;
this.textCarrier = document.createElement("span");
this.carrier.appendChild(this.textCarrier);
Object.assign(this.carrier.style, SexyTooltip.carrierStyle);
Object.assign(this.textCarrier.style, SexyTooltip.textCarrierStyle);
document.querySelectorAll(".tooltip").forEach((element) => {
if (element.nodeName === "SPAN") {
console.log(element.previousElementSibling);
this.elementsWithTooltips.push(element.previousElementSibling);
}
});
document.addEventListener("mousemove", (event) => this.rerenderTooltip({ x: event.pageX, y: event.pageY }));
}
rerenderTooltip(mousePos: Point) {
const newText = this.getTooltipTextForHoveredElement(mousePos);
if (newText !== this.textCarrier.innerHTML) {
this.transitionTooltipSize(newText);
const tooltipHasText = newText !== "";
if (tooltipHasText !== this.active) {
this.toggleActive();
}
}
if (this.isStillVisible()) {
this.updatePosition(mousePos);
}
}
isStillVisible() {
return getComputedStyle(this.carrier).opacity !== "0";
}
updatePosition(pos: Point) {
if (pos.x + 15 + this.carrier.clientWidth <= document.body.scrollWidth) {
this.carrier.style.left = (pos.x + 15) + "px";
} else {
this.carrier.style.left = (document.body.scrollWidth - this.carrier.clientWidth - 5) + "px";
}
if (pos.y + this.carrier.clientHeight <= document.body.scrollHeight) {
this.carrier.style.top = pos.y + "px";
} else {
this.carrier.style.top = (document.body.scrollHeight - this.carrier.clientHeight - 5) + "px";
}
}
toggleActive() {
if (this.active) {
this.active = false;
this.carrier.style.opacity = "0";
this.carrier.style.padding = "0";
} else {
this.active = true;
this.carrier.style.opacity = "1";
this.carrier.style.padding = SexyTooltip.carrierStyle.padding;
}
}
transitionTooltipSize(text: string) {
const testDiv = document.createElement("div");
testDiv.style.height = "auto";
testDiv.style.width = SexyTooltip.defaultWidth + "px";
testDiv.innerHTML = text;
document.body.appendChild(testDiv);
const calculatedHeight = testDiv.scrollHeight;
testDiv.style.display = "inline";
testDiv.style.width = "auto";
document.body.removeChild(testDiv);
const size = { height: calculatedHeight + "px", width: "350px" };
this.carrier.style.height = size.height;
this.carrier.style.width = text === "" ? "0" : size.width;
this.textCarrier.innerHTML = text;
}
getTooltipTextForHoveredElement(mousePos: Point): string {
for (const elem of this.elementsWithTooltips) {
const boundingRect = elem.getBoundingClientRect();
const inYRange = mousePos.y >= boundingRect.top + window.pageYOffset - 1 &&
mousePos.y <= boundingRect.bottom + window.pageYOffset + 1;
const inXRange = mousePos.x >= boundingRect.left + window.pageXOffset - 1 &&
mousePos.x <= boundingRect.right + window.pageXOffset + 1;
if (inYRange && inXRange) {
return (elem.nextElementSibling as HTMLSpanElement)?.innerText ?? "";
}
}
return "";
}
}
export default SexyTooltip;

47
public/home/js/main.ts Normal file
View File

@@ -0,0 +1,47 @@
import SexyTooltip from "./SexyTooltip.js";
const tooltipDiv = document.getElementById("tooltipCarrier") as HTMLDivElement;
const tooltip = new SexyTooltip(tooltipDiv);
const dudes = document.querySelectorAll(".dude") as NodeListOf<HTMLImageElement>;
const shakers = document.querySelectorAll(".shakeable") as NodeListOf<HTMLElement>;
const emailLink = document.getElementById("emailLink") as HTMLAnchorElement;
let numDudesDroppingSickBeats: number = 0;
dudes.forEach((dude) => dude.addEventListener("mouseup", () => toggleDude(dude)));
function toggleDude(dude: HTMLImageElement) {
if (dude.classList.contains("spinMe")) {
numDudesDroppingSickBeats -= 1;
dude.addEventListener("animationiteration", function listener() {
dude.classList.remove("spinMe");
dude.removeEventListener("animationiteration", listener as EventListenerOrEventListenerObject);
});
} else {
numDudesDroppingSickBeats += 1;
dude.classList.add("spinMe");
}
updateShakers();
}
function updateShakers() {
shakers.forEach((shaker) => {
if (numDudesDroppingSickBeats === 0) {
shaker.classList.remove("shakeMe");
} else if (!shaker.classList.contains("shakeMe")) {
shaker.classList.add("shakeMe");
}
});
}
emailLink.addEventListener("click", (event) => {
const myDomain = "gmail";
const myTld = "com";
const myName = "danjledda";
const dot = ".";
const at = "@";
let link = "mailto:" + myName + myDomain + myTld;
link = link.slice(0, 10) + dot + link.slice(10, 11) + dot + link.slice(11, 16) + at + link.slice(16, 21) + dot +
link.slice(21);
window.location.href = link;
});

189
public/home/main.css Normal file
View File

@@ -0,0 +1,189 @@
:root {
--subject-spacing: 40px;
}
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
body {
background-color: #292929;
font-family: "Roboto", serif;
}
.title_name {
font-size: 50px;
color: floralwhite;
font-family: "Roboto Slab", "Times New Roman", Times, serif;
text-align: center;
}
.title_name img {
margin: 20px;
height: 100px;
width: auto;
vertical-align: middle;
}
.spinMe {
animation: spin 1s infinite linear;
}
.shakeMe {
animation: shake 0.2s infinite linear;
}
@keyframes shake {
0% {
transform: scale(1) translate(1px, 1px) rotate(0deg);
}
25% {
transform: scale(0.95) translate(-1px, -2px) rotate(-1deg);
}
50% {
transform: scale(0.9) translate(-3px, 0px) rotate(1deg);
}
75% {
transform: scale(0.95) translate(3px, 2px) rotate(0deg);
}
100% {
transform: scale(1) translate(-1px, 2px) rotate(-1deg);
}
}
@keyframes spin {
0% {
transform: rotate(0deg) scale(1);
filter: hue-rotate(0deg) saturate(5);
}
25% {
transform: rotate(90deg) scale(2);
}
50% {
transform: rotate(180deg) scale(1);
}
75% {
transform: rotate(270deg) scale(2);
}
100% {
transform: rotate(360deg) scale(1);
filter: hue-rotate(360deg) saturate(5);
}
}
.supercontainer {
padding-top: 3em;
margin: auto;
}
.main {
width: 50em;
margin: 20px auto;
text-align: center;
}
@media only screen and (max-width: 1024px) {
.main {
width: 35em;
padding: 20px;
}
:root {
--subject-spacing: 20px;
}
}
@media only screen and (max-width: 768px) {
.title_name img {
margin: 10px;
height: 60px;
width: auto;
}
.title_name {
font-size: 30px;
}
.main {
width: 20em;
padding: 20px;
}
}
span.subjecttitle {
height: 100%;
font-family: "Roboto Slab", "Times New Roman", Times, serif;
font-size: 20px;
padding-right: 5px;
}
.subject:first-child {
margin-top: 0;
}
.subject:last-child {
margin-bottom: 0;
}
.subject {
margin-top: var(--subject-spacing);
margin-bottom: var(--subject-spacing);
}
.resourcelist {
margin-top: 2px;
background-color: white;
border-style: solid;
border-color: #292929;
border-width: 1px;
border-radius: 5px;
a:first-of-type {
.resource {
padding: 15px 20px 10px 20px;
border-radius: 5px 5px 0 0;
}
}
a:last-of-type {
.resource {
padding: 10px 20px 15px 20px;
border-radius: 0 0 5px 5px;
}
}
a:only-of-type {
.resource {
border-radius: 5px;
}
}
}
.resource {
position: relative;
background-color: white;
padding: 10px 20px 10px 20px;
display: block;
color: #333333;
text-decoration: none;
transition: background-color 200ms;
}
.resource:hover {
background-color: #bde4ff;
}
.resource:active {
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.50) inset;
}
a {
color: #000;
text-decoration: none;
cursor: pointer;
}
.tooltip-container {
display: inline-block;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,740 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title></title>
<meta name="generator" content="LibreOffice 6.0.7.3 (Linux)"/>
<meta name="created" content="2020-06-26T11:18:35.466195148"/>
<meta name="changed" content="2020-06-29T14:58:13.220640138"/>
<style type="text/css">
@page { margin: 0.79in }
p { margin-bottom: 0.1in; line-height: 115% }
td p { margin-bottom: 0in }
a:link { so-language: zxx }
table, th, td { border: 1px solid black !important }
td { padding: 10px !important }
td { width: auto !important; height: auto !important }
table { border-collapse: collapse }
</style>
</head>
<body lang="en-US" dir="ltr">
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<table>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Allach">Allach</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Axleigh
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Ahaloh → Allach, Aha → Axe, Loh → Leigh (“Aha + lohe”
→ “Flusslichtung”)</p>
</td>
</tr>
<tr>
<td width="122" height="22" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Altstadt_(M%C3%BCnchen)">Altstadt</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Oldstead</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Am_Hart">Am Hart</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>At the Woods</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Am_Moosfeld">Am Moosfeld</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>At the Mossfield</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Am_Riesenfeld">Am
Riesenfeld</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>At the Giantfield</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Au_(M%C3%BCnchen)">Au</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Ey</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">as in “Romsey”,
“Athelney”, “Beverley Hills”</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Aubing">Aubing</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Eving</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Kein Beleg für Herkunft,
erste Ewähnung 16. April 1010 unter “Ubingun”. Bajuwarischer
Name “Ubo” könnte naheliegen.</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Berg_am_Laim">Berg am
Laim</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Loam Hill</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Bogenhausen">Bogenhausen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Bobehouse</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Bajuware Pubo, Pupi</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Daglfing">Daglfing</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Dighelfing /daɪəlfɪŋ/
</span>
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Bajuware Tagolf/Thachulf</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Denning_(M%C3%BCnchen)">Denning</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Denning </span>
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Bajuware Tenno</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Englschalking">Englschalking</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Angelshalking</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">(en. <i>shalk </i>→ a
servant; Englschalch → “strenger Knecht”)</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Fasangarten">Fasangarten</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Pheasantgarden</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Feldmoching">Feldmoching</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Field Mocking </span>
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Feld der Anhänger des
Mocho, imagined as en. Mocko</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Forstenried">Forstenried</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Forestley</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Freiham">Freiham</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Freeham</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Freimann">Freimann</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Freeman</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/F%C3%BCrstenried">Fürstenried</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Princeley</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Obergiesing">Obergiesing</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Upper </span><span style="background: transparent">Kye</span><span style="background: transparent">sing
/</span><span style="background: transparent">ka</span>ɪz<span style="background: transparent">ɪŋ/
</span>
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Bajuware </span><span style="background: transparent">Kyso
</span><span style="background: transparent"> </span><span style="background: transparent">perhaps
en. </span><i><span style="background: transparent">Kyeso
</span></i><span style="font-style: normal"><span style="background: transparent">(/</span></span><span style="font-style: normal"><span style="background: transparent">ka</span></span><span style="font-style: normal"><span style="background: transparent">ɪ</span></span><span style="font-style: normal"><span style="background: transparent">z</span></span><span style="font-style: normal"><span style="background: transparent">oʊ/)</span></span><span style="background: transparent">?
K and S stay the same, </span><span style="background: transparent">ahd.
</span><i><span style="background: transparent">y</span></i><span style="background: transparent">
is interchangeable with </span><i><span style="background: transparent">i</span></i><span style="background: transparent">,
probably </span><span style="background: transparent">would have
become /</span><span style="background: transparent">a</span><span style="background: transparent">ɪ</span><span style="background: transparent">/</span><span style="background: transparent">)</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Untergiesing">Untergiesing</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Lower Kyesing</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Hadern">Hadern</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Hathern /hei</span>ð<span style="background: transparent">ɜ:</span><span style="background: transparent">n/</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Von ahd. </span><i><span style="background: transparent">Haderun
</span></i><span style="background: transparent">(“bei den
Waldleuten”</span><span style="background: transparent">)</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/w/index.php?title=Holzapfelkreuth&amp;action=edit&amp;redlink=1">Holzapfelkreuth</a>
</p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Woodapple Glade</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><span style="background: transparent">Kreuth → <i>das
Gereutete</i>, nhd. <i>Gerodete</i>, related to “rid” → a
clearing, “rid of trees”</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Haidhausen">Haidhausen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p><span style="background: transparent">Heathhouse</span></p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Harlaching">Harlaching</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Heathleighing
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Von ahd. <i>Hadaleih </i>+ <i>ing →</i><span style="font-style: normal">gleich:
“Heide + lohe + ing”</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Hasenbergl">Hasenbergl</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Hare Hill</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Isarvorstadt">Isarvorstadt</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Isar Forestead</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Johanneskirchen_(M%C3%BCnchen)">Johanneskirchen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Johnkirk</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>en. -<i>kirk → </i><span style="font-style: normal">Church</span></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Laim">Laim</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Loam</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Laim = nhd. <i>Lehm = </i><span style="font-style: normal">en.
</span><i>Loam</i></p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Langwied_(M%C3%BCnchen)">Langwied</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Longwood</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Wied → ahd. witu “Wald”, en. “wood”</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Lehel_(M%C3%BCnchen)">Lehel</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Fief
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>(von nhd. <i>Lehen</i><span style="font-style: normal">,</span><i>
</i>cf. en. <i>Loan</i>)</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Lochhausen_(M%C3%BCnchen)">Lochhausen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Lockhouse</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Ludwigsvorstadt">Ludwigsvorstadt</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Ludwig Forestead</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Maxvorstadt">Maxvorstadt</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Max Forestead</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Milbertshofen_(Bezirksteil)">Milbertshofen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Ilvinghope
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>ca. 1149 “Ilmungeshoven” von Bajuware
Ilbunch/Ilbung/Ilmung. War Einsiedlerhof, auf den man ausgesiedelt
wurde wegen Krankheit oder als Strafe → aussprache wurde
absichtlich undeutlich um die Beziehung zu diesem Dorf zu
verstecken.
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p> <a href="https://de.wikipedia.org/wiki/Moosach_(M%C3%BCnchen)">Moosach</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Moorey</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Moor + ach (“Au”). R wird zu S durch Rhotazismus (cf.
frieren → Frost, verlieren → Verlust, waren → gewesen)</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Neuhausen_(M%C3%BCnchen)">Neuhausen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Newhouse</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Nymphenburg_(M%C3%BCnchen)">Nymphenburg</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Nymphbury</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Oberf%C3%B6hring">Oberföhring</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Upper Fairing</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Bajuware Fero</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Obermenzing">Obermenzing</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Upper Menting</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Bajuware Manzo (wegen -ing umgelautet)</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Pasing">Pasing</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Paising /peɪzɪŋ/</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Bajuware Paoso, Paso</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Perlach_(M%C3%BCnchen)">Perlach</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Pearley</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Ramersdorf_(M%C3%BCnchen)">Ramersdorf</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Rammlethorpe /ræməlθɔ:p/
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>“Rumoltesdorf” ca. 1006. Kein Fugen-S im Englischen</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Riem">Riem</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Rim</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>cf. “Pacific Rim”</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Schwabing">Schwabing</a>
</p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Swabing /sweɪbɪŋ/</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Bajuware Swapo</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Schwanthalerh%C3%B6he">Schwanthalerhöhe</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Swandale Heights</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Sendling">Sendling</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Senthling</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Bajuware Sentilo</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Solln">Solln</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Soal</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Also closely related: en. soil, slough</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Steinhausen_(M%C3%BCnchen)">Steinhausen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Stonehouse</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Thalkirchen">Thalkirchen</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Dalekirk</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Trudering">Trudering</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Droughthring /drauθrɪŋ/
</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p>Bajuware Truchtaro/Truhtheri/Drudheri/Tructeri</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Untermenzing">Untermenzing</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Lower Menting</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
<tr>
<td width="122" style="border: none; padding: 0in">
<p><a href="https://de.wikipedia.org/wiki/Zamdorf">Zamdorf</a></p>
</td>
<td width="188" style="border: none; padding: 0in">
<p>Tamthorpe /tæmθɔ:p/</p>
</td>
<td width="344" style="border: none; padding: 0in">
<p><br/>
</p>
</td>
</tr>
</table>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%">Quellen:</p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%">Wikipedia Seiten zu
den jeweiligen Orten</p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%">Wiktionary fuer
einige Rueckbildungen (zB Slough, Rim, Long<b>weed</b><span style="font-weight: normal">,
Loam, Angel</span><b>shalk</b><span style="font-weight: normal">ing)</span></p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><a href="https://en.wikipedia.org/wiki/List_of_generic_forms_in_place_names_in_Ireland_and_the_United_Kingdom">https://en.wikipedia.org/wiki/List_of_generic_forms_in_place_names_in_Ireland_and_the_United_Kingdom</a></p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><a href="https://de.wikipedia.org/wiki/Kategorie:Ortsnamen-Endung">https://de.wikipedia.org/wiki/Kategorie:Ortsnamen-Endung</a></p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><a href="http://www.fam-schweden.de/Muenchen/muenchen.html">http://www.fam-schweden.de/Muenchen/muenchen.html</a></p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><a href="https://de.wikipedia.org/wiki/Zweite_Lautverschiebung#Phase_1">https://de.wikipedia.org/wiki/Zweite_Lautverschiebung#Phase_1</a></p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><a href="https://www.munichkindl.net/besiedlung">https://www.munichkindl.net/besiedlung</a></p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><a href="https://www.gutenberg.org/files/22636/22636-h/22636-h.htm">https://www.gutenberg.org/files/22636/22636-h/22636-h.htm</a></p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
<p style="margin-bottom: 0in; line-height: 100%"><br/>
</p>
</body>
</html>

View File

@@ -1,22 +0,0 @@
.container {
margin: auto;
text-align: center;
width: 100%;
background-color: #fffafa;
box-shadow: #000000;
padding-top: 10px;
min-height: calc(100vh - 10px);
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.2);
@media (min-width: 992px) {
width: 900px;
}
h1 {
margin: 0;
}
}
body {
margin: 0;
}

88
public/index2.html Normal file
View File

@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DJ Ledda's Homepage</title>
<meta name="description" content="the coolest homepage in the whole galaxy" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="site.webmanifest" />
<link rel="icon" href="img/dj.gif" />
<link rel="stylesheet" href="css/main.css" />
<link
href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab:wght@600&display=swap"
rel="stylesheet"
/>
<meta name="theme-color" content="#fafafa" />
</head>
<body>
<!--[if IE]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience and security.</p>
<![endif]-->
<!-- Site Content -->
<div class="supercontainer">
<div class="shakeable">
<div class="title_name">
<img src="img/dj.gif" alt="dj legt krasse Mucke auf" class="dude" />
<span class="tooltip">I wonder what he's listening to?</span>
<span>DJ Ledda</span>
<span class="tooltip">Easily the coolest guy out there.</span>
<img src="img/dj.gif" alt="dj laying down some sick beats" class="dude" />
<span class="tooltip">I once heard this guy played at revs.</span>
</div>
<div class="main">
<div class="subject">
<div class="resourcelist">
<a class="resource" href="https://drum-slayer.com">
Drum Slayer
</a>
<span class="tooltip"
>Small app for designing multitrack looped rhythms with local save and multiple files.
Originally built using just vanilla TypeScript and CSS, now with Vue.</span>
<a class="resource" href="/somaesque">
Somaesque
</a>
<span class="tooltip"
>Puzzle solver app for puzzle cubes resembling the original Soma Cube puzzle. Save and edit
your own puzzles! Built with Svelte, THREE.js and AssemblyScript.</span>
<a class="resource" href="/generative-energy">
Generative Energy - Ray Peat Resources
</a>
<span class="tooltip">Thyroid calculator, German translations, and more...</span>
<a class="resource" href="/muenchen-auf-englisch.html">
München auf Englisch - Munich in English
</a>
<span class="tooltip"
>Authentic historically accurate translations of all of Munich's S-Bahn and U-Bahn stations,
as well as the main municipalities, into English. You live in Allach? It's Axleigh now.
Giesing? Nope! Kyesing! This is a WIP.</span>
<a class="resource" href="/kadi/">
K A D I: Online Yatzy Scoresheets
</a>
<span class="tooltip"
>Make an account and start saving paper and tracking your Yatzy stats with your friends!
Make your own rulesets, and more. Built with React, express.js, and MongoDB. Currently
inactive.</span>
<a class="resource" href="http://git.djledda.de/Ledda">
My git projects
</a>
<span class="tooltip">Check out what I'm coding!</span>
<a id="emailLink" class="resource">
Click here to get in touch
</a>
<span class="tooltip">You'll see my address when you click here.</span>
</div>
</div>
</div>
<div id="tooltipCarrier"></div>
</div>
</div>
<!-- End Content -->
<script src="js/main.js" type="module"></script>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>G'day</title>
<link rel="icon" href="/home/icon.webp" />
<link rel="stylesheet" href="/home/main.css" />
<link rel="icon" href="img/dj.gif" />
<link
href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab:wght@600&display=swap"
rel="stylesheet"
/>
<!-- SSR HEAD OUTLET -->
</head>
<body>
<div id="app-root"><!-- SSR OUTLET --></div>
</body>
</html>

5
public/robots.txt Normal file
View File

@@ -0,0 +1,5 @@
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow: