| @@ -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 () => ( | |||
| <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> | |||
| ); | |||
| }, | |||
| @@ -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({ | |||
| name: 'ge-calculator', | |||
| name: "ge-calculator", | |||
| setup() { | |||
| const t3Ratio = ref(1); | |||
| const t4Ratio = ref(4); | |||
| @@ -209,12 +22,12 @@ export default defineComponent({ | |||
| unit: "mg", | |||
| }, | |||
| { | |||
| name: 'Liothyronine (Triiodothyronine, "Cytomel", T3)', | |||
| name: 'Liothyronine (Triiodothyronine, "Cytomel/Cynomel", T3)', | |||
| mpg: MPG_T3_SYN, | |||
| unit: "mcg", | |||
| }, | |||
| { | |||
| name: 'Levothyroxine (Thyroxine, "Cynoplus", T4)', | |||
| name: "Levothyroxine (Thyroxine, T4)", | |||
| mpg: MPG_T4_SYN, | |||
| unit: "mcg", | |||
| }, | |||
| @@ -222,7 +35,6 @@ export default defineComponent({ | |||
| const numGrains = ref(2); | |||
| const ratio = computed(() => t3Ratio.value + t4Ratio.value); | |||
| const compounded = computed(() => { | |||
| const total = t3Ratio.value + t4Ratio.value; | |||
| 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> | |||
| { _.name } | |||
| Synthetic T3/T4 Combo | |||
| </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> | |||
| <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> | |||
| <span class="breathe">{ _.unit }</span> | |||
| </td> | |||
| </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 useHead from "@/useHead.ts"; | |||
| import useAsyncState from "@/useAsyncState.ts"; | |||
| import getDJAPI from "@/api.ts"; | |||
| 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 useHead from "@/useHead.ts"; | |||
| export default defineComponent({ | |||
| name: 'ge-deutsch-article', | |||
| name: "ge-deutsch-article", | |||
| props: { | |||
| articleName: { | |||
| type: String, | |||
| @@ -17,24 +18,50 @@ export default defineComponent({ | |||
| 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 { | |||
| if (node.nodeType === node.ELEMENT_NODE) { | |||
| 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++) { | |||
| @@ -46,30 +73,32 @@ export default defineComponent({ | |||
| return h((node as Element).tagName, attrs, children); | |||
| } else { | |||
| return createTextVNode(node.textContent ?? ''); | |||
| return createTextVNode(node.textContent ?? ""); | |||
| } | |||
| } | |||
| function ArticleContentTransformed() { | |||
| if (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>; | |||
| } | |||
| 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> | |||
| <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 { | |||
| name: 'ge-main', | |||
| name: "ge-main", | |||
| 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 DJEmail from "@/DJEmail.tsx"; | |||
| import GEPaypal from "@/generative-energy/GEPaypal.tsx"; | |||
| import GEDeutsch from "@/generative-energy/GEDeutsch.tsx"; | |||
| import GEDeutschArticle from "@/generative-energy/GEDeutschArticle.tsx"; | |||
| import GECalculator from "@/generative-energy/GECalculator.tsx"; | |||
| import DJDonate from "@/DJDonate.tsx"; | |||
| export const routes: RouteRecordRaw[] = [ | |||
| { | |||
| path: '/', | |||
| name: 'GEMain', | |||
| path: "/", | |||
| name: "GEMain", | |||
| component: GEMain, | |||
| }, | |||
| { | |||
| path: '/calculator', | |||
| name: 'GECalculator', | |||
| path: "/calculator", | |||
| name: "GECalculator", | |||
| component: GECalculator, | |||
| }, | |||
| { | |||
| path: '/raypeat-deutsch', | |||
| name: 'GEDeutsch', | |||
| path: "/raypeat-deutsch", | |||
| name: "GEDeutsch", | |||
| component: GEDeutsch, | |||
| }, | |||
| { | |||
| path: '/raypeat-deutsch/article/:articleName', | |||
| name: 'GEDeutschArticle', | |||
| path: "/raypeat-deutsch/article/:articleName", | |||
| name: "GEDeutschArticle", | |||
| component: GEDeutschArticle, | |||
| props: ({ params }) => { | |||
| if ('articleName' in params) { | |||
| if ("articleName" in params) { | |||
| return { articleName: params.articleName }; | |||
| } else { | |||
| return false; | |||
| @@ -41,27 +41,35 @@ export default defineComponent({ | |||
| name: "ge-root", | |||
| setup() { | |||
| 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> | |||
| <GEPaypal /> | |||
| </div> | |||
| </footer> | |||
| </main> | |||
| </>; | |||
| </footer> | |||
| </main> | |||
| </> | |||
| ); | |||
| }, | |||
| }); | |||
| @@ -4,7 +4,7 @@ import GERoot, { routes } from "@/generative-energy/GERoot.tsx"; | |||
| createSSRApp(GERoot) | |||
| .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({ | |||
| name: "app-root", | |||
| setup() { | |||
| const count = ref(0); | |||
| const countDouble = computed(() => count.value * 2); | |||
| count.value++; | |||
| useHead({ title: "DJ Ledda's Homepage" }); | |||
| 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> | |||
| ); | |||
| }, | |||
| @@ -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 { | |||
| // deno-lint-ignore no-var | |||
| 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 hostUrl = isClient ? globalThis.location.origin : 'http://localhost:8080'; | |||
| const hostUrl = isClient ? globalThis.location.origin : "http://localhost:8080"; | |||
| 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/fmt@0.223": "0.223.0", | |||
| "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/http@*": "1.0.9", | |||
| "jsr:@std/io@0.223": "0.223.0", | |||
| "jsr:@std/media-types@^1.0.3": "1.0.3", | |||
| "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@^1.0.7": "1.0.7", | |||
| "jsr:@std/streams@^1.0.7": "1.0.7", | |||
| @@ -34,7 +36,7 @@ | |||
| "dependencies": [ | |||
| "jsr:@deno/graph", | |||
| "jsr:@std/fmt@0.223", | |||
| "jsr:@std/fs", | |||
| "jsr:@std/fs@0.223", | |||
| "jsr:@std/io", | |||
| "jsr:@std/path@0.223" | |||
| ] | |||
| @@ -68,7 +70,11 @@ | |||
| "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" | |||
| }, | |||
| "@std/fs@0.223.0": { | |||
| "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" | |||
| "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c", | |||
| "dependencies": [ | |||
| "jsr:@std/assert", | |||
| "jsr:@std/path@0.223" | |||
| ] | |||
| }, | |||
| "@std/http@1.0.9": { | |||
| "integrity": "d409fc319a5e8d4a154e576c758752e9700282d74f31357a12fec6420f9ecb6c", | |||
| @@ -1,4 +1,6 @@ | |||
| import "jsr:@deno/emit"; | |||
| import "jsr:@std/http"; | |||
| 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 { createSSRApp } from "vue"; | |||
| import { type App, toValue } from "vue"; | |||
| import { type Router } from "vue-router"; | |||
| 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 { 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 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({ | |||
| port: 8080, | |||
| hostname: "0.0.0.0", | |||
| @@ -18,63 +87,78 @@ Deno.serve({ | |||
| console.log(`Listening on port http://${hostname}:${port}/`); | |||
| }, | |||
| }, async (req, _conn) => { | |||
| let response: Response | null = null; | |||
| if (req.method === "GET") { | |||
| 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 { | |||
| 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", () => { | |||
| @@ -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> | |||
| <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"> | |||
| <title>%TITLE%</title> | |||
| <link rel="stylesheet" href="/generative-energy/static/styles.css"> | |||
| <link rel="stylesheet" href="/generative-energy/styles.css"> | |||
| <link | |||
| href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab:wght@600&display=swap" | |||
| rel="stylesheet"> | |||
| @@ -139,17 +139,26 @@ hr { | |||
| background: #999; | |||
| } | |||
| footer .bottom { | |||
| display: flex; | |||
| flex-direction: row; | |||
| justify-content: space-between; | |||
| } | |||
| footer { | |||
| 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 { | |||
| 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: | |||