Files
makelore/.opencode/skills/frontend-slides/references/html-template.md
inman c809002180
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
feat: add bundled OpenCode skills and refine module flow
2026-08-14 18:15:55 +08:00

4.8 KiB

HTML 幻灯片结构参考

每份演示都是一个可独立打开的 HTML 文件。把 viewport-base.css 的完整内容放在 <style> 中,再加入项目自己的变量和组件样式。

最小结构

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>项目演示</title>
    <style>
      :root {
        --stage-bg: #eef3ef;
        --slide-bg: #fffdf7;
        --ink: #17332a;
        --muted: #5c6f66;
        --accent: #f08a63;
        --display-font: "Trebuchet MS", "PingFang SC", sans-serif;
        --body-font: "Avenir Next", "PingFang SC", sans-serif;
      }

      * { box-sizing: border-box; }
      /* 在此处粘贴 viewport-base.css 的完整内容。 */

      .slide-inner {
        width: 100%;
        height: 100%;
        padding: 92px;
        color: var(--ink);
        font-family: var(--body-font);
      }

      .reveal {
        opacity: 0;
        transform: translateY(28px);
        transition: opacity .55s ease, transform .55s ease;
      }

      .slide.visible .reveal {
        opacity: 1;
        transform: translateY(0);
      }

      .reveal:nth-child(2) { transition-delay: .08s; }
      .reveal:nth-child(3) { transition-delay: .16s; }
    </style>
  </head>
  <body>
    <div class="deck-viewport" aria-label="项目演示">
      <main class="deck-stage" id="deckStage">
        <section class="slide title-slide active visible" aria-label="第 1 页">
          <div class="slide-inner">
            <h1 class="reveal">项目标题</h1>
            <p class="reveal">一句话说明项目解决了什么问题</p>
          </div>
        </section>
        <!-- 其余页使用同样的 .slide 结构。 -->
      </main>
    </div>
    <div class="deck-controls" aria-label="演示控制">
      <span id="pageCount">1 / 1</span>
    </div>
    <script>
      class SlidePresentation {
        constructor() {
          this.slides = [...document.querySelectorAll('.slide')];
          this.stage = document.querySelector('#deckStage');
          this.pageCount = document.querySelector('#pageCount');
          this.index = 0;
          this.startX = null;
          this.scaleStage();
          window.addEventListener('resize', () => this.scaleStage());
          window.addEventListener('keydown', (event) => this.onKey(event));
          window.addEventListener('wheel', (event) => {
            if (Math.abs(event.deltaY) > 12) this.show(this.index + Math.sign(event.deltaY));
          }, { passive: true });
          window.addEventListener('touchstart', (event) => {
            this.startX = event.touches[0]?.clientX ?? null;
          }, { passive: true });
          window.addEventListener('touchend', (event) => {
            if (this.startX === null) return;
            const endX = event.changedTouches[0]?.clientX ?? this.startX;
            if (Math.abs(endX - this.startX) > 40) this.show(this.index + (endX < this.startX ? 1 : -1));
            this.startX = null;
          }, { passive: true });
          this.show(0);
        }

        scaleStage() {
          const factor = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
          const x = (window.innerWidth - 1920 * factor) / 2;
          const y = (window.innerHeight - 1080 * factor) / 2;
          this.stage.style.transform = `translate(${x}px, ${y}px) scale(${factor})`;
        }

        onKey(event) {
          if (['ArrowRight', 'ArrowDown', 'PageDown', ' '].includes(event.key)) {
            event.preventDefault(); this.show(this.index + 1);
          }
          if (['ArrowLeft', 'ArrowUp', 'PageUp'].includes(event.key)) {
            event.preventDefault(); this.show(this.index - 1);
          }
        }

        show(index) {
          this.index = Math.max(0, Math.min(index, this.slides.length - 1));
          this.slides.forEach((slide, slideIndex) => {
            const active = slideIndex === this.index;
            slide.classList.toggle('active', active);
            slide.classList.toggle('visible', active);
            slide.setAttribute('aria-hidden', String(!active));
          });
          if (this.pageCount) this.pageCount.textContent = `${this.index + 1} / ${this.slides.length}`;
        }
      }

      new SlidePresentation();
    </script>
  </body>
</html>

内容和可访问性

  • 使用一个明确的 <h1>,后续页面使用有顺序的 <h2>;图片提供简短 alt 文本。
  • 不把重要内容只放在动画、颜色或悬停状态里;对比度要足够。
  • 为每个 .slidearia-label,并在切换时同步 aria-hidden
  • 不在幻灯片内放调试按钮、文件路径、模板名称、内部提示或生成过程文字。
  • 通过相对路径引用 assets/,不要把用户机器路径写入 HTML。