mcpField
K
keynot

keynot

github.com/shawnzam文档处理1589 次安装26 周前更新
代码仓库

关于 keynot

Keynot 是用于制作单文件 HTML 浏览器演示文稿的技能,适合需要快速迭代或遵循品牌规范的演示内容。演示无需运行时外部依赖,并支持键盘、触控与全屏浏览。

主要功能

  1. 01生成独立 HTML 演示文稿
  2. 02键盘与触控导航
  3. 03全屏与动画呈现
  4. 04嵌入图片并匹配品牌样式
  5. 05打印或导出为 PDF
  6. 06提供多种幻灯片布局模式

适用场景

  1. 01制作可在浏览器打开的演示文稿
  2. 02按品牌指南设计演示页面
  3. 03将幻灯片打印或保存为 PDF

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/shawnzam/keynot --skill keynot

安装后在 Claude Code 中输入/keynot确认已加载。

详细介绍

Keynot

用于构建基于浏览器的幻灯片演示文稿的技能,生成单个、自包含的 HTML 文件。支持键盘导航、滑动、全屏、动画逐步显示、嵌入图片和精准还原品牌设计。运行时无需外部依赖。


何时使用此技能

  • 用户要求制作“演示文稿”“演示稿”“幻灯片”或“用于演示的单页材料”
  • 用户提供了品牌风格指南,并希望幻灯片与其保持一致
  • 用户希望在浏览器中快速迭代幻灯片
  • 用户希望获得一个可在任何地方打开、无需安装软件的文件

除非满足以下条件,否则优先使用 HTML,而不是 PPTX:

  • 用户明确要求 .pptx
  • 用户需要在 PowerPoint 中编辑幻灯片
  • 用户需要与必须在 Office 中编辑的人分享

第 1 步:提取品牌指南

如果用户提供了品牌 PDF、URL 或品牌描述,请在编写任何代码之前提取以下信息。务必明确、准确。

要提取的内容

颜色

  • 主色(主要的背景色/文字色)
  • 强调色(号召性用语、线条、高亮)
  • 中性色(背景色、奶油色、灰色)
  • 记录准确的十六进制代码,绝不估算

字体

  • 衬线字体(展示性标题、引言)
  • 无衬线字体(正文、标签、UI)
  • 如果指定了专有字体(例如 ITC Stone Serif、Acumin),请寻找最接近的 Google Fonts 字体
  • 明确记录字体搭配

布局风格

  • 主色使用比例(例如:“始终以蓝色为主,红色仅用作强调色”)
  • 指南中常见的布局模式(分栏面板、线条、分隔线、边距)
  • 风格:正式/编辑风、温暖/亲和、极简/密集

徽标与标识规则

  • 是否允许复现徽标/文字标识
  • 留白要求
  • 获准使用的颜色版本(全彩、白色、黑色)
  • 注意:对于机构品牌(大学、企业),请勿复现商标徽标,改用文字形式的标识
品牌记录示例

提取完成后,将品牌参数记录在演示文稿 <style> 顶部的精简代码块中,以便于审核和替换:

Primary Colors:
  Primary:  #HEX  (dominant — backgrounds, headings)
  Accent:   #HEX  (rules, CTAs, status — used sparingly)
  Cream:    #HEX  (light slide backgrounds)
 
Usage Rule: One color dominates. Accent appears sparingly —
  rules, call-to-action bars, status indicators.
  Never use the accent color as a full background.
 
Typography:
  Serif:      <Brand serif>      → <closest Google Font>
  Sans-serif: <Brand sans-serif> → <closest Google Font>
 
Layout Language:
  - Which colors own which regions (dark panels vs. light panels)
  - Any spine bars, rules, or dividers the brand uses
  - Layout motifs (split panels, stat columns, card grids)
  - Tone: editorial / warm / minimal / dense
 
