keynot

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Keynot

Keynot

A skill for building browser-based slide presentations as single, self-contained HTML files. Keyboard navigation, swipe, fullscreen, animated reveals, embedded images, brand-accurate design. No external dependencies required at runtime.

一款用于构建基于浏览器的幻灯片演示文稿的技能,生成的演示文稿为单一、独立的HTML文件。支持键盘导航、滑动操作、全屏显示、动画展示、嵌入图片以及符合品牌规范的设计。运行时无需任何外部依赖。

When to Use This Skill

何时使用此技能

  • User asks for a "presentation", "deck", "slides", "one-pager to present"
  • User has a brand style guide and wants slides that match it
  • User wants to iterate on slides quickly in the browser
  • User wants a file they can open anywhere without software
Prefer HTML over PPTX unless:
  • User explicitly asks for
    .pptx
  • User needs to edit slides in PowerPoint
  • User needs to share with someone who must edit in Office

  • 用户要求制作“演示文稿”“幻灯片组”“幻灯片”“用于展示的单页内容”
  • 用户拥有品牌风格指南,并希望幻灯片与之匹配
  • 用户希望在浏览器中快速迭代修改幻灯片
  • 用户希望获得一个无需软件即可在任意设备打开的文件
优先选择HTML而非PPTX,除非:
  • 用户明确要求
    .pptx
    格式
  • 用户需要在PowerPoint中编辑幻灯片
  • 用户需要与必须使用Office编辑的人分享

Step 1: Extract the Brand Guide

步骤1:提取品牌指南信息

If the user provides a brand PDF, URL, or describes their brand, extract these before writing any code. Be explicit and precise.
如果用户提供品牌PDF、URL或描述其品牌风格,在编写任何代码前先提取这些信息。提取时需明确且精准。

What to Extract

需要提取的内容

Colors
  • Primary (dominant background/text color)
  • Accent (calls to action, rules, highlights)
  • Neutrals (background, cream, gray)
  • Document exact hex codes — never approximate
Typography
  • Serif font (display headings, pull quotes)
  • Sans-serif font (body, labels, UI)
  • If proprietary fonts are specified (e.g., ITC Stone Serif, Acumin), find the closest Google Fonts equivalent
  • Document the pairing explicitly
Layout Language
  • Dominant color usage ratio (e.g., "blue should always dominate, red as accent only")
  • Typical layout patterns visible in the guide (split panels, rules, dividers, margins)
  • Tone: formal/editorial, warm/approachable, minimal/dense
Logo and Mark Rules
  • Whether the logo/wordmark may be reproduced
  • Clear space requirements
  • Approved color variants (full color, white, black)
  • Note: for institutional brands (universities, corporations), do NOT reproduce trademarked logos — use a text-based wordmark instead
颜色
  • 主色调(主要背景/文本颜色)
  • 强调色(用于号召性按钮、分隔线、高亮内容)
  • 中性色(背景色、米白色、灰色)
  • 记录精确的十六进制代码——绝不近似
排版
  • 衬线字体(用于标题、引用文本)
  • 无衬线字体(用于正文、标签、UI)
  • 如果指定了专有字体(如ITC Stone Serif、Acumin),找到最接近的Google Fonts替代字体
  • 明确记录字体搭配
布局规范
  • 主色调使用比例(例如:“蓝色应始终为主色调,红色仅作为强调色”)
  • 指南中可见的典型布局模式(分栏面板、分隔线、边距)
  • 风格基调:正式/编辑风、温暖/亲和风、极简/密集风
Logo及标识规则
  • 是否允许复制Logo/文字标识
  • 留白要求
  • 批准使用的颜色变体(全彩、白色、黑色)
  • 注意:对于机构品牌(大学、企业),请勿复制商标Logo——改用基于文本的文字标识

Example Brand Record

品牌信息记录示例

Once extracted, record the brand parameters in a compact block at the top of the deck's
<style>
so they're easy to audit and swap:
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>

提取完成后,将品牌参数记录在演示文稿
<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>

Step 2: Deck Architecture

步骤2:演示文稿架构

Every deck is a single HTML file. All assets (fonts via CDN, images via base64) are embedded.
每个演示文稿都是单一HTML文件。所有资源(通过CDN引入的字体、通过base64嵌入的图片)均内嵌在文件中。

Shell Structure

基础结构

html
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Google Fonts import -->
  <!-- All CSS in <style> tag -->
