@profitlich/template-toolkit 2.4.1 → 2.4.3

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.
@@ -0,0 +1,228 @@
1
+ # Plan: Exakte Spaltenlinien via Canvas (Variante E)
2
+
3
+ ## Context
4
+
5
+ Im `lines`-Modus des Dev-Grids werden Spaltenlinien über `linear-gradient` + `background-size` als Kachelmuster erzeugt. Die Kachelbreite löst auf Bruchpixel-Werte auf → die `0.5px`-Gradient-Stops landen an leicht unterschiedlichen physischen Pixeln pro Kachel. Ergebnis: Linien erscheinen wackelnd oder inkonsistent.
6
+
7
+ **Lösung:** `<canvas>`-Element mit korrekter `devicePixelRatio`-Behandlung und dem "Half-Pixel"-Trick für pixelgenaue, scharf gerenderte Linien. Funktioniert auf 1×- und Retina-Displays gleich gut.
8
+
9
+ ---
10
+
11
+ ## Betroffene Dateien
12
+
13
+ - [dev/toolbar/Toolbar.js](dev/toolbar/Toolbar.js)
14
+ - [dev/toolbar/toolbar.scss](dev/toolbar/toolbar.scss)
15
+
16
+ ---
17
+
18
+ ## Architektur
19
+
20
+ ```text
21
+ .dev-toolbar__grid (position: fixed, 100vw×100vh)
22
+ └── <canvas> ← neu, ersetzt ::after für "lines"
23
+ ::after ← bleibt für "ribbons" erhalten
24
+ ```
25
+
26
+ Das Canvas-Element wird als Kind von `#gridElement` angehängt. Es erbt dessen Fixed-Positioning und Visibility-Steuerung über `data-dev`. Für den `lines`-Modus zeichnet das Canvas; für `ribbons` wird das Canvas geleert und `::after` übernimmt.
27
+
28
+ ---
29
+
30
+ ## SCSS — `toolbar.scss`
31
+
32
+ ### 1. CSS Custom Properties im Mixin ergänzen
33
+
34
+ Im Block `body[data-dev='true']` innerhalb von `dev-toolbar-grid`:
35
+
36
+ ```scss
37
+ body[data-dev='true'] {
38
+ --dev-columns: #{$columns}; // unitless integer
39
+ --dev-gutter: #{$gutter}; // CSS-Wert (z. B. "1.5rem" oder "20px")
40
+ --dev-margin-left: #{$margin-left};
41
+ --dev-margin-right: #{$margin-right};
42
+
43
+ .dev-toolbar__grid::after { ... } // unverändert
44
+ }
45
+ ```
46
+
47
+ ### 2. `lines`-Gradient entfernen
48
+
49
+ Den gesamten Block (Zeilen 28–39) entfernen — das Canvas übernimmt die Darstellung:
50
+
51
+ ```scss
52
+ // ENTFERNEN:
53
+ body[data-dev='true'][data-dev-grid="lines"] {
54
+ .dev-toolbar__grid::after { background: ...; }
55
+ }
56
+ ```
57
+
58
+ ### 3. Canvas-Styling ergänzen
59
+
60
+ ```scss
61
+ .dev-toolbar__grid {
62
+ canvas {
63
+ display: block;
64
+ width: 100%;
65
+ height: 100%;
66
+ }
67
+ }
68
+ ```
69
+
70
+ ---
71
+
72
+ ## JavaScript — `Toolbar.js`
73
+
74
+ ### Neue private Fields
75
+
76
+ ```javascript
77
+ #gridElement = null; // bisher nur lokale Variable, jetzt gespeichert
78
+ #canvas = null;
79
+ #ctx = null;
80
+ ```
81
+
82
+ ### `constructor()` — Änderungen
83
+
84
+ 1. `gridOverlay` → `this.#gridElement` (Referenz speichern)
85
+ 2. `#initCanvas()` aufrufen nach `prepend`
86
+
87
+ ```javascript
88
+ this.#gridElement = document.createElement('div');
89
+ this.#gridElement.classList.add('dev-toolbar__grid');
90
+ document.body.prepend(this.#gridElement);
91
+ this.#initCanvas();
92
+ ```
93
+
94
+ ### Neue Methode `#initCanvas()`
95
+
96
+ ```javascript
97
+ #initCanvas() {
98
+ this.#canvas = document.createElement('canvas');
99
+ this.#gridElement.appendChild(this.#canvas);
100
+ this.#ctx = this.#canvas.getContext('2d');
101
+ }
102
+ ```
103
+
104
+ ### Neue Methode `#drawGrid()`
105
+
106
+ Wird aufgerufen bei: State-Wechsel (`#applyState`), Resize (`#onResize`).
107
+
108
+ ```javascript
109
+ #drawGrid() {
110
+ const dpr = window.devicePixelRatio || 1;
111
+ const w = window.innerWidth;
112
+ const h = window.innerHeight;
113
+
114
+ this.#canvas.width = Math.round(w * dpr);
115
+ this.#canvas.height = Math.round(h * dpr);
116
+
117
+ this.#ctx.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
118
+
119
+ if (this.#state.grid !== 'lines') return;
120
+
121
+ this.#ctx.setTransform(dpr, 0, 0, dpr, 0.5, 0); // scale + half-pixel shift
122
+
123
+ this.#drawColumnLines();
124
+ }
125
+ ```
126
+
127
+ **Half-pixel-Trick:** `ctx.setTransform(dpr, 0, 0, dpr, 0.5, 0)` — der 0.5px-Shift zentriert 1px-Linien exakt auf physische Pixel bei dpr=1. Bei dpr=2 (Retina) ist 0.5 CSS-px = 1 physischer Pixel, ebenfalls scharf.
128
+
129
+ ### Neue Methode `#drawColumnLines()`
130
+
131
+ ```javascript
132
+ #drawColumnLines() {
133
+ const style = getComputedStyle(document.body);
134
+ const columns = parseInt(style.getPropertyValue('--dev-columns'));
135
+ const gutter = this.#resolveToPx(style.getPropertyValue('--dev-gutter').trim());
136
+ const marginLeft = this.#resolveToPx(style.getPropertyValue('--dev-margin-left').trim());
137
+
138
+ // Kachelbreite = (Viewport - Margins + Gutter) / Spalten
139
+ // (identisch zur background-size-Formel im SCSS)
140
+ const gridWidth = window.innerWidth - marginLeft
141
+ - this.#resolveToPx(style.getPropertyValue('--dev-margin-right').trim())
142
+ + gutter;
143
+ const tileWidth = gridWidth / columns;
144
+ const colContent = tileWidth - gutter;
145
+
146
+ this.#ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
147
+ this.#ctx.lineWidth = 1;
148
+ const h = window.innerHeight;
149
+
150
+ for (let i = 0; i < columns; i++) {
151
+ const leftEdge = marginLeft + i * tileWidth;
152
+ const rightEdge = leftEdge + colContent;
153
+
154
+ this.#ctx.beginPath();
155
+ this.#ctx.moveTo(Math.round(leftEdge), 0);
156
+ this.#ctx.lineTo(Math.round(leftEdge), h);
157
+ this.#ctx.stroke();
158
+
159
+ this.#ctx.beginPath();
160
+ this.#ctx.moveTo(Math.round(rightEdge), 0);
161
+ this.#ctx.lineTo(Math.round(rightEdge), h);
162
+ this.#ctx.stroke();
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Neue Methode `#resolveToPx(cssValue)`
168
+
169
+ Löst beliebige CSS-Einheiten (rem, vw, calc, …) auf px auf, ohne Unit-Parsing-Logik:
170
+
171
+ ```javascript
172
+ #resolveToPx(cssValue) {
173
+ if (/^-?\d+(\.\d+)?px$/.test(cssValue)) return parseFloat(cssValue);
174
+
175
+ const probe = document.createElement('div');
176
+ probe.style.cssText = `position:fixed;visibility:hidden;width:${cssValue};top:0;left:0;`;
177
+ document.body.appendChild(probe);
178
+ const px = probe.getBoundingClientRect().width;
179
+ probe.remove();
180
+ return px;
181
+ }
182
+ ```
183
+
184
+ ### `#applyState()` — Ergänzung
185
+
186
+ ```javascript
187
+ #applyState() {
188
+ const gridActive = this.#state.grid !== 'aus';
189
+ document.body.setAttribute('data-dev', String(gridActive));
190
+ document.body.setAttribute('data-dev-grid', this.#state.grid);
191
+ this.#updateImageSize();
192
+ this.#drawGrid(); // ← neu
193
+ }
194
+ ```
195
+
196
+ ### `#onResize` — Ergänzung
197
+
198
+ ```javascript
199
+ #onResize = () => {
200
+ this.#gui.title(this.#getViewportText());
201
+ this.#updateImageSize();
202
+ this.#drawGrid(); // ← neu
203
+ }
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Positionsformel (Herleitung)
209
+
210
+ Das SCSS positioniert `::after` mit `margin-left = marginLeft - gutter/2`, sodass die Kacheln mittig zwischen Spalten beginnen. Die resultierenden Canvas-Positionen sind:
211
+
212
+ | | Formel | Beispiel (12 Spalten, gutter=20px, marginLeft=40px) |
213
+ | --- | --- | --- |
214
+ | Linke Kante Spalte i | `marginLeft + i × tileWidth` | i=0: 40px, i=1: 120px, … |
215
+ | Rechte Kante Spalte i | `marginLeft + i × tileWidth + colContent` | i=0: 100px, i=1: 180px, … |
216
+
217
+ Diese Formel ist identisch zur bestehenden `background-size`-Logik, nur ohne Kachel-Grenz-Problem.
218
+
219
+ ---
220
+
221
+ ## Verifikation
222
+
223
+ 1. `ddev npm run dev` starten
224
+ 2. Dev-Toolbar öffnen (Ctrl), Grid auf "lines" schalten
225
+ 3. **Browserfenster in verschiedene Breiten ziehen** → Linien dürfen nicht wackeln
226
+ 4. **Safari (Retina) + Chrome (1×)** vergleichen → gleiche Schärfe
227
+ 5. **Ribbons-Modus** prüfen → Canvas geleert, `::after`-Gradient unverändert
228
+ 6. **Breakpoint-Wechsel** prüfen → CSS Custom Properties werden neu gelesen, Linienanzahl passt sich an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profitlich/template-toolkit",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Shared SCSS layout system, JS utilities, components and build scripts for profitlich template repos",
5
5
  "type": "module",
6
6
  "exports": {
@@ -197,8 +197,6 @@ $mediaqueries: (
197
197
  // Distance
198
198
  @mixin marginPadding($layout, $mode, $position, $top: 0, $right: 0, $bottom: 0, $left: 0, $important: null) {
199
199
 
200
- // @include mode($layout, padding/margin, all/top/right/bottom/left, 0, 0, 0, 0)
201
-
202
200
  @if $important == true {
203
201
  $important: '!important';
204
202
  }
@@ -225,7 +223,7 @@ $mediaqueries: (
225
223
  }
226
224
 
227
225
  @if $position == all {
228
- #{$mode}: $top $right $bottom $left $important;
226
+ #{$mode}: $top $right $bottom $left #{$important};
229
227
  }
230
228
  @if $position == top {
231
229
  #{$mode}-top: $top;
@@ -13,7 +13,7 @@
13
13
  // Wenn es keine $mediaquery in der map gibt, gebe einen Fehler aus
14
14
  @if not $queries {
15
15
  @error "No value could be retrieved from `#{$mediaquery}`. "
16
- +"Please make sure it is defined in `$mediaqueries` map.";
16
+ + "Please make sure it is defined in `$mediaqueries` map.";
17
17
  }
18
18
 
19
19
  // Include the media mixin with $queries