Logo Rule: For institutional or trademarked brands, do NOT
  reproduce the logo. Use a text-based wordmark instead:
  <div class="wordmark">
    <div class="w-line-1">Organization</div>
    <div class="w-line-2">Subtitle or parent entity</div>
  </div>

第 2 步:演示文稿架构

每份演示文稿都是一个 HTML 文件。所有资源(通过 CDN 加载的字体、以 base64 编码的图片)都嵌入其中。

外壳结构
<!DOCTYPE html>
<html lang="en">
<head>
  
  
</head>
<body>
  <div class="deck" id="deck">
    <div class="slide" id="s1">...</div>
    <div class="slide" id="s2">...</div>
    
  </div>
  
  <div class="nav">
    <button id="prev">&#8592;</button>
    <div class="dots" id="dots"></div>
    <button id="next">&#8594;</button>
    <span class="counter" id="counter">1 / N</span>
    <button id="fsBtn" onclick="toggleFS()">&#x26F6;</button>
  </div>
  
  <div class="rotate-overlay" aria-hidden="true">
    <svg class="rotate-icon" width="56" height="56" viewBox="0 0 24 24"
         fill="none" stroke="currentColor" stroke-width="1.4"
         stroke-linecap="round" stroke-linejoin="round">
      <rect x="5" y="2" width="14" height="20" rx="2.5" />
      <line x1="12" y1="18.5" x2="12.01" y2="18.5" />
    </svg>
    <h2>Rotate your device</h2>
    <p>This deck is designed for landscape viewing. Turn your phone sideways to continue.</p>
  </div>
  <script></script>
</body>
</html>
核心 CSS 模式
:root {
  /* Always use CSS variables for brand colors — swap these per brand */
  --primary: #1a1a2e;   /* dominant: dark panels, headings */
  --accent:  #e94560;   /* sparingly: rules, CTAs, status */
  --white:   #ffffff;
  --cream:   #f7f4ee;   /* light slide backgrounds */
  --serif:   'Cormorant Garamond', Georgia, serif;
  --sans:    'DM Sans', system-ui, sans-serif;
}
 
/* Deck shell */
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
.deck { width: 100vw; height: 100vh; position: relative; }
 
/* Slides: opacity transition, stacked absolutely */
.slide {
  position: absolute; inset: 0;
  opacity: 0; pointer-events: none;
  transition: opacity .55s cubic-bezier(.4,0,.2,1);
}
.slide.active { opacity: 1; pointer-events: all; }
 
/* Staggered reveal animation on active slide children */
.slide.active .reveal { animation: fadeUp .6s both; }
.slide.active .reveal:nth-child(2) { animation-delay: .1s; }
.slide.active .reveal:nth-child(3) { animation-delay: .2s; }
/* ...continue for as many children as needed */
 
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: translateY(0); }
}
导航 CSS
.nav {
  position: fixed; bottom: 28px; left: 50%;
  transform: translateX(-50%);
  z-index: 999;
  display: flex; align-items: center; gap: 14px;
  background: rgba(26,26,46,.75);  /* tint from --primary */
  backdrop-filter: blur(10px);
  padding: 9px 20px; border-radius: 40px;
  border: 1px solid rgba(255,255,255,.14);
  opacity: 1;
  transition: opacity .4s ease;   /* required for auto-hide */
}
.nav-hidden { opacity: 0; pointer-events: none; }  /* auto-hide state */
.nav button {
  background: none; border: none; cursor: pointer;
  color: rgba(255,255,255,.65); font-size: 16px;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  transition: color .2s, background .2s;
}
.nav button:hover { color: #fff; background: rgba(255,255,255,.12); }
.dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: rgba(255,255,255,.3); cursor: pointer;
  transition: background .2s, transform .2s;
}
.dot.active { background: var(--accent); transform: scale(1.35); }
JavaScript(完整代码——请勿修改算术逻辑)
// Fullscreen
function toggleFS() {
  if (!document.fullscreenElement) {
    document.documentElement.requestFullscreen().catch(() => {});
    document.getElementById('fsBtn').innerHTML = '&#x2715;';
  } else {
    document.exitFullscreen();
    document.getElementById('fsBtn').innerHTML = '&#x26F6;';
  }
}
document.addEventListener('fullscreenchange', () => {
  if (!document.fullscreenElement)
    document.getElementById('fsBtn').innerHTML = '&#x26F6;';
});
 