</head>
<body>
  <div class="deck" id="deck">
    <div class="slide" id="s1">...</div>
    <div class="slide" id="s2">...</div>
    <!-- more slides -->
  </div>
  <!-- Navigation -->
  <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>
  <!-- Portrait/mobile rotate hint (see Portrait / Mobile Handling below) -->
  <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><!-- All JS inline --></script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Google Fonts import -->
  <!-- All CSS in <style> tag -->
</head>
<body>
  <div class="deck" id="deck">
    <div class="slide" id="s1">...</div>
    <div class="slide" id="s2">...</div>
    <!-- more slides -->
  </div>
  <!-- Navigation -->
  <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>
  <!-- Portrait/mobile rotate hint (see Portrait / Mobile Handling below) -->
  <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><!-- All JS inline --></script>
</body>
</html>

Core CSS Patterns

核心CSS模式

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
: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); }
}

Navigation CSS

导航CSS

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); }
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 (complete — do not modify arithmetic)

JavaScript (complete — do not modify arithmetic)

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 });
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 });

Portrait / Mobile Handling

竖屏/移动端适配

Slide decks are a landscape medium. Rather than reflowing split-panel layouts into unreadable mobile stacks, show a "rotate your device" overlay when the viewport is portrait + narrow. This covers iPhones held vertically without doubling the CSS for every layout pattern. Landscape phone viewing keeps working because
clamp()
font sizing already scales down.
Add this CSS to every deck, near the bottom of the
<style>
block so it overrides nothing:
css
/* 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; }
}
The overlay HTML is included in the Shell Structure above. Drop both in and the deck handles portrait phones gracefully without any per-slide changes.
Why not full mobile responsiveness? Split panels, stat columns, and photo backgrounds all rely on horizontal real estate to communicate. A reflowed mobile version is effectively a different deck — and a worse one. PowerPoint and Google Slides also fail on portrait mobile; the audience already accepts this for the format.
幻灯片是横屏媒介。与其将分栏布局重排为难以阅读的移动端堆叠布局,不如在视口为竖屏且较窄时显示“旋转设备”提示层。这可以覆盖竖屏持握的iPhone,而无需为每种布局模式加倍编写CSS。横屏手机仍可正常查看,因为
clamp()
字体大小已自动缩放。
将以下CSS添加到每个演示文稿的
<style>
标签底部,确保不会覆盖其他样式:
css
/* 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在竖屏移动端也表现不佳,受众已经接受这种格式的局限性。

Print / PDF Export

打印/PDF导出

Users should be able to hit
Cmd+P
→ "Save as PDF" and get a clean one-slide-per-page PDF. The screen deck stacks slides absolutely and hides inactive ones via
opacity: 0
, so a naive print captures only the active slide. The fix: a
@media print
block that un-stacks every slide, kills animations, hides nav/overlays, and forces landscape page size
.
Add this near the bottom of the
<style>
block:
css
/* 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;
  }
}
Usage: user opens the deck in a browser, hits
Cmd+P
(or
Ctrl+P
), picks "Save as PDF" as the destination, and optionally toggles "Background graphics" on if the print dialog offers it. The default paper size in the dialog doesn't matter much — the
@page
rule sets the content frame, and browsers scale to fit. For pixel-perfect output, users can pick "Custom" paper size matching
1600 × 1000
in the dialog.
Gotchas:
  • Gradients and some photo-panel rasterization can look slightly different from screen (Chrome and Safari differ). If the deck must look identical in print, keep backgrounds solid.
  • Slides taller than 1000px will clip. Don't let content overflow the slide bounds on screen and it won't clip in print.
  • Firefox honors
    @page size: <pixels>
    less reliably than Chrome/Safari. For critical Firefox users, swap to
    size: 10in 6.25in
    (same ratio, physical units).

用户应能通过按下
Cmd+P
→ “另存为PDF”来获取每页一张幻灯片的清晰PDF。屏幕版演示文稿将幻灯片绝对定位堆叠,并通过
opacity: 0
隐藏非活动幻灯片,因此直接打印只会捕获当前活动幻灯片。解决方法:添加
@media print
区块,取消幻灯片堆叠、禁用动画、隐藏导航/提示层,并强制页面为横屏
将以下代码添加到
<style>
标签底部:
css
/* 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
    (相同比例,使用物理单位)。

Step 3: Layout Patterns

步骤3:布局模式

Use these as building blocks. Mix and match per slide.
将这些作为构建模块,可根据幻灯片需求混合搭配。

Split Panel (most versatile)

分栏面板(最通用)

Dark panel left (brand primary color), light panel right (white or cream). Diagonal slice or hard edge between them.
html
<div class="slide" id="sN" style="display:flex;">
  <div class="left-panel"><!-- headline, statement, quote --></div>
  <div class="right-panel"><!-- list, stats, details --></div>
</div>
css
.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);
}
左侧深色面板(品牌主色调),右侧浅色面板(白色或米白色)。面板间可采用斜切或硬边分隔。
html
<div class="slide" id="sN" style="display:flex;">
  <div class="left-panel"><!-- headline, statement, quote --></div>
  <div class="right-panel"><!-- list, stats, details --></div>
</div>
css
.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);
}

Stat Column (credentials, metrics)

统计列(资质、指标)

Right-side column of large serif numbers/words with small uppercase labels. Works over photo backgrounds.
html
<div class="stats-col">
  <div class="stat">
    <div class="stat-n">13+</div>
    <div class="stat-l">Years at institution</div>
  </div>
  <!-- divider between stats: border-top: 1px solid rgba(255,255,255,.09) -->
</div>
右侧大字号衬线数字/文字搭配小字号大写标签。适用于图片背景之上。
html
<div class="stats-col">
  <div class="stat">
    <div class="stat-n">13+</div>
    <div class="stat-l">Years at institution</div>
  </div>
  <!-- divider between stats: border-top: 1px solid rgba(255,255,255,.09) -->
</div>

Value Cards (principles, pillars)

价值卡片(原则、支柱)

Left colored bar accent, title + description. One bar color per value for visual differentiation.
html
<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>
左侧彩色强调条,搭配标题+描述。每个价值项使用不同的条色以实现视觉区分。
html
<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>

Approach Rows (process, steps)

流程行(流程、步骤)

Large ghost number, vertical rule, then body content. Good for 3-step processes.
html
<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>
大号空心数字、垂直分隔线,然后是正文内容。适用于三步流程。
html
<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>

Tool/Status Cards

工具/状态卡片

Name + status badge + description. Status badge variants: live (green), in-progress (amber), planned (neutral).
html
<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>
名称+状态徽章+描述。状态徽章变体:已上线(绿色)、进行中(琥珀色)、计划中(中性色)。
html
<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>

Photo Background Panel

图片背景面板

Embed image as base64, apply gradient overlay so text stays readable.
html
<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>
To embed an image as base64:
python
import base64
data = 'data:image/png;base64,' + base64.b64encode(open('image.png','rb').read()).decode()

将图片以base64格式嵌入,添加渐变覆盖层以确保文本可读性。
html
<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格式的方法:
python
import base64
data = 'data:image/png;base64,' + base64.b64encode(open('image.png','rb').read()).decode()

Step 4: Content Slide Sequence

步骤4:内容幻灯片序列

A strong 5-slide deck structure for an institutional introduction:
SlidePurposeLayout
1Who you are + elevator pitchFull bleed dark + photo + stat column
2Values / principlesSplit panel + value cards
3Approach / frameworkLight background + approach rows
4Tools / enablementDark background + two columns
5Close + discussion openerSplit panel + photo background
Adapt freely. The key is each slide has ONE job and ONE dominant visual idea.

适用于机构介绍的5页优质演示文稿结构:
幻灯片用途布局
1自我介绍 + 电梯演讲全幅深色背景+图片+统计列
2价值观/原则分栏面板+价值卡片
3方法/框架浅色背景+流程行
4工具/赋能深色背景+两列布局
5收尾+讨论开场分栏面板+图片背景
可自由调整。关键在于每张幻灯片只有一个核心任务和一个主导视觉元素。

Step 5: Typography Rules

步骤5:排版规则

css
/* 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 */

