#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
RTQ 内容 · 两个参照网络优秀案例的版本（供选择后优化）
  定稿版（用户选定，2026-10-10）：白底大留白 + 大标题 + 细分隔线 + 数据主导（干货感）

网络案例提炼的落地要点（来源见交付说明）：
  · 文字层级：主标题 80–120px 占图宽 40–50%｜副标 40–60px｜正文 24–36px
  · 钩子公式：数字+效果 / 问题+答案
  · 关键文案避开底部 ~15%（平台 UI 会遮挡）
  · 可信度标签（"实测"徽章）
  · 克制装饰：1–2 处引导元素

铁律不变：品牌三色各一处｜全图只用 3 种字号 84/48/30｜无渐变填充文字｜不写价格｜含三码
"""
import base64, io, os, re
from PIL import Image, ImageChops
from playwright.sync_api import sync_playwright

DIR = os.environ.get('POSTER_DIR', '/Users/liuxinyuan/Desktop/Hermes输出-工作类/对外物料/科研朋友圈_20261009')
IMG = os.environ.get('POSTER_IMG_DIR', os.path.join(DIR, 'images'))
QRDIR = os.environ.get('POSTER_QR_DIR',
                       '/Users/liuxinyuan/Desktop/Hermes输出-工作类/科研2026秋季促销/华南农大海报_20260920/qr')
LOGO = '/Users/liuxinyuan/work/公司资料/logo_transparent.png'
TRIAL = os.path.expanduser('~/work/公司资料/试用装申请.png')
SLOGAN, BRAND = '守护生命健康，缔造美好生活', '宝锐生物 BIORI BIOTECH'
W = 1080
FS_ALLOWED = {'84px', '48px', '30px'}


def b64(p):
    return 'data:image/png;base64,' + base64.b64encode(io.open(p, 'rb').read()).decode()


def fit(p, maxw=900, maxh=520):
    im = Image.open(p).convert('RGB')
    bb = ImageChops.difference(im, Image.new('RGB', im.size, (255, 255, 255))).getbbox()
    if bb:
        q = 6
        im = im.crop((max(0, bb[0]-q), max(0, bb[1]-q), min(im.width, bb[2]+q), min(im.height, bb[3]+q)))
    im.thumbnail((maxw, maxh), Image.LANCZOS)
    f = '/tmp/_v_%s.jpg' % os.path.basename(p)
    im.save(f, quality=92)
    return 'data:image/jpeg;base64,' + base64.b64encode(io.open(f, 'rb').read()).decode()


def qr(p, px=176):
    im = Image.open(p).convert('L')
    bb = ImageChops.difference(im, Image.new('L', im.size, 255)).getbbox()
    if bb:
        q = 6
        im = im.crop((max(0, bb[0]-q), max(0, bb[1]-q), min(im.width, bb[2]+q), min(im.height, bb[3]+q)))
    im = im.resize((px, px), Image.NEAREST)
    f = '/tmp/_vqr_%s.png' % os.path.basename(p)
    im.convert('RGB').save(f)
    return 'data:image/png;base64,' + base64.b64encode(io.open(f, 'rb').read()).decode()


LOGO_U = b64(LOGO)
P1, P2 = fit(os.path.join(IMG, 'p1_i1_x19.png')), fit(os.path.join(IMG, 'p1_i2_x20.png'))
D1 = fit(os.path.join(IMG, 'p1_i5_x24.png'), maxw=520, maxh=300)   # 标准曲线
D2 = fit(os.path.join(IMG, 'p1_i7_x26.png'), maxw=520, maxh=340)   # 熔解曲线
D3 = fit(os.path.join(IMG, 'p2_i2_x30.png'), maxw=520, maxh=340)   # 耐抑制性
QR_WX, QR_XHS, QR_SPH = (qr(os.path.join(QRDIR, n)) for n in ('公众号.png', '小红书.png', '视频号.png'))
QR_TRIAL_IMG = qr(TRIAL, 200)
BG = b64(os.path.join(DIR, 'bg', '海报底图_竖版拼接.png'))

ICON = {
    'bolt': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 3 14h7l-1 8 10-12h-7l1-8z"/></svg>',
    'tube': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6v4l3 4v9a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-9l3-4V3z"/><path d="M6 14h12"/></svg>',
    'sliders': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h16"/><circle cx="9" cy="6" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="8" cy="18" r="2"/></svg>',
    'clock': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>',
    'check': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>',
}
STATS = [('5', ' min', '完成逆转录'), ('30', ' min', '跑完 40 循环'), ('100.795', '%', '扩增效率')]
FEATS = [('bolt', '极速上手', '5 min 完成逆转录，快速程序 30 min 跑完 40 循环'),
         ('tube', '一管到底', '单管完成 gDNA 去除与逆转录，告别两步操作与反复开盖'),
         ('sliders', '平台通用', '通用型 ROX 免调浓度，标准程序与快速程序皆可定量')]
FOOT = ('%s ｜ 电话 0756-8699969 ｜ www.biori.com<br>marketing@biori.com ｜ 珠海市香洲区水岸一路88号' % BRAND)


def data_section():
    """实测数据：2×2 网格（3 图表 + 1 结论卡）—— 极简白卡 + 细边框，无阴影无底色"""
    cells = [
        (D1, '标准曲线 · 扩增效率 100.795%，R² = 1.000'),
        (D2, '熔解曲线 · 单峰无杂峰，特异性良好'),
        (D3, '耐抑制性 · 5 种常见抑制剂梯度对比'),
    ]
    cards = ''.join('<div class="dcard"><img src="%s"><div class="dcap">%s</div></div>' % c
                    for c in cells)
    cards += ('<div class="dnote"><div class="dt">抑制剂覆盖</div><div class="dd">'
              '乙醇 · 异丙醇 · 苯酚 · 腐植酸 · SDS<br>宝锐 vs 供应商 A / B（匿名对照）</div></div>')
    return ('<div class="sec"><div class="sec-h"><i></i>实测数据</div>'
            '<div class="sec-n">内部测试 · 与常规体系同条件对比 · 模板 1:10 系列稀释</div>'
            '<div class="dgrid">%s</div></div>' % cards)


def body_sections():
    """装配中段顺序。
       数字 → 产品+货号 → 限时促销 → 产品特点 → 实测数据 → 扫码申请（用户指定顺序）
    """
    prod = ('<div class="figrow"><img src="%s"><img src="%s"><div><div class="pname">BR1D303 ＋ BR1E102</div>'
            '<div class="pen">RT-qPCR 一站式解决方案<br>逆转录预混液 + 快速 qPCR 预混液</div></div></div>'
            % (P1, P2))
    promo = ('<div class="promo"><div class="pi">%s</div><div><div class="pt">限时促销</div>'
             '<div class="pd">10.12 – 10.18</div>'
             '<div class="ps">活动期内下单享专属优惠，更可参加朋友圈点赞好礼活动</div></div></div>'
             % ICON['clock'])
    cta = ('<div class="cta"><img src="%s"><div class="ct"><h3>扫码申请试用装</h3>'
           '<p>BR1D303 ｜ BR1E102 ｜ BR1C103 三款均开放试用</p></div></div>' % QR_TRIAL_IMG)
    st, ft = stats(), feats()
    return st + prod + promo + ft + data_section() + cta


def fill(tpl, vals):
    n = tpl.count('%s')
    assert n == len(vals), '占位符错位：模板 %d 个 %%s，值 %d 个' % (n, len(vals))
    for v in vals:
        tpl = tpl.replace('%s', v, 1)
    return tpl


def stats(_v=None):
    cells = ''.join('<div class="st"><div class="sv">%s<small>%s</small></div>'
                    '<div class="sk">%s</div></div>' % s for s in STATS)
    return '<div class="stats-a">%s</div>' % cells


def feats(_v=None):
    rows = ''.join('<div class="fr"><div class="fn">%02d</div><div><div class="ft">%s</div>'
                   '<div class="fd">%s</div></div></div>' % (i, tt, dd)
                   for i, (_, tt, dd) in enumerate(FEATS, 1))
    return '<div class="feats-a">%s</div>' % rows


CSS_A = '''
*{margin:0;padding:0;box-sizing:border-box}
body{width:1080px;font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;-webkit-font-smoothing:antialiased}
.poster{width:1080px;padding:34px 40px 90px;position:relative;background:#FBFCFE url(%BG%) center/cover no-repeat}
.veil{position:absolute;inset:0;z-index:0;background:linear-gradient(180deg,rgba(255,255,255,.90) 0%,rgba(255,255,255,.83) 42%,rgba(255,255,255,.92) 100%)}
.inner{position:relative;z-index:2}
.topbar{height:166px;border-radius:40px;background:rgba(255,255,255,.86);backdrop-filter:blur(16px);
  -webkit-backdrop-filter:blur(16px);display:flex;align-items:center;justify-content:space-between;
  padding:0 46px;border:1px solid rgba(224,232,244,.9);box-shadow:0 10px 30px rgba(16,40,90,.10);margin-bottom:30px}
.topbar img{height:110px;display:block}
.ttag{font-size:30px;font-weight:700;color:#1852B6;border:2px solid rgba(24,82,182,.32);
  border-radius:999px;padding:5px 18px;letter-spacing:.5px;opacity:.92}
.tag{display:none}
.hook{margin-top:34px;font-size:48px;font-weight:700;color:#5A6475}
h1{margin-top:10px;font-size:84px;line-height:1.12;font-weight:900;color:#0F2A5C;letter-spacing:-2px}
.rule{width:120px;height:8px;background:#1852B6;margin:26px 0 22px}
.sub{font-size:48px;font-weight:700;color:#12203A;line-height:1.32}
.concl{margin-top:14px;font-size:30px;color:#4A5568;line-height:1.6}
.badge{display:inline-flex;align-items:center;gap:8px;margin-top:20px;font-size:30px;font-weight:700;
  color:#0F7B6C;border:2px solid rgba(15,123,108,.45);border-radius:999px;padding:4px 18px}
.badge svg{width:26px;height:26px}
.stats-a{display:flex;margin-top:30px;border-top:2px solid #E4E9F2;border-bottom:2px solid #E4E9F2}
.st{flex:1;padding:24px 6px;text-align:center;border-left:1px solid #E4E9F2}
.st:first-child{border-left:none}
.sv{font-size:84px;font-weight:900;color:#1852B6;line-height:1.02;letter-spacing:-2px}
.sv small{font-size:48px;font-weight:800}
.sk{font-size:30px;color:#5A6475;margin-top:4px}
.figrow{display:flex;align-items:center;gap:26px;margin-top:30px}
.figrow img{display:block;max-height:230px;max-width:430px;object-fit:contain}
.pname{font-size:48px;font-weight:900;color:#0F2A5C}
.pen{font-size:30px;color:#7A8598;margin-top:6px;line-height:1.5}
.hr{height:1px;background:#E4E9F2;margin:34px 0 0}
.sec{margin-top:26px;padding-top:18px;border-top:1px solid #E4E9F2}
.sec-h{font-size:48px;font-weight:900;color:#0F2A5C;display:flex;align-items:center;gap:14px}
.sec-h i{width:10px;height:34px;background:#1852B6;border-radius:3px;display:block}
.sec-n{font-size:30px;color:#8A93A6;margin-top:6px}
.dgrid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:16px}
.dcard{background:#fff;border:1px solid #E4E9F2;border-radius:16px;padding:12px 12px 14px}
.dcard img{display:block;width:100%;height:auto;border-radius:10px}
.dcap{font-size:30px;color:#5A6475;line-height:1.45;margin-top:10px}
.dnote{background:#F7F9FC;border:1px solid #E4E9F2;border-radius:16px;padding:14px 16px;display:flex;flex-direction:column;justify-content:center}
.dnote .dt{font-size:48px;font-weight:900;color:#0F2A5C}
.dnote .dd{font-size:30px;color:#5A6475;line-height:1.55;margin-top:8px}
.feats-a{margin-top:26px}
.fr{display:flex;gap:20px;padding:16px 0}
.fn{flex:0 0 62px;font-size:48px;font-weight:900;color:#C8D4E8;line-height:1.2}
.ft{font-size:48px;font-weight:900;color:#12203A;line-height:1.2}
.fd{font-size:30px;color:#5A6475;line-height:1.55;margin-top:6px}
.promo{margin-top:30px;border:3px solid #F2691E;border-radius:18px;padding:20px 26px;display:flex;
  align-items:center;gap:20px;background:#FFF9F4}
.promo .pi{flex:0 0 54px;height:54px;color:#F2691E}
.promo .pi svg{width:54px;height:54px}
.pt{font-size:30px;font-weight:800;color:#C2410C;letter-spacing:2px}
.pd{font-size:84px;font-weight:900;color:#E8443C;line-height:1.08;letter-spacing:-1px}
.ps{font-size:30px;color:#8A5A00;margin-top:4px}
.cta{margin-top:30px;display:flex;align-items:center;gap:24px;border:2px solid #E4E9F2;border-radius:18px;padding:18px 24px;background:#fff}
.cta img{display:block;width:200px;height:200px}
.cta .ct h3{font-size:48px;font-weight:900;color:#0F2A5C}
.cta .ct p{font-size:30px;color:#5A6475;margin-top:8px;line-height:1.5}
.foot{margin-top:36px;text-align:center;background:rgba(255,255,255,.92);border:1px solid #E4E9F2;
  border-radius:24px;padding:26px 24px;box-shadow:0 10px 30px rgba(16,40,90,.07);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
.slogan{font-size:48px;font-weight:900;color:#0F3A8A;letter-spacing:1px}
.qrs{display:flex;justify-content:center;gap:22px;margin-top:20px}
.qi2 img{display:block;width:176px;height:176px;border-radius:10px;background:#fff;border:1px solid #E4E9F2;padding:6px}
.qi2 span{display:block;font-size:30px;color:#5A6475;margin-top:6px;text-align:center}
.fcon{font-size:30px;color:#8A93A6;margin-top:18px;line-height:1.5}
.safe{height:60px}   /* 平台 UI 遮挡安全区 */
'''



def build(variant='A'):
    css = CSS_A
    h1 = 'RNA 定量难题<br>交给 <span style="color:#1852B6">RTQ</span>'
    css = css.replace('%BG%', BG)
    veil = '<div class="veil"></div>'
    top = ('<div class="topbar"><img src="%s"><div class="ttag">实验加速季 · 科研产品</div></div>'
               % LOGO_U)
    return fill('''<!DOCTYPE html><html><head><meta charset="utf-8"><style>%s</style></head>
<body><div class="poster">%s%s<div class="inner">
%s
<div class="hook">低丰度样本，卡在哪一步？</div>
<h1>%s</h1>
<div class="rule" style="%s"></div>
<div class="sub">高效逆转录 + 稳定 qPCR</div>
<div class="concl">5 min 完成逆转录，30 min 跑完 40 循环 —— 两管跑通 RNA 到结果</div>
<div class="badge">%s实测数据 · 内部验证</div>
%s
<div class="foot"><div class="slogan">%s</div>
  <div class="qrs"><div class="qi2"><img src="%s"><span>公众号</span></div>
  <div class="qi2"><img src="%s"><span>小红书</span></div>
  <div class="qi2"><img src="%s"><span>视频号</span></div></div>
  <div class="fcon">%s</div></div>
<div class="safe"></div>
</div></div></body></html>''', [
        css, '', veil, top, h1, '',
        ICON['check'], body_sections(),
        SLOGAN, QR_WX, QR_XHS, QR_SPH, FOOT])


VARIANTS = [('RTQ_方案A_极简信息图.png', 'RTQ 定稿版')]


def check(html, label):
    sizes = set(re.findall(r'font-size:\s*([\d.]+px)', html))
    bad = sizes - FS_ALLOWED
    flat = html.replace(' ', '')
    errs = []
    if bad:
        errs.append('字号越界 %s' % sorted(bad))
    if 'background-clip:text' in flat or '-webkit-text-fill-color:transparent' in flat:
        errs.append('渐变填充文字')
    if re.search(r'(?<!backdrop-)filter\s*:\s*blur', html):
        errs.append('CSS blur')
    print('  %s 门禁 [%s]：字号 %s' % ('✅' if not errs else '❌ ' + '／'.join(errs), label,
                                     '／'.join(sorted(sizes, key=lambda s: -float(s[:-2])))))
    return not errs


os.makedirs(DIR, exist_ok=True)
ok = True
with sync_playwright() as pw:
    br = pw.chromium.launch(channel='chrome')
    pg = br.new_page(viewport={'width': W, 'height': 900})
    for fn, label in VARIANTS:
        html = build('A')
        pg.set_content(html, wait_until='load')
        pg.wait_for_timeout(500)
        h = pg.evaluate("document.querySelector('.poster').getBoundingClientRect().height")
        pg.set_viewport_size({'width': W, 'height': int(h) + 4})
        pg.wait_for_timeout(200)
        out = os.path.join(DIR, fn)
        pg.screenshot(path=out, clip={'x': 0, 'y': 0, 'width': W, 'height': int(h)})
        im = Image.open(out)
        print('  → %s  %dx%d（1:%.2f）' % (fn, im.width, im.height, im.height / float(im.width)))
        ok = check(html, label) and ok
        for i, sel in enumerate(pg.query_selector_all('.qrs .qi2 img')):
            b = sel.bounding_box()
            Image.open(out).crop((int(b['x']), int(b['y']), int(b['x']+b['width']),
                                  int(b['y']+b['height']))).save('/tmp/v_%d.png' % i)
        im.resize((180, int(im.height * 180 / im.width)), Image.LANCZOS).save(
            '/tmp/thumb_%s.png' % fn.split('_')[1][-1])
    br.close()
print('✅ 全部通过' if ok else '❌ 有失败项')