// Deep-link & session persistence helpers
function parseHash() {
  const m = location.hash.match(/^#slide-(\d+)$/);
  if (!m) return -1;
  const idx = parseInt(m[1], 10) - 1;  // hash is 1-based, array is 0-based
  return (idx >= 0 && idx < document.querySelectorAll('.slide').length) ? idx : -1;
}
function initialSlide() {
  const fromHash = parseHash();
  if (fromHash >= 0) return fromHash;
  const stored = sessionStorage.getItem('keynot-slide');
  if (stored !== null) {
    const idx = parseInt(stored, 10);
    if (idx >= 0 && idx < document.querySelectorAll('.slide').length) return idx;
  }
  return 0;
}
function persistSlide(idx) {
  sessionStorage.setItem('keynot-slide', idx);
  history.replaceState(null, '', '#slide-' + (idx + 1));
}

// Navigation
const slides = document.querySelectorAll('.slide');  // REQUIRED — do not omit
const dotsEl = document.getElementById('dots');
const counter = document.getElementById('counter');
let cur = initialSlide();

// Remove default 'active' class from slide 1 if restoring a different slide
slides.forEach(s => s.classList.remove('active'));

slides.forEach((_, i) => {
  const d = document.createElement('div');
  d.className = 'dot';
  d.addEventListener('click', () => go(i));
  dotsEl.appendChild(d);
});

// Activate the initial slide
slides[cur].classList.add('active');
dotsEl.children[cur].classList.add('active');
counter.textContent = `${cur + 1} / ${slides.length}`;
persistSlide(cur);

function go(n) {
  slides[cur].classList.remove('active');
  dotsEl.children[cur].classList.remove('active');
  cur = (n + slides.length) % slides.length;
  slides[cur].classList.add('active');
  dotsEl.children[cur].classList.add('active');
  counter.textContent = `${cur + 1} / ${slides.length}`;
  persistSlide(cur);
}

// Deep-link: respond to manual hash changes or pasted URLs
window.addEventListener('hashchange', () => {
  const idx = parseHash();
  if (idx >= 0 && idx !== cur) go(idx);
});
 
document.getElementById('next').addEventListener('click', () => go(cur + 1));
document.getElementById('prev').addEventListener('click', () => go(cur - 1));
 
document.addEventListener('keydown', e => {
  if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ') go(cur + 1);
  if (e.key === 'ArrowLeft'  || e.key === 'ArrowUp')                     go(cur - 1);
  if (e.key === 'f' || e.key === 'F') toggleFS();
});
 
// Auto-hide nav after 1.8s of inactivity; reappear on any interaction
const nav = document.querySelector('.nav');
let hideTimer;
function showNav() {
  nav.classList.remove('nav-hidden');
  clearTimeout(hideTimer);
  hideTimer = setTimeout(() => nav.classList.add('nav-hidden'), 1800);
}
document.addEventListener('mousemove', showNav);
document.addEventListener('touchstart', showNav, { passive: true });
document.addEventListener('keydown', showNav);
showNav(); // start timer on load
 
// Swipe
let tx = 0;
document.addEventListener('touchstart', e => { tx = e.touches[0].clientX; }, { passive: true });
document.addEventListener('touchend', e => {
  const dx = e.changedTouches[0].clientX - tx;
  if (Math.abs(dx) > 50) go(dx < 0 ? cur + 1 : cur - 1);
}, { passive: true });
纵向屏幕/移动设备处理

幻灯片演示文稿是横向媒介。与其将分栏面板布局重新排布成难以阅读的移动端堆叠布局,不如在视口为纵向且较窄时显示“旋转设备”提示层。这种做法适用于竖屏使用的 iPhone,无需为每种布局模式编写两套 CSS。横屏手机仍可正常浏览,因为 clamp() 字体大小已能自适应缩小。

请将此 CSS 添加到每份演示文稿的 <style> 块靠近底部的位置,确保不覆盖任何样式:

/* Rotate-to-landscape overlay — only visible on portrait phones */
.rotate-overlay {
  display: none;
  position: fixed; inset: 0;
  z-index: 9999;
  background: var(--primary);
  color: var(--white);
  flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; padding: 40px;
  font-family: var(--sans);
}
.rotate-overlay .rotate-icon {
  margin-bottom: 24px;
  color: var(--accent);
  animation: rotate-hint 2.4s ease-in-out infinite;
}
.rotate-overlay h2 {
  font-family: var(--serif);
  font-size: 30px; font-weight: 400;
  margin-bottom: 12px; letter-spacing: -.01em;
}
.rotate-overlay p {
  font-size: 14px; line-height: 1.6;
  opacity: .72; max-width: 280px;
}
@keyframes rotate-hint {
  0%, 40%, 100% { transform: rotate(0deg); }
  60%, 80%      { transform: rotate(90deg); }
}
@media (orientation: portrait) and (max-width: 900px) {
  .rotate-overlay { display: flex; }
}

提示层 HTML 已包含在上方的外壳结构中。将两者都加入后,无需对单张幻灯片进行任何修改,演示文稿即可妥善适配纵向手机屏幕。

为何不实现完整的移动端响应式布局? 分栏面板、数据列和照片背景都依赖横向空间来传达信息。重新排布后的移动版本实际上会变成另一份演示文稿,而且效果更差。PowerPoint 和 Google Slides 在纵向移动屏幕上也无法正常显示;受众早已接受这种格式的局限。

打印 / PDF 导出

用户应能按 Cmd+P →“另存为 PDF”,生成每页一张幻灯片的清晰 PDF。屏幕版幻灯片会以绝对定位方式堆叠,并通过 opacity: 0 隐藏非活动幻灯片,因此直接打印只会捕获当前活动幻灯片。解决方法:添加一个 @media print 代码块,将所有幻灯片取消堆叠、禁用动画、隐藏导航和叠加层,并强制设置横向页面尺寸。

将以下内容添加到 <style> 代码块靠近底部的位置:

/* Print / PDF export — Cmd+P → Save as PDF gives one slide per page */
@media print {
  @page {
    size: 1600px 1000px;  /* native deck aspect ratio; user can fit-to-page */
    margin: 0;
  }
  html, body {
    width: 1600px; height: auto;
    overflow: visible !important;
    background: #fff;
  }
  .deck {
    width: 1600px; height: auto;
    position: static !important;
  }
  .slide {
    position: relative !important;
    inset: auto !important;
    width: 1600px; height: 1000px;
    opacity: 1 !important;
    pointer-events: auto !important;
    page-break-after: always;
    break-after: page;
    transform: none !important;
  }
  /* Kill all animations and transitions so content is in final state */
  *, *::before, *::after {
    animation: none !important;
    animation-duration: 0s !important;
    animation-delay: 0s !important;
    transition: none !important;
  }
  .slide .reveal {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Hide interactive chrome */
  .nav, .rotate-overlay { display: none !important; }
  /* Preserve backgrounds and colors in print output */
  * {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
  }
}

用法: 用户在浏览器中打开幻灯片,按 Cmd+P(或 Ctrl+P),将“另存为 PDF”选为目标;如果打印对话框提供“背景图形”选项,也可以选择开启。对话框中的默认纸张尺寸影响不大——@page 规则会设置内容框架,浏览器会缩放以适应页面。若要获得像素级精准的输出,用户可以在对话框中选择“自定义”纸张尺寸,并设为 1600 × 1000。

注意事项:

  • 渐变和部分照片面板的栅格化效果在打印时可能与屏幕显示略有不同(Chrome 和 Safari 的处理方式不同)。如果幻灯片在打印时必须保持完全一致,请使用纯色背景。
  • 高度超过 1000px 的幻灯片会被裁切。只要屏幕上内容不超出幻灯片边界,打印时就不会被裁切。
  • Firefox 对 @page size: <pixels> 的支持不如 Chrome 和 Safari 稳定。对于使用 Firefox 的关键用户,可改用 size: 10in 6.25in(比例相同,使用物理单位)。

第 3 步:布局模式

将以下模式作为构建模块,并根据每张幻灯片的需要灵活组合。

分栏面板(用途最广)

左侧为深色面板(品牌主色),右侧为浅色面板(白色或奶油色)。两者之间可使用斜切分隔或直线分隔。

<div class="slide" id="sN" style="display:flex;">
  <div class="left-panel"></div>
  <div class="right-panel"></div>
</div>
.left-panel {
  width: 50%; background: var(--primary);
  display: flex; flex-direction: column; justify-content: center;
  padding: 72px 64px; position: relative; overflow: hidden;
}
.right-panel {
  flex: 1;
  display: flex; flex-direction: column; justify-content: center;
  padding: 72px 56px 72px 64px; background: var(--cream);
}
数据列(资历、指标)

右侧列放置较大的衬线数字或文字,并配以小号大写标签。适用于照片背景。

<div class="stats-col">
  <div class="stat">
    <div class="stat-n">13+</div>
    <div class="stat-l">Years at institution</div>
  </div>
  
</div>
价值观卡片(原则、支柱)

左侧使用彩色强调条,并搭配标题和说明。每项价值观使用一种条形颜色,以便在视觉上区分。

<div class="value-card">
  <div class="value-bar" style="background:#2e7d32"></div>
  <div>
    <div class="value-title">Principle Name</div>
    <div class="value-text">Description of the principle.</div>
  </div>
</div>
方法步骤行(流程、步骤)

使用醒目的浅色数字、竖线,然后是正文内容。适用于 3 步流程。

<div class="approach">
  <div class="approach-num">01</div>
  <div class="approach-body">
    <div class="approach-title">Step Title</div>
    <div class="approach-text">Detail text.</div>
    <div class="approach-tags"><span>Tag 1</span><span>Tag 2</span></div>
  </div>
</div>
工具 / 状态卡片

名称 + 状态徽章 + 说明。状态徽章样式:已上线(绿色)、进行中(琥珀色)、计划中(中性色)。

<div class="tool-card">
  <div class="tool-body">
    <div class="tool-name">Tool Name</div>
    <span class="status status-live">Live</span>
    <div class="tool-desc">What it does and who it's for.</div>
  </div>
</div>
照片背景面板

将图片嵌入为 base64,并应用渐变叠层以确保文字清晰易读。

<div class="photo-panel" style="
  position:absolute; top:0; right:0; bottom:0; width:42%;
  background: url('data:image/png;base64,...') center/cover no-repeat;
  z-index:1;
">
  <div style="position:absolute;inset:0;
    background:linear-gradient(to right, var(--primary) 0%, rgba(0,0,0,.55) 40%, rgba(0,0,0,.1) 100%);
  "></div>
</div>

将图片嵌入为 base64:

import base64
data = 'data:image/png;base64,' + base64.b64encode(open('image.png','rb').read()).decode()

第 4 步:内容幻灯片顺序

机构介绍可采用以下强有力的 5 页幻灯片结构:

幻灯片目的布局
1介绍你是谁 + 简短推介全幅深色背景 + 照片 + 数据列
2价值观 / 原则分栏面板 + 价值观卡片
3方法 / 框架浅色背景 + 方法步骤行
4工具 / 赋能深色背景 + 两列布局
5总结 + 开启讨论分栏面板 + 照片背景

可自由调整。关键在于每张幻灯片只承担一个任务,并且只有一个主导性的视觉构想。


第 5 步:排版规则

/* Display heading — large, light weight, letter-spaced */
.display {
  font-family: var(--serif);
  font-size: clamp(52px, 7vw, 88px);
  font-weight: 300; line-height: .95;
  letter-spacing: -.01em;
}
 
/* Section heading */
.heading {
  font-family: var(--serif);
  font-size: clamp(32px, 4vw, 50px);
  font-weight: 400; line-height: 1.1;
}
 
/* Eyebrow label */
.label {
  font-family: var(--sans);
  font-size: 10px; font-weight: 600;
  letter-spacing: .2em; text-transform: uppercase;
}
 
/* Body */
.body {
  font-family: var(--sans);
  font-size: 13px; font-weight: 300; line-height: 1.65;
}
 
/* Use clamp() for all font sizes to scale with viewport */

关键注意事项

绝不要对包含 JavaScript 的文件进行不加区分的字符串 / 正则表达式替换

这样会破坏算术运算符。错误示例如下:

# DANGEROUS — corrupts JS
content = content.replace(' - ', ', ')
# cur - 1  becomes  cur, 1     (prev button breaks)
# clientX - tx  becomes  clientX, tx  (swipe breaks)

安全做法: 使用目标明确、内容具体且唯一的 str_replace。每次自动替换后,都要检查 JS 部分,确认算术运算符完好无损。

使用前始终先声明 const slides

如果在导航代码块之前插入新函数,请检查 const slides = document.querySelectorAll('.slide') 仍然存在,并且位于 slides.forEach(...) 之前。

str_replace 要求目标字符串唯一

使用 str_replace 前,先通过 grep 查找目标字符串,确认它只出现一次。如果出现多次,请增加更多上下文行,使目标字符串具有唯一性。

Base64 图像会增大文件,但内容完整独立

一张高分辨率 PNG 就可能让文件增大 150-200KB。对于演示文稿来说,这没有问题。使用 cover 尺寸和 center center 定位。始终添加渐变覆盖 div,以确保文字清晰易读。

内容中的 Emoji

避免在列表项和卡片标题开头使用 Emoji——这会让内容显得像 AI 生成,而且很快就会过时。如果用户要求移除 Emoji,请使用 Python Unicode 替换,而不是 sed:

import re
content = re.sub(r'[\U00002600-\U0001FAFF]+', '', content)
content = content.replace('\ufe0f', '')  # variation selector

迭代编辑工作流

初次构建后进行修改时:

  1. 先查看相关部分 — 使用带行范围的 view 工具
  2. 使用 str_replace 进行精确修改 — 小改动时绝不要重写整个文件
  3. 自动处理文本后验证 JS — 检查包含 cur、dx、+、- 运算符的行
  4. 修改后展示最新文件 — 用户可能正在查看缓存版本

当用户提出类似“幻灯片 3:修改 X”的要求时——只做这一处修改。不要顺手改进相邻内容。


Codex / API 使用说明

此技能兼容 Claude API(Codex)的使用。主要注意事项:

  • 完整的 JS 代码块(导航 + 全屏)应作为一个整体处理——原样包含,不要修改算术运算
  • 通过 API 生成时,将文件流式写入磁盘,并在浏览器中打开以供预览
  • Base64 嵌入方式在 API 环境中同样适用
  • CSS 变量(--primary、--accent 等)便于以编程方式设置主题——将品牌颜色作为参数传入并替换模板中的对应值

PPTX 导出(PowerPoint)

当用户明确要求 .pptx 文件时,生成一个使用 python-pptx 以编程方式构建演示文稿的 Python 脚本。用户运行脚本的命令如下:

uv run --with python-pptx <script>.py

无需全系统安装——uv 会内联处理依赖项。

何时使用 PPTX 而不是 HTML
  • 用户明确要求 .pptx 或“PowerPoint”
  • 用户需要收件人能够在 Office 中编辑幻灯片
  • 用户表示“我需要把它分享给使用 PowerPoint 的人”
工作原理

与 HTML 相同,提取内容并应用品牌逻辑——从提示词中提取颜色、字体和布局意图。然后生成独立的 .py 脚本,而不是 .html 文件。

PPTX 生成模式
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE

# Brand colors mapped from CSS variables
PRIMARY = RGBColor(0x1A, 0x1A, 0x2E)
ACCENT = RGBColor(0xE9, 0x45, 0x60)

prs = Presentation()
prs.slide_width = Inches(16)
prs.slide_height = Inches(10)
blank_layout = prs.slide_layouts[6]  # blank — full control

def set_slide_bg(slide, color):
    fill = slide.background.fill
    fill.solid()
    fill.fore_color.rgb = color

def add_text_box(slide, left, top, width, height, text,
                 font_size=18, bold=False, italic=False,
                 color=RGBColor(0xFF,0xFF,0xFF), font_name="Arial"):
    txBox = slide.shapes.add_textbox(left, top, width, height)
    tf = txBox.text_frame
    tf.word_wrap = True
    p = tf.paragraphs[0]
    p.text = text
    p.font.size = Pt(font_size)
    p.font.bold = bold
    p.font.italic = italic
    p.font.color.rgb = color
    p.font.name = font_name

# Build slides...
slide1 = prs.slides.add_slide(blank_layout)
set_slide_bg(slide1, PRIMARY)
add_text_box(slide1, Inches(0.8), Inches(3), Inches(10), Inches(3),
             "Title Here", font_size=72, font_name="Georgia")

prs.save("output.pptx")
print("Saved: output.pptx")
将 HTML 布局映射到 PPTX
HTML 模式PPTX 对应形式
分栏面板(左侧深色 / 右侧浅色)使用矩形作为左侧背景,并在两侧放置文本框
数据列使用大字号文本框,下方放置小号标签文本框
带彩色条的价值卡片使用细矩形作为强调色条,并在旁边放置文本框
方法步骤行(带编号)使用大号数字文本框,右侧错位放置标题和正文文本框
全幅背景使用品牌颜色调用 set_slide_bg()
重要注意事项

提醒用户: PPTX 输出只是对 HTML 演示文稿的近似还原,并非像素级复刻。python-pptx 不支持 CSS、渐变、clamp() 缩放或 Web 字体。生成的演示文稿将会:

  • 使用系统字体(Arial、Georgia)作为 Google Fonts 的后备字体
  • 使用纯色近似渐变
  • 跳过动画和渐显效果(静态幻灯片)
  • 需要手动微调以实现精确对齐

务必告知用户:“.pptx 是一个不错的起点,但可能需要在 PowerPoint 中进行少量调整——演示前请检查对齐和字体渲染效果。”

字体映射
HTML(Google 字体)PPTX 后备字体
Cormorant GaramondGeorgia
DM SansArial
Playfair DisplayGeorgia
InterArial
任意衬线字体Georgia 或 Times New Roman
任意无衬线字体Arial 或 Calibri

快速参考:调色板槽位

每个演示文稿都要填充这些槽位。先根据品牌指南填写,再通过 CSS 变量以编程方式应用主题。

--primary     Dominant backgrounds and dark panels
--accent      Rules, spine bars, CTAs, status dots, hover states
--cream       Light slide backgrounds (warmer than pure white)
--stone       Dividers, subtle borders (one tint above cream)
--ink         Body text on light backgrounds (softer than black)
--muted       Labels, metadata, secondary text
--go          Positive / live / green status
--warn        Review / caution / amber status
--alt         Fifth accent for value/pillar differentiation

在 <style> 顶部填入真实的十六进制颜色代码,并通过 var(--name) 在各处引用。

评论

支持 Markdown