workers: Bot-Leads am Handy mit 40 px Knöpfen, lesbare gekürzte Zeilen, Ort bricht nicht mitten im Wort
Endprüfung P6 (C5, W1, W2):
- Blätterknöpfe 36 px -> 40 px und „Alle anzeigen“ der Einzelansicht (32 px) -> 40 px über
`.wk-bl-pager .btn` und `.wk-bl-single .btn` im Handy-Block; min-height schlägt height: 32px,
keine globale .btn-Regel, kein !important.
- Gekürzte Zeilen: --text-2 statt --text-3 (Kontrast von etwa 2,9:1 auf mehr als 5:1).
- `.wk-bl .wk-bl-table td .cell-sub { overflow-wrap: break-word }` (0,3,1) schlägt das globale
`.table td .cell-sub { overflow-wrap: anywhere }` (0,2,1) („Kaiserslau-tern“ in der Einzelansicht).
- Tests: workersCss.test.mjs (Farbe, Kontrast aus den Token von global.css, Höhen, Spezifität gegen die
globale Regel); Prüfstand botleads und handy-botleads: 81 Prüfungen grün.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ed5dfa9c0e
commit
f80b831b6a
2 files changed
+67
-3
No files matched your search
@@ -246,9 +246,13 @@
|
|||||||
.wk-bl-table { min-width: 760px; }
|
.wk-bl-table { min-width: 760px; }
|
||||||
/* break-word statt anywhere: anywhere ließe die Spalten auf ein Zeichen schrumpfen („Handwer-k“); lange Namen brechen, der Rahmen scrollt */
|
/* break-word statt anywhere: anywhere ließe die Spalten auf ein Zeichen schrumpfen („Handwer-k“); lange Namen brechen, der Rahmen scrollt */
|
||||||
.wk-bl-table td { overflow-wrap: break-word; }
|
.wk-bl-table td { overflow-wrap: break-word; }
|
||||||
|
/* Der Ort steht in .cell-sub, und global.css hat `.table td .cell-sub { overflow-wrap: anywhere }` (0,2,1): die Regel sitzt auf dem Element selbst und schlägt das
|
||||||
|
break-word der Zelle. Mit anywhere schrumpft die Spalte Betrieb unter das Wort („Kaiserslau-tern“, besonders in der Einzelansicht am Handy). Hier drei Klassen
|
||||||
|
(0,3,1): streng größer, nicht gleich, damit nicht die Reihenfolge der Stylesheets im Bündel entscheidet. global.css bleibt, andere Tabellen brauchen anywhere. */
|
||||||
|
.wk-bl .wk-bl-table td .cell-sub { overflow-wrap: break-word; }
|
||||||
.wk-bl-nowrap { white-space: nowrap; font-variant-numeric: tabular-nums; }
|
.wk-bl-nowrap { white-space: nowrap; font-variant-numeric: tabular-nums; }
|
||||||
/* Gekürzte Einträge (Daten nach der Frist entfernt) bleiben als graue Zeile stehen */
|
/* Gekürzte Einträge (Daten nach der Frist entfernt) bleiben als graue Zeile stehen */
|
||||||
.wk-bl-cut td { color: var(--text-3); }
|
.wk-bl-cut td { color: var(--text-2); } /* --text-3 (#8a93a3) hätte nur etwa 3:1; --text-2 (#5e6678) hat mehr als 5:1 auf Weiß und auf dem Zeilengrund */
|
||||||
.wk-bl-tag {
|
.wk-bl-tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
@@ -289,8 +293,10 @@
|
|||||||
Der Dialog hängt nicht unter einer wk-Klasse, die Regel beginnt deshalb mit der Klasse des Knopfs. Mit (0,3,0) (Klasse, Klasse, Attribut) gewinnt sie
|
Der Dialog hängt nicht unter einer wk-Klasse, die Regel beginnt deshalb mit der Klasse des Knopfs. Mit (0,3,0) (Klasse, Klasse, Attribut) gewinnt sie
|
||||||
gegen (0,2,0), gleich in welcher Reihenfolge die Dateien geladen werden. */
|
gegen (0,2,0), gleich in welcher Reihenfolge die Dateien geladen werden. */
|
||||||
.wk-prospect-close.btn[type="button"] { width: 100%; min-height: 40px; justify-content: center; }
|
.wk-prospect-close.btn[type="button"] { width: 100%; min-height: 40px; justify-content: center; }
|
||||||
/* Bot-Leads: Blätterknöpfe wie die anderen Knöpfe der Seite mit 36 px, Zeile darf umbrechen */
|
/* Bot-Leads: Blätterknöpfe und „Alle anzeigen“ der Einzelansicht mit 40 px Tippfläche wie die Knöpfe des Pults; min-height schlägt height: 32px von .btn, und mit
|
||||||
.wk-bl-pager .btn { min-height: 36px; }
|
zwei Klassen (0,2,0) gegen (0,1,0) braucht es kein !important. Keine globale .btn-Regel: Dialoge und andere Seiten behalten ihre Höhe. Die Zeile darf umbrechen */
|
||||||
|
.wk-bl-pager .btn { min-height: 40px; }
|
||||||
|
.wk-bl-single .btn { min-height: 40px; }
|
||||||
.wk-bl .table-foot { flex-wrap: wrap; gap: 8px 16px; }
|
.wk-bl .table-foot { flex-wrap: wrap; gap: 8px 16px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,3 +37,61 @@ test('workers.css: die globale Regel .page section + section (36 px) verlängert
|
|||||||
assert.match(css, /\.wk-prospect\s*\{[^}]*gap:\s*16px/)
|
assert.match(css, /\.wk-prospect\s*\{[^}]*gap:\s*16px/)
|
||||||
assert.doesNotMatch(css.match(/\.wk-prospect-section\s*\{([^}]*)\}/)[1], /margin-top/)
|
assert.doesNotMatch(css.match(/\.wk-prospect-section\s*\{([^}]*)\}/)[1], /margin-top/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ---- Bot-Leads (Endprüfung P6: C5, W1, W2) ----
|
||||||
|
const stripped = (file) => readFileSync(new URL('../../src/styles/' + file, import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
|
||||||
|
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
// Rumpf der Regel mit genau diesem Selektor am Anfang der Regel (weder Teil eines längeren Selektors noch einer Gruppe mit Komma)
|
||||||
|
const body = (css, selector) => css.match(new RegExp(`(?:^|[{}])\\s*${escapeRe(selector)}\\s*\\{([^}]*)\\}`))?.[1] ?? null
|
||||||
|
const decl = (rule, prop) => rule?.match(new RegExp(`(?:^|[;\\s])${prop}\\s*:\\s*([^;]+)`))?.[1].trim() ?? null
|
||||||
|
// Spezifität (ids, Klassen/Attribute/Pseudoklassen, Elemente); Vergleich von links nach rechts
|
||||||
|
const specificity = (selector) => {
|
||||||
|
const s = selector.replace(/\[[^\]]*\]/g, '.a')
|
||||||
|
const ids = (s.match(/#[\w-]+/g) || []).length
|
||||||
|
const classes = (s.match(/\.[\w-]+|:(?!:)[\w-]+/g) || []).length
|
||||||
|
const elements = (s.replace(/\.[\w-]+|#[\w-]+|:+[\w-]+/g, ' ').match(/(?:^|[\s>+~])[a-z][\w-]*/gi) || []).length
|
||||||
|
return [ids, classes, elements]
|
||||||
|
}
|
||||||
|
const beats = (a, b) => { for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i]; return false }
|
||||||
|
const luminance = (hex) => {
|
||||||
|
const c = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255).map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4))
|
||||||
|
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
|
||||||
|
}
|
||||||
|
const contrast = (a, b) => { const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x); return (hi + 0.05) / (lo + 0.05) }
|
||||||
|
const token = (name) => stripped('global.css').match(new RegExp(`${name}:\\s*(#[0-9a-fA-F]{6})`))?.[1]
|
||||||
|
|
||||||
|
test('workers.css: gekürzte Zeilen der Bot-Leads bleiben lesbar (Text >= 4,5:1 auf dem Seiten- und dem Zeilengrund), grau über --text-2 statt --text-3 (C5)', () => {
|
||||||
|
const css = stripped('workers.css')
|
||||||
|
const rule = body(css, '.wk-bl-cut td')
|
||||||
|
assert.ok(rule, 'Regel .wk-bl-cut td')
|
||||||
|
assert.equal(decl(rule, 'color'), 'var(--text-2)')
|
||||||
|
assert.doesNotMatch(css, /\.wk-bl-cut[^{]*\{[^}]*--text-3/) // keine Regel der gekürzten Zeile greift zu --text-3
|
||||||
|
for (const ground of ['--bg', '--bg-subtle']) {
|
||||||
|
const ratio = contrast(token('--text-2'), token(ground))
|
||||||
|
assert.ok(ratio >= 4.5, `${ground}: ${ratio.toFixed(2)}:1`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('workers.css: am Handy sind die Blätterknöpfe und „Alle anzeigen“ der Bot-Leads 40 px hoch, ohne die globale .btn-Regel zu ändern (C5, W1)', () => {
|
||||||
|
const css = stripped('workers.css')
|
||||||
|
const phone = css.slice(css.indexOf('@media (max-width: 860px)'))
|
||||||
|
for (const selector of ['.wk-bl-pager .btn', '.wk-bl-single .btn']) {
|
||||||
|
assert.equal(decl(body(phone, selector), 'min-height'), '40px', selector)
|
||||||
|
// min-height schlägt height: 32px der globalen .btn-Regel, und die Spezifität (0,2,0) schlägt .btn (0,1,0): kein !important nötig
|
||||||
|
assert.ok(beats(specificity(selector), specificity('.btn')), selector)
|
||||||
|
assert.doesNotMatch(body(phone, selector), /!important/)
|
||||||
|
}
|
||||||
|
assert.equal(decl(body(stripped('global.css'), '.btn'), 'height'), '32px') // das, wogegen die Knöpfe sich behaupten
|
||||||
|
assert.equal(body(css, '.btn'), null) // keine globale Änderung aus der Hustler-Datei
|
||||||
|
})
|
||||||
|
|
||||||
|
test('workers.css: der Ort in der Bot-Leads-Tabelle bricht nicht mitten im Wort (break-word schlägt die globale Regel mit anywhere) (W2)', () => {
|
||||||
|
const css = stripped('workers.css')
|
||||||
|
const global = stripped('global.css')
|
||||||
|
const wanted = '.wk-bl .wk-bl-table td .cell-sub'
|
||||||
|
const globalSelector = '.table td .cell-sub'
|
||||||
|
assert.equal(decl(body(global, globalSelector), 'overflow-wrap'), 'anywhere') // die globale Regel, die der Bot-Leads-Ort nicht erben soll
|
||||||
|
assert.equal(decl(body(css, wanted), 'overflow-wrap'), 'break-word')
|
||||||
|
// streng größer, nicht gleich: bei Gleichstand entschiede die Reihenfolge der Stylesheets im Bündel
|
||||||
|
assert.ok(beats(specificity(wanted), specificity(globalSelector)), `${wanted} ${specificity(wanted)} gegen ${specificity(globalSelector)}`)
|
||||||
|
})
|
||||||
Reference in new issue
Block a user