css
/* 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 */

Critical Pitfalls

关键注意事项

NEVER use blanket string/regex replacement on files containing JavaScript

切勿对包含JavaScript的文件使用全局字符串/正则替换

This will corrupt arithmetic operators. Example of what goes wrong:
python
undefined
这会破坏算术运算符。错误示例:
python
undefined

DANGEROUS — corrupts JS

DANGEROUS — corrupts JS

content = content.replace(' - ', ', ')
content = content.replace(' - ', ', ')

cur - 1 becomes cur, 1 (prev button breaks)

cur - 1 becomes cur, 1 (prev button breaks)

clientX - tx becomes clientX, tx (swipe breaks)

clientX - tx becomes clientX, tx (swipe breaks)

 
**Safe approach:** Use targeted `str_replace` with unique, specific strings. Always view the JS section after any automated replacement to verify arithmetic operators are intact.
 
**安全方法:**使用针对性的`str_replace`,替换唯一、特定的字符串。在任何自动替换后,务必查看JavaScript部分,确认算术运算符完好无损。

Always declare
const slides
before using it

务必在使用前声明
const slides

If inserting a new function before the navigation block, check that
const slides = document.querySelectorAll('.slide')
is still present and comes before
slides.forEach(...)
.
如果在导航区块前插入新函数,请检查
const slides = document.querySelectorAll('.slide')
是否仍然存在,且位于
slides.forEach(...)
之前。

