@profitlich/template-toolkit 4.2.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,30 @@
1
1
  import './menu-toggle.scss';
2
2
 
3
+ /**
4
+ * @typedef {Object} MenuToggleOptions
5
+ * @property {string} menuButtonSelector CSS-Selektor (`querySelector`) des Buttons, der das Menü öffnet/schliesst.
6
+ * @property {string} menuSelector CSS-Selektor (`querySelector`) des Menü-Containers.
7
+ * @property {string} menuLinkSelector CSS-Selektor für Menü-Links, die das Menü beim Klick schliessen (z.B. '.menu-link').
8
+ * @property {string} menuItemSelector CSS-Selektor des Menü-Wrappers; Klicks ausserhalb schliessen das Menü (z.B. '.menu').
9
+ * @property {string} [shiftElementSelector] Optional: CSS-Selektor des Elements, das beim Öffnen um die Scrollbar-Breite verschoben/verbreitert wird, damit z.B. ein fixierter Header nicht springt.
10
+ * @property {number} [shiftDelay=0] Verzögerung in Sekunden, bevor Scrollbar gemessen und Body fixiert wird – nützlich, wenn vorher noch eine CSS-Animation läuft, die die Scrollbar entfernt.
11
+ * @property {boolean} [deferPositionFixed=false] Setzt `data-menu-fixed` erst nach `shiftDelay` statt sofort. Nötig, wenn das Fixieren eine laufende Öffnungs-Animation stören würde.
12
+ * @property {boolean} [lockScroll=true] Wenn `false`, bleibt das fixierende Element scrollbar; kein Scrollbar-Ausgleich und kein Shift. Für Menüs, die nur einen Teil der Seite bedecken und Hintergrund-Scroll erlauben sollen.
13
+ * @property {string} [fixElementSelector] Optional: CSS-Selektor des Elements, das beim Öffnen fixiert wird (`position: fixed`, Scrollbar-Ausgleich, Scroll-Position-Trick). Default: `document.body`.
14
+ */
15
+
3
16
  export class MenuToggle {
4
17
  static #instance;
5
18
  #menuButton;
6
19
  #menu;
7
20
  #menuLinkSelector;
8
- #menuItemClass;
21
+ #menuItemSelector;
9
22
  #scrollbarWidth;
10
23
  #shiftElement;
11
24
  #shiftDelay;
12
25
  #deferPositionFixed;
13
- #fixBody;
26
+ #lockScroll;
27
+ #fixElement;
14
28
  #y;
15
29
  #bodyClickHandler;
16
30
  #resizeHandler;
@@ -18,34 +32,28 @@ export class MenuToggle {
18
32
  isActive;
19
33
 
20
34
  /**
21
- * @param {Object} options
22
- * @param {string} options.menuButtonId ID des Buttons, der das Menü öffnet/schliesst.
23
- * @param {string} options.menuId ID des Menü-Containers.
24
- * @param {string} options.menuLinkSelector Selektor für Menü-Links, die das Menü beim Klick schliessen (z.B. '.menu-link').
25
- * @param {string} options.menuItemClass Klassenname (ohne Punkt) des Menü-Wrappers; Klicks ausserhalb schliessen das Menü.
26
- * @param {string?} options.shiftElementId Optional: Element, das beim Öffnen um die Scrollbar-Breite verschoben/verbreitert wird, damit z.B. ein fixierter Header nicht springt.
27
- * @param {number} options.shiftDelay Verzögerung in Sekunden, bevor Scrollbar gemessen und Body fixiert wird – nützlich, wenn vorher noch eine CSS-Animation läuft, die die Scrollbar entfernt.
28
- * @param {boolean} options.deferPositionFixed Setzt `data-menu-fixed` erst nach `shiftDelay` statt sofort. Nötig, wenn das Fixieren eine laufende Öffnungs-Animation stören würde.
29
- * @param {boolean} options.fixBody Wenn `false`, bleibt der Body scrollbar; kein Scrollbar-Ausgleich und kein Shift. Für Menüs, die nur einen Teil der Seite bedecken und Hintergrund-Scroll erlauben sollen.
35
+ * @param {MenuToggleOptions} options
30
36
  */
31
37
  constructor({
32
- menuButtonId,
33
- menuId,
38
+ menuButtonSelector,
39
+ menuSelector,
34
40
  menuLinkSelector,
35
- menuItemClass,
36
- shiftElementId = null,
41
+ menuItemSelector,
42
+ shiftElementSelector = null,
37
43
  shiftDelay = 0,
38
44
  deferPositionFixed = false,
39
- fixBody = true,
45
+ lockScroll = true,
46
+ fixElementSelector = null,
40
47
  }) {
41
- this.#menuButton = document.getElementById(menuButtonId);
42
- this.#menu = document.getElementById(menuId);
48
+ this.#menuButton = document.querySelector(menuButtonSelector);
49
+ this.#menu = document.querySelector(menuSelector);
43
50
  this.#menuLinkSelector = menuLinkSelector;
44
- this.#menuItemClass = menuItemClass;
45
- this.#shiftElement = shiftElementId ? document.getElementById(shiftElementId) : null;
51
+ this.#menuItemSelector = menuItemSelector;
52
+ this.#shiftElement = shiftElementSelector ? document.querySelector(shiftElementSelector) : null;
46
53
  this.#shiftDelay = shiftDelay * 1000;
47
54
  this.#deferPositionFixed = deferPositionFixed;
48
- this.#fixBody = fixBody;
55
+ this.#lockScroll = lockScroll;
56
+ this.#fixElement = fixElementSelector ? document.querySelector(fixElementSelector) : document.body;
49
57
  this.#scrollbarWidth = 0;
50
58
  this.isActive = false;
51
59
  this.#y = 0;
@@ -68,13 +76,20 @@ export class MenuToggle {
68
76
 
69
77
  this.#setBodyAttribute('data-menu-active', 'false');
70
78
  this.#setBodyAttribute('data-menu-moving', 'false');
71
- this.#setBodyAttribute('data-menu-fixed', 'false');
79
+ this.#fixElement.setAttribute('data-menu-fixed', 'false');
72
80
  }
73
81
 
82
+ /**
83
+ * Holt die Singleton-Instanz. Beim ersten Aufruf müssen die Pflicht-Optionen
84
+ * (`menuButtonSelector`, `menuSelector`, `menuLinkSelector`, `menuItemSelector`)
85
+ * gesetzt sein; spätere Aufrufe ignorieren übergebene Optionen.
86
+ * @param {MenuToggleOptions} [options]
87
+ * @returns {MenuToggle}
88
+ */
74
89
  static getInstance(options) {
75
90
  if (!MenuToggle.#instance) {
76
- if (!options || !options.menuButtonId || !options.menuId || !options.menuLinkSelector || !options.menuItemClass) {
77
- throw new Error("MenuToggle muss beim ersten Aufruf mit menuButtonId, menuId, menuLinkSelector und menuItemClass initialisiert werden.");
91
+ if (!options || !options.menuButtonSelector || !options.menuSelector || !options.menuLinkSelector || !options.menuItemSelector) {
92
+ throw new Error("MenuToggle muss beim ersten Aufruf mit menuButtonSelector, menuSelector, menuLinkSelector und menuItemSelector initialisiert werden.");
78
93
  }
79
94
  MenuToggle.#instance = new MenuToggle(options);
80
95
  }
@@ -87,14 +102,14 @@ export class MenuToggle {
87
102
  document.body.removeEventListener('click', this.#bodyClickHandler);
88
103
  window.removeEventListener('resize', this.#resizeHandler);
89
104
  this.#setBodyAttribute('data-menu-active', 'false');
90
- if (this.#fixBody) {
91
- this.#setBodyAttribute('data-menu-fixed', 'false');
105
+ if (this.#lockScroll) {
106
+ this.#fixElement.setAttribute('data-menu-fixed', 'false');
92
107
  if (this.#shiftElement) {
93
108
  this.#shiftElement.style.marginRight = '';
94
109
  this.#shiftElement.style.width = '';
95
110
  }
96
- document.body.style.paddingRight = '';
97
- document.body.style.top = '';
111
+ this.#fixElement.style.paddingRight = '';
112
+ this.#fixElement.style.top = '';
98
113
  window.scrollTo(0, this.#y);
99
114
  }
100
115
  this.#toggleEscape(false);
@@ -102,19 +117,19 @@ export class MenuToggle {
102
117
  this.isActive = true;
103
118
  document.body.addEventListener('click', this.#bodyClickHandler);
104
119
  this.#setBodyAttribute('data-menu-active', 'true');
105
- if (this.#fixBody) {
120
+ if (this.#lockScroll) {
106
121
  window.addEventListener('resize', this.#resizeHandler);
107
122
  this.#setBodyAttribute('data-menu-moving', 'true');
108
123
  if (!this.#deferPositionFixed) {
109
- this.#setBodyAttribute('data-menu-fixed', 'true');
124
+ this.#fixElement.setAttribute('data-menu-fixed', 'true');
110
125
  }
111
126
  setTimeout(() => {
112
127
  this.#scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
113
128
  this.#y = window.scrollY;
114
- document.body.style.paddingRight = `${this.#scrollbarWidth}px`;
115
- document.body.style.top = `-${this.#y}px`;
129
+ this.#fixElement.style.paddingRight = `${this.#scrollbarWidth}px`;
130
+ this.#fixElement.style.top = `-${this.#y}px`;
116
131
  if (this.#deferPositionFixed) {
117
- this.#setBodyAttribute('data-menu-fixed', 'true');
132
+ this.#fixElement.setAttribute('data-menu-fixed', 'true');
118
133
  }
119
134
  if (this.#shiftElement) {
120
135
  const marginOriginal = parseFloat(window.getComputedStyle(this.#menuButton).marginRight);
@@ -138,7 +153,7 @@ export class MenuToggle {
138
153
  }
139
154
 
140
155
  #onBodyClick(event) {
141
- if (this.isActive && !event.target.closest('.' + this.#menuItemClass)) {
156
+ if (this.isActive && !event.target.closest(this.#menuItemSelector)) {
142
157
  this.#toggleMenu();
143
158
  }
144
159
  }
@@ -1,16 +1,12 @@
1
- body {
2
-
3
- &[data-menu-fixed="true"] {
4
- position: fixed;
5
- width: 100%;
6
- }
1
+ [data-menu-fixed="true"] {
2
+ position: fixed;
3
+ width: 100%;
4
+ }
7
5
 
8
- &[data-menu-active="true"] {
9
- // Bedeckt das Menü nur einen Teil der Seite, ist also ein Teil der Seite weiterhin zu sehen,
10
- // soll die Maus Interaktionen ausserhalb des Menüs ignorieren
11
- .main {
12
- pointer-events: none;
13
- }
6
+ body[data-menu-active="true"] {
7
+ // Bedeckt das Menü nur einen Teil der Seite, ist also ein Teil der Seite weiterhin zu sehen,
8
+ // soll die Maus Interaktionen ausserhalb des Menüs ignorieren
9
+ .main {
10
+ pointer-events: none;
14
11
  }
15
-
16
12
  }
@@ -1,6 +1,16 @@
1
1
  import '@mux/mux-player';
2
2
  import './mux-player.scss';
3
3
 
4
+ /**
5
+ * @typedef {Object} MuxPlayerOptions
6
+ * @property {boolean} [loop=false] Wiederholt das Video nach Ende.
7
+ * @property {boolean} [noLowRes=false] Setzt `minResolution` anhand der physischen Container-Grösse, damit auf hochauflösenden Displays keine zu kleine Auflösung gespielt wird.
8
+ * @property {((player: HTMLElement, container: HTMLElement) => void)} [onPlayerCreated] Callback, der nach jedem Lazy-Load-Setup aufgerufen wird (unabhängig von Autoplay).
9
+ * @property {boolean} [disableTracking=false] Setzt das `disable-tracking`-Attribut am Player — verhindert das Mux-Data-Tracking.
10
+ * @property {string} [envKey] Mux-Data-Env-Key. Nur wirksam, wenn `disableTracking=false`.
11
+ * @property {Object} [metadata] Mux-Data-Metadata-Objekt. Nur wirksam, wenn `envKey` gesetzt ist.
12
+ */
13
+
4
14
  /**
5
15
  * Lädt Mux-Videos lazy und steuert Autoplay per IntersectionObserver.
6
16
  *
@@ -30,6 +40,9 @@ export class MuxPlayer {
30
40
  #envKey;
31
41
  #metadata;
32
42
 
43
+ /**
44
+ * @param {MuxPlayerOptions} [options]
45
+ */
33
46
  constructor({
34
47
  loop = false,
35
48
  noLowRes = false,
package/dev/Dev.js CHANGED
@@ -3,7 +3,8 @@ import { Toolbar } from './toolbar/Toolbar.js';
3
3
  let _config = {};
4
4
 
5
5
  /**
6
- * @param {Object} config - Aus config.json des Projekts
6
+ * Hinterlegt die Projekt-Config für die Dev-Toolbar.
7
+ * @param {Object} [config={}] - Inhalt von `src/config.json` des Projekts.
7
8
  */
8
9
  export function initDev(config = {}) {
9
10
  _config = config;
@@ -2,6 +2,10 @@ import GUI from 'lil-gui';
2
2
  import { MediaQueries } from '../../utils/MediaQueries.js';
3
3
  import './toolbar.scss';
4
4
 
5
+ /**
6
+ * Dev-Toolbar (lil-gui) mit Grid-Overlay, Bildgrössen- und Inhaltstyp-Labels.
7
+ * State wird in `localStorage.devTools` persistiert. Toggle via `Ctrl`.
8
+ */
5
9
  export class Toolbar {
6
10
  #mediaQueries = MediaQueries.getInstance();
7
11
  #gui;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profitlich/template-toolkit",
3
- "version": "4.2.0",
3
+ "version": "5.2.0",
4
4
  "description": "Shared SCSS layout system, JS utilities, components and build scripts for profitlich template repos",
5
5
  "type": "module",
6
6
  "sass": "scss/forward.scss",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@capsizecss/core": ">=4.0.0",
48
- "@capsizecss/unpack": ">=2.0.0",
48
+ "@capsizecss/unpack": ">=4.0.0",
49
49
  "@mux/mux-player": ">=3.0.0"
50
50
  },
51
51
  "peerDependenciesMeta": {
@@ -3,6 +3,14 @@ import { glob } from 'glob';
3
3
  import path from 'path';
4
4
  import chokidar from 'chokidar';
5
5
 
6
+ /**
7
+ * @typedef {Object} CopyTask
8
+ * @property {string} name - Anzeigename des Tasks (für Logs).
9
+ * @property {string} src - Glob-Pattern der Quelldateien.
10
+ * @property {string} dest - Zielverzeichnis.
11
+ * @property {string} base - Basis-Pfad; relative Pfade werden ausgehend davon erhalten.
12
+ */
13
+
6
14
  async function runTask(task) {
7
15
  const files = await glob(task.src, { nodir: true, dot: true });
8
16
  if (files.length === 0) return;
@@ -14,6 +22,11 @@ async function runTask(task) {
14
22
  }
15
23
  }
16
24
 
25
+ /**
26
+ * Leert alle Ziel-Verzeichnisse und kopiert anschliessend alle Quelldateien
27
+ * jedes Tasks parallel ins jeweilige Ziel.
28
+ * @param {CopyTask[]} copyTasks
29
+ */
17
30
  export async function copyAll(copyTasks) {
18
31
  console.log('🚀 Starting initial copy of all files...');
19
32
  // empty folders to prevent orphaned files
@@ -25,6 +38,11 @@ export async function copyAll(copyTasks) {
25
38
  console.log('✅ Initial copy complete.');
26
39
  }
27
40
 
41
+ /**
42
+ * Beobachtet `watchDir` und führt bei jeder Änderung `copyAll` aus.
43
+ * @param {CopyTask[]} copyTasks
44
+ * @param {string} [watchDir='src']
45
+ */
28
46
  export function watchFiles(copyTasks, watchDir = 'src') {
29
47
  console.log(`👀 Watching for file changes in ${watchDir}/`);
30
48
 
@@ -58,9 +76,11 @@ export function watchFiles(copyTasks, watchDir = 'src') {
58
76
 
59
77
  /**
60
78
  * Run the copy-files script.
61
- * @param {Array} copyTasks - array of { name, src, dest, base } objects
62
- * @param {object} [options] - optional settings
63
- * @param {string} [options.watchDir] - directory to watch (default: 'src')
79
+ * Liest `process.argv[2]`: `dev` startet einmal `copyAll` und danach `watchFiles`,
80
+ * `build` führt `copyAll` einmal aus.
81
+ * @param {CopyTask[]} copyTasks
82
+ * @param {Object} [options]
83
+ * @param {string} [options.watchDir='src'] - Im Dev-Mode beobachtetes Verzeichnis.
64
84
  */
65
85
  export function run(copyTasks, options = {}) {
66
86
  const command = process.argv[2];
package/scripts/deploy.js CHANGED
@@ -6,6 +6,20 @@ import fs from 'fs/promises';
6
6
  import readline from 'readline';
7
7
  import cliProgress from 'cli-progress';
8
8
 
9
+ /**
10
+ * @typedef {Object} UploadTask
11
+ * @property {string} name - Anzeigename des Tasks (für Logs).
12
+ * @property {string} localPattern - Glob-Pattern der hochzuladenden Dateien.
13
+ * @property {string} localBase - Basis-Pfad; relative Pfade werden auf den `remoteDir` gemappt.
14
+ * @property {string} remoteDir - Ziel-Verzeichnis auf dem FTP-Server.
15
+ * @property {string|string[]} [ignore] - Glob-Pattern, die vom Upload ausgeschlossen werden.
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} DeployOptions
20
+ * @property {number} [parallel=3] - Anzahl paralleler FTP-Verbindungen. `1` deaktiviert Parallelität.
21
+ */
22
+
9
23
  // Helper for making sure the upload progress bars go to 100%
10
24
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
11
25
 
@@ -35,6 +49,13 @@ async function createClient(accessOptions) {
35
49
  return client;
36
50
  }
37
51
 
52
+ /**
53
+ * Lädt die in `uploadTasks` definierten Dateien per FTP hoch.
54
+ * Liest `FTP_HOST_<MODE>`, `FTP_USER_<MODE>`, `FTP_PASSWORD_<MODE>` aus dem env.
55
+ * @param {'staging'|'production'} mode
56
+ * @param {UploadTask[]} uploadTasks
57
+ * @param {DeployOptions} [options]
58
+ */
38
59
  export async function runDeploy(mode, uploadTasks, options = {}) {
39
60
  const parallel = options.parallel ?? 3;
40
61
  const modeUpper = mode.toUpperCase();
@@ -198,8 +219,10 @@ export async function runDeploy(mode, uploadTasks, options = {}) {
198
219
 
199
220
  /**
200
221
  * Run the deploy script.
201
- * @param {Array} uploadTasks - array of { name, localPattern, localBase, remoteDir, ignore? }
202
- * @param {Object} options - { parallel: number } (default: { parallel: 3 })
222
+ * Liest `process.argv[2]` (`'staging'` oder `'production'`) als Mode.
223
+ * Im Production-Mode wird vorher um Bestätigung gefragt.
224
+ * @param {UploadTask[]} uploadTasks
225
+ * @param {DeployOptions} [options]
203
226
  */
204
227
  export function run(uploadTasks, options = {}) {
205
228
  const mode = process.argv[2];
@@ -1,7 +1,7 @@
1
1
  /**
2
- * after scroll set body attribute data-scrolled to true
2
+ * Setzt beim ersten Scroll-Event das Body-Attribut `data-body-scrolled="true"`
3
+ * und feuert das Custom-Event `eventBodyScrolled`. Einmal pro Seitenaufruf.
3
4
  */
4
-
5
5
  export class BodyScrolled {
6
6
  static #instance;
7
7
 
@@ -22,6 +22,10 @@ export class BodyScrolled {
22
22
 
23
23
  }
24
24
 
25
+ /**
26
+ * Holt die Singleton-Instanz und initialisiert beim ersten Aufruf den Scroll-Listener.
27
+ * @returns {BodyScrolled}
28
+ */
25
29
  static getInstance() {
26
30
  if (!BodyScrolled.#instance) {
27
31
  BodyScrolled.#instance = new BodyScrolled();
@@ -1,8 +1,15 @@
1
+ /**
2
+ * Setzt `href` und Linktext aller Elemente mit `data-eml="local-part"` auf
3
+ * `local-part@domain` (Spam-Schutz: Adresse erst per JS zusammengesetzt).
4
+ */
1
5
  export class MailAdresses {
2
6
  static #instance;
3
7
 
4
8
  #domain;
5
9
 
10
+ /**
11
+ * @param {string} domain - Domain-Teil der E-Mail-Adressen (z.B. 'example.com')
12
+ */
6
13
  constructor(domain) {
7
14
  this.#domain = domain;
8
15
  this.init();
@@ -18,6 +25,11 @@ export class MailAdresses {
18
25
  });
19
26
  }
20
27
 
28
+ /**
29
+ * Holt die Singleton-Instanz. Beim ersten Aufruf muss `domain` übergeben werden.
30
+ * @param {string} [domain]
31
+ * @returns {MailAdresses}
32
+ */
21
33
  static getInstance(domain) {
22
34
  if (!MailAdresses.#instance) {
23
35
  MailAdresses.#instance = new MailAdresses(domain);
@@ -1,6 +1,11 @@
1
- /**Media queries
2
- * https://kinsta.com/blog/javamediaqueryipt-media-query/
3
- * option 3 on the linked page
1
+ /**
2
+ * Verwaltet Layout-Wechsel anhand von Breakpoints. Setzt `data-layout` am Body
3
+ * und feuert das Custom-Event `eventLayoutchange` bei jedem Wechsel.
4
+ * Siehe https://kinsta.com/blog/javamediaqueryipt-media-query/ (Option 3).
5
+ *
6
+ * @typedef {Object<string, number|null>} BreakpointsConfig
7
+ * Map: Layout-Name → minimale Viewport-Breite in Pixeln (oder `null` für das kleinste Layout).
8
+ * Reihenfolge der Keys bestimmt die Layout-Priorität (späteres Match überschreibt früheres).
4
9
  */
5
10
 
6
11
  export class MediaQueries {
@@ -8,6 +13,9 @@ export class MediaQueries {
8
13
  layout = 'desktop';
9
14
  #breakpoints;
10
15
 
16
+ /**
17
+ * @param {BreakpointsConfig} breakpoints
18
+ */
11
19
  constructor(breakpoints) {
12
20
  this.#breakpoints = breakpoints;
13
21
  this.layout = 'desktop';
@@ -15,6 +23,11 @@ export class MediaQueries {
15
23
  this.#matchmedia();
16
24
  }
17
25
 
26
+ /**
27
+ * Holt die Singleton-Instanz. Beim ersten Aufruf müssen `breakpoints` übergeben werden.
28
+ * @param {BreakpointsConfig} [breakpoints]
29
+ * @returns {MediaQueries}
30
+ */
18
31
  static getInstance(breakpoints) {
19
32
  if (!MediaQueries.#instance) {
20
33
  if (!breakpoints) {
package/utils/Vh100.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * 100vh problem
2
+ * Setzt die CSS Custom Property `--vh` auf 1% der aktuellen Viewport-Höhe und
3
+ * aktualisiert bei Resize. Workaround für mobiles 100vh.
3
4
  * https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
4
5
  */
5
-
6
6
  export class Vh100 {
7
7
  static #instance;
8
8
  vh = 0;
@@ -22,6 +22,10 @@ export class Vh100 {
22
22
  document.documentElement.style.setProperty('--vh', `${this.vh}px`);
23
23
  }
24
24
 
25
+ /**
26
+ * Holt die Singleton-Instanz und initialisiert beim ersten Aufruf den Resize-Listener.
27
+ * @returns {Vh100}
28
+ */
25
29
  static getInstance() {
26
30
  if (!Vh100.#instance) {
27
31
  Vh100.#instance = new Vh100();
package/utils/VwBody.js CHANGED
@@ -18,6 +18,10 @@ export class VwBody {
18
18
  document.documentElement.style.setProperty('--vw-body', `${this.vw}px`);
19
19
  }
20
20
 
21
+ /**
22
+ * Holt die Singleton-Instanz und initialisiert beim ersten Aufruf den Resize-Listener.
23
+ * @returns {VwBody}
24
+ */
21
25
  static getInstance() {
22
26
  if (!VwBody.#instance) {
23
27
  VwBody.#instance = new VwBody();
@@ -9,6 +9,11 @@ export class ImagesLoaded {
9
9
  #attributeName;
10
10
  #attributeValue;
11
11
 
12
+ /**
13
+ * @param {string} selector CSS-Selektor der zu beobachtenden `<img>`-Elemente.
14
+ * @param {string} [loadedAttribute='data-loaded'] Attribut, das den Ladestatus signalisiert.
15
+ * @param {string} [loadedValue='true'] Wert, der bei erfolgreichem Laden gesetzt wird.
16
+ */
12
17
  constructor(selector, loadedAttribute = 'data-loaded', loadedValue = 'true') {
13
18
  this.#selector = selector;
14
19
  this.#attributeName = loadedAttribute;
@@ -1,4 +1,4 @@
1
- import { fromFile } from '@capsizecss/unpack';
1
+ import { fromFile } from '@capsizecss/unpack/fs';
2
2
  import { createStyleObject, getCapHeight } from '@capsizecss/core';
3
3
  import * as sass from 'sass';
4
4
  import { OrderedMap } from 'immutable';
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Wandelt ein JSON-Objekt in SCSS-Variablen-Deklarationen um.
3
+ * Top-Level-Keys werden zu `$key: value;`-Zeilen, verschachtelte Objekte zu
4
+ * Sass-Maps `(key: value, ...)`. Hex-Farben werden ohne Quotes ausgegeben.
5
+ * Der Key `README` wird übersprungen.
6
+ *
7
+ * @param {Object} json - Quell-Objekt (z.B. Inhalt von `src/config.json`).
8
+ * @returns {string} SCSS-Source mit einer `$variable: ...;`-Zeile pro Top-Level-Key.
9
+ */
1
10
  export function jsonToScss(json) {
2
11
  const toValue = (v) => {
3
12
  if (typeof v === 'object' && v !== null) {