@profitlich/template-toolkit 5.8.0 → 6.0.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.
package/dev/Dev.js CHANGED
@@ -15,6 +15,6 @@ export function initDev(config = {}, options = {}) {
15
15
  }
16
16
 
17
17
  document.addEventListener('DOMContentLoaded', () => {
18
- new Toolbar(_options);
18
+ new Toolbar(_options, _config);
19
19
  console.info('Dev initialized.');
20
20
  });
@@ -1,7 +1,22 @@
1
1
  import GUI from 'lil-gui';
2
+ import { canvasGridLines } from 'canvas-grid-lines';
2
3
  import { MediaQueries } from '../../utils/MediaQueries.js';
4
+ import { gridColumnsTriple } from './gridColumns.js';
3
5
  import './toolbar.scss';
4
6
 
7
+ /**
8
+ * Übersetzt die Auswahl im Grid-Menü in die Rasterart von `canvas-grid-lines`.
9
+ * Die Namen der Auswahl bleiben, wie sie waren — sie stehen im gespeicherten
10
+ * State der Toolbar und wären sonst bei jedem ein Rückschritt auf `aus`.
11
+ *
12
+ * Die Farben standen früher als Sass-Variablen in `toolbar.scss`. Ein Canvas
13
+ * bekommt sie zur Laufzeit, deshalb stehen sie jetzt hier.
14
+ */
15
+ const GRID_MODES = {
16
+ lines: { gridType: 'columns', color: 'rgba(0, 0, 255, 0.5)' },
17
+ ribbons: { gridType: 'ribbons', color: 'rgba(191, 255, 254, 0.5)' },
18
+ };
19
+
5
20
  /**
6
21
  * Beschreibt eine projekteigene Checkbox in der Dev-Toolbar.
7
22
  * @typedef {Object} ToolbarToggle
@@ -29,12 +44,18 @@ export class Toolbar {
29
44
  #pictureElements;
30
45
  #contentTypeContainer;
31
46
  #toggles;
47
+ #config;
48
+ #gridElement;
49
+ #grid = null;
32
50
 
33
51
  /**
34
52
  * @param {ToolbarOptions} [options={}] - Projekteigene Ergänzungen.
53
+ * @param {Object} [config={}] - Inhalt von `src/config.json` des Projekts.
54
+ * Das Grid-Overlay leitet Spaltenzahl, Gutter und Seitenränder daraus ab.
35
55
  */