str_replace
requires unique strings

str_replace
需要唯一字符串

Before using str_replace, grep for the target string to confirm it appears exactly once. If it appears multiple times, add more context lines to make it unique.
使用str_replace前,先搜索目标字符串,确认它只出现一次。如果出现多次,添加更多上下文内容使其唯一。

Base64 images make files large but self-contained

Base64图片会增大文件体积但保证独立性

A single high-res PNG can add 150-200KB to the file. This is fine for a presentation deck. Use
cover
sizing and
center center
positioning. Always add a gradient overlay div for text legibility.
一张高分辨率PNG图片会使文件增大150-200KB。这对于演示文稿来说是可接受的。使用
cover
尺寸和
center center
定位。务必添加渐变覆盖层以确保文本可读性。

Emoji in content

内容中的表情符号

Avoid leading emoji in list items and card titles — they read as AI-generated and date quickly. If the user asks to remove emoji, use Python unicode replacement rather than sed:
python
import re
content = re.sub(r'[\U00002600-\U0001FAFF]+', '', content)
content = content.replace('\ufe0f', '')  # variation selector

避免在列表项和卡片标题前使用表情符号——它们会显得是AI生成的,且很快过时。如果用户要求移除表情符号,请使用Python unicode替换而非sed:
python
import re
content = re.sub(r'[\U00002600-\U0001FAFF]+', '', content)
content = content.replace('\ufe0f', '')  # variation selector

Iterative Editing Workflow

迭代编辑工作流程

When making changes after initial build:
  1. View the relevant section first
    view
    tool with line range
  2. Use
    str_replace
    for surgical edits — never rewrite the whole file for small changes
  3. Verify JS after any automated text processing — check lines with
    cur
    ,
    dx
    ,
    +
    ,
    -
    operators
  4. Present fresh file after changes — user may be viewing cached version
When the user says things like "slide 3: change X" — make ONLY that change. Resist the urge to improve adjacent content.

初始构建后进行修改时:
  1. 先查看相关部分——使用
    view
    工具查看指定行范围
  2. 使用
    str_replace
    进行精准编辑
    ——切勿因小改动重写整个文件
  3. 任何自动文本处理后验证JavaScript——检查包含
    cur
    dx
    +
    -
    运算符的行
  4. 修改后展示新文件——用户可能正在查看缓存版本
当用户说“第3页:修改X”时——只做该修改。不要试图改进相邻内容。

Codex / API Usage Notes

Codex / API使用说明

This skill is compatible with Claude API (Codex) usage. Key considerations:
  • The complete JS block (navigation + fullscreen) should be treated as a unit — include it verbatim and do not modify arithmetic
  • When generating via API, stream the file to disk and open in browser for preview
  • The base64 embedding approach works identically in API context
  • CSS variables (
    --primary
    ,
    --accent
    , etc.) make theming programmatic — pass brand colors as parameters and substitute into the template

此技能兼容Claude API(Codex)使用。关键注意事项:
  • 完整的JavaScript区块(导航+全屏)应作为一个单元处理——完整包含,切勿修改算术部分
  • 通过API生成时,将文件流式传输到磁盘并在浏览器中打开预览
  • Base64嵌入方法在API环境中同样有效
  • CSS变量(
    --primary
    --accent
    等)使主题设置可编程——将品牌颜色作为参数传入并替换到模板中

PPTX Export (PowerPoint)

PPTX导出(PowerPoint)

When the user explicitly asks for a
.pptx
file, generate a Python script that uses
python-pptx
to build the deck programmatically. The user runs it with:
bash
uv run --with python-pptx <script>.py
No system-wide installs required —
uv
handles the dependency inline.
当用户明确要求
.pptx
文件时,生成一个使用
python-pptx
以编程方式构建演示文稿的Python脚本。用户通过以下命令运行:
bash
uv run --with python-pptx <script>.py
无需全局安装依赖——
uv
会自动处理依赖。

