@@ -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> | |||||
); | |||||
}, | |||||
}); |
@@ -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> | ||||
); | ); | ||||
}, | }, | ||||
@@ -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> | |||||
</>; | |||||
}, | |||||
}); |
@@ -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(); | |||||
} |
@@ -1,194 +1,7 @@ | |||||
import { defineComponent, computed, 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), | |||||
}); | |||||
} | |||||
} | |||||
*/ | |||||
import { computed, defineComponent, ref } from "vue"; | |||||
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 () => <> | |||||
<header> | |||||
<h1>Thyroid Calculator</h1> | |||||
</header> | |||||
<div class="text-slab"> | |||||
<p> | |||||
Use this calculator to convert between different forms and units of thyroid hormone. Common | |||||
synthetic, pure and natural dessicated forms are listed. The last section of the table provides | |||||
input fields for a specified ratio of T3 to T4, and will give the dosages required for both the | |||||
synthetic and pure forms. All dosages given are based on the "Grains" field at the beginning of the | |||||
table, but editing any field will automatically update the rest to keep them in sync. | |||||
</p> | |||||
</div> | |||||
<div class="text-slab ge-calculator"> | |||||
<table> | |||||
<tbody> | |||||
{ inputDefs.map((_, i) => ( | |||||
<tr key={_.name}> | |||||
return () => ( | |||||
<> | |||||
<header> | |||||
<h1>Thyroid Calculator</h1> | |||||
</header> | |||||
<div class="text-slab"> | |||||
<p> | |||||
Use this calculator to convert between different forms and units of thyroid hormone. Common | |||||
synthetic, pure and natural dessicated forms are listed. The last section of the table provides | |||||
input fields for a specified ratio of T3 to T4, and will give the dosages required for both the | |||||
synthetic and pure forms. All dosages given are based on the "Grains" field at the beginning of | |||||
the table, but editing any field will automatically update the rest to keep them in sync. | |||||
</p> | |||||
</div> | |||||
<div class="text-slab ge-calculator"> | |||||
<table> | |||||
<tbody> | |||||
{inputDefs.map((_) => ( | |||||
<tr key={_.name}> | |||||
<td> | |||||
{_.name} | |||||
</td> | |||||
<td class="right"> | |||||
<div style="display: inline-block;"> | |||||
<input | |||||
value={_.name === "Grains" ? numGrains.value : _.mpg * numGrains.value} | |||||
onInput={(e) => { | |||||
const val = (e.target as HTMLInputElement).valueAsNumber; | |||||
if (_.name === "Grains") { | |||||
numGrains.value = val; | |||||
} else { | |||||
numGrains.value = _.mpg / val; | |||||
} | |||||
}} | |||||
min="0" | |||||
step={_.step ?? 1} | |||||
type="number" | |||||
/> | |||||
</div> | |||||
<span class="breathe">{_.unit}</span> | |||||
</td> | |||||
</tr> | |||||
))} | |||||
<tr> | |||||
<td colspan="2"> | |||||
<strong>Compounded (T3 and T4, "Cynoplus")</strong> | |||||
</td> | |||||
</tr> | |||||
<tr class="ratios"> | |||||
<td> | |||||
Desired Ratio (T3:T4) | |||||
</td> | |||||
<td class="right"> | |||||
<div> | |||||
<input | |||||
value={t3Ratio.value} | |||||
onInput={(e) => { | |||||
t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" | |||||
/> | |||||
</div>{" "} | |||||
:{" "} | |||||
<div> | |||||
<input | |||||
value={t4Ratio.value} | |||||
onInput={(e) => { | |||||
t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" | |||||
/> | |||||
</div> | |||||
</td> | |||||
</tr> | |||||
<tr class="synthetic"> | |||||
<td> | <td> | ||||
{ _.name } | |||||
Synthetic T3/T4 Combo | |||||
</td> | </td> | ||||
<td class="right"> | <td class="right"> | ||||
<div style="display: inline-block;"> | |||||
<input | |||||
value={_.name === 'Grains' ? numGrains.value : _.mpg * numGrains.value} | |||||
onInput={(e) => { | |||||
const val = (e.target as HTMLInputElement).valueAsNumber; | |||||
if (_.name === 'Grains') { | |||||
numGrains.value = val; | |||||
} else { | |||||
numGrains.value = _.mpg / val; | |||||
} | |||||
}} | |||||
min="0" | |||||
step={ _.step ?? 1 } | |||||
type="number" /> | |||||
<div> | |||||
<input | |||||
value={compounded.value.t3Syn} | |||||
onInput={(e) => { | |||||
t4Ratio.value = compounded.value.t4Syn; | |||||
t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
numGrains.value = t3Ratio.value / MPG_T3_SYN + | |||||
t4Ratio.value / MPG_T4_SYN; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" | |||||
/> | |||||
</div>{" "} | |||||
:{" "} | |||||
<div> | |||||
<input | |||||
value={compounded.value.t4Syn} | |||||
onInput={(e) => { | |||||
t3Ratio.value = compounded.value.t3Syn; | |||||
t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
numGrains.value = t3Ratio.value / MPG_T3_SYN + | |||||
t4Ratio.value / MPG_T4_SYN; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" | |||||
/> | |||||
</div> | </div> | ||||
<span class="breathe">{ _.unit }</span> | |||||
</td> | </td> | ||||
</tr> | </tr> | ||||
))} | |||||
<tr><td colspan="2"><strong>Compounded (T3 and T4, "Cynpolus")</strong></td></tr> | |||||
<tr class="ratios"> | |||||
<td> | |||||
Desired Ratio (T3:T4) | |||||
</td> | |||||
<td class="right"> | |||||
<div> | |||||
<input | |||||
value={t3Ratio.value} | |||||
onInput={(e) => { | |||||
t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" /> | |||||
</div> : <div> | |||||
<input | |||||
value={t4Ratio.value} | |||||
onInput={(e) => { | |||||
t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" /> | |||||
</div> | |||||
</td> | |||||
</tr> | |||||
<tr class="synthetic"> | |||||
<td> | |||||
Synthetic T3/T4 Combo | |||||
</td> | |||||
<td class="right"> | |||||
<div> | |||||
<input | |||||
value={compounded.value.t3Syn} | |||||
onInput={(e) => { | |||||
t4Ratio.value = compounded.value.t4Syn; | |||||
t3Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
numGrains.value = t3Ratio.value / MPG_T3_SYN + t4Ratio.value / MPG_T4_SYN; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" /> | |||||
</div> : <div> | |||||
<input | |||||
value={compounded.value.t4Syn} | |||||
onInput={(e) => { | |||||
t3Ratio.value = compounded.value.t3Syn; | |||||
t4Ratio.value = (e.currentTarget as HTMLInputElement).valueAsNumber; | |||||
numGrains.value = t3Ratio.value / MPG_T3_SYN + t4Ratio.value / MPG_T4_SYN; | |||||
}} | |||||
min="0" | |||||
step="1" | |||||
type="number" /> | |||||
</div> | |||||
</td> | |||||
</tr> | |||||
</tbody> | |||||
</table> | |||||
</div> | |||||
<div class="text-slab"> | |||||
<strong>Changelog:</strong> | |||||
<p> | |||||
<ul> | |||||
<li> | |||||
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> | |||||
</>; | |||||
} | |||||
</tbody> | |||||
</table> | |||||
</div> | |||||
<div class="text-slab"> | |||||
<strong>Changelog:</strong> | |||||
<p> | |||||
<ul> | |||||
<li> | |||||
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> | |||||
</> | |||||
); | |||||
}, | |||||
}); | }); |
@@ -1,61 +1,76 @@ | |||||
import { defineComponent } from 'vue'; | |||||
import { RouterLink } from 'vue-router'; | |||||
import { defineComponent } from "vue"; | |||||
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', | |||||
setup() { | |||||
return () => <> | |||||
<header> | |||||
<h1>Ray Peat Deutsche Übersetzungen</h1> | |||||
</header> | |||||
<div class="text-slab"> | |||||
<p> | |||||
Auf dieser Seite befindet sich eine Auswahl der Artikel von Dr. Raymond Peat, die ich in meiner | |||||
Freizeit ins Deutsche übersetzt habe. | |||||
</p> | |||||
<p> | |||||
Ray Peat war ein US-Amerikaner aus Eugene, Oregon, der neben seinem Studium von Linguistik, | |||||
Literatur, und Malerei eine Promotion in der Biologie und Physiologie des Alterns und degenerativer | |||||
Krankheiten absolvierte, mit besonderem Fokus auf Zellen-Stoffwechsel, Hormone, und | |||||
Ernährungsphysiologie. | |||||
</p> | |||||
<p> | |||||
Nach mehreren Jahren als Professor an diversen Universitäten in den USA und Mexiko begann er Artikel | |||||
in Form eines Newsletters herauszugeben, von denen mehrere auf <a href="http://raypeat.com/" | |||||
>seiner Website</a> zu finden sind. | |||||
</p> | |||||
<p> | |||||
Da er in meinem Leben zu einem enormen Verständnis meines Körpers und einem unglaublichen | |||||
Zurückerlangen meiner Gesundheit und meines Wohlbefindens trotz dem Scheitern mehrerer Ärzte | |||||
beigetragen hat, und sein Nachlass in Deutschland kaum Publikum aufgrund schwieriger physiologischer | |||||
Texte genießt, habe ich entschieden hauptsächlich für Freunde und Bekannte einige seiner Artikel ins | |||||
Deutsche zu übersetzen, damit vielleicht auch ein breiteres Publikum von seinen Ideen profitieren | |||||
könnte. | |||||
</p> | |||||
<p> | |||||
Falls was bei der Übersetzung auffällt oder besonders unidiomatisch klingt, bzw. der deutschen | |||||
Fachsprache der Medizin nicht gerecht sein sollte, kannst du mir unter <DJEmail /> eine Mail senden. | |||||
Meine Muttersprache ist schließlich Englisch und ich bin kein professioneller Übersetzer! | |||||
</p> | |||||
<p> | |||||
Zusätzlich zu der Funktion, den Artikel mit dem Button oben in der Originalversion anzusehen, hat | |||||
jeder Absatz oben rechts beim drüberhovern mit der Maus einen zusätzlichen Button über den man den | |||||
jeweiligen Absatz in die andere Sprache wechseln kann, um die Versionen beim Lesen schnell zu | |||||
vergleichen zu können. | |||||
</p> | |||||
</div> | |||||
<div class="text-slab"> | |||||
<h3>Artikel auswählen:</h3> | |||||
<ul id="article"> | |||||
<li> | |||||
<RouterLink to={{ name: 'GEDeutschArticle', params: { articleName: 'hypothyroidism'}}}>Indikatoren Schilddrüsenunterfunktion</RouterLink> | |||||
</li> | |||||
<li> | |||||
<RouterLink to={{ name: 'GEDeutschArticle', params: { articleName: 'caffeine'}}}>Koffein</RouterLink> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</>; | |||||
} | |||||
name: "ge-deutsch", | |||||
async setup() { | |||||
useHead({ title: "Ray Peat Artikel auf Deutsch" }); | |||||
const { | |||||
result: rpArticles, | |||||
stateIsReady, | |||||
} = useAsyncState("rp-articles", ({ hostUrl }) => getDJAPI(hostUrl, "/rp-articles")); | |||||
await stateIsReady; | |||||
return () => ( | |||||
<> | |||||
<header> | |||||
<h1>Ray Peat Deutsche Übersetzungen</h1> | |||||
</header> | |||||
<div class="text-slab"> | |||||
<p> | |||||
Auf dieser Seite befindet sich eine Auswahl der Artikel von Dr. Raymond Peat, die ich in meiner | |||||
Freizeit ins Deutsche übersetzt habe. | |||||
</p> | |||||
<p> | |||||
Ray Peat war ein US-Amerikaner aus Eugene, Oregon, der neben seinem Studium von Linguistik, | |||||
Literatur, und Malerei eine Promotion in der Biologie und Physiologie des Alterns und | |||||
degenerativer Krankheiten absolvierte, mit besonderem Fokus auf Zellen-Stoffwechsel, Hormone, | |||||
und Ernährungsphysiologie. | |||||
</p> | |||||
<p> | |||||
Nach mehreren Jahren als Professor an diversen Universitäten in den USA und Mexiko begann er | |||||
Artikel in Form eines Newsletters herauszugeben, von denen mehrere auf{" "} | |||||
<a href="http://raypeat.com/">seiner Website</a> zu finden sind. | |||||
</p> | |||||
<p> | |||||
Da er in meinem Leben zu einem enormen Verständnis meines Körpers und einem unglaublichen | |||||
Zurückerlangen meiner Gesundheit und meines Wohlbefindens trotz dem Scheitern mehrerer Ärzte | |||||
beigetragen hat, und sein Nachlass in Deutschland kaum Publikum aufgrund schwieriger | |||||
physiologischer Texte genießt, habe ich entschieden hauptsächlich für Freunde und Bekannte | |||||
einige seiner Artikel ins Deutsche zu übersetzen, damit vielleicht auch ein breiteres Publikum | |||||
von seinen Ideen profitieren könnte. | |||||
</p> | |||||
<p> | |||||
Falls was bei der Übersetzung auffällt oder besonders unidiomatisch klingt, bzw. der deutschen | |||||
Fachsprache der Medizin nicht gerecht sein sollte, kannst du mir unter <DJEmail />{" "} | |||||
eine Mail senden. Meine Muttersprache ist schließlich Englisch und ich bin kein professioneller | |||||
Übersetzer! | |||||
</p> | |||||
<p> | |||||
Zusätzlich zu der Funktion, den Artikel mit dem Button oben in der Originalversion anzusehen, | |||||
hat jeder Absatz oben rechts beim drüberhovern mit der Maus einen zusätzlichen Button über den | |||||
man den jeweiligen Absatz in die andere Sprache wechseln kann, um die Versionen beim Lesen | |||||
schnell zu vergleichen zu können. | |||||
</p> | |||||
</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> | |||||
</> | |||||
); | |||||
}, | |||||
}); | }); |
@@ -1,9 +1,10 @@ | |||||
import { h, inject, defineComponent, ref, createTextVNode, type VNode } from 'vue'; | |||||
import { RouterLink } from 'vue-router'; | |||||
import { createTextVNode, defineComponent, h, inject, onServerPrefetch, ref, type VNode, watchEffect } from "vue"; | |||||
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 articleResponse = await fetch(`${ hostUrl }/generative-energy/static/content/${ props.articleName }.html`); | |||||
return await articleResponse.text(); | |||||
}); | |||||
const title = ref(""); | |||||
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 children = [...node.childNodes].map(_ => transformArticleNode(_)); | |||||
const attrs: Record<string, string> = {}; | |||||
const children = [...node.childNodes].map((_) => transformArticleNode(_)); | |||||
if (el.tagName === 'P') { | |||||
el.classList.add('text-slab'); | |||||
children.unshift(h('button', { class: 'swap', onClick: (e) => { | |||||
e.target.parentElement.classList.toggle('swap'); | |||||
} }, '↻')); | |||||
if (el.tagName === "P") { | |||||
el.classList.add("text-slab"); | |||||
children.unshift(h("button", { | |||||
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"> | |||||
<div class="header"> | |||||
<RouterLink to={{ name: 'GEDeutsch' }}>Zur Artikelübersicht</RouterLink> | |||||
<button onClick={clickBtn}> | |||||
Sprache auf <span>{currentLang.value === "en" ? "Deutsch" : "Englisch"}</span> umschalten | |||||
</button> | |||||
return () => ( | |||||
<div class="ge-article"> | |||||
<div class="header"> | |||||
<RouterLink to={{ name: "GEDeutsch" }}>Zur Artikelübersicht</RouterLink> | |||||
<button onClick={clickBtn}> | |||||
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>; | |||||
} | |||||
); | |||||
}, | |||||
}); | }); |
@@ -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 () => <> | |||||
<header> | |||||
<h1>Generative Energy</h1> | |||||
</header> | |||||
<div class="text-slab"> | |||||
<p> | |||||
This page is dedicated to Dr. Raymond Peat († 2022), who has had a profound impact on my health and | |||||
my life in general. Hover over the links below for more detail. | |||||
</p> | |||||
</div> | |||||
<div class="text-slab"> | |||||
<h2>Links</h2> | |||||
<ul> | |||||
<li> | |||||
<RouterLink to={{ name: 'GECalculator' }}>Thyroid Calculator</RouterLink> | |||||
<span style="display: none" class="tooltip">Convert to and from grains, set ratios, etc.</span> | |||||
</li> | |||||
<li> | |||||
<RouterLink to={{ name: 'GEDeutsch' }}>Ray Peat Articles in German</RouterLink> | |||||
<span style="display: none" class="tooltip"> | |||||
A selection of articles by Ray that I have translated in my spare time into German. | |||||
</span> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</> | |||||
useHead({ title: "Generative Energy" }); | |||||
return () => ( | |||||
<> | |||||
<header> | |||||
<h1>Generative Energy</h1> | |||||
</header> | |||||
<div class="text-slab"> | |||||
<p> | |||||
This page is dedicated to Dr. Raymond Peat († 2022), who has had a profound impact on my health | |||||
and my life in general. Hover over the links below for more detail. | |||||
</p> | |||||
</div> | |||||
<div class="text-slab"> | |||||
<h2>Links</h2> | |||||
<ul> | |||||
<li> | |||||
<RouterLink to={{ name: "GECalculator" }}>Thyroid Calculator</RouterLink> | |||||
<span style="display: none" class="tooltip"> | |||||
Convert to and from grains, set ratios, etc. | |||||
</span> | |||||
</li> | |||||
<li> | |||||
<RouterLink to={{ name: "GEDeutsch" }}>Ray Peat Articles in German</RouterLink> | |||||
<span style="display: none" class="tooltip"> | |||||
A selection of articles by Ray that I have translated in my spare time into German. | |||||
</span> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</> | |||||
); | |||||
}, | }, | ||||
}; | }; |
@@ -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> | |||||
); | |||||
}, | |||||
}); |
@@ -1,34 +1,34 @@ | |||||
import { defineComponent, type VNode, Suspense } from "vue"; | |||||
import { useRoute, RouterLink, RouterView, type RouteRecordRaw } from 'vue-router'; | |||||
import { defineComponent, Suspense, type VNode } from "vue"; | |||||
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: '/', | |||||
name: 'GEMain', | |||||
path: "/", | |||||
name: "GEMain", | |||||
component: GEMain, | component: GEMain, | ||||
}, | }, | ||||
{ | { | ||||
path: '/calculator', | |||||
name: 'GECalculator', | |||||
path: "/calculator", | |||||
name: "GECalculator", | |||||
component: GECalculator, | component: GECalculator, | ||||
}, | }, | ||||
{ | { | ||||
path: '/raypeat-deutsch', | |||||
name: 'GEDeutsch', | |||||
path: "/raypeat-deutsch", | |||||
name: "GEDeutsch", | |||||
component: GEDeutsch, | component: GEDeutsch, | ||||
}, | }, | ||||
{ | { | ||||
path: '/raypeat-deutsch/article/:articleName', | |||||
name: 'GEDeutschArticle', | |||||
path: "/raypeat-deutsch/article/:articleName", | |||||
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 () => <> | |||||
<main> | |||||
<RouterLink class={"home-btn" + (route.name === 'GEMain' ? ' hide' : '') } | |||||
to={{ name: 'GEMain' }}> | |||||
Generative Energy Home | |||||
</RouterLink> | |||||
<RouterView>{{ default: ({ Component }: { Component: VNode }) => (Component && | |||||
<Suspense>{{ | |||||
default: () => Component, | |||||
fallback: () => <div>Page loading...</div>, | |||||
}}</Suspense> | |||||
)}}</RouterView> | |||||
<footer> | |||||
<div class="bottom"> | |||||
<div> | |||||
<a href="https://djledda.de/">djledda.de</a> 2023 - <DJEmail>{ () => 'Contact' }</DJEmail> | |||||
return () => ( | |||||
<> | |||||
<main> | |||||
<RouterLink class={"home-btn" + (route.name === "GEMain" ? " hide" : "")} to={{ name: "GEMain" }}> | |||||
Generative Energy Home | |||||
</RouterLink> | |||||
<RouterView> | |||||
{{ | |||||
default: ({ Component }: { Component: VNode }) => (Component && | |||||
( | |||||
<Suspense> | |||||
{{ | |||||
default: () => Component, | |||||
fallback: () => <div>Page loading...</div>, | |||||
}} | |||||
</Suspense> | |||||
)), | |||||
}} | |||||
</RouterView> | |||||
<footer> | |||||
<div class="bottom"> | |||||
<div> | |||||
<a href="/">djledda.de</a> 2023 - <DJEmail>{() => "Contact"}</DJEmail> | |||||
</div> | |||||
<DJDonate /> | |||||
</div> | </div> | ||||
<GEPaypal /> | |||||
</div> | |||||
</footer> | |||||
</main> | |||||
</>; | |||||
</footer> | |||||
</main> | |||||
</> | |||||
); | |||||
}, | }, | ||||
}); | }); |
@@ -4,7 +4,7 @@ import GERoot, { routes } from "@/generative-energy/GERoot.tsx"; | |||||
createSSRApp(GERoot) | createSSRApp(GERoot) | ||||
.use(createRouter({ | .use(createRouter({ | ||||
routes, | |||||
history: createWebHistory('/generative-energy') | |||||
routes, | |||||
history: createWebHistory("/generative-energy"), | |||||
})) | })) | ||||
.mount('#app-root'); | |||||
.mount("#app-root"); |
@@ -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 }; | |||||
} |
@@ -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); | |||||
const countDouble = computed(() => count.value * 2); | |||||
count.value++; | |||||
useHead({ title: "DJ Ledda's Homepage" }); | |||||
return () => ( | return () => ( | ||||
<div class="app-main"> | |||||
<button class="test" onClick={() => count.value++}>Click me!</button> | |||||
<div>Count: {count.value}</div> | |||||
<div>Count Double: {countDouble.value}</div> | |||||
<p> | |||||
<a href="/generative-energy/">Go to this other site hosted here but a different Vue app!</a> | |||||
</p> | |||||
<div class="supercontainer"> | |||||
<div class="shakeable"> | |||||
<div class="title_name"> | |||||
<DJTooltip tooltip="I wonder what he's listening to?"> | |||||
<img src="/home/img/dj.gif" alt="dj legt krasse Mucke auf" class="dude" /> | |||||
</DJTooltip> | |||||
<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> | ||||
); | ); | ||||
}, | }, | ||||
@@ -0,0 +1,4 @@ | |||||
import { createSSRApp } from "vue"; | |||||
import App from "@/home/App.tsx"; | |||||
createSSRApp(App).mount("#app-root"); |
@@ -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 }; | |||||
} |
@@ -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: {}, | |||||
}; | |||||
} | |||||
} |
@@ -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> | |||||
); | |||||
}, | |||||
}); |
@@ -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> } { | |||||
const ssrContext = useSSRContext<{ registry: Record<string, unknown> }>(); | |||||
const isClient = typeof ssrContext === 'undefined'; | |||||
export default function useAsyncState<T>( | |||||
key: string, | |||||
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); | ||||
@@ -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>(); | |||||
} |
@@ -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; | |||||
} | |||||
}); | |||||
} | |||||
} |
@@ -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", | ||||
@@ -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"; |
@@ -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('/static/')) { | |||||
return serveFile(req, `public/home/${ pathname }`); | |||||
} else { | |||||
return serveFile(req, `public${pathname}`); | |||||
if (pathname.startsWith("/api/")) { | |||||
response = getAPIResponse(req); | |||||
} | |||||
// Public/static files | |||||
if (!response) { | |||||
let filepath = join(".", "public", pathname); | |||||
if (filepath.endsWith("/")) { | |||||
filepath = join(filepath, "index.html"); | |||||
} | } | ||||
} else if (pathname === "/") { | |||||
const rendered = await renderToString(createSSRApp(Home)); | |||||
const content = utf8Decoder.decode(await Deno.readFile("./public/home/index.html")) | |||||
.replace(`<!-- SSR OUTLET -->`, rendered) | |||||
.replace(`<!-- SSR HEAD OUTLET -->`, ""); | |||||
return new Response(content, { headers: { "Content-Type": "text/html" } }); | |||||
} else if (pathname.startsWith('/generative-energy')) { | |||||
const app = createSSRApp(GERoot); | |||||
const router = createRouter({ | |||||
routes: geRoutes, | |||||
history: createMemoryHistory('/generative-energy'), | |||||
}); | |||||
app.use(router); | |||||
app.provide('dom-parse', (innerHTML: string) => { | |||||
return parser.parseFromString(innerHTML, 'text/html').documentElement; | |||||
}); | |||||
const ssrContext = { registry: {}}; | |||||
await router.replace(pathname.split('/generative-energy')[1]); | |||||
await router.isReady(); | |||||
const rendered = await renderToString(app, ssrContext); | |||||
const content = utf8Decoder.decode(await Deno.readFile("./public/generative-energy/index.html")) | |||||
.replace('%TITLE%', 'Generative Energy') | |||||
.replace('<!-- SSR HEAD OUTLET -->', ` | |||||
<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/" | |||||
if (await exists(filepath, { isFile: true })) { | |||||
response = await serveFile(req, filepath); | |||||
} | |||||
} | |||||
// NPM Vendor deps | |||||
if (response === null && pathname.startsWith("/deps")) { | |||||
response = await serveFile(req, `node_modules/${pathname.split("/deps")[1]}`); | |||||
} | |||||
// Transpile Code | |||||
if ( | |||||
response === null && | |||||
pathname.startsWith("/app") && | |||||
(pathname.endsWith(".ts") || pathname.endsWith(".tsx")) && | |||||
!pathname.endsWith("server.ts") | |||||
) { | |||||
response = await serveFile(req, "./" + pathname); | |||||
response = await transpileResponse(response, req.url, pathname); | |||||
} | |||||
// SSR | |||||
if (response === null) { | |||||
const baseDirectoryName = pathname.split("/")[1] ?? ""; | |||||
if (sites.includes(baseDirectoryName)) { | |||||
const appLocation = baseDirectoryName === "" ? "home" : baseDirectoryName; | |||||
const siteTemplate = join("public", baseDirectoryName, "index_template.html"); | |||||
const siteEntry = join("app", appLocation, "server.ts"); | |||||
const clientEntry = join("@", appLocation, "client.ts"); | |||||
const { app, router } = (await import("./" + siteEntry)).default() as { | |||||
app: App; | |||||
router: Router | null; | |||||
}; | |||||
app.provide("dom-parse", (innerHTML: string) => { | |||||
return parser.parseFromString(innerHTML, "text/html").documentElement; | |||||
}); | |||||
const ssrContext: DJSSRContext = { registry: {}, head: { title: "" } }; | |||||
if (router) { | |||||
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" } }); | |||||
} | } | ||||
</script> | |||||
<script> window.appstate = ${ JSON.stringify(ssrContext.registry) }; </script> | |||||
`) | |||||
.replace(`<!-- SSR OUTLET -->`, rendered); | |||||
return new Response(content, { headers: { "Content-Type": "text/html" } }); | |||||
} else if (pathname.startsWith("/app") && (pathname.endsWith(".ts") || pathname.endsWith(".tsx"))) { | |||||
const response = await serveFile(req, "./" + pathname); | |||||
return await transpileResponse(response, req.url, pathname); | |||||
} else if (pathname.startsWith("/deps")) { | |||||
return serveFile(req, `node_modules/${pathname.split("/deps")[1]}`); | |||||
} | |||||
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", () => { | ||||
@@ -1,7 +1,5 @@ | |||||
<h1> | |||||
<span lang="en">Caffeine: A vitamin-like nutrient, or adaptogen</span> | |||||
<span lang="de">Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen</span> | |||||
</h1> | |||||
<h1 lang="en">Caffeine: A vitamin-like nutrient, or adaptogen</h1> | |||||
<h1 lang="de">Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen</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> |
@@ -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&family=Roboto+Slab:wght@600&display=swap" | href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab:wght@600&display=swap" | ||||
rel="stylesheet"> | rel="stylesheet"> |
@@ -139,17 +139,26 @@ 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 { | ||||
@@ -186,13 +195,3 @@ footer { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
.fade-enter-active, | |||||
.fade-leave-active { | |||||
transition: opacity 0.5s ease; | |||||
} | |||||
.fade-enter-from, | |||||
.fade-leave-to { | |||||
opacity: 0; | |||||
} |
@@ -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> |
@@ -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; |
@@ -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; | |||||
}); |
@@ -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; | |||||
} |
@@ -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&action=edit&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> |
@@ -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; | |||||
} |
@@ -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> |
@@ -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> |
@@ -0,0 +1,5 @@ | |||||
# www.robotstxt.org/ | |||||
# Allow crawling of all content | |||||
User-agent: * | |||||
Disallow: |