36
- constructor(options = {}) {
56
+ constructor(options = {}, config = {}) {
37
57
  this.#toggles = options.toggles ?? [];
58
+ this.#config = config;
38
59
 
39
60
  // State aus localStorage laden
40
61
  const defaults = { visible: false, grid: 'aus', imageSize: false, sizes: false, contentType: false };
@@ -44,10 +65,12 @@ export class Toolbar {
44
65
  const saved = localStorage.getItem('devTools');
45
66
  this.#state = saved ? { ...defaults, ...JSON.parse(saved) } : defaults;
46
67
 
47
- // Grid-Overlay DOM-Element erstellen
48
- const gridOverlay = document.createElement('div');
49
- gridOverlay.classList.add('dev-toolbar__grid');
50
- document.body.prepend(gridOverlay);
68
+ // Grid-Overlay DOM-Element erstellen. Das Canvas darin entsteht erst
69
+ // beim ersten Einschalten — es ist eine Bitmap in Viewportgrösse, und
70
+ // die soll nicht zahlen, wer das Raster gar nicht anschaut.
71
+ this.#gridElement = document.createElement('div');
72
+ this.#gridElement.classList.add('dev-toolbar__grid');
73
+ document.body.prepend(this.#gridElement);
51
74
 
52
75
  // Content-Type-Label-Overlay erstellen
53
76
  this.#contentTypeContainer = document.createElement('div');
@@ -94,6 +117,7 @@ export class Toolbar {
94
117
 
95
118
  // Event-Listener
96
119
  window.addEventListener('resize', this.#onResize);
120
+ window.addEventListener('eventLayoutchange', this.#onLayoutChange);
97
121
  document.addEventListener('keydown', this.#handleKeyDown);
98
122
  }
99
123
 
@@ -109,12 +133,49 @@ export class Toolbar {
109
133
  #applyState() {
110
134
  document.body.setAttribute('data-dev-grid', this.#state.grid);
111
135
  document.body.setAttribute('data-dev-content-types', this.#state.contentType);
136
+ this.#updateGrid();
112
137
  this.#applyToggles();
113
138
  this.#updateImageSize();
114
139
  this.#updateSizes();
115
140
  this.#updateContentTypeLabels();
116
141
  }
117
142
 
143
+ /**
144
+ * Baut das Raster beim ersten Einschalten und hält es danach auf dem Stand
145
+ * von Auswahl und Layout. Im Modus `aus` geschieht nichts — das Ausblenden
146
+ * erledigt allein `data-dev-grid` in der CSS.
147
+ */
148
+ #updateGrid() {
149
+ const mode = GRID_MODES[this.#state.grid];
150
+ if (!mode) return;
151
+
152
+ const columns = gridColumnsTriple(this.#config, this.#mediaQueries.layout);
153
+ if (!columns) return;
154
+
155
+ if (!this.#grid) {
156
+ [this.#grid] = canvasGridLines.initGrid({
157
+ targets: this.#gridElement,
158
+ gridType: mode.gridType,
159
+ columns,
160
+ color: mode.color,
161
+ lineWidth: 1,
162
+ units: 'devicepixel',
163
+ }) ?? [];
164
+ return;
165
+ }
166
+
167
+ // Reihenfolge: erst die Spalten, dann die Rasterart. Der gridType-Setter
168
+ // leitet sein Lückenmuster aus dem aktuellen columns-Wert ab, und der
169
+ // muss nach einem Breakpoint-Wechsel schon der neue sein.
170
+ this.#grid.columns = columns;
171
+ this.#grid.gridType = mode.gridType;
172
+ this.#grid.color = mode.color;
173
+ }
174
+
175
+ #onLayoutChange = () => {
176
+ this.#updateGrid();
177
+ }
178
+
118
179
  #updateContentTypeLabels() {
119
180
  this.#contentTypeContainer.innerHTML = '';
120
181
  if (!this.#state.contentType) return;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Rechnet die Spaltenkonfiguration eines Layouts in das Tripel um, das
3
+ * `canvas-grid-lines` für die Rasterarten `columns` und `ribbons` erwartet:
4
+ * `[total, band, gap]` in Rastereinheiten.
5
+ *
6
+ * Die Einheit ist der grösste gemeinsame Teiler von Spaltenbreite und Gutter —
7
+ * aus einem Verhältnis wie 80 : 20 werden damit die ganzen Zahlen 4 : 1, die
8
+ * das Paket als Wiederholungsmuster braucht. Beide Rasterarten teilen sich
9
+ * dieselbe Kantenfolge: `columns` strichelt sie, `ribbons` füllt dazwischen.
10
+ *
11
+ * @param {Object} config - Inhalt von `src/config.json` des Projekts.
12
+ * @param {string} layout - Layoutname, etwa `desktop`.
13
+ * @returns {number[]|undefined} `[total, band, gap]`, oder `undefined`, wenn die
14
+ * Angaben fehlen oder sich das Verhältnis nicht ganzzahlig ausdrücken lässt.
15
+ */
16
+ export function gridColumnsTriple(config, layout) {
17
+ const width = config?.layouts?.[layout];
18
+ const margins = config?.margins?.[layout];
19
+ const gutter = config?.gutter?.[layout];
20
+ const count = config?.columns?.[layout];
21
+
22
+ if (width == null || margins == null || gutter == null || count == null) {
23
+ console.warn(`Dev-Toolbar: Für das Layout "${layout}" fehlen Angaben in der config.json (layouts, margins, columns, gutter). Wird initDev die Config übergeben?`);
24
+ return undefined;
25
+ }
26
+
27
+ // Die Spaltenbreite ist `zaehler / count` und oft kein ganzer Designpixel —
28
+ // in `template-kirbycms` etwa 91,428…. Deshalb wird sie nie ausgerechnet:
29
+ // Das Verhältnis Spalte zu Gutter ist `zaehler/count : gutter`, und mit
30
+ // `count` erweitert wird daraus `zaehler : count · gutter`, also ein
31
+ // Verhältnis zweier ganzer Zahlen. Gekürzt ergibt das exakt das gesuchte
32
+ // Muster, ohne jede Rundung.
33
+ const zaehler = width - margins.left - margins.right - (count - 1) * gutter;
34
+
35
+ if (!Number.isInteger(zaehler) || !Number.isInteger(gutter) || !Number.isInteger(count) || zaehler <= 0) {
36
+ console.warn(`Dev-Toolbar: Layout "${layout}" ergibt keine brauchbare Spaltenaufteilung (Rest ${zaehler}, Gutter ${gutter}, Spalten ${count}) — das Raster bleibt aus.`);
37
+ return undefined;
38
+ }
39
+
40
+ const unit = greatestCommonDivisor(zaehler, count * gutter);
41
+ const band = zaehler / unit;
42
+ const gap = (count * gutter) / unit;
43
+
44
+ return [count * band + (count - 1) * gap, band, gap];
45
+ }
46
+
47
+ function greatestCommonDivisor(a, b) {
48
+ return b === 0 ? a : greatestCommonDivisor(b, a % b);
49
+ }
@@ -7,69 +7,43 @@
7
7
  }
8
8
 
9
9
  // grid
10
- $color--dev-grid: rgba(0, 0, 255, 0.5);
11
- $color--dev-grid-ribbons: rgba(191, 255, 254, 0.5);
12
- $color--dev-grid-center: rgba(0, 0, 0, 0.2);
13
-
14
- @mixin dev-toolbar-grid($layout, $columns, $gutter, $margin-left, $margin-right) {
15
-
16
- $gutter: size($layout, $gutter);
17
- $margin-left: size($layout, $margin-left);
18
- $margin-right: size($layout, $margin-right);
19
-
20
- .dev-toolbar__grid::after {
21
- // the grid is made through repition
22
- // 'important' because background settings are more specific than the grid settings
23
- background-size: calc((100% - ($columns * $gutter)) * calc(1 / $columns) + $gutter) 1px !important;
24
- margin: 0 calc($margin-right - calc($gutter / 2)) 0 calc($margin-left - calc($gutter / 2));
25
- // The grid is calculated starting from the middle
26
- // Thats why its necessary to add the half of the column gap to the left and right side
27
- width: calc(100% - $margin-right + calc($gutter / 2) - $margin-left + calc($gutter / 2));
28
- }
29
-
30
- body[data-dev-grid="lines"] {
31
- .dev-toolbar__grid::after {
32
- background:
33
- // center of the column spacing
34
- linear-gradient(90deg, transparent 0.5px, transparent 0.5px) 0 0,
35
- // Left column line
36
- // position: from the column spacing center to the right by half a column spacing
37
- linear-gradient(90deg, $color--dev-grid 0.5px, transparent 0.5px) calc($gutter / 2) 0,
38
- // right column line
39
- linear-gradient(90deg, $color--dev-grid 0.5px, transparent 0.5px) calc($gutter / 2 * -1) 0;
40
- }
41
- }
42
-
43
- body[data-dev-grid="ribbons"] {
44
- .dev-toolbar__grid::after {
45
- background:
46
- // ceft column ribbon
47
- // position: from the column spacing center to the right by half a column spacing
48
- linear-gradient(90deg, $color--dev-grid-ribbons calc(100% - $gutter), transparent 0.5px) calc($gutter / 2) 0,
49
- }
50
- }
51
- }
52
-
10
+ //
11
+ // Gezeichnet wird das Raster von `canvas-grid-lines`; Toolbar.js hängt das
12
+ // Canvas in dieses Element. Hier steht nur noch, wo das Element liegt.
13
+ // Die Linienfarben stehen als GRID_MODES in Toolbar.js — ein Canvas nimmt sie
14
+ // zur Laufzeit entgegen, nicht als Sass-Variable.
53
15
  .dev-toolbar__grid {
54
- display: none;
55
- width: 100%;
16
+ // Spannt genau die Rasterbreite auf: Seitenränder aus `$margins`, Breite
17
+ // aus `left`/`right`. Die frühere Verschiebung um eine halbe Gutter-Breite
18
+ // entfällt — sie richtete nur die Kachelwiederholung des Gradienten aus.
56
19
  height: 100%;
57
- left: 0;
58
20
  pointer-events: none;
59
21
  position: fixed;
60
22
  top: 0;
61
23
 
62
- body:not([data-dev-grid='aus']) & {
63
- display: block;
64
- }
24
+ // Nicht `display: none`: Ein Container ohne Ausdehnung schickt
25
+ // canvas-grid-lines auf seinen Lazy-Pfad, das Raster bliebe ungezeichnet.
26
+ visibility: hidden;
27
+
28
+ // Immer vor dem Inhalt: Ein Messraster, das hinter dem liegt, was man
29
+ // vermessen will, taugt nicht — früher galt das nur für `lines`, `ribbons`
30
+ // lag dahinter. Der z-index macht zugleich den Stacking Context auf, ohne
31
+ // den das Canvas hinter den Hintergrund des Body rutschte.
32
+ //
33
+ // Gleicher Wert wie `.lil-gui.lil-root`, und das ist Absicht: Bei
34
+ // Gleichstand entscheidet die Dokumentreihenfolge, und die Toolbar hängt
35
+ // sich nach dem Overlay ein. Das Panel bleibt also bedienbar.
36
+ z-index: 9999999;
65
37
 
66
- body[data-dev-grid='lines'] & {
67
- z-index: 9999999;
38
+ body:not([data-dev-grid='aus']) & {
39
+ visibility: visible;
68
40
  }
69
41
 
70
- &::after {
71
- content: "";
72
- height: 100%;
42
+ // Bewusst statt eines Imports von `canvas-grid-lines/css`: Dessen Regel
43
+ // greift über `[data-grid-initialised] canvas` auf jedes Canvas im Dokument
44
+ // und würde die Rasterebenen im Projekt mit `z-index: -1` nach hinten
45
+ // ziehen. Hier bleibt sie auf das Overlay beschränkt.
46
+ canvas {
73
47
  left: 0;
74
48
  position: absolute;
75
49
  top: 0;
@@ -78,7 +52,10 @@ $color--dev-grid-center: rgba(0, 0, 0, 0.2);
78
52
 
79
53
  @each $layout, $breite in $layouts {
80
54
  @include mediaquery($layout) using ($layout) {
81
- @include dev-toolbar-grid($layout, map.get($columns, $layout), map.get($gutter, $layout), map.get(map.get($margins, $layout), 'left'), map.get(map.get($margins, $layout), 'right'));
55
+ .dev-toolbar__grid {
56
+ left: size($layout, map.get(map.get($margins, $layout), 'left'));
57
+ right: size($layout, map.get(map.get($margins, $layout), 'right'));
58
+ }
82
59
  }
83
60
  }
84
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profitlich/template-toolkit",
3
- "version": "5.8.0",
3
+ "version": "6.0.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",
@@ -39,6 +39,7 @@
39
39
  ],
40
40
  "dependencies": {
41
41
  "basic-ftp": "^5.0.0",
42
+ "canvas-grid-lines": "^10.3.0",
42
43
  "chokidar": "^4.0.0",
43
44
  "cli-progress": "^3.12.0",
44
45
  "dotenv": "^17.0.0",
@@ -64,7 +65,7 @@
64
65
  },
65
66
  "repository": {
66
67
  "type": "git",
67
- "url": "git+https://github.com/profitlich-ch/profitlich-template-toolkit.git"
68
+ "url": "git+https://github.com/profitlich-ch/template-toolkit.git"
68
69
  },
69
70
  "license": "MIT"
70
71
  }
@@ -1,228 +0,0 @@
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