|
25 | 25 | ("short", "短线", "1-10个交易日"), |
26 | 26 | ) |
27 | 27 |
|
| 28 | +SITE_ICON_FILENAME = "favicon.svg" |
| 29 | +SITE_ICON_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> |
| 30 | + <defs> |
| 31 | + <linearGradient id="qsl-bg" x1="8" y1="6" x2="58" y2="62" gradientUnits="userSpaceOnUse"> |
| 32 | + <stop stop-color="#1e4dd8"/> |
| 33 | + <stop offset="0.56" stop-color="#00a6c8"/> |
| 34 | + <stop offset="1" stop-color="#d99b2b"/> |
| 35 | + </linearGradient> |
| 36 | + <filter id="qsl-shadow" x="-20%" y="-20%" width="140%" height="140%"> |
| 37 | + <feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#172033" flood-opacity=".28"/> |
| 38 | + </filter> |
| 39 | + </defs> |
| 40 | + <rect x="5" y="5" width="54" height="54" rx="16" fill="#172033"/> |
| 41 | + <path d="M18 43V25.5L29.2 36.7 44.8 20" fill="none" stroke="url(#qsl-bg)" stroke-width="5.5" stroke-linecap="round" stroke-linejoin="round" filter="url(#qsl-shadow)"/> |
| 42 | + <circle cx="18" cy="43" r="4" fill="#fffaf0"/> |
| 43 | + <circle cx="30" cy="36" r="4" fill="#fffaf0"/> |
| 44 | + <circle cx="46" cy="19" r="4.5" fill="#fffaf0"/> |
| 45 | + <path d="M45 45c-4 3.7-9.7 5-15.1 3.3-8.8-2.8-13.7-12.2-10.9-21 2.1-6.7 8.1-11.1 14.7-11.6" fill="none" stroke="#fffaf0" stroke-width="3.2" stroke-linecap="round" opacity=".88"/> |
| 46 | +</svg> |
| 47 | +""" |
| 48 | + |
28 | 49 |
|
29 | 50 | def slug(value: str) -> str: |
30 | 51 | text = re.sub(r"[^a-zA-Z0-9]+", "-", value.strip().lower()).strip("-") |
@@ -74,6 +95,21 @@ def format_candidate_theme_ids(candidate: dict[str, Any]) -> str: |
74 | 95 | return ", ".join(labels) or "无" |
75 | 96 |
|
76 | 97 |
|
| 98 | +def render_site_mark() -> str: |
| 99 | + return """ |
| 100 | + <span class="site-mark" aria-hidden="true"> |
| 101 | + <svg viewBox="0 0 64 64" focusable="false"> |
| 102 | + <rect x="5" y="5" width="54" height="54" rx="16"></rect> |
| 103 | + <path class="mark-line" d="M18 43V25.5L29.2 36.7 44.8 20"></path> |
| 104 | + <circle cx="18" cy="43" r="4"></circle> |
| 105 | + <circle cx="30" cy="36" r="4"></circle> |
| 106 | + <circle cx="46" cy="19" r="4.5"></circle> |
| 107 | + <path class="mark-ring" d="M45 45c-4 3.7-9.7 5-15.1 3.3-8.8-2.8-13.7-12.2-10.9-21 2.1-6.7 8.1-11.1 14.7-11.6"></path> |
| 108 | + </svg> |
| 109 | + </span> |
| 110 | + """ |
| 111 | + |
| 112 | + |
77 | 113 | def horizon_pick_score(pick: dict[str, Any], horizon: str) -> float: |
78 | 114 | score = pick.get("horizon_scores", {}).get(horizon, {}).get("score") |
79 | 115 | return float(score) if isinstance(score, (int, float)) else as_sortable_float(pick.get("combined_score")) |
@@ -232,12 +268,19 @@ def render_report_html(report: dict[str, Any]) -> str: |
232 | 268 | <meta charset="utf-8"> |
233 | 269 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
234 | 270 | <title>{html.escape(title)}</title> |
| 271 | + <link rel="icon" type="image/svg+xml" href="{SITE_ICON_FILENAME}"> |
235 | 272 | <link rel="alternate" type="application/rss+xml" title="量化模型推荐 RSS" href="feed.xml"> |
236 | 273 | <style> |
237 | 274 | :root {{ color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }} |
238 | 275 | body {{ margin: 0; background: #f6f7f9; color: #1b1f24; }} |
239 | 276 | main {{ max-width: 1080px; margin: 0 auto; padding: 32px 20px 56px; }} |
240 | 277 | .hero {{ text-align: center; padding: 8px 0 22px; margin-bottom: 10px; }} |
| 278 | + .site-mark {{ display: inline-flex; width: 58px; height: 58px; margin: 0 auto 14px; filter: drop-shadow(0 12px 22px rgb(9 105 218 / 18%)); }} |
| 279 | + .site-mark svg {{ width: 100%; height: 100%; }} |
| 280 | + .site-mark rect {{ fill: #172033; }} |
| 281 | + .site-mark .mark-line {{ fill: none; stroke: #00a6c8; stroke-width: 5.5; stroke-linecap: round; stroke-linejoin: round; }} |
| 282 | + .site-mark circle {{ fill: #fffaf0; }} |
| 283 | + .site-mark .mark-ring {{ fill: none; stroke: #fffaf0; stroke-width: 3.2; stroke-linecap: round; opacity: .88; }} |
241 | 284 | h1 {{ margin: 0; font-size: clamp(2rem, 5vw, 3.25rem); line-height: 1.15; letter-spacing: -0.04em; }} |
242 | 285 | .warning {{ background: #fff1f2; border: 1px solid #fecdd3; padding: 14px 16px; margin-bottom: 20px; }} |
243 | 286 | .final-decisions {{ margin-bottom: 20px; }} |
@@ -287,6 +330,7 @@ def render_report_html(report: dict[str, Any]) -> str: |
287 | 330 | <body> |
288 | 331 | <main> |
289 | 332 | <section class="hero"> |
| 333 | + {render_site_mark()} |
290 | 334 | <h1>{html.escape(title)}</h1> |
291 | 335 | </section> |
292 | 336 | {final_decisions_html} |
@@ -372,6 +416,7 @@ def render_index_html(reports: list[dict[str, Any]]) -> str: |
372 | 416 | <meta charset="utf-8"> |
373 | 417 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
374 | 418 | <title>量化模型推荐</title> |
| 419 | + <link rel="icon" type="image/svg+xml" href="{SITE_ICON_FILENAME}"> |
375 | 420 | <link rel="alternate" type="application/rss+xml" title="量化模型推荐 RSS" href="feed.xml"> |
376 | 421 | <style> |
377 | 422 | :root {{ |
@@ -410,6 +455,13 @@ def render_index_html(reports: list[dict[str, Any]]) -> str: |
410 | 455 | }} |
411 | 456 | main {{ max-width: 1180px; margin: 0 auto; padding: 34px 20px 64px; position: relative; }} |
412 | 457 | .hero {{ display: grid; grid-template-columns: 1fr auto; gap: 24px; align-items: end; padding: 22px 0 28px; }} |
| 458 | + .brand-lockup {{ display: flex; align-items: center; gap: 16px; margin-bottom: 16px; }} |
| 459 | + .site-mark {{ flex: 0 0 auto; display: inline-flex; width: 64px; height: 64px; filter: drop-shadow(0 18px 28px rgba(30,77,216,.22)); }} |
| 460 | + .site-mark svg {{ width: 100%; height: 100%; }} |
| 461 | + .site-mark rect {{ fill: #172033; }} |
| 462 | + .site-mark .mark-line {{ fill: none; stroke: var(--cyan); stroke-width: 5.5; stroke-linecap: round; stroke-linejoin: round; }} |
| 463 | + .site-mark circle {{ fill: var(--paper); }} |
| 464 | + .site-mark .mark-ring {{ fill: none; stroke: var(--paper); stroke-width: 3.2; stroke-linecap: round; opacity: .88; }} |
413 | 465 | .eyebrow {{ margin: 0 0 10px; color: var(--blue); font-weight: 800; letter-spacing: .16em; text-transform: uppercase; font-size: .78rem; }} |
414 | 466 | h1 {{ margin: 0; max-width: 780px; font-family: "Iowan Old Style", Georgia, ui-serif, serif; font-size: clamp(2.55rem, 7vw, 5.7rem); line-height: .92; letter-spacing: -.07em; }} |
415 | 467 | .hero p {{ margin: 18px 0 0; max-width: 680px; color: var(--muted); font-size: 1.06rem; line-height: 1.7; }} |
@@ -461,8 +513,13 @@ def render_index_html(reports: list[dict[str, Any]]) -> str: |
461 | 513 | <main> |
462 | 514 | <section class="hero"> |
463 | 515 | <div> |
464 | | - <p class="eyebrow">QuantStrategyLab</p> |
465 | | - <h1>量化模型推荐</h1> |
| 516 | + <div class="brand-lockup"> |
| 517 | + {render_site_mark()} |
| 518 | + <div> |
| 519 | + <p class="eyebrow">QuantStrategyLab</p> |
| 520 | + <h1>量化模型推荐</h1> |
| 521 | + </div> |
| 522 | + </div> |
466 | 523 | <p>把主题动量、市场确认和政策/新闻证据合成为非个性化研究结论。页面只展示推荐、周期、背景、理由和风险。</p> |
467 | 524 | </div> |
468 | 525 | <aside class="rss-card"> |
@@ -517,6 +574,9 @@ def publish_reports(report_paths: list[str | Path], output_dir: str | Path, *, s |
517 | 574 | path = output / report_filename(report) |
518 | 575 | path.write_text(render_report_html(report), encoding="utf-8") |
519 | 576 | written.append(path) |
| 577 | + icon_path = output / SITE_ICON_FILENAME |
| 578 | + icon_path.write_text(SITE_ICON_SVG, encoding="utf-8") |
| 579 | + written.append(icon_path) |
520 | 580 | index_path = output / "index.html" |
521 | 581 | index_path.write_text(render_index_html(reports), encoding="utf-8") |
522 | 582 | written.append(index_path) |
|
0 commit comments