When to use PPTX instead of HTML

何时使用PPTX而非HTML

  • User explicitly asks for
    .pptx
    or "PowerPoint"
  • User needs recipients to edit slides in Office
  • User says "I need to share this with someone who uses PowerPoint"
  • 用户明确要求
    .pptx
    或“PowerPoint”
  • 用户需要收件人在Office中编辑幻灯片
  • 用户说“我需要和使用PowerPoint的人分享这个”

How it works

工作原理

Same content extraction and brand logic as HTML — extract colors, fonts, layout intent from the prompt. Then emit a self-contained
.py
script instead of an
.html
file.
与HTML版本使用相同的内容提取和品牌逻辑——从提示中提取颜色、字体、布局意图。然后生成独立的
.py
脚本而非
.html
文件。

PPTX Generation Pattern

PPTX生成模式

python
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
python
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

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
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...

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")
undefined
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")
undefined

Mapping HTML layouts to PPTX

HTML布局映射到PPTX

HTML PatternPPTX Equivalent
Split panel (dark left / light right)Rectangle shape as left bg + text boxes on each side
Stat columnsLarge-font text boxes with small label text boxes below
Value cards with colored barsThin rectangle shapes as accent bars + adjacent text boxes
Approach rows (numbered steps)Large number text box + title/body text boxes offset right
Full-bleed background
set_slide_bg()
with brand color
HTML模式PPTX等效实现
分栏面板(左深/右浅)矩形形状作为左侧背景 + 两侧文本框
统计列大字号文本框 + 下方小字号标签文本框
带彩色条的价值卡片细矩形形状作为强调条 + 相邻文本框
流程行(编号步骤)大号数字文本框 + 右侧偏移的标题/正文文本框
全幅背景使用
set_slide_bg()
设置品牌颜色

Important caveats

重要提示

Warn the user: PPTX output is an approximation of the HTML deck, not a pixel-perfect replica. python-pptx does not support CSS, gradients,
clamp()
scaling, or web fonts. The generated deck will:
  • Use system fonts (Arial, Georgia) as fallbacks for Google Fonts
  • Approximate gradients with solid colors
  • Skip animations and reveals (static slides)
  • Require manual touch-up for precise alignment
Always tell the user: "The .pptx is a solid starting point but may need minor adjustments in PowerPoint — check alignment and font rendering before presenting."
**告知用户:**PPTX输出是HTML演示文稿的近似版本,而非像素级完美复刻。python-pptx不支持CSS、渐变、
clamp()
缩放或网页字体。生成的演示文稿将:
  • 使用系统字体(Arial、Georgia)作为Google Fonts的替代
  • 用纯色近似渐变效果
  • 跳过动画和逐步展示(静态幻灯片)
  • 需要手动调整以实现精准对齐
务必告知用户:“此.pptx文件是良好的起点,但在PowerPoint中可能需要微调——展示前请检查对齐和字体渲染效果。”

Font mapping

字体映射

HTML (Google Font)PPTX fallback
Cormorant GaramondGeorgia
DM SansArial
Playfair DisplayGeorgia
InterArial
Any serifGeorgia or Times New Roman
Any sans-serifArial or Calibri

HTML(Google Font)PPTX替代字体
Cormorant GaramondGeorgia
DM SansArial
Playfair DisplayGeorgia
InterArial
任意衬线字体Georgia或Times New Roman
任意无衬线字体Arial或Calibri

Quick Reference: Palette Slots

快速参考:调色板插槽

Every deck fills these slots. Populate from the brand guide, then theme programmatically via CSS variables.
--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
Fill with real hex codes at the top of
<style>
and reference everywhere via
var(--name)
.
每个演示文稿都会填充这些插槽。从品牌指南中获取内容,然后通过CSS变量进行主题设置。
--primary     主要背景和深色面板
--accent      分隔线、侧边栏、号召性按钮、状态点、悬停状态
--cream       浅色幻灯片背景(比纯白更温暖)
--stone       分隔线、细微边框(比cream深一个色调)
--ink         浅色背景上的正文文本(比黑色柔和)
--muted       标签、元数据、次要文本
--go          积极/已上线/绿色状态
--warn        审核/警告/琥珀色状态
--alt         用于价值/支柱区分的第五种强调色
<style>
标签顶部填入真实的十六进制代码,并通过
var(--name)
在各处引用。