Compare commits

..

4 commits

Author SHA1 Message Date
a49f96f827 Final pass for basic implementation of /gear/gear.mjs 2025-12-15 18:31:48 +01:00
f352b04b64 Adding dynamic generation of the gear list
The gear list is generated based on `/gear/gear.json`, which contains
a list of objects following the schema defined in `/gear/gear.mjs`.
2025-12-15 18:29:12 +01:00
0abf209616 Better devex
Using ES6 modueles and adding a JS LSP to `flake.nix`
2025-12-15 18:27:19 +01:00
32ee2de008 Header 2025-12-14 21:30:22 +01:00
15 changed files with 236 additions and 14 deletions

View file

@ -35,6 +35,8 @@
http-server
prettier
vscode-langservers-extracted
typescript-language-server
typescript
];
};
};

View file

@ -1 +0,0 @@
<!-- Gear list fragment -->

View file

@ -1 +0,0 @@
// Gear list JS

13
gear/gear.json Normal file
View file

@ -0,0 +1,13 @@
{
"bondage": {
"name": "Bondage",
"id": "bondage",
"values": [
{
"name": "Rope",
"id": "rope",
"desc": "10m pink, black, or purple"
}
]
}
}

115
gear/gear.mjs Normal file
View file

@ -0,0 +1,115 @@
// Gear list JS
// @ts-check
/**
* @typedef {{name: string, id: string, desc: string}} GearListItem
*
* @typedef {{name: string, id: string, values: GearListItem[]}} GearListSection
*
* @typedef {{bondage: GearListSection}} GearList
*/
/**
* Fetches the gear.json to be parsed seperately
* @returns {Promise<GearList>}
*/
async function getGearList() {
try {
const response = await fetch("/gear/gear.json");
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching JSON:", error);
throw "Could not load gear.json";
}
}
/**
* @param {GearListItem} gear
* @param {string} group
* @returns {HTMLLIElement}
*/
function gearToElement(gear, group) {
let li = document.createElement("li");
li.setAttribute("id", `gl-${group}-${gear.id}`);
let heading = document.createElement("h3");
heading.setAttribute("class", "gl-item-name");
heading.textContent = gear.name;
li.appendChild(heading);
let description = document.createElement("p");
description.setAttribute("class", "gl-item-desc");
li.appendChild(description);
return li;
}
/**
* @param {GearListSection} gear
* @returns {HTMLLIElement[]}
*/
function gearListToList(gear) {
let lis = [];
for (const item of gear.values) {
lis.push(gearToElement(item, gear.id));
}
return lis;
}
/**
* @param {GearListSection} items
* @returns {HTMLUListElement}
*/
function bondageItemsToUl(items) {
let ul = document.createElement("ul");
ul.setAttribute("id", `gl-${items.id}-list`);
for (const i of gearListToList(items)) {
ul.appendChild(i);
}
return ul;
}
/**
* @returns {Promise<HTMLElement>}
*/
async function generateGearListArticle() {
let list = await getGearList();
let bondageItems = document.createElement("article");
bondageItems.setAttribute("id", "gl-body");
let heading = document.createElement("h2");
heading.textContent = "Gear List";
bondageItems.appendChild(heading);
for (const section of Object.values(list)) {
let listSection = buildSection(section);
bondageItems.appendChild(listSection);
}
return bondageItems;
}
/**
* @param {GearListSection} items
* @returns {HTMLElement}
*/
function buildSection(items) {
let section = document.createElement("article");
section.setAttribute("id", `gl-${items.id}-body`);
let heading = document.createElement("h3");
heading.textContent = items.name;
section.appendChild(heading);
let ul = bondageItemsToUl(items);
section.appendChild(ul);
return section;
}
/**
* @param {string} position
* @returns void
*/
export async function attachGearList(position) {
document
.getElementById(position)
.replaceWith(await generateGearListArticle());
}

View file

@ -7,17 +7,19 @@
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Place favicon.ico in the root directory -->
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Truculenta:opsz,wght@12..72,100..900&display=swap"
rel="stylesheet"
/>
<link href="/global/global.css" rel="stylesheet" />
<script type="module" src="/global/loadin.mjs"></script>
<script type="module" src="/gear/page.mjs"></script>
<script type="module" src="/gear/gear.mjs"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
your experience.
</p>
<![endif]-->
<header id="hd-box"></header>
This will be my gear
<article id="gl-body"></article>
</body>
</html>

9
gear/page.mjs Normal file
View file

@ -0,0 +1,9 @@
import { attachGearList } from "/gear/gear.mjs";
import { loadHTML } from "/global/loadin.mjs";
export function loadDependencies() {
loadHTML("hd-box", "/header/header.html");
attachGearList("gl-body");
}
loadDependencies();

View file

@ -1 +1,10 @@
/* Global CSS options */
body {
font-family: "Truculenta", sans-serif;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-variation-settings: "wdth" 100;
font-size: 16pt;
}

View file

@ -1,4 +1,4 @@
function loadHTML(elementId, url) {
export function loadHTML(elementId, url) {
fetch(url)
.then((response) => response.text())
.then((data) => {

View file

@ -1 +1,44 @@
/* Header CSS */
header {
font-family: "Roboto Mono", monospace;
font-optical-sizing: auto;
font-weight: 600;
font-style: normal;
font-size: 14pt;
height: 1.5rem;
width: calc(100% - 1.5rem);
margin-bottom: 0.25rem;
padding: 0.5rem;
border-width: 3px;
border-style: solid;
position: sticky;
top: 0;
display: flex;
#hd-left {
width: min-width;
padding-left: 0.25rem;
padding-right: 0.25rem;
font-style: italic;
}
#hd-mid {
width: 100%;
}
#hd-right {
display: flex;
width: fit-content;
padding-left: 0.25rem;
padding-right: 0.25rem;
}
.spacer {
width: 2rem;
display: block;
}
}

View file

@ -1 +1,16 @@
<!-- Site header -->
<link
rel="stylesheet"
href="/header/header.css"
type="text/css"
media="screen"
/>
<div id="hd-left">nekoapril.blog</div>
<div id="hd-mid"></div>
<div id="hd-right">
<a href="/">Home</a>
<span class="spacer"></span>
<span>Kinks</span>
<span class="spacer"></span>
<a href="/gear">Gear</a>
</div>

View file

@ -6,8 +6,16 @@
<title>Kinklist</title>
<meta name="description" content="Personal kinklist and gearlist" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Truculenta:opsz,wght@12..72,100..900&display=swap"
rel="stylesheet"
/>
<link href="/global/global.css" rel="stylesheet" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<script type="module" src="/global/loadin.mjs"></script>
<script type="module" src="/index.mjs"></script>
<!-- Place favicon.ico in the root directory -->
</head>
<body>
@ -18,6 +26,7 @@
your experience.
</p>
<![endif]-->
<header id="hd-box"></header>
Look ma, I'm horny!
</body>
</html>

View file

@ -1 +0,0 @@
// Landing page JS

8
index.mjs Normal file
View file

@ -0,0 +1,8 @@
// Landing page JS
import { loadHTML } from "/global/loadin.mjs";
function loadDependencies() {
loadHTML("hd-box", "/header/header.html");
}
loadDependencies();