        def lum(hex_value):
            parts = [int(hex_value[i:i+2], 16)/255 for i in [1, 3, 5]]
            return sum((v/12.92 if v <= .04045 else ((v+.055)/1.055)**2.4)*w for v, w in zip(parts, [.2126, .7152, .0722]))
        def contrast(a, b):
            a, b = sorted([lum(a), lum(b)]);return (b+.05)/(a+.05)
        modes = [('浅色', '#ffffff', '#352244', '#503373', '#ffffff'), ('暗色', '#171121', '#f3edf8', '#f5c242', '#291343')]
        body = '<div style="display:grid;grid-template-columns:1fr 1fr;gap:22px">';ratios = []
        for name, bg, fg, button, label in modes:
            ratio = {'mode': name, 'body': contrast(bg, fg), 'button': contrast(button, label)};ratios.append(ratio)
            body += '<article style="padding:30px;background:'+bg+';color:'+fg+'"><h2>'+name+'</h2><h3>'+html.escape(output.get('subject', ''))+'</h3><p>'+html.escape(output.get('date', ''))+'</p><a href="https://demo.example/material" style="display:inline-block;padding:14px;background:'+button+';color:'+label+'">'+html.escape(output.get('cta_label', ''))+'</a></article>'
        body += '</div>'
        check('两种主题正文和按钮对比度达标', all(x['body'] >= 4.5 and x['button'] >= 4.5 for x in ratios))
        check('模型保留活动日期与按钮文字', output.get('date') == '2026-11-07' and output.get('cta_label') == '查看演示资料')
        (folder / 'preview.html').write_text(_shell(case['title'], body));artifacts.append('preview.html');result = {'copy': output, 'contrast': ratios, 'scope': 'browser preview; no mail sent'}
