Initial: pi-skill — 68 skills, 43 extensions, 11 themes for Pi

This commit is contained in:
Kunthawat Greethong
2026-05-25 16:38:02 +07:00
commit 69f7d8bdda
1689 changed files with 342427 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html lang="en" data-theme="aurora">
<head>
<meta charset="utf-8"><title>Animation + FX Showcase — html-ppt</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../assets/themes/aurora.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
<style>
.fx-stage{
position:relative;
margin:20px auto 0;
width:min(900px, 92%);
height:380px;
border-radius:var(--radius-lg, 16px);
background:rgba(10,12,22,0.55);
border:1px solid var(--border,#2a2a3a);
box-shadow:var(--shadow-lg, 0 20px 60px rgba(0,0,0,.4));
overflow:hidden;
}
.fx-label{
position:absolute;top:14px;left:16px;z-index:5;
font-family:var(--font-mono, ui-monospace,Menlo,monospace);
font-size:11px;letter-spacing:.14em;color:var(--text-3,#a0a0b8);
text-transform:uppercase;
background:rgba(0,0,0,0.35);padding:4px 10px;border-radius:999px;
border:1px solid rgba(255,255,255,0.08);
}
.fx-replay{
position:absolute;top:12px;right:14px;z-index:5;
appearance:none;cursor:pointer;
padding:7px 14px;border-radius:999px;
background:linear-gradient(135deg, var(--accent,#7c5cff), var(--accent-2,#22d3ee));
color:#fff;font:600 12px system-ui,sans-serif;
border:0;letter-spacing:.05em;
box-shadow:0 4px 16px rgba(124,92,255,0.35);
}
.fx-replay:hover{ filter:brightness(1.1); transform:translateY(-1px); }
.fx-indicator{position:absolute;top:24px;right:40px;font-family:var(--font-mono);font-size:11px;color:var(--text-3);letter-spacing:.1em}
.anim-box{margin:24px auto 0;width:640px;height:240px;border-radius:var(--radius-lg);background:var(--grad-soft);display:flex;align-items:center;justify-content:center;font-size:44px;font-weight:800;color:var(--text-1);box-shadow:var(--shadow-lg);border:1px solid var(--border)}
</style>
</head>
<body>
<div class="deck"></div>
<script>
const FX = [
['particle-burst', 'Particles explode from center, gravity + fade, re-bursts every ~2.5s.'],
['confetti-cannon', 'Colored rotating rects arcing from both bottom corners.'],
['firework', 'Rockets launch from the bottom and burst into colored sparks.'],
['starfield', '3D perspective starfield — infinite flythrough.'],
['matrix-rain', 'Classic green katakana + hex columns raining down.'],
['knowledge-graph', 'Force-directed graph, 28 labeled nodes, live physics + springs.'],
['neural-net', '4-6-6-3 feedforward net with pulses traveling along edges.'],
['constellation', 'Drifting points connect when close — ambient background.'],
['orbit-ring', '5 concentric rings with dots rotating at different speeds.'],
['galaxy-swirl', 'Logarithmic spiral with ~800 particles.'],
['word-cascade', 'Words fall from top and pile up at the bottom.'],
['letter-explode', 'Letters fly in from random directions, loops.'],
['chain-react', 'Row of 8 circles — a wave pulse dominoes across them.'],
['magnetic-field', 'Particles follow sine curves leaving gradient trails.'],
['data-stream', 'Rows of scrolling hex/binary text, cyberpunk feel.'],
['gradient-blob', '4 drifting blurred radial gradients (additive blending).'],
['sparkle-trail', 'Sparkles emit at your cursor (auto-wiggles if idle).'],
['shockwave', 'Expanding rings emanating from center, looping.'],
['typewriter-multi', 'Three lines typing concurrently with blinking block cursors.'],
['counter-explosion', 'Number counts 0 → 2400, bursts particles, resets.']
];
const CSS_ANIMS = [
['fade-up','Translate from +32 px, fade.'],
['fade-down','Translate from -32 px, fade.'],
['fade-left','From left.'],
['fade-right','From right.'],
['rise-in','Rise + blur-off.'],
['drop-in','Drop from above.'],
['zoom-pop','Elastic scale pop.'],
['blur-in','Blur clears.'],
['glitch-in','Glitch jitter.'],
['typewriter','Typewriter reveal.'],
['neon-glow','Neon pulse.'],
['shimmer-sweep','Sheen sweep.'],
['gradient-flow','Gradient flow.'],
['stagger-list','Staggered children.'],
['counter-up','Number tick.'],
['path-draw','SVG strokes draw.'],
['parallax-tilt','3D hover tilt.'],
['card-flip-3d','Y-axis flip.'],
['cube-rotate-3d','Cube rotate.'],
['page-turn-3d','Page turn.'],
['perspective-zoom','Pull from -400 Z.'],
['marquee-scroll','Infinite marquee.'],
['kenburns','Ken Burns zoom.'],
['confetti-burst','Pseudo confetti.'],
['spotlight','Circular clip reveal.'],
['morph-shape','SVG d morph.'],
['ripple-reveal','Corner ripple.']
];
const deck = document.querySelector('.deck');
const total = FX.length + CSS_ANIMS.length;
// Build FX slides (1..20)
FX.forEach((f, i) => {
const idx = i + 1;
const [name, desc] = f;
const sec = document.createElement('section');
sec.className = 'slide';
sec.setAttribute('data-title', 'fx / ' + name);
const extraAttrs = name === 'letter-explode'
? 'data-fx-text-value="' + name.toUpperCase() + '"'
: name === 'counter-explosion'
? 'data-fx-to="2400"'
: name === 'typewriter-multi'
? 'data-fx-line1="> initializing knowledge graph..." data-fx-line2="> loading 28 concept nodes" data-fx-line3="> agent ready. awaiting prompt_"'
: '';
sec.innerHTML = `
<span class="fx-indicator">${idx}/${total}</span>
<p class="kicker">FX · canvas · ${String(idx).padStart(2,'0')}</p>
<h1 class="h1"><span class="gradient-text">${name}</span></h1>
<p class="lede">${desc}</p>
<div class="fx-stage">
<span class="fx-label">data-fx="${name}"</span>
<button class="fx-replay" type="button">Replay</button>
<div class="fx-host" style="position:absolute;inset:0;" data-fx="${name}" ${extraAttrs}></div>
</div>
<div class="deck-footer"><span class="dim2">Press → for next slide</span><span class="slide-number" data-current="${idx}" data-total="${total}"></span></div>
`;
deck.appendChild(sec);
// Wire Replay button
sec.querySelector('.fx-replay').addEventListener('click', () => {
const host = sec.querySelector('.fx-host');
const name2 = host.getAttribute('data-fx');
const attrs = {};
for (const a of host.attributes) attrs[a.name] = a.value;
const parent = host.parentNode;
// stop existing
if (window.__hpxActive && window.__hpxActive.has(host)){
try{ window.__hpxActive.get(host).stop(); }catch(e){}
window.__hpxActive.delete(host);
}
const fresh = document.createElement('div');
for (const k in attrs) fresh.setAttribute(k, attrs[k]);
fresh.style.cssText = host.style.cssText;
parent.replaceChild(fresh, host);
if (window.__hpxReinit) window.__hpxReinit(sec);
});
});
// Build CSS animation slides (legacy, kept for completeness)
CSS_ANIMS.forEach((a, i) => {
const idx = FX.length + i + 1;
const sec = document.createElement('section');
sec.className = 'slide';
sec.setAttribute('data-title', a[0]);
sec.innerHTML = `
<span class="fx-indicator">${idx}/${total}</span>
<p class="kicker">CSS anim · ${String(idx).padStart(2,'0')}</p>
<h1 class="h1"><span class="gradient-text">${a[0]}</span></h1>
<p class="lede">${a[1]}</p>
<div class="anim-box anim-${a[0]}" data-anim="${a[0]}" data-anim-target>${a[0]}</div>
<div class="deck-footer"><span class="dim2">Press A to cycle</span><span class="slide-number" data-current="${idx}" data-total="${total}"></span></div>
`;
deck.appendChild(sec);
});
</script>
<script src="../assets/runtime.js"></script>
<script src="../assets/animations/fx-runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,42 @@
# Astro + Tina CMS Environment Variables
# Site URL
PUBLIC_SITE_URL=https://your-domain.com
# Tina CMS (optional - for production Tina admin)
TINA_TOKEN=your-tina-token-from-tina-cloud
TINA_CLIENT_ID=your-client-id
# ConsentOS - Consent Management (moreminimore.com)
PUBLIC_CONSENT_SITE_ID=your-consent-site-id
PUBLIC_CONSENT_API_BASE=https://consent.moreminimore.com
# ===== TRACKING SCRIPTS =====
# All tracking scripts are blocked by ConsentOS until user gives consent
# --- Analytics ---
# Google Analytics 4
PUBLIC_GA4_ID=G-XXXXXXXXXX
# Google Tag Manager
PUBLIC_GTM_ID=GTM-XXXXXXX
# Umami (self-hosted analytics)
PUBLIC_UMAMI_URL=https://umami.example.com
PUBLIC_UMAMI_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Microsoft Clarity (heatmaps)
PUBLIC_CLARITY_ID=xxxxxxxxxx
# --- Marketing / Advertising ---
# Facebook Pixel
PUBLIC_FB_PIXEL_ID=123456789
# Google Ads Conversion
PUBLIC_GOOGLE_ADS_ID=AW-123456789
# TikTok Pixel
PUBLIC_TIKTOK_PIXEL_ID=XXXXXXXX
# LINE Channel Tag (Thailand)
PUBLIC_LINE_CHANNEL_ID=1234567890

View File

@@ -0,0 +1,18 @@
import { defineConfig } from 'tinacms';
import { schema } from './schema';
export default defineConfig({
schema,
ui: {
navigation: {
'content/posts': { label: 'Posts' },
'content/pages': { label: 'Pages' },
},
},
media: {
tina: {
publicFolder: 'public',
mediaRoot: 'uploads',
},
},
});

View File

@@ -0,0 +1,91 @@
import { defineSchema } from 'tinacms'
export const schema = defineSchema({
collections: [
{
name: 'post',
label: 'Posts',
path: 'src/content/posts',
format: 'mdx',
fields: [
{
type: 'string',
name: 'title',
label: 'Title',
required: true,
},
{
type: 'string',
name: 'description',
label: 'Description',
},
{
type: 'datetime',
name: 'publishedAt',
label: 'Published At',
},
{
type: 'string',
name: 'category',
label: 'Category',
options: ['news', 'blog', 'tutorial'],
},
{
type: 'rich-text',
name: 'body',
label: 'Body',
isBody: true,
},
],
},
{
name: 'page',
label: 'Pages',
path: 'src/content/pages',
format: 'mdx',
fields: [
{
type: 'string',
name: 'title',
label: 'Title',
required: true,
},
{
type: 'string',
name: 'description',
label: 'Description',
},
{
type: 'rich-text',
name: 'body',
label: 'Body',
isBody: true,
},
],
},
{
name: 'settings',
label: 'Settings',
path: 'src/content/settings',
format: 'json',
fields: [
{
type: 'string',
name: 'siteName',
label: 'Site Name',
},
{
type: 'string',
name: 'siteDescription',
label: 'Site Description',
},
{
type: 'string',
name: 'language',
label: 'Language',
options: ['th', 'en', 'th-en'],
},
],
},
],
})

View File

@@ -0,0 +1,207 @@
# Astro Tina Starter - Agent Knowledge Base
**Generated:** 2026-04-17
**Version:** 2.0.0
**Type:** Astro 6 + Tina CMS Starter Template
---
## OVERVIEW
Starter template for building websites with Astro 6, Tina CMS, and Tailwind CSS 4.x.
### Tech Stack
| Component | Technology | Version |
|-----------|------------|---------|
| Framework | Astro | 6.1.7 |
| CMS | Tina CMS | 2.x |
| Styling | Tailwind CSS | 4.x |
### Key Features
- Self-hosted Tina CMS with schema-based content
- Tailwind CSS 4.x using `@tailwindcss/vite` plugin
- External consent system integration
- Thai language support with Noto Sans Thai
- Docker-ready with nginx
---
## PROJECT STRUCTURE
```
astro-tina-starter/
├── .tina/
│ ├── config.ts # Tina CMS configuration
│ └── schema.ts # Content schema definitions
├── src/
│ ├── styles/
│ │ └── global.css # Tailwind v4 styles + @theme
│ ├── layouts/
│ │ └── Layout.astro
│ ├── pages/
│ │ └── index.astro
│ ├── components/
│ │ ├── Header.astro
│ │ └── TrackingScripts.astro # Tracking scripts (GA4, FB Pixel, etc.)
│ └── content/
│ ├── config.ts # Astro content collections
│ ├── posts/ # Blog posts (MDX)
│ ├── pages/ # Static pages (MDX)
│ └── settings/ # Site settings (JSON)
├── public/
│ └── favicon.svg
├── Dockerfile
├── nginx.conf
├── astro.config.mjs
├── tsconfig.json
└── package.json
```
---
## IMPORTANT CONVENTIONS
### Tailwind CSS 4.x Setup
**CRITICAL:** This template uses `@tailwindcss/vite` plugin, NOT `@astrojs/tailwind`.
```javascript
// astro.config.mjs
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
})
```
```css
/* src/styles/global.css */
@import "tailwindcss";
@theme {
--color-primary: #1a1a1a;
--color-accent: #3b82f6;
}
```
### Tina CMS Content
Tina CMS manages content in `src/content/`:
- `posts/` - Blog posts (MDX format)
- `pages/` - Static pages (MDX format)
- `settings/` - Site settings (JSON format)
Schema defined in `.tina/schema.ts`.
### ConsentOS + Tracking System
ConsentOS (`consent.moreminimore.com`) manages consent and blocking:
```bash
# ConsentOS
PUBLIC_CONSENT_SITE_ID=your-site-id
PUBLIC_CONSENT_API_BASE=https://consent.moreminimore.com
# Analytics
PUBLIC_GA4_ID=G-XXXXXXXXXX
PUBLIC_GTM_ID=GTM-XXXXXXX
PUBLIC_UMAMI_URL=https://umami.example.com
PUBLIC_UMAMI_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
PUBLIC_CLARITY_ID=xxxxxxxxxx
# Marketing
PUBLIC_FB_PIXEL_ID=123456789
PUBLIC_GOOGLE_ADS_ID=AW-123456789
PUBLIC_TIKTOK_PIXEL_ID=XXXXXXXX
PUBLIC_LINE_CHANNEL_ID=1234567890
```
**Tracking Scripts:**
- `TrackingScripts.astro` - contains all tracking scripts
- Scripts are auto-blocked by ConsentOS until user consent
- Categories: `analytics`, `marketing`
---
## CREDENTIALS
No external API credentials required for this template.
### Optional Environment Variables
| Variable | Description |
|----------|-------------|
| `TINA_TOKEN` | Tina CMS production authentication |
| `TINA_CLIENT_ID` | Tina CMS client ID |
| `PUBLIC_CONSENT_SITE_ID` | ConsentOS site ID |
| `PUBLIC_CONSENT_API_BASE` | ConsentOS API base URL |
| `PUBLIC_GA4_ID` | Google Analytics 4 |
| `PUBLIC_GTM_ID` | Google Tag Manager |
| `PUBLIC_UMAMI_URL` | Umami analytics URL |
| `PUBLIC_UMAMI_WEBSITE_ID` | Umami website ID |
| `PUBLIC_CLARITY_ID` | Microsoft Clarity |
| `PUBLIC_FB_PIXEL_ID` | Facebook Pixel |
| `PUBLIC_GOOGLE_ADS_ID` | Google Ads conversion ID |
| `PUBLIC_TIKTOK_PIXEL_ID` | TikTok Pixel |
| `PUBLIC_LINE_CHANNEL_ID` | LINE Channel Tag |
---
## COMMANDS
```bash
# Install dependencies
npm install
# Development
npm run dev
# Build
npm run build
# Preview
npm run preview
```
---
## ANTI-PATTERNS
- **NEVER** use `@astrojs/tailwind` (deprecated)
- **ALWAYS** use `@tailwindcss/vite` for Tailwind v4
- **NEVER** commit environment files (.env)
---
## DEPLOYMENT
### Docker
```bash
docker build -t astro-tina-starter .
docker run -p 8080:80 astro-tina-starter
```
### Easypanel
Static hosting - no persistent volume needed.
### Manual
```bash
npm install
npm run build
# Serve dist/ folder with any static server
```
---
## NOTES
- Tina CMS admin: http://localhost:4321/admin
- Astro default port: 4321
- Tina dev server: 3001

View File

@@ -0,0 +1,24 @@
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Static files stage
FROM nginx:alpine AS runner
WORKDIR /app
# Copy static files from builder
COPY --from=builder /app/dist ./dist
# Copy nginx config
COPY nginx.conf /etc/nginx/http.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,121 @@
# Astro Tina Starter
Astro 6.1.7 + Tina CMS starter template with Tailwind CSS 4.x
## Tech Stack
- **Framework:** Astro 6.1.7 (static mode)
- **CMS:** Tina CMS (self-hosted)
- **Styling:** Tailwind CSS 4.x with `@tailwindcss/vite`
- **Language:** TypeScript
## Features
- Self-hosted Tina CMS with schema-based content
- Tailwind CSS 4.x using `@tailwindcss/vite` plugin
- ConsentOS + Tracking Scripts (GA4, Facebook Pixel, etc.)
- Docker-ready with nginx
- Thai language support foundation
## Quick Start
```bash
# Install dependencies
npm install
# Start development (accessible at http://0.0.0.0:4321)
npm run dev
# Build for production
npm run build
```
## Tina CMS Access
During development, access Tina CMS at:
- http://localhost:4321/admin
For production, you'll need a TINA_TOKEN environment variable.
## ConsentOS + Tracking
This template includes ConsentOS (`consent.moreminimore.com`) for PDPA-compliant consent management and auto-blocking of tracking scripts.
### Environment Variables
```bash
# ConsentOS
PUBLIC_CONSENT_SITE_ID=your-site-id
PUBLIC_CONSENT_API_BASE=https://consent.moreminimore.com
# Analytics
PUBLIC_GA4_ID=G-XXXXXXXXXX
PUBLIC_GTM_ID=GTM-XXXXXXX
PUBLIC_UMAMI_URL=https://umami.example.com
PUBLIC_UMAMI_WEBSITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
PUBLIC_CLARITY_ID=xxxxxxxxxx
# Marketing
PUBLIC_FB_PIXEL_ID=123456789
PUBLIC_GOOGLE_ADS_ID=AW-123456789
PUBLIC_TIKTOK_PIXEL_ID=XXXXXXXX
PUBLIC_LINE_CHANNEL_ID=1234567890
```
### How It Works
1. `TrackingScripts.astro` contains all tracking codes with `data-consent-category` attributes
2. ConsentOS `consent-loader.js` scans and auto-blocks scripts until user consent
3. Categories: `analytics`, `marketing`
## Project Structure
```
astro-tina-starter/
├── .tina/
│ ├── config.ts # Tina CMS configuration
│ └── schema.ts # Content schema definitions
├── src/
│ ├── styles/
│ │ └── global.css # Tailwind v4 styles
│ ├── layouts/
│ │ └── Layout.astro
│ ├── pages/
│ │ └── index.astro
│ ├── components/
│ │ ├── Header.astro
│ │ └── TrackingScripts.astro # Tracking scripts
│ └── content/
│ └── config.ts # Tina content collections
├── Dockerfile
├── nginx.conf
└── package.json
```
## Tailwind CSS 4.x
This template uses Tailwind CSS 4.x with the `@tailwindcss/vite` plugin.
The configuration is done via CSS `@theme` block in `src/styles/global.css`.
```css
@import "tailwindcss";
@theme {
--color-primary: #1a1a1a;
--color-accent: #3b82f6;
}
```
## Docker
```bash
# Build
docker build -t astro-tina .
# Run
docker run -p 8080:80 astro-tina
```
## License
MIT

View File

@@ -0,0 +1,39 @@
import { defineConfig } from 'astro/config'
import tailwindcss from '@tailwindcss/vite'
import tina from 'tinacms'
import { fileURLToPath } from 'url'
import path from 'path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default defineConfig({
site: 'https://example.com',
integrations: [
tina({
enabled: !!process.env.TINA_TOKEN,
sidebar: {
partials: [],
},
}),
],
vite: {
plugins: [tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@layouts': path.resolve(__dirname, './src/layouts'),
'@styles': path.resolve(__dirname, './src/styles'),
'@content': path.resolve(__dirname, './src/content'),
},
},
},
output: 'static',
build: {
assets: '_assets',
},
server: {
host: '0.0.0.0',
port: 4321,
},
})

View File

@@ -0,0 +1,22 @@
import { defineDb, defineTable, column } from 'astro:db';
const ConsentLog = defineTable({
columns: {
id: column.number({ primaryKey: true }),
action: column.text(),
purpose: column.text(),
analytics: column.boolean({ default: false }),
marketing: column.boolean({ default: false }),
functional: column.boolean({ default: false }),
userAgent: column.text({ optional: true }),
ip: column.text({ optional: true }),
timestamp: column.date(),
sessionId: column.text({ optional: true }),
},
});
export default defineDb({
tables: {
ConsentLog,
},
});

View File

@@ -0,0 +1,7 @@
import { db } from 'astro:db'
import { sql } from 'astro/db'
export default async function seed() {
// Seed default settings if needed
console.log('Database seeded successfully')
}

View File

@@ -0,0 +1,14 @@
server {
listen 80;
root /app/dist;
index index.html;
location / {
try_files $uri $uri/ $uri.html =404;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

View File

@@ -0,0 +1,34 @@
{
"name": "astro-tina-starter",
"type": "module",
"version": "2.0.0",
"description": "Astro 6 + Tina CMS starter template with Tailwind CSS 4.x",
"scripts": {
"dev": "tinacms dev --port 3001 & astro dev",
"dev:astro": "astro dev",
"dev:tina": "tinacms dev --port 3001",
"build": "tinacms build && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.0.0",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0",
"astro": "^6.1.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^4.0.0",
"tina": "^2.1.4",
"tinacms": "^2.2.4",
"typescript": "^5.6.3"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1"
},
"engines": {
"node": ">=20.0.0"
}
}

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3b82f6"/>
<stop offset="100%" style="stop-color:#1d4ed8"/>
</linearGradient>
</defs>
<rect width="32" height="32" rx="6" fill="url(#grad)"/>
<text x="16" y="22" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white" text-anchor="middle">A</text>
</svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@@ -0,0 +1,27 @@
---
interface Props {
siteName?: string
}
const { siteName = "Astro Tina Starter" } = Astro.props
---
<header class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-primary-200">
<nav class="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
<a href="/" class="font-bold text-xl text-primary-900 hover:text-accent-600 transition-colors">
{siteName}
</a>
<div class="flex items-center gap-6">
<a href="/" class="text-primary-600 hover:text-primary-900 transition-colors">
Home
</a>
<a href="/blog" class="text-primary-600 hover:text-primary-900 transition-colors">
Blog
</a>
<a href="/about" class="text-primary-600 hover:text-primary-900 transition-colors">
About
</a>
</div>
</nav>
</header>

View File

@@ -0,0 +1,167 @@
---
const ga4Id = import.meta.env.PUBLIC_GA4_ID
const gtmId = import.meta.env.PUBLIC_GTM_ID
const umamiUrl = import.meta.env.PUBLIC_UMAMI_URL
const umamiWebsiteId = import.meta.env.PUBLIC_UMAMI_WEBSITE_ID
const clarityId = import.meta.env.PUBLIC_CLARITY_ID
const fbPixelId = import.meta.env.PUBLIC_FB_PIXEL_ID
const googleAdsId = import.meta.env.PUBLIC_GOOGLE_ADS_ID
const tiktokPixelId = import.meta.env.PUBLIC_TIKTOK_PIXEL_ID
const lineChannelId = import.meta.env.PUBLIC_LINE_CHANNEL_ID
---
<!-- Google Analytics 4 -->
{ga4Id && (
<script
data-consent-category="analytics"
async
src={`https://www.googletagmanager.com/gtag/js?id=${ga4Id}`}
></script>
)}
{ga4Id && (
<script
data-consent-category="analytics"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${ga4Id}');
`
}}
></script>
)}
<!-- Google Tag Manager -->
{gtmId && (
<script
data-consent-category="analytics"
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${gtmId}');
`
}}
></script>
)}
<!-- Umami Analytics -->
{umamiUrl && umamiWebsiteId && (
<script
data-consent-category="analytics"
async
src={`${umamiUrl}/script.js`}
data-website-id={umamiWebsiteId}
></script>
)}
<!-- Microsoft Clarity -->
{clarityId && (
<script
data-consent-category="analytics"
dangerouslySetInnerHTML={{
__html: `
(function(c,l,a,r,i,t,y){
a[q]=a[q]||function(){(a[q].q=a[q].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "${clarityId}");
`
}}
></script>
)}
<!-- Facebook Pixel -->
{fbPixelId && (
<script
data-consent-category="marketing"
dangerouslySetInnerHTML={{
__html: `
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '${fbPixelId}');
fbq('track', 'PageView');
`
}}
></script>
)}
{fbPixelId && (
<noscript data-consent-category="marketing">
<img
height="1"
width="1"
style="display:none"
src={`https://www.facebook.com/tr?id=${fbPixelId}&ev=PageView&noscript=1`}
alt=""
/>
</noscript>
)}
<!-- Google Ads Conversion -->
{googleAdsId && (
<script
data-consent-category="marketing"
async
src={`https://www.googletagmanager.com/gtag/js?id=${googleAdsId}`}
></script>
)}
<!-- TikTok Pixel -->
{tiktokPixelId && (
<script
data-consent-category="marketing"
dangerouslySetInnerHTML={{
__html: `
!function (w, d, t) {
w.TiktokAnalyticsObject = t;
var ttq = w[t] = w[t] || [];
ttq.methods = ["page", "track", "identify", "instances", "debug", "on", "off", "once", "ready", "alias", "group", "enableCookie", "disableCookie"];
ttq.setAndDefer = function (t, e) { t[e] = function () { t.push([e].concat(Array.prototype.slice.call(arguments, 0))) } };
for (var i = 0; i < ttq.methods.length; i++) ttq.setAndDefer(ttq, ttq.methods[i]);
ttq.instance = function (t) {
var e = t.slice(0);
return ttq.push([e]), ttq
};
for (var i = 0; i < ttq.methods.length; i++) {
var e = ttq.methods[i];
ttq[e] = ttq.instance.bind(ttq, e)
}
ttq.load = function (t, e) {
var n = "https://analytics.tiktok.com/i18n/pixel/events.js";
n = n + "?sdkid=" + t + "&lib=" + e;
var i = d.createElement("script");
i.type = "text/javascript";
i.src = n;
d.getElementsByTagName("head")[0].appendChild(i)
};
ttq.load("${tiktokPixelId}", "exc");
ttq.page()
}(window, document, 'ttq');
`
}}
></script>
)}
<!-- LINE Channel Tag -->
{lineChannelId && (
<script
data-consent-category="marketing"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${lineChannelId}');
`
}}
></script>
)}

View File

@@ -0,0 +1,34 @@
import { defineCollection, z } from "astro:content"
const postCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string().optional(),
publishedAt: z.date().optional(),
category: z.enum(["news", "blog", "tutorial"]).optional(),
}),
})
const pageCollection = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string().optional(),
}),
})
const settingsCollection = defineCollection({
type: "data",
schema: z.object({
siteName: z.string(),
siteDescription: z.string(),
language: z.enum(["th", "en", "th-en"]).default("th"),
}),
})
export const collections = {
posts: postCollection,
pages: pageCollection,
settings: settingsCollection,
}

View File

@@ -0,0 +1,17 @@
---
title: Welcome to Astro Tina Starter
description: A modern starter template with Astro 6, Tina CMS, and Thai language support.
publishedAt: 2026-04-17
category: blog
---
Welcome to our new blog built with Astro and Tina CMS!
## Features
- **Tina CMS** - Self-hosted content management
- **Tailwind CSS v4** - Latest styling with @tailwindcss/vite
- **ConsentOS** - PDPA-compliant consent management
- **Thai Support** - Ready for Thai language content
Stay tuned for more updates!

View File

@@ -0,0 +1,5 @@
{
"siteName": "Astro Tina Starter",
"siteDescription": "Astro 6 + Tina CMS starter template with Thai language support",
"language": "th"
}

View File

@@ -0,0 +1,41 @@
---
import "@/styles/global.css"
import TrackingScripts from "@/components/TrackingScripts.astro"
interface Props {
title?: string
description?: string
}
const {
title = "Astro Tina Starter",
description = "Astro 6 + Tina CMS starter template",
} = Astro.props
const consentSiteId = import.meta.env.PUBLIC_CONSENT_SITE_ID || 'demo'
const consentApiBase = import.meta.env.PUBLIC_CONSENT_API_BASE || 'https://consent.moreminimore.com'
---
<!doctype html>
<html lang="th">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
</head>
<body class="bg-primary-50 text-primary-900 min-h-screen">
<slot />
<!-- Tracking Scripts (ConsentOS will auto-block until consent) -->
<TrackingScripts />
<!-- ConsentOS - Consent Management -->
<script
src={`${consentApiBase}/consent-loader.js`}
data-site-id={consentSiteId}
data-api-base={consentApiBase}
></script>
</body>
</html>

View File

@@ -0,0 +1,47 @@
---
import Layout from "@/layouts/Layout.astro"
---
<Layout>
<main>
<section class="px-6 py-24 max-w-4xl mx-auto">
<h1 class="text-4xl md:text-5xl font-bold tracking-tight mb-6">
Welcome to Astro Tina Starter
</h1>
<p class="text-lg text-primary-600 mb-8 max-w-2xl">
A modern starter template with Astro 6, Tina CMS, Tailwind CSS 4.x,
and Thai language support.
</p>
<div class="grid gap-6 md:grid-cols-2">
<div class="p-6 bg-white rounded-xl border border-primary-200">
<h2 class="text-xl font-semibold mb-3">Tina CMS</h2>
<p class="text-primary-600">
Self-hosted content management with schema-based editing.
</p>
</div>
<div class="p-6 bg-white rounded-xl border border-primary-200">
<h2 class="text-xl font-semibold mb-3">Tailwind v4</h2>
<p class="text-primary-600">
Latest Tailwind CSS with @tailwindcss/vite plugin.
</p>
</div>
<div class="p-6 bg-white rounded-xl border border-primary-200">
<h2 class="text-xl font-semibold mb-3">ConsentOS</h2>
<p class="text-primary-600">
PDPA-compliant consent management with auto-blocking tracking.
</p>
</div>
<div class="p-6 bg-white rounded-xl border border-primary-200">
<h2 class="text-xl font-semibold mb-3">Thai Support</h2>
<p class="text-primary-600">
Ready for Thai language content with Noto Sans Thai.
</p>
</div>
</div>
</section>
</main>
</Layout>

View File

@@ -0,0 +1,57 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@theme {
--font-sans: "Inter", "Noto Sans Thai", system-ui, sans-serif;
--font-serif: "Merriweather", Georgia, serif;
--color-primary-50: #f8fafc;
--color-primary-100: #f1f5f9;
--color-primary-200: #e2e8f0;
--color-primary-300: #cbd5e1;
--color-primary-400: #94a3b8;
--color-primary-500: #64748b;
--color-primary-600: #475569;
--color-primary-700: #334155;
--color-primary-800: #1e293b;
--color-primary-900: #0f172a;
--color-primary-950: #020617;
--color-accent-50: #eff6ff;
--color-accent-100: #dbeafe;
--color-accent-200: #bfdbfe;
--color-accent-300: #93c5fd;
--color-accent-400: #60a5fa;
--color-accent-500: #3b82f6;
--color-accent-600: #2563eb;
--color-accent-700: #1d4ed8;
--color-accent-800: #1e40af;
--color-accent-900: #1e3a8a;
--color-success-500: #22c55e;
--color-warning-500: #f59e0b;
--color-error-500: #ef4444;
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
--radius-full: 9999px;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-sans);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
::selection {
background-color: var(--color-accent-200);
color: var(--color-primary-900);
}

View File

@@ -0,0 +1,17 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@layouts/*": ["./src/layouts/*"],
"@styles/*": ["./src/styles/*"],
"@content/*": ["./src/content/*"]
},
"jsx": "react-jsx",
"jsxImportSource": "react"
},
"include": ["src/**/*", ".tina/**/*", "db/**/*"],
"exclude": ["node_modules", "dist", ".astro"]
}

View File

@@ -0,0 +1,275 @@
# Brand Guidelines v1.0
> Last updated: {DATE}
> Status: Draft
## Quick Reference
| Element | Value |
|---------|-------|
| Primary Color | #2563EB |
| Secondary Color | #8B5CF6 |
| Primary Font | Inter |
| Voice | Professional, Helpful, Clear |
---
## 1. Color Palette
### Primary Colors
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| Primary Blue | #2563EB | rgb(37,99,235) | CTAs, headers, links |
| Primary Dark | #1D4ED8 | rgb(29,78,216) | Hover states, emphasis |
### Secondary Colors
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| Secondary Purple | #8B5CF6 | rgb(139,92,246) | Accents, highlights |
| Accent Green | #10B981 | rgb(16,185,129) | Success, positive states |
### Neutral Palette
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| Background | #FFFFFF | rgb(255,255,255) | Page backgrounds |
| Surface | #F9FAFB | rgb(249,250,251) | Cards, sections |
| Text Primary | #111827 | rgb(17,24,39) | Headings, body text |
| Text Secondary | #6B7280 | rgb(107,114,128) | Captions, muted text |
| Border | #E5E7EB | rgb(229,231,235) | Dividers, borders |
### Semantic Colors
| State | Hex | Usage |
|-------|-----|-------|
| Success | #22C55E | Positive actions, confirmations |
| Warning | #F59E0B | Cautions, pending states |
| Error | #EF4444 | Errors, destructive actions |
| Info | #3B82F6 | Informational messages |
### Accessibility
- Text on white background: 7.2:1 contrast ratio (AAA)
- Primary on white: 4.6:1 contrast ratio (AA)
- All interactive elements meet WCAG 2.1 AA standards
---
## 2. Typography
### Font Stack
```css
--font-heading: 'Inter', system-ui, -apple-system, sans-serif;
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
```
### Type Scale
| Element | Size (Desktop) | Size (Mobile) | Weight | Line Height |
|---------|----------------|---------------|--------|-------------|
| H1 | 48px | 32px | 700 | 1.2 |
| H2 | 36px | 28px | 600 | 1.25 |
| H3 | 28px | 24px | 600 | 1.3 |
| H4 | 24px | 20px | 600 | 1.35 |
| Body | 16px | 16px | 400 | 1.5 |
| Body Large | 18px | 18px | 400 | 1.6 |
| Small | 14px | 14px | 400 | 1.5 |
| Caption | 12px | 12px | 400 | 1.4 |
### Font Loading
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
```
---
## 3. Logo Usage
### Variants
| Variant | File | Use Case |
|---------|------|----------|
| Full Horizontal | logo-full-horizontal.svg | Headers, documents |
| Stacked | logo-stacked.svg | Square spaces |
| Icon Only | logo-icon.svg | Favicons, small spaces |
| Monochrome | logo-mono.svg | Limited color contexts |
### Clear Space
Minimum clear space = height of the logo icon (mark)
### Minimum Size
| Context | Minimum Width |
|---------|---------------|
| Digital - Full Logo | 120px |
| Digital - Icon | 24px |
| Print - Full Logo | 35mm |
| Print - Icon | 10mm |
### Don'ts
- Don't rotate or skew the logo
- Don't change colors outside approved palette
- Don't add shadows or effects
- Don't crop or modify proportions
- Don't place on busy backgrounds without sufficient contrast
---
## 4. Voice & Tone
### Brand Personality
| Trait | Description |
|-------|-------------|
| **Professional** | Expert knowledge, authoritative yet approachable |
| **Helpful** | Solution-focused, actionable guidance |
| **Clear** | Direct communication, jargon-free |
| **Confident** | Assured without being arrogant |
### Voice Chart
| Trait | We Are | We Are Not |
|-------|--------|------------|
| Professional | Expert, knowledgeable | Stuffy, corporate |
| Helpful | Supportive, empowering | Patronizing |
| Clear | Direct, concise | Vague, wordy |
| Confident | Assured, trustworthy | Arrogant, overselling |
### Tone by Context
| Context | Tone | Example |
|---------|------|---------|
| Marketing | Engaging, benefit-focused | "Create campaigns that convert." |
| Documentation | Clear, instructional | "Run the command to start." |
| Error messages | Calm, solution-focused | "Try refreshing the page." |
| Success | Brief, celebratory | "Campaign published!" |
### Prohibited Terms
| Avoid | Reason |
|-------|--------|
| Revolutionary | Overused |
| Best-in-class | Vague claim |
| Seamless | Overused |
| Synergy | Corporate jargon |
| Leverage | Use "use" instead |
---
## 5. Imagery Guidelines
### Photography Style
- **Lighting:** Natural, soft lighting preferred
- **Subjects:** Real people, authentic scenarios
- **Color treatment:** Maintain brand colors in post
- **Composition:** Clean, focused subjects
### Illustrations
- Style: Modern, flat design with subtle gradients
- Colors: Brand palette only
- Line weight: 2px consistent stroke
- Corners: 4px rounded
### Icons
- Style: Outlined, 24px base grid
- Stroke: 1.5px consistent
- Corner radius: 2px
- Fill: None (outline only)
---
## 6. Design Components
### Buttons
| Type | Background | Text | Border Radius |
|------|------------|------|---------------|
| Primary | #2563EB | #FFFFFF | 8px |
| Secondary | Transparent | #2563EB | 8px |
| Tertiary | Transparent | #6B7280 | 8px |
### Spacing Scale
| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Tight spacing |
| sm | 8px | Compact elements |
| md | 16px | Standard spacing |
| lg | 24px | Section spacing |
| xl | 32px | Large gaps |
| 2xl | 48px | Section dividers |
### Border Radius
| Element | Radius |
|---------|--------|
| Buttons | 8px |
| Cards | 12px |
| Inputs | 8px |
| Modals | 16px |
| Pills/Tags | 9999px |
---
## AI Image Generation
### Base Prompt Template
Always prepend to image generation prompts:
```
{DESCRIBE YOUR VISUAL STYLE HERE - mood, colors with hex codes, lighting, atmosphere}
```
### Style Keywords
| Category | Keywords |
|----------|----------|
| **Lighting** | {e.g., soft lighting, dramatic, natural} |
| **Mood** | {e.g., professional, energetic, calm} |
| **Composition** | {e.g., centered, rule of thirds, minimal} |
| **Treatment** | {e.g., high contrast, muted, vibrant} |
| **Aesthetic** | {e.g., modern, vintage, minimalist} |
### Visual Mood Descriptors
- {Mood descriptor 1}
- {Mood descriptor 2}
- {Mood descriptor 3}
### Visual Don'ts
| Avoid | Reason |
|-------|--------|
| {Item to avoid} | {Why to avoid it} |
### Example Prompts
**Hero Banner:**
```
{Example prompt for hero banners}
```
**Social Media Post:**
```
{Example prompt for social graphics}
```
---
## Changelog
| Version | Date | Changes |
|---------|------|---------|
| 1.0 | {DATE} | Initial guidelines |

View File

@@ -0,0 +1,69 @@
import type { CollectionConfig } from 'payload'
import { admins, adminsOnly, adminsOrSelf, anyone } from './access'
export const Users: CollectionConfig = {
slug: 'users',
admin: {
useAsTitle: 'email',
},
auth: {
forgotPassword: {
generateEmailHTML: ({ token }) => {
const resetPasswordURL = `${process.env.SERVER_URL}/reset-password?token=${token}`
return `
<!doctype html>
<html>
<body>
<p>คุณได้รับอีเมลนี้เนื่องจากมีการขอตั้ง密码ใหม่สำหรับบัญชีของคุณ</p>
<p>กรุณาคลิกที่ลิงก์ด้านล่างเพื่อตั้ง密码ใหม่:</p>
<a href="${resetPasswordURL}">${resetPasswordURL}</a>
<p>หากคุณไม่ได้เป็นผู้ร้องขอ กรุณาเพิกเฉยต่ออีเมลนี้</p>
</body>
</html>
`
},
},
},
access: {
create: anyone, // Allow anyone to create a user account (for registration)
read: adminsOrSelf, // Allow users to read their own profile, admins can read all
update: adminsOrSelf, // Allow users to update their own profile, admins can update all
delete: admins, // Only admins can delete users
admin: adminsOnly,
},
fields: [
{
name: 'role',
type: 'select',
options: [
{ label: 'ผู้ดูแลระบบ', value: 'admin' },
{ label: 'ผู้ใช้งาน', value: 'user' },
],
defaultValue: 'user',
required: true,
access: {
read: adminsOnly,
create: adminsOnly,
update: adminsOnly,
},
},
{
name: 'firstName',
type: 'text',
required: true,
admin: {
description: 'ชื่อจริง',
},
},
{
name: 'lastName',
type: 'text',
required: true,
admin: {
description: 'นามสกุล',
},
},
// Email is added by Payload auth automatically
// Password is handled by Payload auth automatically
],
}

View File

@@ -0,0 +1,44 @@
import type { Access } from 'payload'
import type { User } from '../../payload-types'
// Utility function to check if user has specific roles
export const checkRole = (allRoles: User['role'][] = [], user: User | null = null): boolean => {
if (user) {
if (allRoles.some((role) => user?.role === role)) {
return true
}
}
return false
}
// Common access patterns
export const anyone: Access = () => true
export const admins: Access = ({ req: { user } }) => checkRole(['admin'], user)
export const adminsOnly: Access = ({ req: { user } }: { req: { user: User | null } }) =>
checkRole(['admin'], user)
export const authenticated: Access = ({ req: { user } }) => !!user
export const adminsOrSelf: Access = ({ req: { user } }) => {
if (!user) return false
if (checkRole(['admin'], user)) return true
return {
id: {
equals: user.id,
},
}
}
export const adminsOrOwner = (ownerField: string = 'user'): Access => {
return ({ req: { user } }) => {
if (!user) return false
if (checkRole(['admin'], user)) return true
return {
[ownerField]: {
equals: user.id,
},
}
}
}

View File

@@ -0,0 +1,447 @@
---
/**
* PDPA Consent Banner Component for Astro + Tina
* Replaces cookie-banner.tsx from Next.js+Payload
*
* Usage: Import and add <ConsentBanner /> to your layout
*/
interface Props {
/** Optional: Custom privacy policy URL */
privacyPolicyUrl?: string;
}
const { privacyPolicyUrl = "/privacy-policy" } = Astro.props;
---
<div
id="pdpa-consent-banner"
class="consent-banner"
role="dialog"
aria-label="Cookie Consent Banner"
aria-hidden="true"
>
<div class="consent-banner__content">
<!-- Main Banner -->
<div id="consent-main" class="consent-banner__main">
<h3 class="consent-banner__title">
🍪 การยินยอมตาม พ.ร.บ.คุ้มครองข้อมูลส่วนบุคคล
</h3>
<p class="consent-banner__text">
เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์การใช้งานเว็บไซต์ของคุณ การเข้าชมเว็บไซต์ต่อถือว่าคุณยินยอมให้เราใช้คุกกี้{' '}
<a href={privacyPolicyUrl} class="consent-banner__link">เรียนรู้เพิ่มเติม</a>
</p>
<div class="consent-banner__buttons">
<button
id="consent-accept-all"
class="consent-btn consent-btn--accept"
type="button"
>
ยอมรับทั้งหมด
</button>
<button
id="consent-reject-all"
class="consent-btn consent-btn--reject"
type="button"
>
ปฏิเสธทั้งหมด
</button>
<button
id="consent-show-preferences"
class="consent-btn consent-btn--preferences"
type="button"
>
ตั้งค่าคุกกี้
</button>
</div>
</div>
<!-- Preferences Panel -->
<div id="consent-preferences" class="consent-banner__preferences" style="display: none;">
<h3 class="consent-banner__title">ตั้งค่าคุกกี้</h3>
<p class="consent-banner__text" style="margin-bottom: 1rem; color: #555; font-size: 0.875rem;">
จัดการการตั้งค่าคุกกี้ของคุณด้านล่าง
</p>
<div class="consent-banner__options">
<!-- Functional Cookies -->
<div class="consent-option consent-option--disabled">
<div class="consent-option__header">
<div>
<h4 class="consent-option__title">คุกกี้ที่จำเป็น</h4>
<p class="consent-option__desc">
จำเป็นสำหรับการทำงานของเว็บไซต์ ไม่สามารปิดได้
</p>
</div>
<span class="consent-option__badge">เปิดอยู่เสมอ</span>
</div>
</div>
<!-- Analytics Cookies -->
<div class="consent-option">
<div class="consent-option__header">
<div>
<h4 class="consent-option__title">คุกกี้วิเคราะห์</h4>
<p class="consent-option__desc">
ช่วยเราเข้าใจว่าผู้เยี่ยมชมใช้งานเว็บไซต์ของเราอย่างไร
</p>
</div>
<label class="consent-checkbox">
<input
type="checkbox"
id="consent-analytics"
name="analytics"
class="consent-checkbox__input"
/>
</label>
</div>
</div>
<!-- Marketing Cookies -->
<div class="consent-option">
<div class="consent-option__header">
<div>
<h4 class="consent-option__title">คุกกี้การตลาด</h4>
<p class="consent-option__desc">
ใช้ติดตามผู้เยี่ยมชมข้ามเว็บไซต์เพื่อการโฆษณา
</p>
</div>
<label class="consent-checkbox">
<input
type="checkbox"
id="consent-marketing"
name="marketing"
class="consent-checkbox__input"
/>
</label>
</div>
</div>
</div>
<div class="consent-banner__buttons">
<button
id="consent-save-preferences"
class="consent-btn consent-btn--save"
type="button"
>
บันทึกการตั้งค่า
</button>
<button
id="consent-back"
class="consent-btn consent-btn--back"
type="button"
>
กลับ
</button>
</div>
</div>
</div>
</div>
<style>
.consent-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
padding: 1.5rem;
z-index: 9999;
border-top: 1px solid #e5e5e5;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.consent-banner__content {
max-width: 1200px;
margin: 0 auto;
}
.consent-banner__title {
margin: 0 0 0.75rem 0;
font-size: 1.125rem;
font-weight: 600;
color: #1a1a1a;
}
.consent-banner__text {
margin: 0 0 1rem 0;
color: #555;
font-size: 0.9375rem;
line-height: 1.5;
}
.consent-banner__link {
color: #0066cc;
text-decoration: underline;
}
.consent-banner__link:hover {
color: #004499;
}
.consent-banner__buttons {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.consent-btn {
padding: 0.625rem 1.25rem;
border-radius: 6px;
font-size: 0.9375rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.consent-btn--accept {
background-color: #22c55e;
color: white;
border: none;
}
.consent-btn--accept:hover {
background-color: #16a34a;
}
.consent-btn--reject {
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
}
.consent-btn--reject:hover {
background-color: #e5e5e5;
}
.consent-btn--preferences {
background-color: transparent;
color: #0066cc;
border: 1px solid #0066cc;
}
.consent-btn--preferences:hover {
background-color: #f0f9ff;
}
.consent-btn--save {
background-color: #0066cc;
color: white;
border: none;
}
.consent-btn--save:hover {
background-color: #004499;
}
.consent-btn--back {
background-color: transparent;
color: #666;
border: none;
}
.consent-btn--back:hover {
color: #333;
}
/* Preferences Panel */
.consent-banner__options {
margin-bottom: 1rem;
}
.consent-option {
padding: 1rem;
background-color: #fff;
border-radius: 8px;
margin-bottom: 0.75rem;
border: 1px solid #e5e5e5;
}
.consent-option--disabled {
background-color: #f9f9f9;
opacity: 0.7;
}
.consent-option__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.consent-option__title {
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
color: #1a1a1a;
}
.consent-option__desc {
margin: 0.25rem 0 0 0;
font-size: 0.8125rem;
color: #666;
}
.consent-option__badge {
padding: 0.25rem 0.75rem;
background-color: #e5e5e5;
color: #666;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
}
.consent-checkbox__input {
width: 18px;
height: 18px;
cursor: pointer;
}
/* Hide initially via JS */
.consent-banner[hidden] {
display: none;
}
</style>
<script>
import { consentStore, type ConsentState } from './stores/consent';
// DOM Elements
const banner = document.getElementById('pdpa-consent-banner');
const mainPanel = document.getElementById('consent-main');
const prefsPanel = document.getElementById('consent-preferences');
const analyticsCheckbox = document.getElementById('consent-analytics') as HTMLInputElement;
const marketingCheckbox = document.getElementById('consent-marketing') as HTMLInputElement;
// Button handlers
const acceptAllBtn = document.getElementById('consent-accept-all');
const rejectAllBtn = document.getElementById('consent-reject-all');
const showPrefsBtn = document.getElementById('consent-show-preferences');
const savePrefsBtn = document.getElementById('consent-save-preferences');
const backBtn = document.getElementById('consent-back');
// Default consent state
const defaultConsent: ConsentState = {
analytics: false,
marketing: false,
functional: false,
hasConsented: false,
};
const STORAGE_KEY = 'pdpa_consent';
// Save consent to localStorage and server
async function saveConsent(newConsent: ConsentState) {
// Save to localStorage
localStorage.setItem(STORAGE_KEY, JSON.stringify(newConsent));
// Update nanostore
consentStore.set(newConsent);
// Hide banner
if (banner) {
banner.setAttribute('hidden', 'true');
}
// Log to server
try {
await fetch('/api/consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: newConsent.hasConsented ? 'accept' : 'reject',
purpose: 'all',
analytics: newConsent.analytics,
marketing: newConsent.marketing,
functional: newConsent.functional,
}),
});
} catch (error) {
console.error('Failed to log consent:', error);
}
}
// Accept all cookies
acceptAllBtn?.addEventListener('click', () => {
saveConsent({
analytics: true,
marketing: true,
functional: true,
hasConsented: true,
timestamp: new Date().toISOString(),
});
});
// Reject all cookies
rejectAllBtn?.addEventListener('click', () => {
saveConsent({
analytics: false,
marketing: false,
functional: false,
hasConsented: true,
timestamp: new Date().toISOString(),
});
});
// Show preferences panel
showPrefsBtn?.addEventListener('click', () => {
if (mainPanel && prefsPanel) {
mainPanel.style.display = 'none';
prefsPanel.style.display = 'block';
}
});
// Save custom preferences
savePrefsBtn?.addEventListener('click', () => {
saveConsent({
analytics: analyticsCheckbox?.checked ?? false,
marketing: marketingCheckbox?.checked ?? false,
functional: true, // Always on
hasConsented: true,
timestamp: new Date().toISOString(),
});
});
// Back to main panel
backBtn?.addEventListener('click', () => {
if (mainPanel && prefsPanel) {
prefsPanel.style.display = 'none';
mainPanel.style.display = 'block';
}
});
// Check for existing consent on load
function initBanner() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
const parsed = JSON.parse(stored);
// Already consented - hide banner
if (banner) {
banner.setAttribute('hidden', 'true');
}
// Sync with nanostore
consentStore.set(parsed);
} catch {
// No valid consent - show banner
if (banner) {
banner.removeAttribute('hidden');
}
}
} else {
// No consent yet - show banner
if (banner) {
banner.removeAttribute('hidden');
}
}
}
// Initialize on page load
initBanner();
</script>

View File

@@ -0,0 +1,147 @@
# PDPA Consent Logging Template
Template สำหรับเพิ่ม PDPA consent logging ใน Astro + Tina (Astro DB)
## Files
```
consent/
├── ConsentBanner.astro # Consent banner component
├── api/
│ └── consent.ts # API endpoints (GET, POST, DELETE)
├── db/
│ └── config.ts # Astro DB schema (defineTable)
├── stores/
│ └── consent.ts # Nano Stores for client state
└── README.md # This file
```
## วิธีใช้ (Astro)
### 1. เพิ่ม Astro DB Schema
Copy `db/config.ts` ไปที่ `src/db/config.ts`:
```ts
// src/db/config.ts
import { defineTable, column } from 'astro:db';
export const ConsentLog = defineTable({
columns: {
id: column.number({ primaryKey: true }),
action: column.text(),
purpose: column.text(),
analytics: column.boolean({ default: false }),
marketing: column.boolean({ default: false }),
functional: column.boolean({ default: false }),
userAgent: column.text({ optional: true }),
ip: column.text({ optional: true }),
timestamp: column.date(),
sessionId: column.text({ optional: true }),
},
});
```
### 2. สร้าง API Endpoint
Copy `api/consent.ts` ไปที่ `src/pages/api/consent.ts`
### 3. เพิ่ม ConsentBanner Component
Copy `ConsentBanner.astro` ไปที่ `src/components/consent/ConsentBanner.astro`
### 4. เพิ่มใน Layout
เพิ่ม `<ConsentBanner />` ใน `src/layouts/Layout.astro`:
```astro
---
import ConsentBanner from '../components/consent/ConsentBanner.astro';
---
<html lang="th">
<body>
<slot />
<ConsentBanner />
</body>
</html>
```
## API
### POST /api/consent
บันทึก consent action
**Request:**
```json
{
"action": "accept",
"purpose": "all",
"analytics": true,
"marketing": false,
"functional": true
}
```
**Response:**
```json
{
"success": true,
"doc": {
"id": 1,
"action": "accept",
"purpose": "all",
"analytics": true,
"marketing": false,
"functional": true,
"userAgent": "Mozilla/5.0...",
"ip": "127.0.0.1",
"timestamp": "2026-04-10T00:00:00.000Z"
}
}
```
### GET /api/consent
ดึง consent logs
```bash
curl "http://localhost:4321/api/consent"
```
### DELETE /api/consent
Right to be forgotten (ลบข้อมูลตาม พ.ร.บ.)
```bash
curl -X DELETE "http://localhost:4321/api/consent?sessionId=xxx"
```
## Nano Stores Usage
```ts
import { consentStore, hasAnalyticsConsent, hasMarketingConsent } from './stores/consent';
// Subscribe to changes
consentStore.subscribe((state) => {
console.log('Consent changed:', state);
});
// Check consent
if (hasAnalyticsConsent()) {
// Load analytics
}
```
## UX
- **ยอมรับทั้งหมด** - เปิดทุกคุกกี้
- **ปฏิเสธทั้งหมด** - ปิดทุกคุกกี้ (ยกเว้น functional)
- **ตั้งค่าคุกกี้** - แผงปรับแต่งเอง
## ⚠️ Pitfalls สำคัญ
1. **Astro DB ต้องรันบน server-side** - ใช้ `APIRoute` import
2. **Nano Stores รันบน client-side** - ใช้ `<script>` tag ใน Astro
3. **import ถูกต้อง** - ใช้ `import { db } from 'astro:db'` ไม่ใช่ `defineDb`

View File

@@ -0,0 +1,120 @@
import type { APIRoute } from 'astro';
import { db, eq } from 'astro:db';
import { ConsentLog } from '../db/config';
export const POST: APIRoute = async ({ request, clientAddress }) => {
try {
const body = await request.json();
const {
action = 'accept',
purpose = 'all',
analytics = false,
marketing = false,
functional = false,
} = body;
const ip = clientAddress || 'unknown';
const userAgent = request.headers.get('user-agent') || 'unknown';
const doc = await db.insert(ConsentLog).values({
action,
purpose,
analytics,
marketing,
functional,
ip,
userAgent,
timestamp: new Date(),
});
return new Response(JSON.stringify({
success: true,
doc,
}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error('Consent API error:', error);
return new Response(JSON.stringify({
success: false,
error: 'Failed to log consent',
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
};
export const GET: APIRoute = async ({ request }) => {
try {
const url = new URL(request.url);
const sessionId = url.searchParams.get('sessionId');
let docs;
if (sessionId) {
docs = await db.select().from(ConsentLog).where(
eq(ConsentLog.sessionId, sessionId)
);
} else {
docs = await db.select().from(ConsentLog);
}
return new Response(JSON.stringify({
success: true,
docs,
}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error('Consent GET error:', error);
return new Response(JSON.stringify({
success: false,
error: 'Failed to retrieve consent logs',
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
};
export const DELETE: APIRoute = async ({ request }) => {
try {
const url = new URL(request.url);
const sessionId = url.searchParams.get('sessionId');
if (!sessionId) {
return new Response(JSON.stringify({
success: false,
error: 'sessionId is required',
}), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
const deleted = await db.delete(ConsentLog).where(
eq(ConsentLog.sessionId, sessionId)
);
return new Response(JSON.stringify({
success: true,
deleted,
message: 'All consent records for this session have been deleted',
}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error('Right to be forgotten error:', error);
return new Response(JSON.stringify({
success: false,
error: 'Failed to delete consent records',
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
};

View File

@@ -0,0 +1,38 @@
import { defineDb, defineTable, column } from 'astro:db';
const ConsentLog = defineTable({
columns: {
id: column.number({ primaryKey: true }),
action: column.text(),
purpose: column.text(),
analytics: column.boolean({ default: false }),
marketing: column.boolean({ default: false }),
functional: column.boolean({ default: false }),
userAgent: column.text({ optional: true }),
ip: column.text({ optional: true }),
timestamp: column.date(),
sessionId: column.text({ optional: true }),
},
});
export type ConsentAction = 'accept' | 'reject' | 'update';
export type ConsentPurpose = 'analytics' | 'marketing' | 'functional' | 'all';
export interface ConsentRow {
id: number;
action: ConsentAction;
purpose: ConsentPurpose;
analytics: boolean;
marketing: boolean;
functional: boolean;
userAgent?: string;
ip?: string;
timestamp: Date;
sessionId?: string;
}
export default defineDb({
tables: {
ConsentLog,
},
});

View File

@@ -0,0 +1,75 @@
import { map } from 'nanostores';
export interface ConsentState {
analytics: boolean;
marketing: boolean;
functional: boolean;
hasConsented: boolean;
timestamp?: string;
}
export interface ConsentLogData extends ConsentState {
ip?: string;
userAgent?: string;
}
export const defaultConsent: ConsentState = {
analytics: false,
marketing: false,
functional: false,
hasConsented: false,
};
export const consentStore = map<ConsentState>(defaultConsent);
export const STORAGE_KEY = 'pdpa_consent';
export function loadConsent(): ConsentState {
if (typeof localStorage === 'undefined') {
return defaultConsent;
}
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
const parsed = JSON.parse(stored) as ConsentState;
consentStore.set(parsed);
return parsed;
} catch {
return defaultConsent;
}
}
return defaultConsent;
}
export function saveConsentLocally(state: ConsentState): void {
if (typeof localStorage === 'undefined') return;
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
consentStore.set(state);
}
export function hasAnalyticsConsent(): boolean {
const state = consentStore.get();
return state.hasConsented && state.analytics;
}
export function hasMarketingConsent(): boolean {
const state = consentStore.get();
return state.hasConsented && state.marketing;
}
export function hasFunctionalConsent(): boolean {
const state = consentStore.get();
return state.hasConsented;
}
export function resetConsent(): void {
if (typeof localStorage === 'undefined') return;
localStorage.removeItem(STORAGE_KEY);
consentStore.set(defaultConsent);
}
export function hasConsented(): boolean {
return consentStore.get().hasConsented;
}

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="zh-CN" data-theme="minimal-white">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>html-ppt · Deck</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
</head>
<body data-themes="minimal-white,editorial-serif,soft-pastel,arctic-cool,sunset-warm,catppuccin-mocha,tokyo-night,aurora,xiaohongshu-white,neo-brutalism" data-theme-base="../assets/themes/">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<p class="kicker">html-ppt · 2026</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">用模板,<span class="gradient-text">换主题</span><br>讲任何事情</h1>
<p class="lede">24 themes · 30 layouts · 25 animations · zero build</p>
<div class="deck-footer"><span class="dim2">lewis</span><span class="slide-number" data-current="1" data-total="6"></span></div>
<div class="notes">这是一个最小可用的 deck。你可以复制这个文件作为新 deck 的起点。</div>
</section>
<!-- 2. TOC -->
<section class="slide" data-title="目录">
<p class="kicker">Agenda</p>
<h2 class="h2">我们会讲三件事</h2>
<div class="grid g3 mt-l anim-stagger-list" data-anim-target>
<div class="card"><h4>01 · Tokens</h4><p class="dim">把颜色/字体/圆角收进 CSS 变量。</p></div>
<div class="card"><h4>02 · Layouts</h4><p class="dim">30 种可复用单页。</p></div>
<div class="card"><h4>03 · Runtime</h4><p class="dim">键盘驱动、按 T 换主题。</p></div>
</div>
</section>
<!-- 3. Stat -->
<section class="slide center tc" data-title="Stat">
<div>
<p class="kicker">Result</p>
<div style="font-size:220px;font-weight:900;line-height:1"><span class="counter gradient-text" data-to="92">0</span><span class="gradient-text">%</span></div>
<h3>的准备时间被省下</h3>
</div>
</section>
<!-- 4. Two column -->
<section class="slide" data-title="Tokens">
<p class="kicker">Under the hood</p>
<h2 class="h2">换主题 = 换一组变量</h2>
<div class="grid g2 mt-l">
<div class="card"><h4>语义变量</h4><p class="dim"><code>var(--surface)</code>,不写具体色值。</p></div>
<div class="card"><h4>一键切换</h4><p class="dim">按 T 循环所有主题——所有 slide 同步更新。</p></div>
</div>
</section>
<!-- 5. CTA -->
<section class="slide center tc" data-title="CTA">
<div>
<p class="kicker">Your turn</p>
<h1 class="h1 anim-rise-in" data-anim="rise-in">开始做你的 deck</h1>
<p class="lede" style="margin:16px auto">按 ← → 翻页 · T 切主题 · A 切动效 · F 全屏 · O 概览 · S 备注</p>
</div>
</section>
<!-- 6. Thanks -->
<section class="slide center tc" data-title="Thanks">
<h1 class="h1" style="font-size:160px;line-height:1"><span class="gradient-text">Thanks</span></h1>
<p class="lede">lewis · sudolewis@gmail.com</p>
</section>
</div>
<script src="../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,143 @@
{
"$schema": "https://design-tokens.org/schema.json",
"primitive": {
"color": {
"gray": {
"50": { "$value": "#F9FAFB", "$type": "color" },
"100": { "$value": "#F3F4F6", "$type": "color" },
"200": { "$value": "#E5E7EB", "$type": "color" },
"300": { "$value": "#D1D5DB", "$type": "color" },
"400": { "$value": "#9CA3AF", "$type": "color" },
"500": { "$value": "#6B7280", "$type": "color" },
"600": { "$value": "#4B5563", "$type": "color" },
"700": { "$value": "#374151", "$type": "color" },
"800": { "$value": "#1F2937", "$type": "color" },
"900": { "$value": "#111827", "$type": "color" },
"950": { "$value": "#030712", "$type": "color" }
},
"blue": {
"50": { "$value": "#EFF6FF", "$type": "color" },
"500": { "$value": "#3B82F6", "$type": "color" },
"600": { "$value": "#2563EB", "$type": "color" },
"700": { "$value": "#1D4ED8", "$type": "color" },
"800": { "$value": "#1E40AF", "$type": "color" }
},
"red": {
"500": { "$value": "#EF4444", "$type": "color" },
"600": { "$value": "#DC2626", "$type": "color" },
"700": { "$value": "#B91C1C", "$type": "color" }
},
"green": {
"500": { "$value": "#22C55E", "$type": "color" },
"600": { "$value": "#16A34A", "$type": "color" }
},
"yellow": {
"500": { "$value": "#EAB308", "$type": "color" }
},
"white": { "$value": "#FFFFFF", "$type": "color" }
},
"spacing": {
"0": { "$value": "0", "$type": "dimension" },
"1": { "$value": "0.25rem", "$type": "dimension" },
"2": { "$value": "0.5rem", "$type": "dimension" },
"3": { "$value": "0.75rem", "$type": "dimension" },
"4": { "$value": "1rem", "$type": "dimension" },
"5": { "$value": "1.25rem", "$type": "dimension" },
"6": { "$value": "1.5rem", "$type": "dimension" },
"8": { "$value": "2rem", "$type": "dimension" },
"10": { "$value": "2.5rem", "$type": "dimension" },
"12": { "$value": "3rem", "$type": "dimension" },
"16": { "$value": "4rem", "$type": "dimension" }
},
"fontSize": {
"xs": { "$value": "0.75rem", "$type": "dimension" },
"sm": { "$value": "0.875rem", "$type": "dimension" },
"base": { "$value": "1rem", "$type": "dimension" },
"lg": { "$value": "1.125rem", "$type": "dimension" },
"xl": { "$value": "1.25rem", "$type": "dimension" },
"2xl": { "$value": "1.5rem", "$type": "dimension" },
"3xl": { "$value": "1.875rem", "$type": "dimension" },
"4xl": { "$value": "2.25rem", "$type": "dimension" }
},
"radius": {
"none": { "$value": "0", "$type": "dimension" },
"sm": { "$value": "0.125rem", "$type": "dimension" },
"default": { "$value": "0.25rem", "$type": "dimension" },
"md": { "$value": "0.375rem", "$type": "dimension" },
"lg": { "$value": "0.5rem", "$type": "dimension" },
"xl": { "$value": "0.75rem", "$type": "dimension" },
"full": { "$value": "9999px", "$type": "dimension" }
},
"shadow": {
"none": { "$value": "none", "$type": "shadow" },
"sm": { "$value": "0 1px 2px 0 rgb(0 0 0 / 0.05)", "$type": "shadow" },
"default": { "$value": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", "$type": "shadow" },
"md": { "$value": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", "$type": "shadow" },
"lg": { "$value": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)", "$type": "shadow" }
},
"duration": {
"fast": { "$value": "150ms", "$type": "duration" },
"normal": { "$value": "200ms", "$type": "duration" },
"slow": { "$value": "300ms", "$type": "duration" }
}
},
"semantic": {
"color": {
"background": { "$value": "{primitive.color.gray.50}", "$type": "color" },
"foreground": { "$value": "{primitive.color.gray.900}", "$type": "color" },
"primary": { "$value": "{primitive.color.blue.600}", "$type": "color" },
"primary-hover": { "$value": "{primitive.color.blue.700}", "$type": "color" },
"primary-foreground": { "$value": "{primitive.color.white}", "$type": "color" },
"secondary": { "$value": "{primitive.color.gray.100}", "$type": "color" },
"secondary-foreground": { "$value": "{primitive.color.gray.900}", "$type": "color" },
"muted": { "$value": "{primitive.color.gray.100}", "$type": "color" },
"muted-foreground": { "$value": "{primitive.color.gray.500}", "$type": "color" },
"destructive": { "$value": "{primitive.color.red.600}", "$type": "color" },
"destructive-foreground": { "$value": "{primitive.color.white}", "$type": "color" },
"border": { "$value": "{primitive.color.gray.200}", "$type": "color" },
"ring": { "$value": "{primitive.color.blue.500}", "$type": "color" }
},
"spacing": {
"component": { "$value": "{primitive.spacing.4}", "$type": "dimension" },
"section": { "$value": "{primitive.spacing.12}", "$type": "dimension" }
}
},
"component": {
"button": {
"bg": { "$value": "{semantic.color.primary}", "$type": "color" },
"fg": { "$value": "{semantic.color.primary-foreground}", "$type": "color" },
"hover-bg": { "$value": "{semantic.color.primary-hover}", "$type": "color" },
"padding-x": { "$value": "{primitive.spacing.4}", "$type": "dimension" },
"padding-y": { "$value": "{primitive.spacing.2}", "$type": "dimension" },
"radius": { "$value": "{primitive.radius.md}", "$type": "dimension" },
"font-size": { "$value": "{primitive.fontSize.sm}", "$type": "dimension" }
},
"input": {
"bg": { "$value": "{semantic.color.background}", "$type": "color" },
"border": { "$value": "{semantic.color.border}", "$type": "color" },
"focus-ring": { "$value": "{semantic.color.ring}", "$type": "color" },
"padding-x": { "$value": "{primitive.spacing.3}", "$type": "dimension" },
"padding-y": { "$value": "{primitive.spacing.2}", "$type": "dimension" },
"radius": { "$value": "{primitive.radius.md}", "$type": "dimension" }
},
"card": {
"bg": { "$value": "{primitive.color.white}", "$type": "color" },
"border": { "$value": "{semantic.color.border}", "$type": "color" },
"shadow": { "$value": "{primitive.shadow.default}", "$type": "shadow" },
"padding": { "$value": "{primitive.spacing.6}", "$type": "dimension" },
"radius": { "$value": "{primitive.radius.lg}", "$type": "dimension" }
}
},
"dark": {
"semantic": {
"color": {
"background": { "$value": "{primitive.color.gray.950}", "$type": "color" },
"foreground": { "$value": "{primitive.color.gray.50}", "$type": "color" },
"secondary": { "$value": "{primitive.color.gray.800}", "$type": "color" },
"muted": { "$value": "{primitive.color.gray.800}", "$type": "color" },
"muted-foreground": { "$value": "{primitive.color.gray.400}", "$type": "color" },
"border": { "$value": "{primitive.color.gray.800}", "$type": "color" }
}
}
}
}

View File

@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><title>Full-Deck Gallery — html-ppt v2</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<style>
html,body{background:#0b0c10;color:#e8ebf4;font-family:var(--font-sans)}
.deck{background:#0b0c10}
.slide{padding:60px 80px;color:#e8ebf4;background:transparent;display:flex;flex-direction:column}
.slide h1{color:#fff;font-size:48px;margin:0 0 6px;letter-spacing:-.02em}
.slide .sub{color:#aab0c0;font-size:18px;margin:0 0 22px}
.frame-wrap{flex:1;border-radius:14px;overflow:hidden;border:1px solid rgba(255,255,255,.12);
box-shadow:0 30px 80px rgba(0,0,0,.5);position:relative;background:#fff}
iframe.tpl{position:absolute;inset:0;width:200%;height:200%;border:0;
transform:scale(.5);transform-origin:top left}
.meta{position:absolute;top:24px;right:40px;font-family:'JetBrains Mono',monospace;
font-size:12px;color:#6a7086;letter-spacing:.14em;text-transform:uppercase;z-index:30}
.tag{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.08);
color:#cfd3dc;font-size:11px;margin-right:6px}
.cover{align-items:center;justify-content:center;text-align:center}
.cover h1{font-size:84px;background:linear-gradient(135deg,#60a5fa,#a78bfa,#f472b6);
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.cover p{color:#aab0c0;max-width:60ch;font-size:20px}
.legend{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
</style>
</head>
<body>
<div class="deck">
<section class="slide cover">
<p class="kicker" style="color:#a78bfa">HTML-PPT v2 · Full-Deck Gallery</p>
<h1>14 full-deck templates</h1>
<p>Press → to browse. Each slide is a live iframe preview of a complete, multi-slide deck template. Open any <code>templates/full-decks/&lt;name&gt;/index.html</code> to see the full deck, or copy the folder to scaffold your own.</p>
<div class="legend">
<span class="tag">8 extracted from real decks</span>
<span class="tag">6 scenario scaffolds</span>
<span class="tag">scoped .tpl-&lt;name&gt; CSS</span>
<span class="tag">36 themes compatible</span>
</div>
</section>
<!-- Template preview slides generated via JS below -->
</div>
<script>
const TPLS = [
['xhs-white-editorial', '白底杂志风', 'extracted', 'xhs posts, editorial lifestyle'],
['graphify-dark-graph', '暗底知识图谱', 'extracted', 'AI/graph/data products'],
['knowledge-arch-blueprint', '奶油蓝图架构', 'extracted', 'architecture, systems thinking'],
['hermes-cyber-terminal', '暗终端 cyber', 'extracted', 'devtool, honest-review, agent demos'],
['obsidian-claude-gradient', 'GitHub 暗紫渐变', 'extracted', 'tool walkthroughs, LLM product'],
['testing-safety-alert', '红琥珀警示', 'extracted', 'security, incident review, AI safety'],
['xhs-pastel-card', '柔和马卡龙', 'extracted', 'lifestyle, soft emotional'],
['dir-key-nav-minimal', '方向键 8 色极简', 'extracted', 'keynote, one-idea-per-slide'],
['pitch-deck', 'Pitch Deck YC 风', 'scenario', 'fundraising, startup pitch'],
['product-launch', 'Product Launch', 'scenario', 'product announcement, launch keynote'],
['tech-sharing', 'Tech Sharing 技术分享','scenario','internal tech talk, conference talk'],
['weekly-report', 'Weekly Report 周报','scenario', 'status update, business review'],
['xhs-post', '小红书 图文 9 屏 3:4','scenario', 'xiaohongshu / ig carousel'],
['course-module', 'Course Module 教学模块','scenario','online course, workshop module'],
['presenter-mode-reveal', '🎤 Presenter Mode 演讲者模式','scenario','tech sharing, talk with 逐字稿, speaker view']
];
const deck = document.querySelector('.deck');
TPLS.forEach((t,i)=>{
const s = document.createElement('section');
s.className = 'slide';
s.setAttribute('data-title',t[0]);
s.innerHTML = `
<span class="meta">${i+1}/${TPLS.length+1}</span>
<h1>${t[0]}</h1>
<p class="sub">${t[1]} · <span class="tag">${t[2]}</span> ${t[3]}</p>
<div class="frame-wrap">
<iframe class="tpl" src="full-decks/${t[0]}/index.html" loading="eager" title="${t[0]}"></iframe>
</div>`;
deck.appendChild(s);
});
</script>
<script src="../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,8 @@
# course-module · 教学模块
7-slide teaching module: cover (title + meta), objectives, core concept, worked example, exercise, check-your-understanding (MCQ), summary.
Academic but friendly look: warm off-white paper, Playfair Display display type, a green/terracotta accent pair. A persistent **left sidebar** on content slides lists the module's learning objectives and checks them off as you progress — students always know where they are.
**Use when:** online course modules, lecture handouts, onboarding curricula, workshop units.
**Feel:** a good textbook opened to a chapter — structured, quiet, encouraging.

View File

@@ -0,0 +1,189 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Module 04 · Recursion · CS101</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-course-module">
<div class="deck">
<!-- 1. Cover -->
<section class="slide full" data-title="Cover">
<p class="kicker">CS 101 · MODULE 04</p>
<h1 class="h1 mt-s">Recursion: solving<br>problems by <em>calling yourself</em>.</h1>
<p class="lede mt-l" style="max-width:62ch">In this module you'll learn why a function that calls itself is not a trick, but the most natural way to describe problems that contain smaller copies of themselves.</p>
<div class="row mt-l" style="gap:16px">
<span class="pill-academic">~ 45 min read</span>
<span class="pill-academic">prereq · functions, if/else</span>
<span class="pill-academic">lang · Python</span>
</div>
<div class="deck-footer"><span>Dr. A. Rivera · Spring 2026</span><span class="slide-number" data-current="1" data-total="7"></span></div>
</section>
<!-- 2. Objectives -->
<section class="slide" data-title="Objectives">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="current">Define recursion</li>
<li>Identify a base case</li>
<li>Trace a recursive call</li>
<li>Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Module progress</h5>
<p class="dim" style="font-size:13px">Page 2 of 7 · ~5 min in</p>
</aside>
<div class="main">
<p class="kicker">OBJECTIVES</p>
<h2 class="h2 mt-s">By the end, you will be able to…</h2>
<div class="stack mt-l">
<div class="concept-box"><h4>① Explain recursion in one sentence.</h4><p class="dim">"A function that solves a problem by calling itself on a smaller version of that problem."</p></div>
<div class="concept-box"><h4>② Write a base case that always terminates.</h4><p class="dim">Every recursive function must have an exit door, or it runs forever.</p></div>
<div class="concept-box"><h4>③ Trace a call stack on paper.</h4><p class="dim">Given <code>fact(4)</code>, draw the stack frames top-to-bottom.</p></div>
<div class="concept-box"><h4>④ Convert a while-loop to a recursive equivalent.</h4><p class="dim">And explain when one is clearer than the other.</p></div>
</div>
</div>
</section>
<!-- 3. Concept -->
<section class="slide" data-title="Concept">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="current">Identify a base case</li>
<li>Trace a recursive call</li>
<li>Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Key terms</h5>
<p class="dim" style="font-size:13px">base case · recursive case · call stack · tail call</p>
</aside>
<div class="main">
<p class="kicker">CORE CONCEPT</p>
<h2 class="h2 mt-s">Two parts, always.</h2>
<p class="lede mt-m">A recursive function has exactly two things inside it: a <b>base case</b> (when to stop) and a <b>recursive case</b> (how to shrink the problem before calling yourself).</p>
<div class="callout">
<b>Rule of thumb.</b> If you can't name the base case out loud, don't write the recursion yet. Draw it on paper first.
</div>
<div class="grid g2 mt-l">
<div class="concept-box"><h4>Base case</h4><p class="dim">The smallest possible input — one the function answers directly, without calling itself.</p><p class="pill-academic">e.g. <b>n == 0</b></p></div>
<div class="concept-box"><h4>Recursive case</h4><p class="dim">Every other input — delegate to a smaller version of the same problem.</p><p class="pill-academic">e.g. <b>n × fact(n-1)</b></p></div>
</div>
</div>
</section>
<!-- 4. Example -->
<section class="slide" data-title="Example">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="done">Identify a base case</li>
<li class="current">Trace a recursive call</li>
<li>Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Try it yourself</h5>
<p class="dim" style="font-size:13px">Open repl.it and run the code on the right. Then try <code>fact(10)</code>.</p>
</aside>
<div class="main">
<p class="kicker">WORKED EXAMPLE</p>
<h2 class="h2 mt-s">Factorial, 7 lines.</h2>
<div class="code mt-m"><pre style="margin:0"><span class="cmt"># fact(n) = n × (n-1) ×× 1, and fact(0) = 1</span>
<span class="kw">def</span> fact(n):
<span class="kw">if</span> n == <span class="str">0</span>: <span class="cmt"># base case</span>
<span class="kw">return</span> <span class="str">1</span>
<span class="kw">return</span> n * fact(n - <span class="str">1</span>) <span class="cmt"># recursive case</span>
<span class="kw">print</span>(fact(<span class="str">4</span>)) <span class="cmt"># → 24</span></pre></div>
<div class="callout">
<b>Trace fact(4).</b> 4 × fact(3) → 4 × (3 × fact(2)) → 4 × 3 × (2 × fact(1)) → 4 × 3 × 2 × 1 × fact(0) → 4 × 3 × 2 × 1 × 1 = <b>24</b>.
</div>
</div>
</section>
<!-- 5. Exercise -->
<section class="slide" data-title="Exercise">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="done">Identify a base case</li>
<li class="done">Trace a recursive call</li>
<li class="current">Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Time</h5>
<p class="dim" style="font-size:13px">~10 minutes · solo</p>
</aside>
<div class="main">
<p class="kicker">EXERCISE 4.1</p>
<h2 class="h2 mt-s">Write <em>sum_to(n)</em>.</h2>
<p class="lede mt-m">Return <code>1 + 2 + … + n</code> using recursion — no loops allowed.</p>
<div class="exercise mt-l">
<p style="margin:0;font-size:18px;color:var(--text-1)"><b>Your task</b></p>
<ol style="color:var(--text-2);line-height:1.8;margin:10px 0 0">
<li>Write the base case. What does <code>sum_to(0)</code> return?</li>
<li>Write the recursive case in terms of <code>sum_to(n - 1)</code>.</li>
<li>Test it: <code>sum_to(5) == 15</code>, <code>sum_to(10) == 55</code>.</li>
<li>Bonus: what happens if you call <code>sum_to(-3)</code>? Fix it.</li>
</ol>
</div>
<p class="dim mt-m" style="font-size:14px">Stuck? Remember: a base case is the smallest input you already know the answer to.</p>
</div>
</section>
<!-- 6. Check understanding -->
<section class="slide" data-title="Check">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="done">Identify a base case</li>
<li class="done">Trace a recursive call</li>
<li class="done">Convert loop ↔ recursion</li>
<li class="current">Recognize when recursion helps</li>
</ul>
<h5>Self-assess</h5>
<p class="dim" style="font-size:13px">You should get 3/3.</p>
</aside>
<div class="main">
<p class="kicker">CHECK YOUR UNDERSTANDING</p>
<h2 class="h2 mt-s">Which function will recurse forever?</h2>
<div class="mt-l">
<div class="mcq"><div class="letter">A</div><div><b>def f(n): return 1 if n == 0 else n * f(n - 1)</b><p class="dim" style="font-size:13px;margin:4px 0 0">Base case <code>n == 0</code>, shrinks toward it. Terminates.</p></div></div>
<div class="mcq correct"><div class="letter">B</div><div><b>def f(n): return n + f(n + 1)</b><p class="dim" style="font-size:13px;margin:4px 0 0"><b style="color:var(--accent)">✓ Correct.</b> No base case, and <code>n</code> grows — infinite recursion.</p></div></div>
<div class="mcq"><div class="letter">C</div><div><b>def f(n): return n if n &lt; 2 else f(n - 1) + f(n - 2)</b><p class="dim" style="font-size:13px;margin:4px 0 0">Classic Fibonacci. Base case on <code>n &lt; 2</code>. Terminates.</p></div></div>
</div>
</div>
</section>
<!-- 7. Summary -->
<section class="slide full" data-title="Summary">
<p class="kicker">SUMMARY · MODULE 04</p>
<h1 class="h1 mt-s">You can now…</h1>
<div class="grid g2 mt-l">
<div class="concept-box"><h4>✓ Define recursion</h4><p class="dim">A function that calls itself on a smaller input.</p></div>
<div class="concept-box"><h4>✓ Write a safe base case</h4><p class="dim">Every recursion needs an exit door.</p></div>
<div class="concept-box"><h4>✓ Trace a call stack</h4><p class="dim">You can unwind <code>fact(4)</code> by hand.</p></div>
<div class="concept-box"><h4>✓ Judge when to use it</h4><p class="dim">Trees and self-similar problems → recursion. Flat iteration → loop.</p></div>
</div>
<div class="callout mt-l">
<b>Up next · Module 05.</b> Divide &amp; conquer: merge sort. We'll use everything you just learned — but on lists, not numbers.
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,46 @@
/* course-module — academic but friendly */
.tpl-course-module{
--bg:#fbfaf6;--bg-soft:#f4f1e8;--surface:#ffffff;--surface-2:#f6f3ea;
--border:rgba(60,45,20,.12);--border-strong:rgba(60,45,20,.24);
--text-1:#2a2418;--text-2:#5a5140;--text-3:#8a7f68;
--accent:#2d7d6e;--accent-2:#d88a3a;--accent-3:#c4593f;
--grad:linear-gradient(135deg,#2d7d6e,#4ea893);
--radius:14px;--radius-lg:20px;
--shadow:0 12px 30px rgba(60,45,20,.07);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-course-module .slide{padding:64px 80px;background:var(--bg);display:grid;grid-template-columns:260px 1fr;gap:56px;align-content:start}
.tpl-course-module .slide.full{grid-template-columns:1fr;display:flex;flex-direction:column;justify-content:center}
.tpl-course-module .sidebar{border-right:1px solid var(--border);padding-right:32px;position:relative}
.tpl-course-module .sidebar .brand{font-family:'Playfair Display',serif;font-size:22px;font-weight:700;color:var(--accent)}
.tpl-course-module .sidebar .brand::before{content:"✦ ";color:var(--accent-2)}
.tpl-course-module .sidebar h5{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--text-3);margin:32px 0 12px}
.tpl-course-module .obj-list{list-style:none;padding:0;margin:0;font-size:13px;color:var(--text-2);line-height:1.5}
.tpl-course-module .obj-list li{padding:8px 0 8px 22px;position:relative;border-bottom:1px dashed var(--border)}
.tpl-course-module .obj-list li::before{content:"○";position:absolute;left:0;top:8px;color:var(--accent)}
.tpl-course-module .obj-list li.done::before{content:"●";color:var(--accent)}
.tpl-course-module .obj-list li.current{color:var(--text-1);font-weight:700}
.tpl-course-module .obj-list li.current::before{content:"▸";color:var(--accent-2)}
.tpl-course-module .main{min-width:0}
.tpl-course-module .h1{font-family:'Playfair Display',serif;font-size:72px;line-height:1.02;font-weight:800;letter-spacing:-.02em;color:var(--text-1)}
.tpl-course-module .h2{font-family:'Playfair Display',serif;font-size:48px;line-height:1.1;font-weight:700;letter-spacing:-.015em;color:var(--text-1)}
.tpl-course-module h3,.tpl-course-module h4{color:var(--text-1)}
.tpl-course-module .kicker{color:var(--accent-2);font-size:12px;font-weight:700;letter-spacing:.14em}
.tpl-course-module .lede{font-size:20px;color:var(--text-2);line-height:1.7}
.tpl-course-module .callout{border-left:4px solid var(--accent-2);background:var(--surface-2);padding:20px 24px;border-radius:0 var(--radius) var(--radius) 0;margin-top:24px}
.tpl-course-module .callout b{color:var(--accent-2)}
.tpl-course-module .concept-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px 26px;box-shadow:var(--shadow)}
.tpl-course-module .concept-box h4{margin-top:0;color:var(--accent)}
.tpl-course-module .exercise{background:#fff8ed;border:1.5px dashed var(--accent-2);border-radius:var(--radius);padding:24px 28px}
.tpl-course-module .exercise::before{content:"✎ Exercise";display:block;font-size:12px;font-weight:700;letter-spacing:.12em;color:var(--accent-2);margin-bottom:10px;text-transform:uppercase}
.tpl-course-module .code{background:#2a2418;color:#f4f1e8;border-radius:var(--radius);padding:20px 24px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.7;overflow:auto}
.tpl-course-module .code .cmt{color:#8a7f68;font-style:italic}
.tpl-course-module .code .kw{color:#e8a770}
.tpl-course-module .code .str{color:#8ec6b2}
.tpl-course-module .mcq{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px 22px;margin-bottom:10px;display:flex;gap:14px;align-items:flex-start;cursor:pointer}
.tpl-course-module .mcq .letter{flex:none;width:28px;height:28px;border-radius:50%;border:2px solid var(--text-3);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;color:var(--text-2)}
.tpl-course-module .mcq.correct{border-color:var(--accent);background:rgba(45,125,110,.06)}
.tpl-course-module .mcq.correct .letter{border-color:var(--accent);background:var(--accent);color:#fff}
.tpl-course-module .pill-academic{display:inline-block;padding:4px 12px;border-radius:4px;background:var(--surface-2);border:1px solid var(--border);font-size:12px;color:var(--text-2);font-family:'JetBrains Mono',monospace}
.tpl-course-module .slide.full .h1{font-size:88px}
.tpl-course-module .deck-footer{color:var(--text-3)}

View File

@@ -0,0 +1,11 @@
# dir-key-nav-minimal
8 张幻灯片,每张一个纯色/渐变 mono-backgroundindigo / cream / crimson / emerald / slate / violet / white / charcoal。灵感直接来自 `20260405 演示幻灯片【方向键版】.html` —— 八个 `t-*` 主题类,每张幻灯一个背景,方向键切换,极简 editorial 气质。
**Visual traits:** 每张独立背景色 + 单一 accent、巨大 160px 标题无副图、4px 短粗 accent line divider、arrow-prefixed mono list、左下 `← →` 键盘提示 + 右下 page label、全屏 breathing negative space、JetBrains Mono 做数字 / 代码 / 键盘 hint、每个背景有自己的 `.dk-accent` 色。
**Use when:** 有话要说、没太多图、希望用排版节奏推进观众注意力keynote 式的极简讲稿;每张幻灯只讲一件事;公开分享 / keynote / 演讲稿。
**Source inspiration:** `20260405-Karpathy-知识库/20260405 演示幻灯片【方向键版】.html`.
**Path:** `templates/full-decks/dir-key-nav-minimal/index.html`

View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dir-Key Nav Minimal</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-dir-key-nav-minimal">
<div class="deck">
<!-- 1. COVER · indigo -->
<section class="slide t-indigo is-active">
<div class="dk-snum">01 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Karpathy LLM Wiki</div>
<h1 class="dk-h0">为什么笔记<br>治不了 <span class="dk-accent">LLM</span></h1>
<span class="dk-line"></span>
<p class="dk-lede">8 种背景、8 张幻灯,一个关于如何把 AI 变成「长期记忆外挂」的最短陈述。<strong>按 → 继续。</strong></p>
</div>
<div class="dk-keyhint">nav · <kbd></kbd> <kbd></kbd> · <kbd>space</kbd></div>
<div class="dk-page">cover</div>
</section>
<!-- 2. SECTION · cream -->
<section class="slide t-cream">
<div class="dk-snum">02 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Chapter 01</div>
<h1 class="dk-h0">The <span class="dk-accent">Problem</span>.</h1>
<span class="dk-line"></span>
<p class="dk-lede">Token 上限是一个物理事实。你每次和 LLM 说话,它都是一个失忆症患者。</p>
</div>
<div class="dk-keyhint">chapter · 01 / 04</div>
<div class="dk-page">section</div>
</section>
<!-- 3. CONTENT · crimson -->
<section class="slide t-crimson">
<div class="dk-snum">03 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Symptoms</div>
<h2 class="dk-h1">四种你已经<br>受够的<br><span class="dk-accent">遗忘</span></h2>
<ul class="dk-list">
<li>昨天聊过的项目,今天重新解释一遍</li>
<li>上下文窗口一到,它开始「编造记忆」</li>
<li>不同 session 之间毫无关联,就像第一次见</li>
<li>你的真正偏好从未被记住,每次都要 re-prompt</li>
</ul>
</div>
<div class="dk-keyhint">content · list</div>
<div class="dk-page">03</div>
</section>
<!-- 4. CONTENT · emerald -->
<section class="slide t-emerald">
<div class="dk-snum">04 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">The Fix</div>
<h2 class="dk-h1">答案不是<br><span class="dk-accent">更大</span> 的窗口。</h2>
<p class="dk-lede" style="margin-top:10px">而是:把你的知识、偏好、历史都<strong>写进文件系统</strong><br>让 LLM 每次对话前,先去读那个系统。</p>
<div class="dk-grid-2">
<div class="dk-col"><h3>× 窗口 stuffing</h3><p>把所有东西塞 prompt贵、慢、最终溢出。</p></div>
<div class="dk-col"><h3>✓ 文件 + 检索</h3><p>按需加载,永远不溢出,结构化可 diff。</p></div>
</div>
</div>
<div class="dk-keyhint">content · compare</div>
<div class="dk-page">04</div>
</section>
<!-- 5. CODE · slate -->
<section class="slide t-slate">
<div class="dk-snum">05 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Minimal Setup</div>
<h2 class="dk-h2"><span class="dk-accent">4 行</span> YAML<br>就能开始。</h2>
<pre class="dk-code">memory:
root: ~/.llm-wiki
format: markdown
retrieval: hybrid # embedding + bm25</pre>
<p class="dk-lede" style="margin-top:16px;font-size:20px">你现在拥有一个会随时间增长的 <strong>第二大脑</strong>。每次对话它都会被读、被更新。</p>
</div>
<div class="dk-keyhint">content · code</div>
<div class="dk-page">05</div>
</section>
<!-- 6. CHART · violet — big number with bar -->
<section class="slide t-violet">
<div class="dk-snum">06 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">30-day result</div>
<div class="dk-big dk-accent">87%</div>
<p class="dk-lede" style="margin-top:14px;font-size:26px">的 re-explain 被消除。平均每次对话节省 <strong>4.2 分钟</strong> 的 re-context。</p>
<svg viewBox="0 0 900 80" style="width:100%;max-width:900px;margin-top:30px">
<rect x="0" y="30" width="900" height="22" rx="11" fill="rgba(255,255,255,.12)"/>
<rect x="0" y="30" width="783" height="22" rx="11" fill="#c4b5fd"/>
<text x="792" y="47" font-family="JetBrains Mono" font-size="16" fill="#c4b5fd" font-weight="700">87%</text>
</svg>
</div>
<div class="dk-keyhint">chart · big-num</div>
<div class="dk-page">06</div>
</section>
<!-- 7. CTA · white -->
<section class="slide t-white">
<div class="dk-snum">07 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Start tonight</div>
<h2 class="dk-h1">开始<br>你的 <span class="dk-accent">wiki</span></h2>
<span class="dk-line"></span>
<p class="dk-lede">不是装又一个插件。是决定:从今晚起,<strong>你的所有 AI 对话都要有一个共同的 vault</strong></p>
<pre class="dk-code" style="font-size:18px">$ mkdir ~/llm-wiki && cd ~/llm-wiki
$ git init
$ echo "# my brain" > README.md</pre>
</div>
<div class="dk-keyhint">cta · three-commands</div>
<div class="dk-page">07</div>
</section>
<!-- 8. THANKS · charcoal -->
<section class="slide t-charcoal">
<div class="dk-snum">08 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">End · thanks for staying</div>
<h1 class="dk-h0"><span class="dk-accent">謝謝</span></h1>
<span class="dk-line"></span>
<p class="dk-lede">Karpathy 的原始 thread + 我的 vault 结构都在 <strong>github.com/lewis/llm-wiki</strong>。欢迎按 ← 再看一遍。</p>
</div>
<div class="dk-keyhint">press <kbd></kbd> to rewind · <kbd>F</kbd> for fullscreen</div>
<div class="dk-page">fin</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,60 @@
/* dir-key-nav-minimal — 方向键极简 · 8 种 mono-background 切换 */
.tpl-dir-key-nav-minimal{
--dk-font:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
--dk-mono:'JetBrains Mono',monospace;
background:#000;
color:#fff;
font-family:var(--dk-font);
}
.tpl-dir-key-nav-minimal .slide{padding:80px 104px;overflow:hidden;position:absolute;inset:0}
/* 8 background themes */
.tpl-dir-key-nav-minimal .t-indigo{background:linear-gradient(135deg,#0f172a 0%,#1e1b4b 100%);color:#fff}
.tpl-dir-key-nav-minimal .t-cream{background:#F5F0E8;color:#1a1a1a}
.tpl-dir-key-nav-minimal .t-crimson{background:linear-gradient(135deg,#7f1d1d 0%,#991b1b 100%);color:#fff}
.tpl-dir-key-nav-minimal .t-emerald{background:linear-gradient(135deg,#052e16 0%,#064e3b 100%);color:#ecfdf5}
.tpl-dir-key-nav-minimal .t-slate{background:linear-gradient(135deg,#0f1923 0%,#1a2942 100%);color:#e6edf3}
.tpl-dir-key-nav-minimal .t-violet{background:linear-gradient(135deg,#1e0a2e 0%,#2e1065 100%);color:#f5f3ff}
.tpl-dir-key-nav-minimal .t-white{background:#ffffff;color:#111216}
.tpl-dir-key-nav-minimal .t-charcoal{background:linear-gradient(135deg,#111827 0%,#1f2937 100%);color:#f3f4f6}
.tpl-dir-key-nav-minimal .dk-snum{position:absolute;top:30px;right:48px;font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;font-family:var(--dk-mono)}
.tpl-dir-key-nav-minimal .t-cream .dk-snum,
.tpl-dir-key-nav-minimal .t-white .dk-snum{color:#999}
.tpl-dir-key-nav-minimal .t-indigo .dk-snum,
.tpl-dir-key-nav-minimal .t-crimson .dk-snum,
.tpl-dir-key-nav-minimal .t-emerald .dk-snum,
.tpl-dir-key-nav-minimal .t-slate .dk-snum,
.tpl-dir-key-nav-minimal .t-violet .dk-snum,
.tpl-dir-key-nav-minimal .t-charcoal .dk-snum{color:rgba(255,255,255,.38)}
.tpl-dir-key-nav-minimal .dk-eyebrow{font-size:12px;font-weight:700;letter-spacing:3.5px;text-transform:uppercase;opacity:.55;margin-bottom:22px;display:flex;align-items:center;gap:14px}
.tpl-dir-key-nav-minimal .dk-eyebrow::after{content:'';flex:1;max-width:120px;height:1px;background:currentColor;opacity:.3}
.tpl-dir-key-nav-minimal .dk-h0{font-size:160px;font-weight:900;line-height:.9;letter-spacing:-5px;margin:0 0 20px}
.tpl-dir-key-nav-minimal .dk-h1{font-size:100px;font-weight:900;line-height:.98;letter-spacing:-3px;margin:0 0 18px}
.tpl-dir-key-nav-minimal .dk-h2{font-size:72px;font-weight:800;line-height:1.05;letter-spacing:-2px;margin:0 0 16px}
.tpl-dir-key-nav-minimal .dk-lede{font-size:26px;line-height:1.45;opacity:.72;max-width:900px;font-weight:300}
.tpl-dir-key-nav-minimal .dk-lede strong{font-weight:700;opacity:1}
.tpl-dir-key-nav-minimal .dk-big{font-family:var(--dk-mono);font-size:240px;font-weight:800;line-height:.9;letter-spacing:-10px}
.tpl-dir-key-nav-minimal .dk-line{display:block;width:90px;height:4px;background:currentColor;margin:30px 0;opacity:.85}
.tpl-dir-key-nav-minimal .t-indigo .dk-accent{color:#a5b4fc}
.tpl-dir-key-nav-minimal .t-cream .dk-accent{color:#B5392A}
.tpl-dir-key-nav-minimal .t-crimson .dk-accent{color:#fecaca}
.tpl-dir-key-nav-minimal .t-emerald .dk-accent{color:#6ee7b7}
.tpl-dir-key-nav-minimal .t-slate .dk-accent{color:#7dd3fc}
.tpl-dir-key-nav-minimal .t-violet .dk-accent{color:#c4b5fd}
.tpl-dir-key-nav-minimal .t-white .dk-accent{color:#6366f1}
.tpl-dir-key-nav-minimal .t-charcoal .dk-accent{color:#fbbf24}
.tpl-dir-key-nav-minimal .dk-list{list-style:none;padding:0;margin:28px 0 0;font-family:var(--dk-mono);font-size:22px;line-height:2}
.tpl-dir-key-nav-minimal .dk-list li{padding-left:30px;position:relative;font-weight:400;opacity:.85}
.tpl-dir-key-nav-minimal .dk-list li::before{content:'→';position:absolute;left:0;opacity:.5}
.tpl-dir-key-nav-minimal .dk-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:56px;margin-top:36px}
.tpl-dir-key-nav-minimal .dk-col h3{font-size:28px;font-weight:700;margin-bottom:10px}
.tpl-dir-key-nav-minimal .dk-col p{font-size:19px;line-height:1.55;opacity:.72;font-weight:300}
.tpl-dir-key-nav-minimal .dk-code{font-family:var(--dk-mono);font-size:16px;line-height:1.9;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:24px 28px;margin-top:24px;white-space:pre}
.tpl-dir-key-nav-minimal .t-cream .dk-code,
.tpl-dir-key-nav-minimal .t-white .dk-code{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.1)}
.tpl-dir-key-nav-minimal .dk-keyhint{position:absolute;bottom:34px;left:104px;font-family:var(--dk-mono);font-size:12px;letter-spacing:2px;text-transform:uppercase;opacity:.45}
.tpl-dir-key-nav-minimal .dk-keyhint kbd{display:inline-block;padding:2px 10px;margin:0 3px;border:1px solid currentColor;border-radius:4px;font-size:12px}
.tpl-dir-key-nav-minimal .dk-page{position:absolute;bottom:34px;right:48px;font-family:var(--dk-mono);font-size:12px;letter-spacing:2px;opacity:.45}

View File

@@ -0,0 +1,11 @@
# graphify-dark-graph
Deep-night 暗底 + 力导向知识图谱覆盖层 + 温暖玻璃拟态卡片。灵感来自 `20260413-graphify/ppt/graphify.html``#06060c` 渐变底、飘移 orb 光晕、glass 卡片warm/blue/green/purple 五变体)和 rainbow-text 标题。
**Visual traits:** `#06060c → #0e1020` 斜向渐变、三颗 400-520px blur orb 慢飘动、cover SVG 力导向图谱作为背景、rainbow shift 渐变标题、JetBrains Mono 的 `.cmd-glow` 命令行、玻璃拟态卡片带顶部高光和微妙内阴影、温暖色系 accent (#e8a87c 琥珀 / #7ed3a4 薄荷 / #7eb8da 雾蓝 / #b8a4d6 丁香).
**Use when:** 介绍一个开发者工具、命令行产品、知识图谱 / 数据可视化相关项目你希望现场演示时视觉有「AI native + 科技感 + 温度」。
**Source inspiration:** `20260413-graphify/ppt/graphify.html`.
**Path:** `templates/full-decks/graphify-dark-graph/index.html`

View File

@@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Graphify Dark Graph</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-graphify-dark-graph">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-2"></div><div class="gd-orb gd-orb-3"></div></div>
<!-- live force-directed graph bg -->
<svg viewBox="0 0 1600 900" style="position:absolute;inset:0;width:100%;height:100%;opacity:.38;z-index:1" xmlns="http://www.w3.org/2000/svg">
<g stroke="#7eb8da" stroke-width="1" stroke-opacity=".5" fill="none">
<line x1="300" y1="200" x2="520" y2="340"/>
<line x1="520" y1="340" x2="780" y2="260"/>
<line x1="780" y1="260" x2="1040" y2="420"/>
<line x1="520" y1="340" x2="640" y2="560"/>
<line x1="640" y1="560" x2="900" y2="620"/>
<line x1="900" y1="620" x2="1040" y2="420"/>
<line x1="1040" y1="420" x2="1260" y2="300"/>
<line x1="1260" y1="300" x2="1380" y2="500"/>
<line x1="900" y1="620" x2="1120" y2="720"/>
<line x1="300" y1="200" x2="200" y2="420"/>
<line x1="200" y1="420" x2="360" y2="640"/>
<line x1="360" y1="640" x2="640" y2="560"/>
</g>
<g>
<circle cx="300" cy="200" r="10" fill="#e8a87c"/>
<circle cx="520" cy="340" r="14" fill="#7eb8da"/>
<circle cx="780" cy="260" r="9" fill="#7ed3a4"/>
<circle cx="1040" cy="420" r="18" fill="#b8a4d6"/>
<circle cx="640" cy="560" r="11" fill="#d4a0b9"/>
<circle cx="900" cy="620" r="12" fill="#e8a87c"/>
<circle cx="1260" cy="300" r="8" fill="#7ed3a4"/>
<circle cx="1380" cy="500" r="10" fill="#7eb8da"/>
<circle cx="1120" cy="720" r="9" fill="#d4a0b9"/>
<circle cx="200" cy="420" r="8" fill="#b8a4d6"/>
<circle cx="360" cy="640" r="11" fill="#7eb8da"/>
</g>
</svg>
<div class="gd-snum">01 / 08</div>
<div style="margin-top:auto">
<p class="gd-eyebrow">Tech Sharing · 纯干货</p>
<h1 class="gd-h1" style="font-size:88px"><span class="gd-rainbow">手把手用 Graphify<br>搭建个人知识图谱</span></h1>
<p class="gd-lede" style="margin-top:20px">一行命令 · 全多模态 · 诚实审计 —— <span class="gd-accent">把任何文件夹变成可导航的知识网络。</span></p>
<p class="gd-eyebrow" style="margin-top:26px">↑ 背景就是 Graphify 真实跑出来的知识图谱</p>
</div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-2"></div></div>
<div class="gd-snum">02 / 08</div>
<div style="margin:auto 0">
<div class="gd-eyebrow">Part 01</div>
<h1 class="gd-h1" style="font-size:120px">Why <span class="gd-grad">Graph</span>?</h1>
<p class="gd-lede">folder → tree → graph人类认知的下一步</p>
</div>
</section>
<!-- 3. CONTENT — plugin grid -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div><div class="gd-orb gd-orb-3"></div></div>
<div class="gd-snum">03 / 08</div>
<p class="gd-eyebrow">Feature Map</p>
<h2 class="gd-h2">一个工具,<span class="gd-grad">四件事</span></h2>
<div class="gd-grid-4">
<div class="gd-glass gd-glass-warm"><div style="font-size:30px">📂</div><h4 style="margin:10px 0 6px">Folder Ingest</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">递归扫描任意路径,支持 md / pdf / 代码 / 图片</p></div>
<div class="gd-glass gd-glass-blue"><div style="font-size:30px">🧠</div><h4 style="margin:10px 0 6px">Entity Extract</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">用 LLM 抽概念、人物、事件、关系</p></div>
<div class="gd-glass gd-glass-green"><div style="font-size:30px">🕸️</div><h4 style="margin:10px 0 6px">Force Graph</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">D3 力导向,点击即跳转原文</p></div>
<div class="gd-glass"><div style="font-size:30px">🔍</div><h4 style="margin:10px 0 6px">Audit Trail</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">每条边都能追溯到 source span</p></div>
</div>
<div class="gd-glass gd-glass-warm" style="margin-top:24px"><p style="font-size:18px;line-height:1.6">它不是「又一个 RAG」—— 它是 <span class="gd-accent">把检索结果画出来,让你一眼就知道信息长什么样</span></p></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div></div>
<div class="gd-snum">04 / 08</div>
<p class="gd-eyebrow">One command</p>
<h2 class="gd-h2">从 0 到图谱,<span class="gd-grad">大概 90 秒</span></h2>
<p class="gd-cmd" style="margin:16px 0 22px">$ graphify ~/notes --out ./graph</p>
<pre class="gd-codebox"><span class="cm"># graphify.config.yaml</span>
<span class="kw">ingest</span>:
paths: [<span class="st">~/notes</span>, <span class="st">~/code/docs</span>]
include: [<span class="st">"*.md"</span>, <span class="st">"*.pdf"</span>, <span class="st">"*.py"</span>]
<span class="kw">extract</span>:
model: <span class="st">claude-opus-4-6</span>
schema: [<span class="st">concept</span>, <span class="st">person</span>, <span class="st">event</span>, <span class="st">relation</span>]
<span class="kw">render</span>:
engine: <span class="st">d3-force</span>
audit: <span class="fn">true</span> <span class="cm"># 每条边带 source span</span></pre>
</section>
<!-- 5. CHART — race diagram -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-3"></div></div>
<div class="gd-snum">05 / 08</div>
<p class="gd-eyebrow">Efficiency Race</p>
<h2 class="gd-h2">没有知识库 vs 有知识库</h2>
<div style="max-width:900px;margin-top:30px">
<div style="display:flex;align-items:center;gap:16px;margin-bottom:20px">
<div style="width:110px;text-align:right;font-weight:700;color:var(--gd-danger)">没有<br>知识库</div>
<div style="flex:1;position:relative;height:70px;background:rgba(224,112,112,.06);border:1px solid rgba(224,112,112,.2);border-radius:16px">
<div style="position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:32px">🛵</div>
<div style="position:absolute;left:72px;top:50%;transform:translateY(-50%);color:var(--gd-danger);font-size:14px">反复喂信息…整理…又忘了…</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:16px">
<div style="width:110px;text-align:right;font-weight:700;color:var(--gd-green)"><br>知识库</div>
<div style="flex:1;position:relative;height:70px;background:rgba(126,211,164,.06);border:1px solid rgba(126,211,164,.25);border-radius:16px">
<div style="position:absolute;right:16px;top:50%;transform:translateY(-50%);font-size:32px">🏎️</div>
<div style="position:absolute;right:72px;top:50%;transform:translateY(-50%);color:var(--gd-green);font-size:14px">AI 自己找 → 确认 → 干活!</div>
</div>
</div>
</div>
<div class="gd-grid-3" style="margin-top:36px">
<div class="gd-glass gd-glass-warm"><div class="gd-big gd-grad">5×</div><p class="gd-dim" style="margin-top:6px">速度提升</p></div>
<div class="gd-glass gd-glass-green"><div class="gd-big gd-grad">-80%</div><p class="gd-dim" style="margin-top:6px">重复喂信息</p></div>
<div class="gd-glass gd-glass-blue"><div class="gd-big gd-grad"></div><p class="gd-dim" style="margin-top:6px">记忆持久化</p></div>
</div>
</section>
<!-- 6. PIPELINE -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div></div>
<div class="gd-snum">06 / 08</div>
<p class="gd-eyebrow">Pipeline</p>
<h2 class="gd-h2">端到端 <span class="gd-grad">4 步走</span></h2>
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-top:36px">
<div class="gd-glass" style="flex:1;text-align:center"><div style="font-size:34px">📂</div><div style="font-weight:600;margin-top:8px">Scan</div><div class="gd-dim" style="font-size:13px">递归读文件</div></div>
<div style="color:var(--gd-text3);font-size:24px"></div>
<div class="gd-glass gd-glass-blue" style="flex:1;text-align:center"><div style="font-size:34px">🔬</div><div style="font-weight:600;margin-top:8px">Extract</div><div class="gd-dim" style="font-size:13px">LLM 抽实体</div></div>
<div style="color:var(--gd-text3);font-size:24px"></div>
<div class="gd-glass gd-glass-green" style="flex:1;text-align:center"><div style="font-size:34px">🕸️</div><div style="font-weight:600;margin-top:8px">Build</div><div class="gd-dim" style="font-size:13px">构图 + 去重</div></div>
<div style="color:var(--gd-text3);font-size:24px"></div>
<div class="gd-glass gd-glass-warm" style="flex:1;text-align:center"><div style="font-size:34px">🎨</div><div style="font-weight:600;margin-top:8px">Render</div><div class="gd-dim" style="font-size:13px">D3 交互图</div></div>
</div>
<div class="gd-glass" style="margin-top:32px"><p style="font-size:16px;line-height:1.6;color:var(--gd-text2)">每一步都有 audit log你永远知道某个节点为什么存在、它来自哪个文件的哪一行。</p></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-3"></div></div>
<div class="gd-snum">07 / 08</div>
<p class="gd-eyebrow">Try it tonight</p>
<h2 class="gd-h1" style="font-size:80px">Graphify <span class="gd-grad">your folders</span></h2>
<p class="gd-cmd" style="margin-top:22px">$ npm i -g @lewis/graphify</p>
<p class="gd-cmd" style="margin-top:10px;color:var(--gd-warm);text-shadow:0 0 30px rgba(232,168,124,.45)">$ graphify ~/obsidian-vault</p>
<div style="margin-top:32px">
<span class="gd-tag">#knowledge-graph</span>
<span class="gd-tag">#open-source</span>
<span class="gd-tag">#claude-agent</span>
<span class="gd-tag">#obsidian</span>
<span class="gd-tag">#d3-force</span>
</div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div></div>
<div class="gd-snum">08 / 08</div>
<div style="margin:auto 0;text-align:center">
<div class="gd-big gd-rainbow" style="font-size:180px">Thanks.</div>
<p class="gd-lede" style="margin:28px auto 0">github.com/lewis/graphify · 欢迎 star / issue / PR</p>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,54 @@
/* graphify-dark-graph — 暗底玻璃 + 力导向知识图谱 */
.tpl-graphify-dark-graph{
--gd-bg:#06060c;
--gd-bg2:#0e1020;
--gd-text:#f0ece4;
--gd-text2:#b0a99e;
--gd-text3:#7a746c;
--gd-warm:#e8a87c;
--gd-blue:#7eb8da;
--gd-green:#7ed3a4;
--gd-rose:#d4a0b9;
--gd-purple:#b8a4d6;
--gd-danger:#e07070;
background:var(--gd-bg);
color:var(--gd-text);
font-family:'Inter','Noto Sans SC',-apple-system,sans-serif;
letter-spacing:-.01em;
}
.tpl-graphify-dark-graph .slide{background:linear-gradient(160deg,#08080f,#0e1020 50%,#08080f);color:var(--gd-text);padding:64px 88px;overflow:hidden}
.tpl-graphify-dark-graph .gd-ambient{position:absolute;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.tpl-graphify-dark-graph .gd-orb{position:absolute;border-radius:50%;filter:blur(110px);opacity:.35;animation:gdDrift 22s ease-in-out infinite alternate}
.tpl-graphify-dark-graph .gd-orb-1{width:520px;height:520px;background:radial-gradient(circle,rgba(126,184,218,.55),transparent 70%);top:-12%;left:-6%}
.tpl-graphify-dark-graph .gd-orb-2{width:460px;height:460px;background:radial-gradient(circle,rgba(232,168,124,.45),transparent 70%);top:55%;right:-8%;animation-delay:-6s}
.tpl-graphify-dark-graph .gd-orb-3{width:420px;height:420px;background:radial-gradient(circle,rgba(184,164,214,.4),transparent 70%);bottom:-8%;left:30%;animation-delay:-11s}
@keyframes gdDrift{0%{transform:translate(0,0) scale(1)}100%{transform:translate(25px,-20px) scale(1.08)}}
.tpl-graphify-dark-graph .slide > *{position:relative;z-index:2}
.tpl-graphify-dark-graph .gd-snum{position:absolute;top:28px;right:40px;font-size:12px;letter-spacing:.25em;color:var(--gd-text3);z-index:3}
.tpl-graphify-dark-graph .gd-eyebrow{font-size:13px;letter-spacing:.2em;text-transform:uppercase;color:var(--gd-text3);font-weight:500}
.tpl-graphify-dark-graph .gd-h1{font-size:74px;font-weight:800;line-height:1.08;letter-spacing:-.02em;margin:16px 0 10px;color:var(--gd-text)}
.tpl-graphify-dark-graph .gd-h2{font-size:52px;font-weight:700;line-height:1.12;margin:0 0 14px}
.tpl-graphify-dark-graph .gd-lede{font-size:22px;line-height:1.65;font-weight:300;color:var(--gd-text2);max-width:850px}
.tpl-graphify-dark-graph .gd-rainbow{background:linear-gradient(90deg,#ff0080,#ff4d00,#ff9900,#ffe600,#00c853,#0091ea,#6200ea,#ff0080);background-size:200% auto;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gdRainbow 4s linear infinite}
@keyframes gdRainbow{0%{background-position:0% center}100%{background-position:200% center}}
.tpl-graphify-dark-graph .gd-grad{background:linear-gradient(135deg,var(--gd-warm),var(--gd-rose),var(--gd-purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.tpl-graphify-dark-graph .gd-accent{color:var(--gd-warm);font-weight:500}
.tpl-graphify-dark-graph .gd-green{color:var(--gd-green)}
.tpl-graphify-dark-graph .gd-blue{color:var(--gd-blue)}
.tpl-graphify-dark-graph .gd-dim{color:var(--gd-text2)}
.tpl-graphify-dark-graph .gd-mono{font-family:'JetBrains Mono',monospace}
.tpl-graphify-dark-graph .gd-glass{position:relative;overflow:hidden;border-radius:20px;padding:22px 26px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(20px) saturate(160%);box-shadow:0 8px 32px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.08)}
.tpl-graphify-dark-graph .gd-glass::before{content:'';position:absolute;top:0;left:0;right:0;height:50%;background:linear-gradient(180deg,rgba(255,255,255,.05),transparent);pointer-events:none}
.tpl-graphify-dark-graph .gd-glass-warm{background:rgba(232,168,124,.06);border-color:rgba(232,168,124,.2)}
.tpl-graphify-dark-graph .gd-glass-green{background:rgba(126,211,164,.06);border-color:rgba(126,211,164,.2)}
.tpl-graphify-dark-graph .gd-glass-blue{background:rgba(126,184,218,.06);border-color:rgba(126,184,218,.2)}
.tpl-graphify-dark-graph .gd-grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:24px}
.tpl-graphify-dark-graph .gd-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:24px}
.tpl-graphify-dark-graph .gd-tag{display:inline-block;border-radius:999px;padding:5px 14px;font-size:12px;font-weight:500;margin:2px 4px 2px 0;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:var(--gd-text2)}
.tpl-graphify-dark-graph .gd-cmd{font-family:'JetBrains Mono',monospace;font-size:32px;font-weight:700;color:var(--gd-green);text-shadow:0 0 30px rgba(126,211,164,.45),0 0 60px rgba(126,211,164,.15);letter-spacing:-.01em}
.tpl-graphify-dark-graph .gd-big{font-size:120px;font-weight:900;letter-spacing:-.04em;line-height:1}
.tpl-graphify-dark-graph .gd-codebox{background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.8;color:#d8d4c8}
.tpl-graphify-dark-graph .gd-codebox .cm{color:#6b6a62}
.tpl-graphify-dark-graph .gd-codebox .kw{color:var(--gd-warm)}
.tpl-graphify-dark-graph .gd-codebox .st{color:var(--gd-green)}
.tpl-graphify-dark-graph .gd-codebox .fn{color:var(--gd-blue)}

View File

@@ -0,0 +1,11 @@
# hermes-cyber-terminal
黑底 (`#0a0c10`) + 终端 chrome + 扫描线 + 薄荷绿 glow 大字 + JetBrains Mono 全文打字机感。灵感来自 `20260414-hermes-agent/ppt/hermes-record.html``codebox #15151b` 深色代码盒和 `hermes-vs-openclaw.html` 的实测对比气质 —— 把两者合成一份「honest cyber review」。
**Visual traits:** 56px cyber 网格 + CRT vignette + 半透明 scanlines 叠层、窗口 traffic-light chrome、`$ prompt` 开头的 command-line 标题、薄荷绿 text-shadow glow `#7ed3a4`、monospace 全局、虚拟 bar chart 用 stroke-only 呈现、blinking cursor、amber/green/red 分级标签。
**Use when:** 评测一个开发者工具 / CLI / agent展示跑分数据、trace、diff想要即刻给出「技术人 honest review」的视觉语气适合长 trace / long code 的场景。
**Source inspiration:** `20260414-hermes-agent/ppt/hermes-record.html` + `hermes-vs-openclaw.html`.
**Path:** `templates/full-decks/hermes-cyber-terminal/index.html`

View File

@@ -0,0 +1,199 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hermes Cyber Terminal</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-hermes-cyber-terminal">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="hc-grid"></div>
<div class="hc-vignette"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>~/hermes · zsh · 118x42 · 01:37:04</div></div>
<div style="margin:auto 0">
<p class="hc-prompt">whoami --hermes</p>
<h1 class="hc-h1">HERMES<br>AGENT / v0.9.2<span class="hc-cursor"></span></h1>
<p class="hc-lede">一个号称能「自主跑完整软件工程任务」的命令行 agent。<br>真的好用?还是又一轮营销?—— 我连续跑了 72 小时,告诉你答案。</p>
<div style="margin-top:26px">
<span class="hc-tag">rust-core</span>
<span class="hc-tag">mcp-native</span>
<span class="hc-tag amber">72h-benchmark</span>
<span class="hc-tag red">honest-review</span>
</div>
</div>
<div class="hc-footer"><span>hermes-review · lewis · 2026</span><span>01 / 08</span></div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>section · 01/04</div></div>
<div style="margin:auto 0">
<p class="hc-prompt">cat chapter_01.md</p>
<h1 class="hc-h1" style="font-size:110px">// Setup</h1>
<p class="hc-lede"><code style="color:var(--hc-amber)">brew install hermes</code> 到第一次 prompt —— 一共 4 分 22 秒。</p>
</div>
<div class="hc-footer"><span>section · setup</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT — spec cards -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>benchmark · cold-start</div></div>
<h2 class="hc-h2">开箱数据</h2>
<p class="hc-lede">cold start → first-successful-task 三次平均</p>
<div class="hc-grid-3">
<div class="hc-card"><div class="lbl">install time</div><div class="val">42s</div><div class="desc">单 binary无 docker无 python env。</div></div>
<div class="hc-card"><div class="lbl">first token</div><div class="val">1.8s</div><div class="desc">接入 claude-opus-4-6无预热。</div></div>
<div class="hc-card"><div class="lbl">first PR merged</div><div class="val">4m22s</div><div class="desc">跑的是 fix-a-typo 级别的低难度任务。</div></div>
</div>
<div class="hc-grid-2">
<div class="hc-card"><div class="lbl">// verdict +</div><div class="val" style="color:var(--hc-green);font-size:18px">冷启动是真的快</div><div class="desc">和 OpenClaw 的 docker + pip 流程比,快不止一个数量级。</div></div>
<div class="hc-card"><div class="lbl">// verdict -</div><div class="val" style="color:var(--hc-red);font-size:18px">MCP 服务器配置不够友好</div><div class="desc">env 变量需要手动塞进 ~/.hermes/env文档几乎没写。</div></div>
</div>
<div class="hc-footer"><span>data · verified 3 runs</span><span>03 / 08</span></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>trace · hermes run</div></div>
<p class="hc-prompt">hermes run "refactor auth module to use pkce"</p>
<h3 class="hc-h3" style="margin-top:12px">↓ 真实 trace (节选)</h3>
<pre class="hc-codebox" style="margin-top:10px"><span class="cm"># hermes v0.9.2 · session 42a1</span>
[<span class="fn">plan</span>] <span class="st">"分析 src/auth/*.ts → 找 oauth flow → 抽成 pkce"</span>
[<span class="fn">read</span>] src/auth/oauth.ts <span class="cm">// 214 lines</span>
[<span class="fn">read</span>] src/auth/token.ts <span class="cm">// 88 lines</span>
[<span class="kw">think</span>] <span class="st">"发现 implicit flow改为 code+pkce需新 state param"</span>
[<span class="fn">edit</span>] src/auth/oauth.ts <span class="hl">+43 -17</span>
[<span class="fn">edit</span>] src/auth/token.ts <span class="hl">+12 -4</span>
[<span class="fn">test</span>] pnpm vitest auth <span class="st">PASS 18/18</span>
[<span class="fn">commit</span>] <span class="var">"feat(auth): migrate to oauth2 code+pkce"</span>
[<span class="fn">push</span>] origin feat/pkce-auth <span class="st">ok</span>
<span class="cm"># 总耗时 3m 14s · 14k tokens · $0.21</span></pre>
<div class="hc-footer"><span>trace · live</span><span>04 / 08</span></div>
</section>
<!-- 5. CHART -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>benchmark · hermes vs openclaw</div></div>
<h2 class="hc-h2">72 小时对比</h2>
<p class="hc-lede">同一组 48 个 GitHub issue两个 agent 各跑一遍</p>
<svg viewBox="0 0 1000 380" style="width:100%;max-width:1040px;margin-top:24px" xmlns="http://www.w3.org/2000/svg">
<g font-family="JetBrains Mono, monospace" font-size="13" fill="#8a8892">
<!-- axis -->
<line x1="80" y1="40" x2="80" y2="320" stroke="rgba(126,211,164,.2)"/>
<line x1="80" y1="320" x2="960" y2="320" stroke="rgba(126,211,164,.2)"/>
<!-- y labels -->
<text x="70" y="46" text-anchor="end">100%</text>
<text x="70" y="116" text-anchor="end">75%</text>
<text x="70" y="186" text-anchor="end">50%</text>
<text x="70" y="256" text-anchor="end">25%</text>
<text x="70" y="324" text-anchor="end">0</text>
<!-- bars: hermes -->
<g>
<rect x="130" y="80" width="80" height="240" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="170" y="76" text-anchor="middle" fill="#7ed3a4" font-weight="700">82%</text>
<text x="170" y="345" text-anchor="middle">resolved</text>
<rect x="240" y="146" width="80" height="174" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="280" y="142" text-anchor="middle" fill="#7ed3a4" font-weight="700">58%</text>
<text x="280" y="345" text-anchor="middle">one-shot</text>
<rect x="350" y="60" width="80" height="260" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="390" y="56" text-anchor="middle" fill="#7ed3a4" font-weight="700">89%</text>
<text x="390" y="345" text-anchor="middle">test-pass</text>
<rect x="460" y="110" width="80" height="210" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="500" y="106" text-anchor="middle" fill="#7ed3a4" font-weight="700">71%</text>
<text x="500" y="345" text-anchor="middle">pr-merged</text>
</g>
<!-- bars: openclaw -->
<g>
<rect x="570" y="150" width="80" height="170" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
<text x="610" y="146" text-anchor="middle" fill="#e9c58a" font-weight="700">60%</text>
<text x="610" y="345" text-anchor="middle">resolved</text>
<rect x="680" y="212" width="80" height="108" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
<text x="720" y="208" text-anchor="middle" fill="#e9c58a" font-weight="700">38%</text>
<text x="720" y="345" text-anchor="middle">one-shot</text>
<rect x="790" y="130" width="80" height="190" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
<text x="830" y="126" text-anchor="middle" fill="#e9c58a" font-weight="700">67%</text>
<text x="830" y="345" text-anchor="middle">test-pass</text>
</g>
<!-- legend -->
<g transform="translate(820,50)">
<rect x="0" y="0" width="14" height="14" fill="rgba(126,211,164,.15)" stroke="#7ed3a4"/>
<text x="22" y="12" fill="#7ed3a4">hermes 0.9.2</text>
<rect x="0" y="22" width="14" height="14" fill="rgba(233,197,138,.12)" stroke="#e9c58a"/>
<text x="22" y="34" fill="#e9c58a">openclaw 2.1</text>
</g>
</g>
</svg>
<div class="hc-footer"><span>benchmark · n=48</span><span>05 / 08</span></div>
</section>
<!-- 6. STATS -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>tldr</div></div>
<p class="hc-prompt">echo $VERDICT</p>
<div class="hc-big">7.8<span style="font-size:60px;color:var(--hc-ink2)">/ 10</span></div>
<p class="hc-lede" style="margin-top:14px">值得装,还不值得完全依赖。</p>
<div class="hc-grid-2" style="margin-top:24px">
<div class="hc-card"><div class="lbl">+ strong points</div><div class="desc">• rust 本体冷启快<br>• trace 可读性极强<br>• diff 审核友好commit message 也写得合格</div></div>
<div class="hc-card"><div class="lbl">- weak points</div><div class="desc">• plan 阶段偶尔跳步<br>• 超 50k LoC 仓库会 OOM<br>• MCP 配置需要手动塞 env</div></div>
</div>
<div class="hc-footer"><span>verdict · honest</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>install</div></div>
<h2 class="hc-h2">想自己跑一遍?</h2>
<p class="hc-lede">三条命令,不到 5 分钟就能看见它干第一件事。</p>
<pre class="hc-codebox" style="margin-top:22px"><span class="cm"># 1. install</span>
<span class="kw">$</span> brew install hermes-agent/tap/hermes
<span class="cm"># 2. auth (先准备好 anthropic api key)</span>
<span class="kw">$</span> hermes auth login
<span class="cm"># 3. first task</span>
<span class="kw">$</span> cd ~/your-repo && hermes run <span class="st">"add a CHANGELOG.md from git log"</span></pre>
<div style="margin-top:26px">
<span class="hc-tag">brew-ready</span>
<span class="hc-tag">opus-4.6</span>
<span class="hc-tag amber">needs-api-key</span>
</div>
<div class="hc-footer"><span>try-it-now</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>EOF</div></div>
<div style="margin:auto 0">
<p class="hc-prompt">exit 0</p>
<h1 class="hc-h1" style="font-size:120px">// thanks<span class="hc-cursor"></span></h1>
<p class="hc-lede">完整 trace、48 个任务的 PR 列表、benchmark 脚本都在 <span style="color:var(--hc-amber)">github.com/lewis/hermes-review</span></p>
</div>
<div class="hc-footer"><span>session closed</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,55 @@
/* hermes-cyber-terminal — 暗终端 + 霓虹绿青 + 扫描线 */
.tpl-hermes-cyber-terminal{
--hc-bg:#0a0c10;
--hc-bg2:#15151b;
--hc-surface:#12141a;
--hc-border:rgba(126,211,164,.18);
--hc-ink:#e4e2d8;
--hc-ink2:#8a8892;
--hc-green:#7ed3a4;
--hc-cyan:#64dfdf;
--hc-amber:#e9c58a;
--hc-rose:#d4a0b9;
--hc-red:#ff6b6b;
background:var(--hc-bg);
color:var(--hc-ink);
font-family:'JetBrains Mono','SF Mono','Inter','Noto Sans SC',monospace;
}
.tpl-hermes-cyber-terminal .slide{background:var(--hc-bg);color:var(--hc-ink);padding:60px 84px;overflow:hidden}
.tpl-hermes-cyber-terminal .hc-scanlines{position:absolute;inset:0;pointer-events:none;z-index:3;background:repeating-linear-gradient(180deg,transparent 0,transparent 3px,rgba(126,211,164,.025) 3px,rgba(126,211,164,.025) 4px);mix-blend-mode:screen}
.tpl-hermes-cyber-terminal .hc-grid{position:absolute;inset:0;pointer-events:none;opacity:.35;background-image:linear-gradient(rgba(126,211,164,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(126,211,164,.08) 1px,transparent 1px);background-size:56px 56px;mask-image:radial-gradient(ellipse at 50% 50%,black 30%,transparent 80%)}
.tpl-hermes-cyber-terminal .hc-vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse at 50% 50%,transparent 50%,rgba(0,0,0,.6) 100%)}
.tpl-hermes-cyber-terminal .slide > *{position:relative;z-index:2}
.tpl-hermes-cyber-terminal .hc-chrome{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;font-size:11px;color:var(--hc-ink2);letter-spacing:.18em;text-transform:uppercase}
.tpl-hermes-cyber-terminal .hc-chrome .dots{display:flex;gap:8px}
.tpl-hermes-cyber-terminal .hc-chrome .dots span{width:11px;height:11px;border-radius:50%;background:#2a2d33}
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(1){background:#ff5f57}
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(2){background:#febc2e}
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(3){background:var(--hc-green)}
.tpl-hermes-cyber-terminal .hc-prompt{color:var(--hc-green);font-weight:500}
.tpl-hermes-cyber-terminal .hc-prompt::before{content:'$ ';color:var(--hc-cyan)}
.tpl-hermes-cyber-terminal .hc-h1{font-family:'JetBrains Mono',monospace;font-size:72px;font-weight:700;line-height:1.05;letter-spacing:-.02em;color:var(--hc-green);text-shadow:0 0 30px rgba(126,211,164,.35),0 0 60px rgba(126,211,164,.1);margin:14px 0 12px}
.tpl-hermes-cyber-terminal .hc-h2{font-size:46px;font-weight:600;color:var(--hc-ink);margin:0 0 10px;letter-spacing:-.015em}
.tpl-hermes-cyber-terminal .hc-h3{font-size:22px;font-weight:600;color:var(--hc-amber);margin:0 0 10px}
.tpl-hermes-cyber-terminal .hc-lede{font-size:18px;line-height:1.7;color:var(--hc-ink2);max-width:780px;font-family:'Inter','Noto Sans SC',sans-serif}
.tpl-hermes-cyber-terminal .hc-cursor{display:inline-block;width:12px;height:1em;background:var(--hc-green);vertical-align:middle;margin-left:6px;animation:hcBlink 1s steps(2) infinite}
@keyframes hcBlink{50%{opacity:0}}
.tpl-hermes-cyber-terminal .hc-card{background:var(--hc-surface);border:1px solid var(--hc-border);border-radius:10px;padding:20px 24px;position:relative}
.tpl-hermes-cyber-terminal .hc-card::before{content:'';position:absolute;top:-1px;left:12px;right:12px;height:2px;background:linear-gradient(90deg,transparent,var(--hc-green),transparent)}
.tpl-hermes-cyber-terminal .hc-card .lbl{font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:var(--hc-ink2);margin-bottom:8px}
.tpl-hermes-cyber-terminal .hc-card .val{font-size:22px;font-weight:700;color:var(--hc-green);font-family:'JetBrains Mono',monospace}
.tpl-hermes-cyber-terminal .hc-card .desc{font-size:13px;color:var(--hc-ink2);margin-top:10px;line-height:1.55;font-family:'Inter',sans-serif}
.tpl-hermes-cyber-terminal .hc-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:24px}
.tpl-hermes-cyber-terminal .hc-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:24px}
.tpl-hermes-cyber-terminal .hc-codebox{background:#0c0d12;border:1px solid var(--hc-border);border-radius:10px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;color:#d8d4c8;box-shadow:inset 0 0 60px rgba(126,211,164,.04)}
.tpl-hermes-cyber-terminal .hc-codebox .cm{color:#5a6068}
.tpl-hermes-cyber-terminal .hc-codebox .kw{color:var(--hc-amber)}
.tpl-hermes-cyber-terminal .hc-codebox .st{color:var(--hc-green)}
.tpl-hermes-cyber-terminal .hc-codebox .fn{color:var(--hc-cyan)}
.tpl-hermes-cyber-terminal .hc-codebox .var{color:var(--hc-rose)}
.tpl-hermes-cyber-terminal .hc-codebox .hl{color:#fff;background:rgba(126,211,164,.15);padding:0 4px;border-radius:3px}
.tpl-hermes-cyber-terminal .hc-tag{display:inline-block;font-family:'JetBrains Mono',monospace;font-size:11px;padding:3px 10px;border:1px solid var(--hc-border);border-radius:4px;color:var(--hc-green);background:rgba(126,211,164,.04);margin:2px 6px 2px 0;text-transform:uppercase;letter-spacing:.1em}
.tpl-hermes-cyber-terminal .hc-tag.amber{color:var(--hc-amber);border-color:rgba(233,197,138,.2);background:rgba(233,197,138,.04)}
.tpl-hermes-cyber-terminal .hc-tag.red{color:var(--hc-red);border-color:rgba(255,107,107,.25);background:rgba(255,107,107,.05)}
.tpl-hermes-cyber-terminal .hc-big{font-family:'JetBrains Mono',monospace;font-size:140px;font-weight:700;line-height:1;color:var(--hc-green);text-shadow:0 0 40px rgba(126,211,164,.4),0 0 80px rgba(126,211,164,.15);letter-spacing:-.04em}
.tpl-hermes-cyber-terminal .hc-footer{position:absolute;left:84px;right:84px;bottom:32px;display:flex;justify-content:space-between;font-size:10px;color:var(--hc-ink2);letter-spacing:.2em;text-transform:uppercase;border-top:1px solid rgba(126,211,164,.1);padding-top:14px}

View File

@@ -0,0 +1,11 @@
# knowledge-arch-blueprint
奶油纸 (`#F0EAE0`) 底 + 锈红 (`#B5392A`) 单一 accent + 硬黑描边卡片 + 虚线反馈回路箭头。灵感来自 `20260405 架构图v2.html` —— 那是一张真正的「技术白皮书架构图」,像建筑蓝图。
**Visual traits:** 暖米色纸底、微弱 48px 网格做 blueprint 感、硬朗 2px 黑边卡片、pipeline step-box 一字排开配 hero box 凸起、右上 insight 红色 callout、大小写 kicker 2.5-4px 字距、SVG 反馈回路虚线 + 箭头、Playfair 大字号衬线数据、无渐变无阴影极度克制。
**Use when:** 讲系统架构、数据流向、流程拆解;你想让内容看起来像一份正经技术白皮书而不是营销贴;需要严肃感、印刷感、可直接截图塞进 README。
**Source inspiration:** `20260405-Karpathy-知识库/20260405 架构图v2.html`.
**Path:** `templates/full-decks/knowledge-arch-blueprint/index.html`

View File

@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Knowledge Arch Blueprint</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-knowledge-arch-blueprint">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="kb-grid-bg"></div>
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:40px;margin-bottom:44px">
<div>
<div class="kb-kicker">Karpathy Stack · 架构图 v2</div>
<h1 class="kb-h1">LLM <span class="rust">知识库</span><br>工程化蓝图</h1>
<p class="kb-sub">从「乱贴笔记」到「可审计、可纠错、可复用」的第二大脑 —— 这是我读完 Karpathy 的分享后画的一张系统图。</p>
</div>
<div class="kb-insight"><span class="kk">KEY INSIGHT</span>Karpathy 原版缺一块:<br>反馈闭环让错误能回流纠正。</div>
</div>
<div class="kb-section-label">Pipeline · End-to-end</div>
<div class="kb-pipeline">
<div class="kb-step"><div class="kb-step-num">STEP 01</div><div class="kb-step-title">采集</div><div class="kb-step-body">浏览器剪藏、PDF、Podcast 转写、聊天记录</div></div>
<div class="kb-step"><div class="kb-step-num">STEP 02</div><div class="kb-step-title">去噪</div><div class="kb-step-body">清洗导航栏、广告、重复段落、低信噪素材</div></div>
<div class="kb-step hero"><div class="kb-step-num">STEP 03 · CORE</div><div class="kb-step-title">Wiki 化</div><div class="kb-step-body">结构化成双链笔记,实体、关系、属性全在一起</div></div>
<div class="kb-step"><div class="kb-step-num">STEP 04</div><div class="kb-step-title">使用</div><div class="kb-step-body">Agent 随时检索、回答、再写入</div></div>
</div>
<div class="kb-footer"><span>Blueprint · v2 · 2026.04</span><span>01 / 08</span></div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div style="margin:auto 0">
<div class="kb-kicker">Chapter One</div>
<h1 class="kb-h1" style="font-size:120px">为什么 <span class="rust">笔记</span><br>不够用了</h1>
<p class="kb-sub" style="font-size:24px;margin-top:20px">当你的知识量超过记忆容量,<br>你需要的不是更多文件,而是一张<b>可导航的图</b></p>
</div>
<div class="kb-footer"><span>Section · Chapter 1</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT 2-col -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">Problem · Solution</div>
<h1 class="kb-h1" style="font-size:48px">原版 vs <span class="rust">升级版</span></h1>
<div class="kb-grid-2">
<div class="kb-card">
<div class="kb-kicker" style="color:#888">原版 Karpathy</div>
<h4>一次性写入</h4>
<p>采集 → 转写 → 存档,错了就错了。没有回路,没有修正机制,笔记越多越混乱。</p>
</div>
<div class="kb-card" style="background:var(--kb-rust-soft);border-color:var(--kb-rust)">
<div class="kb-kicker">升级 v2</div>
<h4>反馈闭环</h4>
<p>AI 使用知识库时记录每次 miss / 幻觉 / 过期事实,自动回灌到源文件,让笔记会自我修正。</p>
</div>
</div>
<div class="kb-legend">
<div class="d"><span class="b"></span>普通节点</div>
<div class="d"><span class="b rust"></span>核心节点 · 反馈回路入口</div>
<div class="d">—— 数据流 &nbsp;&nbsp; ┈┈ 反馈回路</div>
</div>
<div class="kb-footer"><span>Content · Compare</span><span>03 / 08</span></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">Implementation · Skill Manifest</div>
<h1 class="kb-h1" style="font-size:48px">反馈回路 <span class="rust">怎么实现</span></h1>
<p class="kb-sub">一个 100 行的 Agent Skill把「AI 用得顺不顺」回写成 vault 的一条条修订记录。</p>
<pre class="kb-codebox"><span class="cm"># skills/wiki-feedback/SKILL.md</span>
<span class="kw">name</span>: wiki-feedback
<span class="kw">trigger</span>: <span class="st">"after every retrieval"</span>
<span class="kw">on_hit</span>: record(<span class="st">query, path, used=true</span>)
<span class="kw">on_miss</span>: record(<span class="st">query, reason=</span><span class="hl">"not-in-vault"</span>)
<span class="kw">on_wrong</span>: record(<span class="st">query, correction, path</span>)
<span class="kw">nightly</span>:
- <span class="st">aggregate misses → suggest new notes</span>
- <span class="st">aggregate wrongs → diff-patch old notes</span>
- <span class="st">commit to git, open PR for human review</span></pre>
<div class="kb-footer"><span>Content · Code</span><span>04 / 08</span></div>
</section>
<!-- 5. DIAGRAM - SVG feedback loop -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">System Diagram</div>
<h1 class="kb-h1" style="font-size:44px">反馈回路全貌</h1>
<svg viewBox="0 0 1200 520" style="width:100%;max-width:1200px;margin-top:20px" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#1a1a1a"/>
</marker>
<marker id="arrow-r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#B5392A"/>
</marker>
</defs>
<g font-family="Inter, sans-serif" font-size="16" font-weight="700">
<!-- boxes -->
<rect x="40" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
<text x="140" y="220" text-anchor="middle">Sources</text>
<text x="140" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">web · pdf · chat</text>
<rect x="300" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
<text x="400" y="220" text-anchor="middle">Clean + Split</text>
<text x="400" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">defuddle / chunker</text>
<rect x="560" y="160" width="220" height="160" rx="10" fill="#B5392A" stroke="#B5392A" stroke-width="2"/>
<text x="670" y="210" text-anchor="middle" fill="#fff" font-size="20">Vault (Wiki)</text>
<text x="670" y="240" text-anchor="middle" font-size="12" font-weight="400" fill="rgba(255,255,255,.8)">markdown · links</text>
<text x="670" y="262" text-anchor="middle" font-size="12" font-weight="400" fill="rgba(255,255,255,.8)">bases · canvas</text>
<rect x="840" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
<text x="940" y="220" text-anchor="middle">Agent Use</text>
<text x="940" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">retrieve / answer</text>
<!-- forward arrows -->
<line x1="245" y1="240" x2="295" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="505" y1="240" x2="555" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="785" y1="240" x2="835" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
<!-- feedback dashed -->
<path d="M 940 180 Q 940 80, 670 80 Q 400 80, 400 180" fill="none" stroke="#B5392A" stroke-width="2" stroke-dasharray="6 6" marker-end="url(#arrow-r)"/>
<rect x="580" y="58" width="180" height="30" rx="6" fill="#F0EAE0" stroke="#B5392A" stroke-width="1"/>
<text x="670" y="78" text-anchor="middle" fill="#B5392A" font-size="12">FEEDBACK · wrong / miss</text>
<!-- bottom feedback to sources -->
<path d="M 940 300 Q 940 420, 670 420 Q 140 420, 140 300" fill="none" stroke="#B5392A" stroke-width="2" stroke-dasharray="6 6" marker-end="url(#arrow-r)"/>
<rect x="560" y="400" width="220" height="30" rx="6" fill="#F0EAE0" stroke="#B5392A" stroke-width="1"/>
<text x="670" y="420" text-anchor="middle" fill="#B5392A" font-size="12">NIGHTLY · suggest new sources</text>
</g>
</svg>
<div class="kb-footer"><span>Diagram · Feedback Loop</span><span>05 / 08</span></div>
</section>
<!-- 6. STATS -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">After 6 Months</div>
<h1 class="kb-h1" style="font-size:44px">升级版 <span class="rust">跑了半年</span> 的数据</h1>
<div style="display:grid;grid-template-columns:1.3fr 1fr 1fr;gap:24px;margin-top:28px;align-items:center">
<div style="text-align:center"><div class="kb-big-num">13</div><p style="font-size:14px;color:#666;margin-top:6px;letter-spacing:.1em;text-transform:uppercase">关键优化项 · 全部落地</p></div>
<div class="kb-card"><h4 style="color:var(--kb-rust)">-62%</h4><p>幻觉率(相比无反馈回路版本)</p></div>
<div class="kb-card"><h4 style="color:var(--kb-rust)">+4.1×</h4><p>单次检索命中率</p></div>
</div>
<div class="kb-grid-2" style="margin-top:18px">
<div class="kb-card"><h4>自动修订 227 条</h4><p>其中 189 条被人工批准合并38 条被拒绝(数据已归档)。</p></div>
<div class="kb-card"><h4>新增笔记 412 篇</h4><p>从 miss 日志聚类而来,每篇都有来源追溯。</p></div>
</div>
<div class="kb-footer"><span>Content · Stats</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">Next Step</div>
<h1 class="kb-h1" style="font-size:60px">开始你的 <span class="rust">Wiki v2</span></h1>
<p class="kb-sub">不用重写所有笔记。先接一条回路,让 AI 的每次使用都在「改好」你的 vault。</p>
<div class="kb-pipeline">
<div class="kb-step"><div class="kb-step-num">TONIGHT</div><div class="kb-step-title">装 Skill</div><div class="kb-step-body">pnpm i -g @lewis/wiki-feedback</div></div>
<div class="kb-step"><div class="kb-step-num">DAY 2</div><div class="kb-step-title">跑 7 天</div><div class="kb-step-body">观察 miss log 自动累积</div></div>
<div class="kb-step hero"><div class="kb-step-num">DAY 8 · CORE</div><div class="kb-step-title">第一次审 PR</div><div class="kb-step-body">花 15 分钟 review 自动生成的修订</div></div>
<div class="kb-step"><div class="kb-step-num">MONTH 1</div><div class="kb-step-title">开始信它</div><div class="kb-step-body">你的 vault 会变成活的</div></div>
</div>
<div class="kb-footer"><span>CTA</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div style="margin:auto 0;text-align:center">
<div class="kb-kicker">END · blueprint v2</div>
<h1 class="kb-h1" style="font-size:140px;margin-top:24px">谢谢 <span class="rust">·</span> thanks</h1>
<p class="kb-sub" style="margin:0 auto;font-size:22px">图纸、Skill、笔记模板都在 <b>github.com/lewis/karpathy-wiki-v2</b></p>
</div>
<div class="kb-footer"><span>End of deck</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,49 @@
/* knowledge-arch-blueprint — 奶油纸 + 建筑蓝图风 */
.tpl-knowledge-arch-blueprint{
--kb-bg:#F0EAE0;
--kb-ink:#1a1a1a;
--kb-ink2:#555;
--kb-ink3:#aaa;
--kb-rust:#B5392A;
--kb-rust-soft:rgba(181,57,42,.08);
--kb-line:#cec8be;
background:var(--kb-bg);
color:var(--kb-ink);
font-family:'Inter','Noto Sans SC',-apple-system,sans-serif;
}
.tpl-knowledge-arch-blueprint .slide{background:var(--kb-bg);color:var(--kb-ink);padding:64px 80px}
.tpl-knowledge-arch-blueprint .kb-grid-bg{position:absolute;inset:0;pointer-events:none;opacity:.35;background-image:linear-gradient(rgba(26,26,26,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(26,26,26,.06) 1px,transparent 1px);background-size:48px 48px;mask-image:radial-gradient(ellipse at center,black 40%,transparent 85%)}
.tpl-knowledge-arch-blueprint .slide > *{position:relative;z-index:2}
.tpl-knowledge-arch-blueprint .kb-kicker{font-size:13px;font-weight:800;letter-spacing:4px;text-transform:uppercase;color:var(--kb-rust);margin-bottom:12px}
.tpl-knowledge-arch-blueprint .kb-h1{font-size:66px;font-weight:900;line-height:1.08;color:#111;margin:0 0 14px;letter-spacing:-.02em}
.tpl-knowledge-arch-blueprint .kb-h1 span.rust{color:var(--kb-rust)}
.tpl-knowledge-arch-blueprint .kb-sub{font-size:20px;color:#666;line-height:1.55;max-width:780px}
.tpl-knowledge-arch-blueprint .kb-insight{display:inline-block;background:var(--kb-rust);color:#fff;border-radius:10px;padding:16px 22px;font-size:14px;font-weight:700;line-height:1.5;max-width:340px;box-shadow:0 8px 24px rgba(181,57,42,.22)}
.tpl-knowledge-arch-blueprint .kb-insight .kk{font-size:10px;letter-spacing:2px;opacity:.7;display:block;margin-bottom:6px;font-weight:800}
.tpl-knowledge-arch-blueprint .kb-section-label{font-size:11px;font-weight:800;letter-spacing:3.5px;text-transform:uppercase;color:#aaa;margin:30px 0 12px;display:flex;align-items:center;gap:14px}
.tpl-knowledge-arch-blueprint .kb-section-label::after{content:'';flex:1;height:1px;background:var(--kb-line)}
.tpl-knowledge-arch-blueprint .kb-pipeline{display:flex;align-items:stretch;gap:14px;margin-top:24px}
.tpl-knowledge-arch-blueprint .kb-step{flex:1;border:2px solid #1a1a1a;border-radius:12px;padding:22px 18px;background:#fff;position:relative;min-height:200px;display:flex;flex-direction:column}
.tpl-knowledge-arch-blueprint .kb-step.hero{background:var(--kb-rust);border-color:var(--kb-rust);color:#fff;flex:1.25;box-shadow:0 10px 32px rgba(181,57,42,.28);transform:translateY(-10px)}
.tpl-knowledge-arch-blueprint .kb-step-num{font-size:10px;font-weight:800;letter-spacing:2.5px;color:#bbb;margin-bottom:8px;text-transform:uppercase}
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-num{color:rgba(255,255,255,.6)}
.tpl-knowledge-arch-blueprint .kb-step-title{font-size:22px;font-weight:900;line-height:1.15;color:#111;margin-bottom:8px}
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-title{color:#fff}
.tpl-knowledge-arch-blueprint .kb-step-body{font-size:13px;line-height:1.55;color:#555;margin-top:auto}
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-body{color:rgba(255,255,255,.88)}
.tpl-knowledge-arch-blueprint .kb-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:24px}
.tpl-knowledge-arch-blueprint .kb-card{background:#fff;border:2px solid #1a1a1a;border-radius:12px;padding:22px 24px}
.tpl-knowledge-arch-blueprint .kb-card h4{font-size:20px;font-weight:900;margin-bottom:6px}
.tpl-knowledge-arch-blueprint .kb-card p{font-size:14px;color:#555;line-height:1.55}
.tpl-knowledge-arch-blueprint .kb-legend{display:flex;gap:18px;flex-wrap:wrap;margin-top:22px;font-size:12px;color:#666}
.tpl-knowledge-arch-blueprint .kb-legend .d{display:flex;align-items:center;gap:8px}
.tpl-knowledge-arch-blueprint .kb-legend .b{width:14px;height:14px;border:2px solid #1a1a1a;border-radius:3px}
.tpl-knowledge-arch-blueprint .kb-legend .b.rust{background:var(--kb-rust);border-color:var(--kb-rust)}
.tpl-knowledge-arch-blueprint .kb-footer{position:absolute;left:80px;right:80px;bottom:36px;display:flex;justify-content:space-between;font-size:11px;color:#999;letter-spacing:.15em;text-transform:uppercase;border-top:1px solid var(--kb-line);padding-top:16px}
.tpl-knowledge-arch-blueprint .kb-mono{font-family:'JetBrains Mono',monospace}
.tpl-knowledge-arch-blueprint .kb-codebox{background:#1a1a1a;color:#f0eae0;border-radius:12px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.8;margin-top:20px;border:2px solid #1a1a1a}
.tpl-knowledge-arch-blueprint .kb-codebox .cm{color:#7a766c}
.tpl-knowledge-arch-blueprint .kb-codebox .kw{color:#e8a87c}
.tpl-knowledge-arch-blueprint .kb-codebox .st{color:#b3d1bc}
.tpl-knowledge-arch-blueprint .kb-codebox .hl{color:var(--kb-rust);background:rgba(255,255,255,.08);padding:0 4px;border-radius:3px}
.tpl-knowledge-arch-blueprint .kb-big-num{font-family:'Playfair Display',Georgia,serif;font-size:200px;font-weight:900;line-height:.9;color:var(--kb-rust);letter-spacing:-.04em}

View File

@@ -0,0 +1,11 @@
# obsidian-claude-gradient
GitHub-dark (`#0d1117`) + 紫色 ambient radial + 60px 遮罩网格 + 紫→蓝→绿渐变文字。灵感来自 `20260406-obsidian-claude/slides.html``--accent #7c3aed``.cbg` 双 radial cover、`.cgrid` 60px 遮罩网格以及 `.g` 三停渐变。
**Visual traits:** 深灰蓝底 + 紫蓝 radial 晕染 + 暗网格遮罩、居中对齐的标题/正文、圆角紫色 pill tag、linear `#a855f7→#60a5fa→#34d399` 渐变字、GitHub-ish 代码色 (`#010409` 背景 + 紫/蓝/橙/绿 token)、紫色左边框 highlight 块、简洁 step 列表。
**Use when:** 讲一个开发者友好的工作流、MCP / Agent / Dev tool 教程;你希望气质接近 GitHub Blog / Linear Changelog内容以配置文件 + 步骤为主。
**Source inspiration:** `20260406-obsidian-claude/slides.html`.
**Path:** `templates/full-decks/obsidian-claude-gradient/index.html`

View File

@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Obsidian × Claude Gradient</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-obsidian-claude-gradient">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">01 / 08</div>
<div class="oc-tag">● OBSIDIAN × CLAUDE · 第二大脑</div>
<h1 class="oc-h1">把 Obsidian 和 Claude<br>拧成 <span class="oc-g">一条神经</span></h1>
<p class="oc-sub">不是又一个 AI 笔记插件 —— 是让 Claude 真正理解你 vault 的结构、链接、双向引用,<br>然后在你想写东西之前就把资料准备好。</p>
<div style="margin-top:32px">
<span class="oc-pill">🧠 Markdown-native</span>
<span class="oc-pill">⚡ MCP-ready</span>
<span class="oc-pill">🔗 双链理解</span>
</div>
</section>
<!-- 2. SECTION -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">02 / 08</div>
<div class="oc-tag">● CHAPTER 01</div>
<h1 class="oc-h1" style="font-size:110px">Why <span class="oc-g">not</span> Notion?</h1>
<p class="oc-sub">当你的知识多到会互相引用时,<br>「文件夹」就不够了,「数据库」也不是答案。</p>
</section>
<!-- 3. CONTENT — compare cards -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">03 / 08</div>
<div class="oc-tag">● COMPARE</div>
<h2 class="oc-h2">Notion vs <span class="oc-g">Obsidian</span> · 对 AI 友好度</h2>
<div class="oc-grid-2">
<div class="oc-card">
<span class="oc-badge oc-bb">NOTION</span>
<h4 style="font-size:20px;margin-bottom:10px">数据库原生</h4>
<p style="color:var(--oc-dim);font-size:14px;line-height:1.65">适合结构化任务、团队协作,但是——<br>• AI 要走 API拿不到实时全文<br>• 嵌套块结构复杂token 成本高<br>• 本地化差,没法当长期记忆</p>
</div>
<div class="oc-card" style="border-color:rgba(168,85,247,.35);background:rgba(124,58,237,.05)">
<span class="oc-badge oc-bp">OBSIDIAN</span>
<h4 style="font-size:20px;margin-bottom:10px">纯 Markdown + 双链</h4>
<p style="color:var(--oc-dim);font-size:14px;line-height:1.65">对 AI 天生友好 ——<br>• 所有东西就是文件Claude 直接读<br>• 双链 = 天然 graph抽实体几乎零成本<br>• 离线、可 git、可 diff、可回滚</p>
</div>
</div>
<div class="oc-hl" style="margin-top:26px">💡 <b>关键洞察:</b>AI 不需要「更聪明的数据库」,它需要「能被它自己读懂的文件系统」。</div>
</section>
<!-- 4. STEPS -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">04 / 08</div>
<div class="oc-tag">● SETUP · 4 STEPS</div>
<h2 class="oc-h2">从 0 到第一次「AI 写笔记」</h2>
<div class="oc-steps">
<div class="oc-step"><div class="oc-sn">1</div><div class="oc-sc"><h4>装 Obsidian + 开 Local REST API 插件</h4><p>社区插件,一个勾就开。它让外部进程能 read/write 你的 vault。</p></div></div>
<div class="oc-step"><div class="oc-sn">2</div><div class="oc-sc"><h4>接 Claude Desktop + obsidian-mcp server</h4><p>MCP 一个配置文件就能接token 填 vault 的 api key。</p></div></div>
<div class="oc-step"><div class="oc-sn">3</div><div class="oc-sc"><h4>装 5 个 obsidian-skills</h4><p>markdown / bases / canvas / cli / defuddle —— 让 Claude 知道怎么正确使用 Obsidian。</p></div></div>
<div class="oc-step"><div class="oc-sn">4</div><div class="oc-sc"><h4>让 Claude 自己整理一次</h4><p>「帮我把最近 10 篇笔记里的重复概念合并,生成一张新的 MOC」—— 90 秒出结果。</p></div></div>
</div>
</section>
<!-- 5. CODE -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">05 / 08</div>
<div class="oc-tag">● MCP CONFIG</div>
<h2 class="oc-h2">claude_desktop_config.json</h2>
<pre class="oc-code"><span class="cm">// ~/Library/Application Support/Claude/claude_desktop_config.json</span>
{
<span class="cc">"mcpServers"</span>: {
<span class="cc">"obsidian"</span>: {
<span class="cc">"command"</span>: <span class="cs">"npx"</span>,
<span class="cc">"args"</span>: [<span class="cs">"-y"</span>, <span class="cs">"@modelcontextprotocol/server-obsidian"</span>],
<span class="cc">"env"</span>: {
<span class="cc">"OBSIDIAN_API_KEY"</span>: <span class="cs">"xxxxxxxxxxxxxxxx"</span>,
<span class="cc">"OBSIDIAN_HOST"</span>: <span class="cs">"http://127.0.0.1:27123"</span>
}
}
}
}</pre>
<p class="oc-sub" style="margin-top:18px">重启 Claude Desktop输入 <b style="color:var(--oc-accent3)">/mcp</b>,你会看到 obsidian 已连。</p>
</section>
<!-- 6. STATS -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">06 / 08</div>
<div class="oc-tag">● 3 MONTHS IN</div>
<h2 class="oc-h2">跑了 90 天,我的 <span class="oc-g">vault 数据</span></h2>
<div class="oc-grid-3" style="margin-top:28px">
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">1,842</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">notes in vault</p></div>
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">6.3k</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">backlinks (由 AI 自动补)</p></div>
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">-74%</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">找资料平均耗时</p></div>
</div>
<div class="oc-hl" style="margin-top:26px">最大收益不是「AI 帮我写」而是「AI 帮我把旧笔记重新连起来」—— 每周 30 分钟vault 就会主动生长。</div>
</section>
<!-- 7. QUOTE / CTA -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">07 / 08</div>
<div class="oc-tag">● CTA · 今晚可以做</div>
<div class="oc-quote">
<blockquote>不要再找「AI 笔记应用」了。<br>你要的是一个 <span class="oc-g">文件夹 + 一条神经</span></blockquote>
<div class="attr">— 我自己,用了 90 天后</div>
</div>
<div style="margin-top:36px">
<span class="oc-pill">⬇ obsidian.md</span>
<span class="oc-pill">⬇ Claude Desktop</span>
<span class="oc-pill">⬇ obsidian-mcp</span>
<span class="oc-pill">⬇ obsidian-skills × 5</span>
</div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">08 / 08</div>
<div class="oc-big oc-g">Thanks.</div>
<p class="oc-sub" style="margin-top:26px">配置模板、skill manifest、我的 vault 结构图都在 <b style="color:var(--oc-accent3)">github.com/lewis/obsidian-claude</b></p>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,59 @@
/* obsidian-claude-gradient — 紫色暗底 + GitHub-ish 渐变卡 */
.tpl-obsidian-claude-gradient{
--oc-bg:#0d1117;
--oc-surface:#161b22;
--oc-surface2:#21262d;
--oc-border:#30363d;
--oc-accent:#7c3aed;
--oc-accent2:#a855f7;
--oc-accent3:#c084fc;
--oc-green:#3fb950;
--oc-blue:#58a6ff;
--oc-orange:#f97316;
--oc-yellow:#fbbf24;
--oc-red:#f87171;
--oc-text:#e6edf3;
--oc-dim:#8b949e;
--oc-dimmer:#484f58;
background:var(--oc-bg);
color:var(--oc-text);
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
}
.tpl-obsidian-claude-gradient .slide{background:var(--oc-bg);color:var(--oc-text);padding:64px 88px;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;overflow:hidden}
.tpl-obsidian-claude-gradient .oc-cbg{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse at 28% 38%,rgba(124,58,237,.25) 0%,transparent 60%),radial-gradient(ellipse at 72% 62%,rgba(88,166,255,.18) 0%,transparent 60%)}
.tpl-obsidian-claude-gradient .oc-cgrid{position:absolute;inset:0;pointer-events:none;background-image:linear-gradient(rgba(48,54,61,.4) 1px,transparent 1px),linear-gradient(90deg,rgba(48,54,61,.4) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(ellipse at center,black 35%,transparent 80%)}
.tpl-obsidian-claude-gradient .slide > *{position:relative;z-index:2}
.tpl-obsidian-claude-gradient .oc-snum{position:absolute;top:24px;right:36px;color:var(--oc-dimmer);font-size:12px;letter-spacing:.1em;z-index:3}
.tpl-obsidian-claude-gradient .oc-tag{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--oc-accent3);background:rgba(124,58,237,.14);border:1px solid rgba(168,85,247,.3);padding:5px 16px;border-radius:999px;margin-bottom:22px}
.tpl-obsidian-claude-gradient .oc-h1{font-size:72px;font-weight:800;line-height:1.08;letter-spacing:-.02em;margin:0 0 10px;color:var(--oc-text)}
.tpl-obsidian-claude-gradient .oc-h2{font-size:44px;font-weight:700;line-height:1.18;letter-spacing:-.015em;margin:0 0 14px}
.tpl-obsidian-claude-gradient .oc-sub{font-size:19px;color:var(--oc-dim);line-height:1.65;max-width:720px;margin-top:14px}
.tpl-obsidian-claude-gradient .oc-g{background:linear-gradient(135deg,#a855f7 0%,#60a5fa 55%,#34d399 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.tpl-obsidian-claude-gradient .oc-card{background:var(--oc-surface);border:1px solid var(--oc-border);border-radius:14px;padding:22px 26px;text-align:left;position:relative;overflow:hidden}
.tpl-obsidian-claude-gradient .oc-card::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(168,85,247,.4),transparent)}
.tpl-obsidian-claude-gradient .oc-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:18px;width:100%;max-width:1000px;margin-top:24px}
.tpl-obsidian-claude-gradient .oc-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;width:100%;max-width:1080px;margin-top:24px}
.tpl-obsidian-claude-gradient .oc-badge{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:3px 11px;border-radius:999px;margin-bottom:10px}
.tpl-obsidian-claude-gradient .oc-bp{background:rgba(168,85,247,.15);color:var(--oc-accent3)}
.tpl-obsidian-claude-gradient .oc-bb{background:rgba(88,166,255,.15);color:var(--oc-blue)}
.tpl-obsidian-claude-gradient .oc-bg{background:rgba(63,185,80,.15);color:var(--oc-green)}
.tpl-obsidian-claude-gradient .oc-bo{background:rgba(249,115,22,.15);color:var(--oc-orange)}
.tpl-obsidian-claude-gradient .oc-code{background:#010409;border:1px solid var(--oc-border);border-radius:12px;padding:20px 24px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;width:100%;max-width:860px;text-align:left;color:#e6edf3}
.tpl-obsidian-claude-gradient .oc-code .cp{color:var(--oc-green)}
.tpl-obsidian-claude-gradient .oc-code .cc{color:var(--oc-blue)}
.tpl-obsidian-claude-gradient .oc-code .ca{color:var(--oc-accent3)}
.tpl-obsidian-claude-gradient .oc-code .cm{color:var(--oc-dimmer)}
.tpl-obsidian-claude-gradient .oc-code .cs{color:var(--oc-orange)}
.tpl-obsidian-claude-gradient .oc-hl{background:rgba(124,58,237,.1);border:1px solid rgba(168,85,247,.3);border-left:4px solid var(--oc-accent2);border-radius:0 12px 12px 0;padding:16px 22px;font-size:16px;line-height:1.7;max-width:860px;text-align:left}
.tpl-obsidian-claude-gradient .oc-steps{display:flex;flex-direction:column;gap:0;width:100%;max-width:820px;text-align:left}
.tpl-obsidian-claude-gradient .oc-step{display:flex;gap:20px;align-items:flex-start;padding:18px 0;border-bottom:1px solid var(--oc-border)}
.tpl-obsidian-claude-gradient .oc-step:last-child{border-bottom:none}
.tpl-obsidian-claude-gradient .oc-sn{width:36px;height:36px;flex-shrink:0;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:15px;background:linear-gradient(135deg,var(--oc-accent),var(--oc-blue));color:#fff}
.tpl-obsidian-claude-gradient .oc-sc h4{font-size:17px;font-weight:600;margin-bottom:6px;color:var(--oc-text)}
.tpl-obsidian-claude-gradient .oc-sc p{font-size:14px;color:var(--oc-dim);line-height:1.6}
.tpl-obsidian-claude-gradient .oc-pill{display:inline-flex;align-items:center;gap:8px;background:var(--oc-surface2);border:1px solid var(--oc-border);border-radius:999px;padding:7px 18px;font-size:14px;font-weight:500;color:var(--oc-text);margin:4px 6px 4px 0}
.tpl-obsidian-claude-gradient .oc-quote{max-width:800px}
.tpl-obsidian-claude-gradient .oc-quote blockquote{font-size:26px;font-weight:500;line-height:1.6;position:relative;padding:0 36px;margin:0;color:var(--oc-text)}
.tpl-obsidian-claude-gradient .oc-quote blockquote::before{content:'"';position:absolute;left:-6px;top:-22px;font-size:78px;color:var(--oc-accent);opacity:.4;font-family:Georgia,serif;line-height:1}
.tpl-obsidian-claude-gradient .oc-quote .attr{margin-top:20px;font-size:13px;color:var(--oc-dim)}
.tpl-obsidian-claude-gradient .oc-big{font-size:140px;font-weight:900;line-height:.95;letter-spacing:-.04em}

View File

@@ -0,0 +1,9 @@
# pitch-deck
Classic 10-slide YC/VC seed pitch: cover, problem, solution, product, market, business model, traction, team, ask, thanks.
Clean white background, bold blue→purple gradient accent, oversized headlines and big numbers — the look investors expect when they skim 40 decks a day.
**Use when:** pitching a fundraise, office hours, or a "state of the company" update. Swap copy, keep structure.
**Feel:** confident, data-forward, founder-friendly.
**Brand color:** override `--grad` in `style.css` to re-skin.

View File

@@ -0,0 +1,148 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Lumen · Pitch Deck</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-pitch-deck">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<div class="cover-bg"></div>
<div class="cover-blob"></div>
<div style="position:absolute;top:56px;left:112px"><span class="brand-dot"></span><span class="brand">Lumen</span></div>
<p class="kicker">Seed round · 2026</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">The operating system<br>for <span class="gradient-text">solo founders</span>.</h1>
<p class="lede mt-m">One workspace for billing, CRM, contracts and taxes — built for the 70M people running a business of one.</p>
<div class="deck-footer"><span>Maya Chen · CEO</span><span class="slide-number" data-current="1" data-total="10"></span></div>
</section>
<!-- 2. Problem -->
<section class="slide" data-title="Problem">
<span class="section-num">01</span>
<p class="num-tag">PROBLEM</p>
<h2 class="h2 mt-s">Solo founders duct-tape<br>7+ tools to stay alive.</h2>
<div class="grid g3 mt-l">
<div class="card"><h4>Fragmentation</h4><p class="dim">Stripe, QuickBooks, HubSpot, DocuSign, Notion, Gusto, a spreadsheet. Nothing talks.</p></div>
<div class="card"><h4>$480/mo wasted</h4><p class="dim">Average solo founder pays for 9 SaaS seats they only half-use.</p></div>
<div class="card"><h4>14 hrs / week lost</h4><p class="dim">Copy-pasting between tools instead of selling.</p></div>
</div>
</section>
<!-- 3. Solution -->
<section class="slide" data-title="Solution">
<span class="section-num">02</span>
<p class="num-tag">SOLUTION</p>
<h2 class="h2 mt-s">Lumen is <span class="gradient-text">one spine</span><br>for the business of one.</h2>
<p class="lede mt-m">Invoice a client → the payment lands → the tax is reserved → the contract is filed → your dashboard updates. In one app. Without plumbing.</p>
<div class="row mt-l">
<span class="pill pill-accent">Billing</span>
<span class="pill pill-accent">CRM</span>
<span class="pill pill-accent">Contracts</span>
<span class="pill pill-accent">Taxes</span>
<span class="pill pill-accent">Banking</span>
</div>
</section>
<!-- 4. Product -->
<section class="slide" data-title="Product">
<span class="section-num">03</span>
<p class="num-tag">PRODUCT</p>
<h2 class="h2 mt-s">Built around "jobs to be done".</h2>
<div class="grid g2 mt-l">
<div class="card card-hover"><h4>Get paid</h4><p class="dim">Invoices, subscriptions and Stripe/Wise payouts with a single click. ACH, card, wire, crypto.</p></div>
<div class="card card-hover"><h4>Stay legal</h4><p class="dim">E-sign contracts from templates. Auto-file 1099s and quarterly estimates.</p></div>
<div class="card card-hover"><h4>Sell smarter</h4><p class="dim">Lead inbox, pipeline, email sequences. No separate CRM.</p></div>
<div class="card card-hover"><h4>See the business</h4><p class="dim">Live P&amp;L, runway, top customers, churn. One dashboard, zero spreadsheets.</p></div>
</div>
</section>
<!-- 5. Market -->
<section class="slide" data-title="Market">
<span class="section-num">04</span>
<p class="num-tag">MARKET</p>
<h2 class="h2 mt-s">A very big small business.</h2>
<div class="grid g3 mt-l">
<div class="metric"><div class="n">73M</div><div class="l">solo businesses in the US + EU</div></div>
<div class="metric"><div class="n">$186B</div><div class="l">TAM · horizontal SaaS spend</div></div>
<div class="metric"><div class="n">9.4%</div><div class="l">CAGR through 2030</div></div>
</div>
<p class="lede mt-l">Creators, consultants, indie devs, coaches, freelancers — the fastest-growing segment of the workforce, and the most under-served by tooling.</p>
</section>
<!-- 6. Business model -->
<section class="slide" data-title="Business Model">
<span class="section-num">05</span>
<p class="num-tag">BUSINESS MODEL</p>
<h2 class="h2 mt-s">Flat SaaS + payment rake.</h2>
<div class="grid g3 mt-l">
<div class="card"><h4>Starter</h4><div class="metric mt-s"><div class="n" style="font-size:56px">$29</div><div class="l">/ month · core billing + CRM</div></div></div>
<div class="card card-accent"><h4>Pro</h4><div class="metric mt-s"><div class="n" style="font-size:56px">$79</div><div class="l">/ month · contracts, taxes, banking</div></div></div>
<div class="card"><h4>+ Payments</h4><div class="metric mt-s"><div class="n" style="font-size:56px">0.4%</div><div class="l">interchange rake on processed volume</div></div></div>
</div>
<p class="dim mt-l">Blended LTV $1,920 · CAC payback 5 months at current funnel.</p>
</section>
<!-- 7. Traction -->
<section class="slide" data-title="Traction">
<span class="section-num">06</span>
<p class="num-tag">TRACTION</p>
<h2 class="h2 mt-s">6 months, growing 38% MoM.</h2>
<div class="traction-bar mt-l">
<div class="bar" style="height:18%"><em>$6k</em><span>Oct</span></div>
<div class="bar" style="height:30%"><em>$11k</em><span>Nov</span></div>
<div class="bar" style="height:44%"><em>$17k</em><span>Dec</span></div>
<div class="bar" style="height:62%"><em>$26k</em><span>Jan</span></div>
<div class="bar" style="height:82%"><em>$38k</em><span>Feb</span></div>
<div class="bar" style="height:100%"><em>$54k</em><span>Mar</span></div>
</div>
<p class="dim mt-l" style="margin-top:48px">2,140 paying customers · NPS 72 · Net retention 118%</p>
</section>
<!-- 8. Team -->
<section class="slide" data-title="Team">
<span class="section-num">07</span>
<p class="num-tag">TEAM</p>
<h2 class="h2 mt-s">Shipped at scale before.</h2>
<div class="grid g3 mt-l">
<div class="card team-card"><div class="avatar">MC</div><h4>Maya Chen</h4><p class="dim">CEO · ex-Stripe billing lead. 8 yrs in payments.</p></div>
<div class="card team-card"><div class="avatar">RP</div><h4>Raj Patel</h4><p class="dim">CTO · ex-Linear. Built multiplayer sync at 10M users.</p></div>
<div class="card team-card"><div class="avatar">EK</div><h4>Elena Kim</h4><p class="dim">Head of Design · ex-Notion. Shipped the mobile relaunch.</p></div>
</div>
</section>
<!-- 9. Ask -->
<section class="slide" data-title="The Ask">
<p class="num-tag">THE ASK</p>
<div class="ask-box mt-m">
<h2 class="h2">Raising $4.5M seed.</h2>
<p class="lede" style="color:rgba(255,255,255,.9);max-width:60ch">18 months of runway to reach $3M ARR. 40% engineering, 35% growth, 15% compliance/banking licenses, 10% runway buffer.</p>
<div class="row mt-l" style="gap:40px">
<div><div style="font-size:44px;font-weight:900">$4.5M</div><div class="dim">SAFE · post-money cap $28M</div></div>
<div><div style="font-size:44px;font-weight:900">18 mo</div><div class="dim">runway to Series A</div></div>
<div><div style="font-size:44px;font-weight:900">$3M</div><div class="dim">ARR target by close</div></div>
</div>
</div>
</section>
<!-- 10. Thanks -->
<section class="slide center tc" data-title="Thanks">
<div class="cover-bg"></div>
<div>
<div class="mega">Thanks.</div>
<p class="mega-sub">maya@lumen.app · lumen.app/investors</p>
<div class="row mt-l" style="justify-content:center;gap:24px">
<span class="pill pill-accent">Let's talk</span>
<span class="pill">Deck v4.2 · Apr 2026</span>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,40 @@
/* pitch-deck — classic YC/VC pitch */
.tpl-pitch-deck{
--bg:#ffffff;--bg-soft:#f6f7fb;--surface:#ffffff;--surface-2:#f2f4fa;
--border:rgba(20,25,60,.08);--border-strong:rgba(20,25,60,.18);
--text-1:#0d1130;--text-2:#4a5070;--text-3:#8a90ad;
--accent:#3b5bff;--accent-2:#7a46ff;--accent-3:#d94cff;
--grad:linear-gradient(135deg,#3b5bff 0%,#7a46ff 55%,#d94cff 100%);
--grad-soft:linear-gradient(135deg,#eef1ff,#f4edff 55%,#fbedff);
--radius:20px;--radius-lg:28px;
--shadow:0 14px 40px rgba(20,25,60,.08),0 2px 8px rgba(20,25,60,.04);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-pitch-deck .slide{padding:88px 112px}
.tpl-pitch-deck .kicker{color:var(--accent);font-weight:700}
.tpl-pitch-deck .h1{font-size:86px;line-height:1.02;font-weight:900;letter-spacing:-.035em}
.tpl-pitch-deck .h2{font-size:62px;font-weight:800;letter-spacing:-.03em}
.tpl-pitch-deck .mega{font-size:180px;font-weight:900;line-height:.95;letter-spacing:-.05em;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.tpl-pitch-deck .mega-sub{font-size:28px;color:var(--text-2);margin-top:18px}
.tpl-pitch-deck .cover-bg{position:absolute;inset:0;background:var(--grad-soft);z-index:-1}
.tpl-pitch-deck .cover-blob{position:absolute;right:-140px;top:-140px;width:560px;height:560px;border-radius:50%;background:var(--grad);filter:blur(8px);opacity:.35;z-index:-1}
.tpl-pitch-deck .brand-dot{display:inline-block;width:14px;height:14px;border-radius:50%;background:var(--grad);margin-right:10px;vertical-align:middle}
.tpl-pitch-deck .brand{font-weight:800;font-size:22px;letter-spacing:-.02em}
.tpl-pitch-deck .card{border-radius:var(--radius)}
.tpl-pitch-deck .num-tag{font-family:'Inter',sans-serif;font-size:14px;font-weight:700;color:var(--accent);letter-spacing:.12em}
.tpl-pitch-deck .big-q{font-family:'Playfair Display',serif;font-size:56px;line-height:1.15;font-weight:700;letter-spacing:-.02em;max-width:22ch}
.tpl-pitch-deck .metric{display:flex;flex-direction:column;gap:6px}
.tpl-pitch-deck .metric .n{font-size:72px;font-weight:900;letter-spacing:-.035em;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;line-height:1}
.tpl-pitch-deck .metric .l{color:var(--text-2);font-size:16px}
.tpl-pitch-deck .team-card{text-align:center;padding:32px 20px}
.tpl-pitch-deck .avatar{width:96px;height:96px;border-radius:50%;margin:0 auto 14px;background:var(--grad);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:32px}
.tpl-pitch-deck .ask-box{background:var(--grad);color:#fff;padding:56px 64px;border-radius:var(--radius-lg);box-shadow:0 30px 70px rgba(59,91,255,.35)}
.tpl-pitch-deck .ask-box .h2{color:#fff}
.tpl-pitch-deck .ask-box .dim{color:rgba(255,255,255,.85)}
.tpl-pitch-deck .traction-bar{display:flex;align-items:flex-end;gap:14px;height:240px;margin-top:24px}
.tpl-pitch-deck .traction-bar .bar{flex:1;background:var(--grad);border-radius:8px 8px 0 0;position:relative;min-height:20px}
.tpl-pitch-deck .traction-bar .bar span{position:absolute;bottom:-28px;left:0;right:0;text-align:center;font-size:13px;color:var(--text-3)}
.tpl-pitch-deck .traction-bar .bar em{position:absolute;top:-28px;left:0;right:0;text-align:center;font-size:14px;font-weight:700;font-style:normal;color:var(--text-1)}
.tpl-pitch-deck .section-num{font-size:220px;font-weight:900;line-height:.9;color:var(--surface-2);position:absolute;right:72px;bottom:40px;z-index:0;letter-spacing:-.05em}
.tpl-pitch-deck .slide > *{position:relative;z-index:1}
.tpl-pitch-deck .deck-footer{color:var(--text-3)}

View File

@@ -0,0 +1,102 @@
# presenter-mode-reveal · 演讲者模式模板
一份专为**带逐字稿的技术分享**设计的 full-deck 模板。核心卖点是真正可用的**磁吸卡片式演讲者视图**:当前页 iframe 预览 + 下页 iframe 预览 + 大字号逐字稿 + 计时器4 个卡片可任意拖拽/缩放,全部集成在 `runtime.js` 里,零依赖。
## 使用场景
- 技术分享 / tech talk30-60 min
- 产品发布会主讲
- 课程讲授
- 任何**需要照着讲、但不能念稿**的正式演讲
## 快速开始
```bash
cp -r templates/full-decks/presenter-mode-reveal examples/my-talk
open examples/my-talk/index.html
```
## 键盘操作
| 键 | 动作 |
|---|---|
| `S` | 打开演讲者窗口(弹出新窗口,原页面不动) |
| `T` | 切换主题5 种预设) |
| `←` `→` | 翻页 |
| `Space` / `PgDn` | 下一页 |
| `F` | 全屏 |
| `O` | 总览缩略图 |
| `R` | 重置计时器(仅演讲者视图下) |
| `Esc` | 关闭所有浮层 |
## 主题切换
模板预设了 5 个适配演讲场景的主题,在 `<html data-themes="...">` 属性里:
```html
<html lang="zh-CN" data-themes="tokyo-night,dracula,catppuccin-mocha,nord,corporate-clean">
```
`T` 循环切换。可以改成任何 `assets/themes/*.css` 里的主题。
## 写逐字稿的规范
**每一页的 `<aside class="notes">` 里写 150300 字**。三条铁律:
1. **不是讲稿,是提示信号** — 核心点加粗、过渡句成段、数据列清楚
2. **150300 字/页** — 按 23 分钟/页的节奏
3. **用口语写** — "因此" → "所以""该方案" → "这个方案";读一遍不拗口才对
示例:
```html
<aside class="notes">
<p>大家好,今天跟大家聊一个 <strong>很多人忽略的问题</strong>——...</p>
<p>我先抛一个观点:<em>做 PPT 和讲 PPT 是两件事</em></p>
<p>接下来我会用 3 个例子证明这个观点...</p>
</aside>
```
支持的 inline 标签:
- `<strong>` — 高亮(橘色)
- `<em>` — 斜体强调(蓝色)
- `<code>` — 等宽字体
- `<p>` — 分段(推荐每段讲 30-60 秒的内容)
## 文件结构
```
presenter-mode-reveal/
├── index.html # 6 张示例 slide每页都有完整逐字稿
├── style.css # scoped .tpl-presenter-mode-reveal 样式
└── README.md # 本文件
```
## 修改 / 扩展
- **加页**:复制任意 `<section class="slide">` 块,改内容和 `<aside class="notes">`
- **换主题**:改 `data-themes` 列表,或直接改 `<link id="theme-link" href="...">`
- **改样式**:只动 `style.css`,不要碰根目录的 `assets/base.css`
- **加动效**:在元素上加 `data-anim="fade-up"` 等(参考 `references/animations.md`
## 演讲者窗口的 4 个卡片
`S` 后弹出的窗口里有:
- 🔵 **CURRENT** — 当前页 iframe 预览(加载 `?preview=N` 模式,像素级完美,与观众端同 CSS/主题/字体)
- 🟣 **NEXT** — 下一页预览,帮助准备过渡
- 🟠 **SPEAKER SCRIPT** — 大字号逐字稿,可滚动
- 🟢 **TIMER** — 经过时间 + 页码 + Prev/Next/Reset 按钮
卡片操作:
- **拖卡片头**(彩色圆点 + 标题的顶部条)→ 移动卡片
- **拖卡片右下角** → 调整大小
- 位置 + 尺寸自动存 localStorage下次打开恢复
- 底部 "重置布局" 按钮可恢复默认卡片排列
翻页丝滑iframe 只加载一次,后续翻页通过 `postMessage` 切换内部 slide**不重新加载不闪烁**。两窗口通过 `BroadcastChannel` 双向同步。
## 注意事项
- **观众永远看不到 `.notes` 内容** — CSS 默认 `display:none`,只在演讲者视图里可见
- **别把只给自己看的话写在 slide 本体上** — 所有提词必须在 `<aside class="notes">`
- **双屏演讲**:打开 `index.html` 按 S 弹出演讲者窗口,把观众窗口拖到投影/外接屏 F 全屏,演讲者窗口留在自己屏幕

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="zh-CN" data-themes="tokyo-night,dracula,catppuccin-mocha,nord,corporate-clean">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>演讲者模式示例 · Presenter Mode Deck</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../../assets/themes/tokyo-night.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-presenter-mode-reveal">
<div class="deck">
<!-- ============ 1. COVER ============ -->
<section class="slide" data-title="Cover">
<p class="kicker">presenter-mode / demo</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">如何做一场<br><span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">有逐字稿</span>的技术分享</h1>
<p class="lede mt-m"><span class="mono">S</span> 进入演讲者视图 · <span class="mono">T</span> 切换主题 · <span class="mono">← →</span> 翻页</p>
<div class="speaker">
<div class="av"></div>
<div><b>@lewis</b><span>sharing talk · 30 min</span></div>
</div>
<div class="deck-footer">
<span class="mono">#presenter #逐字稿 #tech-talk</span>
<span class="slide-number" data-current="1" data-total="6"></span>
</div>
<aside class="notes">
<p>大家好,欢迎来到今天的技术分享。我是 lewis今天想跟大家聊一个很多人忽略但其实非常影响演讲效果的话题——<strong>如何让一场技术分享既有深度,又讲得不卡壳</strong></p>
<p>在正式开始之前,先跟大家介绍一下这份 deck 本身:这是一个支持<em>演讲者模式</em>的 HTML 幻灯片模板。现在你们看到的是观众视图,但我自己的屏幕上看到的是完全不一样的东西——当前页、下一页、完整逐字稿、计时器,全在一块屏幕上。</p>
<p>为什么我要专门做这个?因为我发现自己做技术分享时最大的痛点不是 PPT 不够好看,而是<strong>讲到某一页突然不知道该说什么,或者忘了过渡怎么接</strong>。今天这份分享既是内容本身,也是个演示——我会一直开着演讲者模式讲,你们可以观察我讲得有多流畅。</p>
<p>今天分享大概 30 分钟,分 5 个部分。有问题随时打断。Let's go.</p>
</aside>
</section>
<!-- ============ 2. AGENDA ============ -->
<section class="slide" data-title="Agenda">
<p class="kicker">agenda</p>
<h2 class="h2">今天要讲的 5 件事</h2>
<div class="stack mt-l">
<div class="agenda-row"><span class="num">01</span><span class="t">为什么 PPT 本身做得好还不够</span><span class="d">~5min</span></div>
<div class="agenda-row"><span class="num">02</span><span class="t">演讲者模式到底该有哪些信息</span><span class="d">~6min</span></div>
<div class="agenda-row"><span class="num">03</span><span class="t">逐字稿怎么写才不像念稿</span><span class="d">~8min</span></div>
<div class="agenda-row"><span class="num">04</span><span class="t">Live demo · html-ppt skill 怎么用</span><span class="d">~8min</span></div>
<div class="agenda-row"><span class="num">05</span><span class="t">Takeaways + Q&amp;A</span><span class="d">~3min</span></div>
</div>
<aside class="notes">
<p>先过一下今天的议程。</p>
<p>第一部分我想先说服你们<strong>"PPT 做得漂亮≠讲得好"</strong>。我见过太多很精致的 deck但讲的人一上去就开始 "嗯…这个…就是…"。</p>
<p>第二部分聊演讲者视图。业界的产品其实差别蛮大的Keynote、PowerPoint、reveal.js 都有各自的方案,但真正好用的设计逻辑是什么,我会给出我的答案。</p>
<p>第三部分是今天的<em>核心</em>——逐字稿。很多人以为逐字稿就是把要说的话一字不差写下来,错。逐字稿的目的是让你<strong>"看一眼就接得上"</strong>,写法完全不一样。</p>
<p>第四部分会现场 demo 我自己用的 html-ppt skill展示如何 30 分钟出一份带逐字稿的 deck。</p>
<p>最后收尾 + 答疑。</p>
<p>OK进入第一部分。</p>
</aside>
</section>
<!-- ============ 3. PROBLEM ============ -->
<section class="slide" data-title="Problem">
<p class="kicker">// part 01 · problem</p>
<h2 class="h2">做 PPT 和讲 PPT<br><span class="accent">两件事</span></h2>
<div class="grid g3 mt-l">
<div class="card card-accent">
<h4>✅ PPT 做得好</h4>
<p class="dim">主题统一、排版干净、图表清晰、动效克制。这些是"静态作品"的质量。</p>
</div>
<div class="card card-accent">
<h4>❌ 讲得好</h4>
<p class="dim">逻辑连贯、语速稳定、不 "嗯啊"、能接住问题、能当场调整节奏。</p>
</div>
<div class="card card-accent">
<h4>💡 差别在哪</h4>
<p class="dim">前者是<strong>纸上功夫</strong>,后者需要你<strong>"看一眼幻灯片就知道下句话说什么"</strong></p>
</div>
</div>
<aside class="notes">
<p>我先抛一个可能有争议的观点——<strong>做 PPT 和讲 PPT 是两件完全不同的事</strong></p>
<p>大家看左边这张卡片,"PPT 做得好" 意味着什么?主题统一、排版干净、图表清晰、动效克制——这些都是<em>静态作品</em>的质量标准,可以离线评判。</p>
<p>但中间这张卡片就不一样了:"讲得好" 意味着逻辑连贯、语速稳定、不卡壳、能接住提问、能根据现场反应调整节奏——这些是<strong>临场能力</strong>,跟 PPT 好不好看基本没关系。</p>
<p>最关键的是右边这句话——讲得好的人,本质上是"<strong>看一眼幻灯片就知道下句话说什么</strong>"。这个能力靠什么?不是背稿,也不是即兴发挥,而是靠<em>合理设计的提词器系统</em></p>
<p>今天接下来 25 分钟,我就是围绕这个核心问题展开的。</p>
</aside>
</section>
<!-- ============ 4. SOLUTION ============ -->
<section class="slide" data-title="Presenter View">
<p class="kicker">// part 02 · presenter view</p>
<h2 class="h2">演讲者视图应该有<span class="accent">四块信息</span></h2>
<div class="grid g2 mt-l">
<div>
<div class="feature-row"><span class="num blue"></span><div><b>当前页大图</b><p class="dim">占视图一半以上,保证你能扫一眼就知道观众现在看到什么。</p></div></div>
<div class="feature-row"><span class="num green"></span><div><b>下一页预览</b><p class="dim">帮你提前准备过渡句,避免"下一页我忘了讲什么了"。</p></div></div>
</div>
<div>
<div class="feature-row"><span class="num orange"></span><div><b>逐字稿区域</b><p class="dim">大字号、高对比度、支持滚动,这才是演讲者真正在看的东西。</p></div></div>
<div class="feature-row"><span class="num purple"></span><div><b>计时器 + 页码</b><p class="dim">知道自己讲了多久、还剩几页,节奏全凭这个。</p></div></div>
</div>
</div>
<aside class="notes">
<p>演讲者模式应该给你四块信息。我按重要性排序。</p>
<p>第一块,<strong>当前页大图</strong>。这个必须占据视图一半以上空间,因为它是你跟观众的"同步锚"——观众看到什么,你脑子里也得是什么。</p>
<p>第二块,<strong>下一页预览</strong>。这个很多人不理解为什么要放,我解释一下:演讲最卡的瞬间不是讲某一页,而是<em>翻到下一页的那 2 秒</em>。如果你提前看到下一页长什么样,过渡句自然就有了。</p>
<p>第三块,<strong>逐字稿区域</strong>——这是今天的重点,下一部分我会专门讲。这里先说一个硬性要求:字号必须大、对比度必须高、必须能滚动。因为你讲的时候<em>只有余光瞄一下</em>,字小了根本来不及读。</p>
<p>第四块,<strong>计时器和页码</strong>。知道自己讲了多久、还剩几页——节奏感全靠它。Keynote 做得最好reveal.js 默认不够清楚。</p>
<p>这四块缺一不可。今天这个 deck 我把这四块都做出来了,按 S 大家可以试试。</p>
</aside>
</section>
<!-- ============ 5. SCRIPT ============ -->
<section class="slide" data-title="Script">
<p class="kicker">// part 03 · script</p>
<h2 class="h2">逐字稿的<span class="accent">3 条铁律</span></h2>
<div class="stack mt-l">
<div class="rule-row">
<span class="num red">01</span>
<div>
<b>不是一字不差的讲稿,是<span class="accent">"提示信号"</span></b>
<p class="dim">把要讲的核心点加粗,把过渡句单独成段,把数据和名字列清楚——<em>让你看一眼就接得上</em></p>
</div>
</div>
<div class="rule-row">
<span class="num red">02</span>
<div>
<b>每页 <span class="accent">150300 字</span>,不多不少</b>
<p class="dim">少于 150 字提示不够,多于 300 字你没时间读。按 23 分钟/页的节奏控制。</p>
</div>
</div>
<div class="rule-row">
<span class="num red">03</span>
<div>
<b><span class="accent">口语</span>写,不用书面语</b>
<p class="dim">"因此" → "所以""该方案" → "这个方案"。写的时候读一遍,听起来像说话才对。</p>
</div>
</div>
</div>
<aside class="notes">
<p>进入最核心的一部分——逐字稿怎么写。我总结了 3 条铁律。</p>
<p><strong>第一条,逐字稿不是讲稿</strong>。很多人一听"逐字稿"就以为要把每句话一字不差写下来。错。如果你照着稿念,观众会立刻看出来,信任感瞬间崩塌。</p>
<p>逐字稿的真实作用是<em>"提示信号"</em>——把核心要点加粗,把过渡句单独成段,把数据和专有名词列清楚。这样你讲的时候<strong>瞄一眼就能接得上</strong>,但说出来的还是你自己的话。</p>
<p><strong>第二条,每页控制在 150 到 300 字</strong>。这个是我做了十几场分享摸出来的经验值。少于 150 字提示不够,讲到一半卡住;多于 300 字你根本来不及扫完。按一页讲 2 到 3 分钟算,这个字数刚好。</p>
<p><strong>第三条,用口语写</strong>。这条最多人栽跟头。你写"因此",讲出来会变成"所以";你写"该方案",讲出来会变成"这个方案"。<em>写的时候读一遍</em>,不拗口才对。</p>
<p>这三条配合起来,你会发现讲 PPT 突然变成了一件很舒服的事。</p>
</aside>
</section>
<!-- ============ 6. DEMO + CLOSING ============ -->
<section class="slide" data-title="Demo & Close">
<p class="kicker">// part 04-05 · demo + close</p>
<h2 class="h2">现在<span class="accent">你也能做到</span></h2>
<div class="code-block mt-m">
<span class="comment"># 安装 html-ppt skill</span>
<span class="cmd">npx</span> skills add <span class="flag">https://github.com/lewislulu/html-ppt-skill</span>
<span class="comment"># 复制演讲者模式模板</span>
<span class="cmd">cp -r</span> templates/full-decks/presenter-mode-reveal examples/my-talk
<span class="cmd">open</span> examples/my-talk/index.html
<span class="comment"># 键盘操作</span>
<span class="flag">S</span> <span class="comment">→ 进入演讲者视图</span>
<span class="flag">T</span> <span class="comment">→ 切换主题5 种预设)</span>
<span class="flag">← →</span> <span class="comment">→ 翻页</span>
<span class="flag">R</span> <span class="comment">→ 重置计时器</span>
</div>
<p class="lede mt-m tc">关键是:<strong>每一页 &lt;aside class="notes"&gt; 里写 150300 字逐字稿</strong></p>
<div class="deck-footer">
<span class="mono">#thanks · Q&amp;A</span>
<span class="slide-number" data-current="6" data-total="6"></span>
</div>
<aside class="notes">
<p>最后我演示一下这个 skill 怎么用,给大家省点时间自己摸索。</p>
<p>第一步,装 html-ppt skill一行命令。第二步把我这个 <code>presenter-mode-reveal</code> 模板复制到你自己的 examples 目录。第三步,打开 html按 S。</p>
<p>键盘操作我列在这里了。<strong>S 进入演讲者视图、T 切换主题、左右键翻页、R 重置计时器</strong>。主题默认带 5 个——tokyo-night、dracula、catppuccin-mocha、nord、corporate-clean——基本覆盖了深色技术分享、浅色商务汇报两种常见场景。</p>
<p>最关键的一步——<em>每一页底部的 <code>&lt;aside class="notes"&gt;</code> 里,老老实实写 150 到 300 字的逐字稿</em>。这是整个方法论的交付物。AI 可以帮你写初稿,但你一定要自己过一遍,读出来听听是不是你会说的话。</p>
<p>好,我今天就讲到这里。如果你做下一场分享的时候想起了这个"演讲者视图 + 逐字稿"的组合,并且觉得讲得比以前顺——那就是我最大的收获。谢谢大家,有问题现在开始。</p>
</aside>
</section>
</div>
<div style="position:fixed;bottom:12px;left:12px;font-size:11px;color:#484f5866;z-index:100;pointer-events:none">
S 演讲者视图 · T 切换主题 · ← → 翻页 · F 全屏 · O 总览 · R 重置计时
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,216 @@
/* tpl-presenter-mode-reveal · scoped styles
* Presenter-mode demo deck. Inherits tokens from active theme.
* Minimal overrides — focus is on content + notes structure.
*/
.tpl-presenter-mode-reveal .slide {
padding: 72px 96px;
}
.tpl-presenter-mode-reveal .kicker {
font-family: var(--font-mono, monospace);
font-size: 13px;
color: var(--text-3);
letter-spacing: 0.14em;
text-transform: uppercase;
margin: 0 0 18px 0;
}
.tpl-presenter-mode-reveal .h1 {
font-size: clamp(44px, 5.6vw, 76px);
line-height: 1.12;
letter-spacing: -0.02em;
margin: 0 0 24px 0;
}
.tpl-presenter-mode-reveal .h2 {
font-size: clamp(32px, 3.6vw, 48px);
line-height: 1.22;
letter-spacing: -0.01em;
margin: 0 0 28px 0;
}
.tpl-presenter-mode-reveal .lede {
font-size: 20px;
line-height: 1.55;
color: var(--text-2);
}
.tpl-presenter-mode-reveal .mono {
font-family: var(--font-mono, monospace);
font-size: 0.9em;
padding: 2px 8px;
border-radius: 6px;
background: rgba(255,255,255,0.08);
color: var(--accent, #58a6ff);
}
.tpl-presenter-mode-reveal .accent {
color: var(--accent, #f0883e);
font-weight: 700;
}
.tpl-presenter-mode-reveal .speaker {
display: flex;
align-items: center;
gap: 12px;
margin-top: 32px;
}
.tpl-presenter-mode-reveal .speaker .av {
width: 42px;
height: 42px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent, #58a6ff), #bc8cff);
}
.tpl-presenter-mode-reveal .speaker b {
display: block;
font-size: 16px;
}
.tpl-presenter-mode-reveal .speaker span {
font-size: 13px;
color: var(--text-3);
}
/* Agenda rows */
.tpl-presenter-mode-reveal .agenda-row {
display: grid;
grid-template-columns: 48px 1fr auto;
gap: 16px;
align-items: center;
padding: 14px 18px;
border: 1px solid var(--border, rgba(255,255,255,0.1));
border-radius: 10px;
margin-bottom: 10px;
background: var(--surface, rgba(255,255,255,0.03));
}
.tpl-presenter-mode-reveal .agenda-row .num {
font-family: var(--font-mono, monospace);
font-size: 14px;
color: var(--accent, #58a6ff);
font-weight: 700;
}
.tpl-presenter-mode-reveal .agenda-row .t {
font-size: 17px;
font-weight: 500;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .agenda-row .d {
font-family: var(--font-mono, monospace);
font-size: 12px;
color: var(--text-3);
}
/* Cards */
.tpl-presenter-mode-reveal .card {
background: var(--surface, rgba(255,255,255,0.03));
border: 1px solid var(--border, rgba(255,255,255,0.1));
border-radius: 12px;
padding: 22px 24px;
}
.tpl-presenter-mode-reveal .card-accent {
border-top: 3px solid var(--accent, #58a6ff);
}
.tpl-presenter-mode-reveal .card h4 {
margin: 0 0 10px 0;
font-size: 18px;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .card .dim {
color: var(--text-2);
font-size: 14px;
line-height: 1.6;
margin: 0;
}
/* Feature rows (presenter view features) */
.tpl-presenter-mode-reveal .feature-row {
display: flex;
gap: 14px;
padding: 14px 0;
border-bottom: 1px solid var(--border, rgba(255,255,255,0.08));
}
.tpl-presenter-mode-reveal .feature-row:last-child { border-bottom: none; }
.tpl-presenter-mode-reveal .feature-row .num {
font-size: 24px;
font-weight: 700;
line-height: 1;
flex-shrink: 0;
}
.tpl-presenter-mode-reveal .feature-row b {
display: block;
font-size: 17px;
margin-bottom: 4px;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .feature-row .dim {
font-size: 14px;
color: var(--text-2);
line-height: 1.55;
margin: 0;
}
.tpl-presenter-mode-reveal .blue { color: #58a6ff; }
.tpl-presenter-mode-reveal .green { color: #3fb950; }
.tpl-presenter-mode-reveal .orange { color: #f0883e; }
.tpl-presenter-mode-reveal .purple { color: #bc8cff; }
.tpl-presenter-mode-reveal .red { color: #f85149; }
/* Rule rows (3 铁律) */
.tpl-presenter-mode-reveal .rule-row {
display: grid;
grid-template-columns: 56px 1fr;
gap: 20px;
align-items: start;
padding: 18px 22px;
border: 1px solid var(--border, rgba(255,255,255,0.1));
border-radius: 12px;
margin-bottom: 14px;
background: var(--surface, rgba(255,255,255,0.03));
}
.tpl-presenter-mode-reveal .rule-row .num {
font-size: 28px;
font-weight: 800;
font-family: var(--font-mono, monospace);
line-height: 1;
}
.tpl-presenter-mode-reveal .rule-row b {
display: block;
font-size: 18px;
margin-bottom: 6px;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .rule-row .dim {
font-size: 15px;
color: var(--text-2);
line-height: 1.6;
margin: 0;
}
/* Code block */
.tpl-presenter-mode-reveal .code-block {
background: #0d1117;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 12px;
padding: 20px 26px;
font-family: var(--font-mono, "SF Mono", monospace);
font-size: 15px;
line-height: 1.8;
color: #e6edf3;
white-space: pre-wrap;
text-align: left;
}
.tpl-presenter-mode-reveal .code-block .comment { color: #8b949e; }
.tpl-presenter-mode-reveal .code-block .cmd { color: #3fb950; font-weight: 600; }
.tpl-presenter-mode-reveal .code-block .flag { color: #f0883e; }
/* Stack helper */
.tpl-presenter-mode-reveal .stack > * + * { margin-top: 0; }
/* Grid helpers */
.tpl-presenter-mode-reveal .grid { display: grid; gap: 20px; }
.tpl-presenter-mode-reveal .grid.g2 { grid-template-columns: 1fr 1fr; }
.tpl-presenter-mode-reveal .grid.g3 { grid-template-columns: repeat(3, 1fr); }
.tpl-presenter-mode-reveal .mt-m { margin-top: 20px; }
.tpl-presenter-mode-reveal .mt-l { margin-top: 32px; }
.tpl-presenter-mode-reveal .mt-s { margin-top: 10px; }
.tpl-presenter-mode-reveal .tc { text-align: center; }

View File

@@ -0,0 +1,8 @@
# product-launch
8-slide consumer product announcement deck: hero cover, "introducing" moment, three feature slides, how-it-works, pricing tiers, and a closing testimonial + pre-order CTA.
Mixes dark hero slides (for show-off moments) with light slides (for details and pricing). Warm orange→peach gradient accent feels confident and human; easy to re-skin for any brand.
**Use when:** launching a product, announcing a v2, internal all-hands reveals, press kit decks.
**Feel:** Apple-event-on-a-budget — confident, tactile, uncluttered.

View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Halo v2 · Launch</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-product-launch">
<div class="deck">
<!-- 1. Cover / hero -->
<section class="slide dark" data-title="Cover">
<div class="hero-shot"></div>
<div style="position:absolute;top:56px;left:112px" class="brand">◎ Halo</div>
<p class="kicker">Launch · April 2026</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">Meet Halo v2.<br>Your ears,<br><span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">rewritten.</span></h1>
<p class="lede mt-m" style="max-width:42ch">Studio-grade spatial audio in the lightest open-ear earbuds ever made.</p>
<div class="deck-footer"><span>halo.audio</span><span class="slide-number" data-current="1" data-total="8"></span></div>
</section>
<!-- 2. Introducing -->
<section class="slide center tc" data-title="Introducing">
<div>
<p class="kicker">Introducing</p>
<h1 class="h1" style="font-size:140px">Halo v2</h1>
<p class="lede" style="margin:24px auto;max-width:56ch">Four years of research. Three generations of silicon. One product you'll forget you're wearing.</p>
</div>
</section>
<!-- 3. Feature 1 -->
<section class="slide" data-title="Sound">
<p class="kicker">01 · The sound</p>
<h2 class="h2">Hear the room<br>around the music.</h2>
<div class="grid g3 mt-l">
<div class="feature-card"><div class="icon"></div><h4>Open-ear spatial</h4><p class="dim">16mm titanium drivers angled into the ear canal. You hear the song and the world at once.</p></div>
<div class="feature-card"><div class="icon"></div><h4>Lossless 24-bit</h4><p class="dim">aptX Lossless and Hi-Res LDAC over Bluetooth 5.4. No dongles, no compromises.</p></div>
<div class="feature-card"><div class="icon"></div><h4>Adaptive EQ</h4><p class="dim">Tunes itself to the shape of your ear every 120 seconds.</p></div>
</div>
</section>
<!-- 4. Feature 2 -->
<section class="slide dark" data-title="Fit">
<p class="kicker">02 · The fit</p>
<h2 class="h2">4.9 grams.<br>All-day forgettable.</h2>
<div class="grid g3 mt-l">
<div class="card"><h4>Liquid-silicone hook</h4><p>Wraps behind the ear like a glasses arm. Never falls out on a run.</p></div>
<div class="card"><h4>IP57 sweat + rain</h4><p>Take them in the ocean. Rinse them under the tap. We dare you.</p></div>
<div class="card"><h4>14h + 42h case</h4><p>A full workweek of commutes on one charge of the case.</p></div>
</div>
</section>
<!-- 5. Feature 3 -->
<section class="slide" data-title="Intelligence">
<p class="kicker">03 · The intelligence</p>
<h2 class="h2">An AI that listens<br>so you don't have to.</h2>
<div class="grid g2 mt-l">
<div class="feature-card"><div class="icon"></div><h4>Live translate</h4><p class="dim">Real-time translation in 41 languages. Whispered directly into your ear, with a 380ms lag.</p></div>
<div class="feature-card"><div class="icon"></div><h4>Meeting recap</h4><p class="dim">Double-tap to record. Walk away with a summary, action items, and a searchable transcript.</p></div>
</div>
</section>
<!-- 6. How it works -->
<section class="slide" data-title="How it works">
<p class="kicker">How it works</p>
<h2 class="h2">Three taps. You're in.</h2>
<div class="stack mt-l" style="max-width:900px">
<div class="step"><div class="n">1</div><div><h4>Open the case near your phone</h4><p class="dim">iOS and Android pair automatically over Bluetooth LE. No app downloads required.</p></div></div>
<div class="step"><div class="n">2</div><div><h4>Pick your profile</h4><p class="dim">Commute, Focus, Workout, Cinema. Each is a complete audio + transparency recipe.</p></div></div>
<div class="step"><div class="n">3</div><div><h4>Just listen</h4><p class="dim">Halo adapts to your ear shape, your environment, and your hearing profile — continuously.</p></div></div>
</div>
</section>
<!-- 7. Pricing -->
<section class="slide" data-title="Pricing">
<p class="kicker">Pricing</p>
<h2 class="h2">Pick your Halo.</h2>
<div class="grid g3 mt-l" style="align-items:start">
<div class="price-card">
<h4>Halo Lite</h4>
<div class="amount">$179</div>
<p class="dim">Open-ear audio, IP57, 12h battery.</p>
<ul><li>AAC + SBC</li><li>Single-tap controls</li><li>USB-C charging</li></ul>
</div>
<div class="price-card pro">
<h4>Halo v2 · Pro</h4>
<div class="amount">$279</div>
<p class="dim">Everything, in its best form.</p>
<ul><li>Hi-Res Lossless</li><li>Live translate · 41 lang</li><li>Wireless + MagSafe charging</li><li>Adaptive EQ</li></ul>
</div>
<div class="price-card">
<h4>Halo Studio</h4>
<div class="amount">$399</div>
<p class="dim">For creators and field recorders.</p>
<ul><li>32-bit binaural capture</li><li>XLR dongle included</li><li>Lifetime firmware</li></ul>
</div>
</div>
</section>
<!-- 8. Testimonial + CTA combined? Task says 8 slides w/ testimonial + CTA as separate. Keep 8: testimonial on 7, but we've used 7 already. Re-plan: cover(1) intro(2) f1(3) f2(4) f3(5) how(6) pricing(7) testimonial+CTA(8) -->
<section class="slide dark" data-title="Ship">
<p class="kicker">One more thing</p>
<div class="row" style="gap:80px;align-items:center">
<div style="flex:1">
<p class="testimonial">"I forgot I was wearing them. Then I remembered, and I didn't want to take them off."</p>
<p class="dim mt-m">— Marques Lin, The Verge · early review</p>
</div>
<div style="flex:0 0 auto;text-align:center">
<p class="dim mb-m">Ships May 14 · from</p>
<div style="font-size:96px;font-weight:900;letter-spacing:-.04em">$279</div>
<a class="cta-btn mt-l" href="#">Pre-order Halo v2 →</a>
<p class="dim mt-m" style="font-size:13px">Free shipping · 45-day return · 2-year warranty</p>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,39 @@
/* product-launch — modern announcement deck */
.tpl-product-launch{
--bg:#ffffff;--bg-soft:#f5f5f7;--surface:#ffffff;--surface-2:#f2f2f6;
--ink:#0a0a12;--ink-2:#3a3a44;
--border:rgba(10,10,18,.08);--border-strong:rgba(10,10,18,.18);
--text-1:#0a0a12;--text-2:#4a4a58;--text-3:#8a8a96;
--accent:#ff5a36;--accent-2:#ff8c5a;--accent-3:#ffb36b;
--grad:linear-gradient(120deg,#ff5a36 0%,#ff8c5a 60%,#ffb36b 100%);
--radius:22px;--radius-lg:32px;
--shadow:0 20px 60px rgba(10,10,18,.1);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-product-launch .slide{padding:80px 112px}
.tpl-product-launch .slide.dark{background:#0a0a12;color:#f5f5f7}
.tpl-product-launch .slide.dark .h1,.tpl-product-launch .slide.dark .h2,.tpl-product-launch .slide.dark h3,.tpl-product-launch .slide.dark h4{color:#fff}
.tpl-product-launch .slide.dark .lede,.tpl-product-launch .slide.dark .dim{color:rgba(245,245,247,.72)}
.tpl-product-launch .slide.dark .card{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.12);box-shadow:none;backdrop-filter:blur(20px)}
.tpl-product-launch .slide.dark .kicker{color:var(--accent-2)}
.tpl-product-launch .h1{font-size:96px;line-height:.98;font-weight:900;letter-spacing:-.045em}
.tpl-product-launch .h2{font-size:64px;font-weight:800;letter-spacing:-.035em}
.tpl-product-launch .hero-shot{position:absolute;right:-60px;top:50%;transform:translateY(-50%);width:640px;height:640px;border-radius:50%;background:var(--grad);filter:blur(2px);opacity:.85}
.tpl-product-launch .hero-shot::after{content:"";position:absolute;inset:80px;border-radius:40px;background:linear-gradient(160deg,rgba(255,255,255,.3),transparent 60%),#1a1a28;box-shadow:inset 0 2px 0 rgba(255,255,255,.2)}
.tpl-product-launch .hero-shot::before{content:"Halo v2";position:absolute;inset:80px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:44px;font-weight:900;letter-spacing:-.02em;z-index:2;border-radius:40px}
.tpl-product-launch .brand{font-size:18px;font-weight:800;letter-spacing:-.02em}
.tpl-product-launch .feature-card{padding:40px 36px;border-radius:var(--radius-lg);background:var(--surface);border:1px solid var(--border);position:relative;overflow:hidden}
.tpl-product-launch .feature-card .icon{width:60px;height:60px;border-radius:18px;background:var(--grad);display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;font-weight:900;margin-bottom:20px}
.tpl-product-launch .step{display:flex;gap:24px;align-items:flex-start}
.tpl-product-launch .step .n{flex:none;width:56px;height:56px;border-radius:50%;background:var(--grad);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:900;font-size:22px}
.tpl-product-launch .price-card{padding:40px 32px;border-radius:var(--radius-lg);border:1.5px solid var(--border);background:var(--surface);text-align:left}
.tpl-product-launch .price-card.pro{background:#0a0a12;color:#fff;border-color:#0a0a12;transform:scale(1.04);box-shadow:0 30px 80px rgba(255,90,54,.25)}
.tpl-product-launch .price-card.pro .dim{color:rgba(255,255,255,.7)}
.tpl-product-launch .price-card h4{font-size:16px;text-transform:uppercase;letter-spacing:.1em;color:var(--accent)}
.tpl-product-launch .price-card.pro h4{color:var(--accent-2)}
.tpl-product-launch .price-card .amount{font-size:64px;font-weight:900;letter-spacing:-.035em;margin:14px 0}
.tpl-product-launch .price-card ul{list-style:none;padding:0;margin:20px 0 0}
.tpl-product-launch .price-card li{padding:8px 0;font-size:15px;color:var(--text-2);border-top:1px solid var(--border)}
.tpl-product-launch .price-card.pro li{color:rgba(255,255,255,.8);border-color:rgba(255,255,255,.12)}
.tpl-product-launch .cta-btn{display:inline-block;padding:20px 40px;border-radius:999px;background:var(--grad);color:#fff;font-weight:700;font-size:20px;box-shadow:0 20px 50px rgba(255,90,54,.4)}
.tpl-product-launch .testimonial{max-width:44ch;font-family:'Playfair Display',serif;font-size:44px;line-height:1.25;font-weight:500;letter-spacing:-.01em}

View File

@@ -0,0 +1,8 @@
# tech-sharing · 技术分享
8-slide engineering talk deck: cover (topic + speaker), agenda, context, two deep-dive slides, a code example, takeaways, Q&A.
Dark GitHub-ish palette (`#0d1117`) with JetBrains Mono accents and syntax-highlighted terminal blocks. Built to be screenshotted and shared on an internal wiki or Twitter.
**Use when:** tech-sharing Fridays, brown-bag talks, lunch & learns, conference submissions.
**Feel:** GitHub README meets a good conference talk — dark, monospaced, dense but readable.

View File

@@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Rust 异步运行时内部机制 · Tech Sharing</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-tech-sharing">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<p class="kicker">tech-sharing / 2026-04-15</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">Rust 异步运行时<br>到底在<span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">调度什么</span>?</h1>
<p class="lede mt-m"><span class="mono">Future::poll</span> 到 tokio 的 work-stealing一次讲清楚。</p>
<div class="speaker"><div class="av"></div><div><b>@lewis</b><span>platform infra · 45 min + Q&amp;A</span></div></div>
<div class="deck-footer"><span class="mono">#async #rust #tokio</span><span class="slide-number" data-current="1" data-total="8"></span></div>
</section>
<!-- 2. Agenda -->
<section class="slide" data-title="Agenda">
<p class="kicker">agenda.toml</p>
<h2 class="h2">今天的路线图</h2>
<div class="stack mt-l">
<div class="agenda-row"><span class="num">01</span><span class="t">Context: 为什么需要 async</span><span class="d">~5min</span></div>
<div class="agenda-row"><span class="num">02</span><span class="t">Deep dive 1: Future &amp; Waker</span><span class="d">~12min</span></div>
<div class="agenda-row"><span class="num">03</span><span class="t">Deep dive 2: Tokio scheduler</span><span class="d">~15min</span></div>
<div class="agenda-row"><span class="num">04</span><span class="t">Code: 手写一个 mini-runtime</span><span class="d">~8min</span></div>
<div class="agenda-row"><span class="num">05</span><span class="t">Takeaways + Q&amp;A</span><span class="d">~5min</span></div>
</div>
</section>
<!-- 3. Context -->
<section class="slide" data-title="Context">
<p class="kicker">// context</p>
<h2 class="h2">问题:一个线程一个连接,<br>撑不住 10 万并发。</h2>
<div class="grid g3 mt-l">
<div class="card card-accent"><h4>Thread-per-conn</h4><p class="dim">每条连接一根 OS 线程,栈 28MB。10 万连接 = 几百 GB RAM。</p><span class="tag mt-s">❌ 不现实</span></div>
<div class="card card-accent"><h4>Event loop (C)</h4><p class="dim">epoll/kqueue + 回调地狱。快,但写起来痛苦且容易出 bug。</p><span class="tag mt-s">😩 callback hell</span></div>
<div class="card card-accent"><h4>Async / await</h4><p class="dim">看起来像同步代码,编译成状态机。一根线程跑几千任务。</p><span class="tag mt-s">✅ Rust 选这个</span></div>
</div>
</section>
<!-- 4. Deep dive 1 -->
<section class="slide" data-title="Deep Dive 1">
<p class="kicker">deep-dive · 1 / 2</p>
<h2 class="h2">Future 其实只有一个方法。</h2>
<div class="grid g2 mt-l" style="align-items:start">
<div>
<p class="lede">编译器把 <span class="mono">async fn</span> 变成一个实现了 <span class="mono">Future</span> trait 的匿名状态机。运行时只做一件事:反复 <span class="mono">poll</span> 它,直到返回 <span class="mono">Ready</span></p>
<div class="mt-l">
<span class="tag">Pending</span> <span class="tag">Ready(T)</span> <span class="tag">Waker.wake()</span>
</div>
</div>
<div class="terminal">
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>future.rs</span></div>
<pre><span class="kw">pub trait</span> <span class="fn">Future</span> {
<span class="kw">type</span> Output;
<span class="kw">fn</span> <span class="fn">poll</span>(
<span class="kw">self</span>: Pin&lt;&amp;<span class="kw">mut Self</span>&gt;,
cx: &amp;<span class="kw">mut</span> Context&lt;<span class="str">'_</span>&gt;,
) -&gt; Poll&lt;<span class="kw">Self</span>::Output&gt;;
}
<span class="cmt">// Poll::Pending → 挂起,等 waker 唤醒</span>
<span class="cmt">// Poll::Ready(v) → 完成,产出 v</span></pre>
</div>
</div>
</section>
<!-- 5. Deep dive 2 -->
<section class="slide" data-title="Deep Dive 2">
<p class="kicker">deep-dive · 2 / 2</p>
<h2 class="h2">Tokio 是一个偷任务的小工。</h2>
<div class="grid g2 mt-l" style="align-items:start">
<div>
<p class="lede">Multi-thread runtime = N 个 worker每个 worker 有自己的本地队列。空闲的 worker 会去别人队列里"偷"任务。</p>
<div class="stack mt-m">
<div class="tag">✦ local queue · 256 slots</div>
<div class="tag">✦ global injection queue</div>
<div class="tag">✦ work-stealing @ 50% steal ratio</div>
<div class="tag">✦ LIFO slot for cache locality</div>
</div>
</div>
<div class="card" style="padding:32px">
<h4 class="mono" style="color:var(--accent-2)">scheduler tick loop</h4>
<div class="stack mt-m" style="font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.9;color:var(--text-2)">
<div><span style="color:var(--accent)">1.</span> pop from LIFO slot</div>
<div><span style="color:var(--accent)">2.</span> else pop from local queue</div>
<div><span style="color:var(--accent)">3.</span> else drain global queue (every 61 ticks)</div>
<div><span style="color:var(--accent)">4.</span> else steal from random victim</div>
<div><span style="color:var(--accent)">5.</span> else park the thread</div>
</div>
</div>
</div>
</section>
<!-- 6. Code example -->
<section class="slide" data-title="Code">
<p class="kicker">mini-runtime.rs · ~40 LOC</p>
<h2 class="h2">手写一个最小 runtime。</h2>
<div class="terminal mt-m">
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>src/main.rs</span></div>
<pre><span class="kw">use</span> std::collections::VecDeque;
<span class="kw">use</span> std::sync::{Arc, Mutex};
<span class="kw">use</span> std::task::{Context, Poll, Wake, Waker};
<span class="kw">struct</span> Task(Mutex&lt;Pin&lt;Box&lt;<span class="kw">dyn</span> Future&lt;Output = ()&gt; + Send&gt;&gt;&gt;);
<span class="kw">impl</span> Wake <span class="kw">for</span> Task {
<span class="kw">fn</span> <span class="fn">wake</span>(<span class="kw">self</span>: Arc&lt;<span class="kw">Self</span>&gt;) { QUEUE.lock().unwrap().push_back(<span class="kw">self</span>); }
}
<span class="kw">fn</span> <span class="fn">block_on</span>&lt;F: Future&lt;Output = ()&gt; + Send + <span class="str">'static</span>&gt;(fut: F) {
<span class="fn">spawn</span>(fut);
<span class="kw">while let Some</span>(task) = QUEUE.lock().unwrap().pop_front() {
<span class="kw">let</span> waker = Waker::from(task.clone());
<span class="kw">let mut</span> cx = Context::from_waker(&amp;waker);
<span class="kw">let mut</span> fut = task.<span class="num">0</span>.lock().unwrap();
<span class="kw">let</span> _ = fut.as_mut().<span class="fn">poll</span>(&amp;<span class="kw">mut</span> cx); <span class="cmt">// 就是这一行</span>
}
}</pre>
</div>
</section>
<!-- 7. Takeaways -->
<section class="slide" data-title="Takeaways">
<p class="kicker">// takeaways</p>
<h2 class="h2">三件事带回去。</h2>
<div class="grid g3 mt-l">
<div class="card card-accent"><h4>1 · async 是零成本抽象</h4><p class="dim">编译成状态机,没有运行时虚表,没有 GC。</p></div>
<div class="card card-accent"><h4>2 · Waker 是脉搏</h4><p class="dim">Future 不主动做事,运行时靠 waker 决定"什么时候再 poll"。</p></div>
<div class="card card-accent"><h4>3 · 别在 async 里阻塞</h4><p class="dim">一行 <span class="mono">std::fs::read</span> 能让整个 worker 停摆。用 <span class="mono">spawn_blocking</span></p></div>
</div>
<p class="lede mt-l">延伸阅读:<span class="mono">tokio.rs/blog/2019-10-scheduler</span> · <span class="mono">rust-lang.github.io/async-book</span></p>
</section>
<!-- 8. Q&A -->
<section class="slide center tc" data-title="Q and A">
<div>
<div class="mono" style="font-size:120px;color:var(--accent);font-weight:800;letter-spacing:-.04em">?</div>
<h2 class="h2">Questions?</h2>
<p class="lede" style="margin:14px auto">github.com/lewis · @lewis on slack</p>
<div class="row mt-l" style="justify-content:center">
<span class="tag">slides: git.co/rt-deck</span>
<span class="tag">code: git.co/mini-rt</span>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,49 @@
/* tech-sharing — 技术分享 dark, code-forward */
.tpl-tech-sharing{
--bg:#0d1117;--bg-soft:#161b22;--surface:#161b22;--surface-2:#1c2230;
--border:rgba(139,148,158,.22);--border-strong:rgba(139,148,158,.4);
--text-1:#e6edf3;--text-2:#8b949e;--text-3:#6e7681;
--accent:#7ee787;--accent-2:#79c0ff;--accent-3:#ff7b72;
--grad:linear-gradient(120deg,#7ee787 0%,#79c0ff 60%,#d2a8ff 100%);
--radius:14px;--radius-lg:20px;
--shadow:0 20px 60px rgba(0,0,0,.5);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-tech-sharing{background:#0d1117;color:var(--text-1)}
.tpl-tech-sharing .slide{padding:72px 96px;background:#0d1117;color:var(--text-1)}
.tpl-tech-sharing .slide::before{content:"";position:absolute;inset:0;background:
radial-gradient(60% 50% at 90% 10%,rgba(121,192,255,.12),transparent 60%),
radial-gradient(50% 50% at 10% 90%,rgba(126,231,135,.08),transparent 60%);
pointer-events:none;z-index:0}
.tpl-tech-sharing .slide>*{position:relative;z-index:1}
.tpl-tech-sharing .h1{font-size:78px;line-height:1.03;font-weight:800;letter-spacing:-.03em;color:#fff}
.tpl-tech-sharing .h2{font-size:54px;font-weight:700;letter-spacing:-.025em;color:#fff}
.tpl-tech-sharing h3,.tpl-tech-sharing h4{color:#fff}
.tpl-tech-sharing .kicker{color:var(--accent);font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;text-transform:none;letter-spacing:.02em}
.tpl-tech-sharing .kicker::before{content:"> "}
.tpl-tech-sharing .mono{font-family:'JetBrains Mono','IBM Plex Mono',monospace}
.tpl-tech-sharing .terminal{background:#010409;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.6);font-family:'JetBrains Mono',monospace;font-size:15px;line-height:1.65}
.tpl-tech-sharing .terminal .bar{display:flex;align-items:center;gap:8px;padding:12px 16px;background:#161b22;border-bottom:1px solid var(--border);font-size:12px;color:var(--text-3)}
.tpl-tech-sharing .terminal .dot{width:12px;height:12px;border-radius:50%;background:#ff5f56}
.tpl-tech-sharing .terminal .dot:nth-child(2){background:#ffbd2e}
.tpl-tech-sharing .terminal .dot:nth-child(3){background:#27c93f}
.tpl-tech-sharing .terminal pre{margin:0;padding:24px 28px;color:#e6edf3;overflow:auto;max-height:440px}
.tpl-tech-sharing .kw{color:#ff7b72}
.tpl-tech-sharing .fn{color:#d2a8ff}
.tpl-tech-sharing .str{color:#a5d6ff}
.tpl-tech-sharing .cmt{color:#8b949e;font-style:italic}
.tpl-tech-sharing .num{color:#79c0ff}
.tpl-tech-sharing .card{background:var(--surface);border:1px solid var(--border);box-shadow:none}
.tpl-tech-sharing .card-accent{border-top:3px solid var(--accent)}
.tpl-tech-sharing .pill{background:var(--surface-2);color:var(--text-2);border-color:var(--border)}
.tpl-tech-sharing .pill-accent{background:rgba(126,231,135,.12);color:var(--accent);border-color:rgba(126,231,135,.35)}
.tpl-tech-sharing .tag{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:6px;font-family:'JetBrains Mono',monospace;font-size:12px;background:var(--surface-2);border:1px solid var(--border);color:var(--text-2)}
.tpl-tech-sharing .agenda-row{display:flex;align-items:baseline;gap:24px;padding:18px 0;border-bottom:1px dashed var(--border);font-family:'JetBrains Mono',monospace}
.tpl-tech-sharing .agenda-row .num{color:var(--accent);flex:none;width:48px}
.tpl-tech-sharing .agenda-row .t{color:#fff;font-size:24px;flex:1;font-family:'Inter',sans-serif;font-weight:600}
.tpl-tech-sharing .agenda-row .d{color:var(--text-3);font-size:13px}
.tpl-tech-sharing .speaker{display:flex;align-items:center;gap:14px;margin-top:28px}
.tpl-tech-sharing .speaker .av{width:56px;height:56px;border-radius:50%;background:var(--grad)}
.tpl-tech-sharing .speaker b{display:block;color:#fff;font-size:18px}
.tpl-tech-sharing .speaker span{color:var(--text-3);font-size:13px;font-family:'JetBrains Mono',monospace}
.tpl-tech-sharing .lede{color:var(--text-2)}

View File

@@ -0,0 +1,11 @@
# testing-safety-alert
白底 + 红琥珀警示色 + 条纹危险边 + 大红 strike 和 pill。灵感来自 `20260412-AI测试与安全/xhs-ai-testing-safety-v2.html``.focus` 黑底白字块、hero quote box 和高对比 black-on-white 气质 —— 但把语气推到「警示 / 风控 / 事故报告」层级。
**Visual traits:** 顶部 45° 红黑斜条纹警示带、底部副条纹、`strike-through` 红色斜切的否定大字、L1/L2/L3 三档色卡 (绿/琥珀/红)、圆形前置指示灯 alert-box、policy-yaml 深色代码块带红色左边框 + `bad` 关键词高亮、红/绿复选框 checklist、Q1 事故柱状图。
**Use when:** 讲安全 / 风控 / 事故复盘 / 红队测试 / AI 上线前评估 / policy as code你需要让观众立刻感到「这事严肃别马虎」。
**Source inspiration:** `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`.
**Path:** `templates/full-decks/testing-safety-alert/index.html`

View File

@@ -0,0 +1,183 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Testing Safety Alert</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-testing-safety-alert">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag">ai safety · 高优先级</span><span class="ts-page">01 / 08</span></div>
<div class="ts-kicker">2026 年最重要的一条判断</div>
<h1 class="ts-h1">别再追问<br><span class="strike">AI 会不会干活</span><br>开始问:<span class="red">它出事谁负责</span></h1>
<p class="ts-sub">AI 出错的代价,不再是一次 bad response 这么简单 —— 它可能一次性写 300 份工单、提 80 个 PR、发 5000 封邮件。</p>
<div class="ts-alert-box">
<h3>风险已经规模化</h3>
<p>「做错」成本 × N「做对」收益 × N。<br>这就是为什么 <b>测试、验收、安全、风控</b> 会变成未来 3 年最贵的能力。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>AI SAFETY BRIEF · LEWIS · 2026.04</span><span>01 / 08</span></div>
</section>
<!-- 2. SECTION -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag amber">section · risk 分级</span><span class="ts-page">02 / 08</span></div>
<div style="margin:auto 0">
<div class="ts-kicker">Chapter One</div>
<h1 class="ts-h1" style="font-size:130px">先分 <span class="red">等级</span></h1>
<p class="ts-sub" style="font-size:28px">不是所有 AI 行为都同等危险。<br>先把「可撤销」和「不可撤销」分开,再谈流程。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>section · level taxonomy</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT risk levels -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag">风险分级 · 3 levels</span><span class="ts-page">03 / 08</span></div>
<h2 class="ts-h2">三档风险,三种处理</h2>
<div class="ts-grid-3">
<div class="ts-card" style="border-top:4px solid var(--ts-green)"><div class="lbl">L1 · 绿色</div><h4>可撤销</h4><p>写 draft、生成图片、起草文档。<br>错了 Ctrl+Z零代价。<br><b style="color:var(--ts-green)">策略:放开跑</b></p></div>
<div class="ts-card" style="border-top:4px solid var(--ts-amber)"><div class="lbl">L2 · 琥珀</div><h4>半可撤销</h4><p>发 draft 邮件、提 PR、改 staging 数据。<br>错了要道歉 / 回滚。<br><b style="color:var(--ts-amber)">策略:人工复核</b></p></div>
<div class="ts-card" style="border-top:4px solid var(--ts-red)"><div class="lbl">L3 · 红色</div><h4>不可撤销</h4><p>发真实邮件、付款、删库、删 prod 数据。<br>错了就真错了。<br><b style="color:var(--ts-red)">策略:硬卡 + 双人审</b></p></div>
</div>
<div class="ts-alert-box amber">
<h3>绝不要让 agent 自己升级</h3>
<p>L1 的任务不能自己变成 L2。授权必须是显式的、可撤销的、带过期时间的。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>risk · 3 levels</span><span>03 / 08</span></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag">policy as code</span><span class="ts-page">04 / 08</span></div>
<div class="ts-kicker">别用文档管规则 · 用代码管规则</div>
<h2 class="ts-h2">三十行 YAML<br><span class="ts-highlight-red">红线硬卡</span></h2>
<pre class="ts-codebox"><span class="cm"># safety-policy.yaml · compiled → runtime guard</span>
<span class="kw">level_1_allow</span>:
- tools: [<span class="st">write_draft</span>, <span class="st">generate_image</span>, <span class="st">read_docs</span>]
<span class="kw">level_2_require_review</span>:
- tools: [<span class="st">send_email_draft</span>, <span class="st">open_pr</span>, <span class="st">write_staging_db</span>]
reviewer: <span class="st">human</span>
<span class="kw">level_3_hard_block</span>:
- tools: [<span class="st">send_real_email</span>, <span class="st">transfer_money</span>, <span class="st">delete_prod</span>]
unless: <span class="st">two_human_sign_off AND within_24h</span>
<span class="bad">forbidden_always</span>:
- <span class="bad">"r&#109; &#45;rf /"</span>
- <span class="bad">"dr&#111;p table"</span>
- <span class="bad">"force push &#111;rigin main"</span></pre>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>policy · yaml-as-guard</span><span>04 / 08</span></div>
</section>
<!-- 5. CHART -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag amber">incident report · q1</span><span class="ts-page">05 / 08</span></div>
<h2 class="ts-h2">我们 Q1 的 <span class="red">12 起 AI 事故</span></h2>
<p class="ts-sub">幸好全部捕获在 staging。但每一起都能上生产。</p>
<svg viewBox="0 0 1040 360" style="width:100%;max-width:1040px;margin-top:18px" xmlns="http://www.w3.org/2000/svg">
<g font-family="Inter,sans-serif" font-size="14" fill="#4a4955">
<line x1="70" y1="320" x2="1000" y2="320" stroke="#eaecf3" stroke-width="2"/>
<!-- month columns: Jan Feb Mar, L1/L2/L3 stacked -->
<g transform="translate(120,0)">
<rect x="0" y="220" width="60" height="100" fill="#067647"/>
<rect x="0" y="160" width="60" height="60" fill="#d97706"/>
<rect x="0" y="130" width="60" height="30" fill="#e0314a"/>
<text x="30" y="345" text-anchor="middle" font-weight="700">Jan</text>
<text x="30" y="120" text-anchor="middle" font-weight="800" fill="#14141a">5</text>
</g>
<g transform="translate(320,0)">
<rect x="0" y="240" width="60" height="80" fill="#067647"/>
<rect x="0" y="200" width="60" height="40" fill="#d97706"/>
<rect x="0" y="180" width="60" height="20" fill="#e0314a"/>
<text x="30" y="345" text-anchor="middle" font-weight="700">Feb</text>
<text x="30" y="170" text-anchor="middle" font-weight="800" fill="#14141a">3</text>
</g>
<g transform="translate(520,0)">
<rect x="0" y="250" width="60" height="70" fill="#067647"/>
<rect x="0" y="220" width="60" height="30" fill="#d97706"/>
<rect x="0" y="210" width="60" height="10" fill="#e0314a"/>
<text x="30" y="345" text-anchor="middle" font-weight="700">Mar</text>
<text x="30" y="200" text-anchor="middle" font-weight="800" fill="#14141a">4</text>
</g>
<!-- legend -->
<g transform="translate(720,60)">
<rect x="0" y="0" width="16" height="16" fill="#e0314a"/><text x="24" y="13" font-weight="700">L3 不可撤销 (3)</text>
<rect x="0" y="26" width="16" height="16" fill="#d97706"/><text x="24" y="39" font-weight="700">L2 需复核 (4)</text>
<rect x="0" y="52" width="16" height="16" fill="#067647"/><text x="24" y="65" font-weight="700">L1 可恢复 (5)</text>
<text x="0" y="100" font-size="13" fill="#8a8892">全部被 safety-policy 在 runtime 拦下,</text>
<text x="0" y="118" font-size="13" fill="#8a8892">未进 prod。但 3 起 L3 非常惊险。</text>
</g>
</g>
</svg>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>incident · q1 summary</span><span>05 / 08</span></div>
</section>
<!-- 6. CHECKLIST -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag green">red-team checklist</span><span class="ts-page">06 / 08</span></div>
<h2 class="ts-h2">上线前 <span class="red">必过 7 道题</span></h2>
<div class="ts-checklist">
<div class="ts-check ok"><div class="box"></div><div class="txt">它能删除东西吗?有人类 review 吗?能 60 秒内回滚吗?</div></div>
<div class="ts-check ok"><div class="box"></div><div class="txt">它的 prompt 注入能让它越权吗?(跑过红队提示词)</div></div>
<div class="ts-check"><div class="box">!</div><div class="txt">它处理 PII 吗?日志里是不是也有 PII</div></div>
<div class="ts-check ok"><div class="box"></div><div class="txt">上下游失败时,它会不会开始乱改其他资源?</div></div>
<div class="ts-check"><div class="box">!</div><div class="txt">并发 100 个 agent 一起跑会不会死锁?</div></div>
<div class="ts-check ok"><div class="box"></div><div class="txt">错了能不能 <b>立刻</b>kill switch 能 2 秒内生效吗)</div></div>
<div class="ts-check"><div class="box">!</div><div class="txt">出事时有没有人值班?值班手册有没有 agent 专属章节?</div></div>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>checklist · pre-launch</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag green">今晚就能动</span><span class="ts-page">07 / 08</span></div>
<h2 class="ts-h2">今晚先做 <span class="ts-highlight-red">三件事</span></h2>
<div class="ts-grid-3">
<div class="ts-card"><div class="lbl">1 · 分级</div><h4>给你的 agent<br>写 L1/L2/L3</h4><p>把所有工具列出来,标上等级。不标的一律按 L3。</p></div>
<div class="ts-card"><div class="lbl">2 · 写 policy</div><h4>policy.yaml<br>接 runtime</h4><p>不要信 prompt 里的 "be careful",要信执行层的硬卡。</p></div>
<div class="ts-card"><div class="lbl">3 · kill switch</div><h4>红按钮<br>能在 2 秒内停</h4><p>CTO / on-call 都得知道怎么按。演练一次。</p></div>
</div>
<div class="ts-alert-box green">
<h3>真正的安全不是 prompt是流程</h3>
<p>prompt 会被注入,流程不会。—— 把保护放在不可被说服的一层。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>cta · tonight</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag amber">please stay safe</span><span class="ts-page">08 / 08</span></div>
<div style="margin:auto 0">
<div class="ts-kicker">end of brief</div>
<h1 class="ts-h1" style="font-size:140px">谢谢 <span class="red">·</span> thanks</h1>
<p class="ts-sub" style="font-size:24px">policy.yaml 模板、红队 prompt 清单、事故复盘模板 —— 评论区扣「安全」。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>end of brief</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,62 @@
/* testing-safety-alert — 红/琥珀 警示风 · 白底高对比 */
.tpl-testing-safety-alert{
--ts-bg:#fffaf7;
--ts-ink:#14141a;
--ts-ink2:#4a4955;
--ts-muted:#8a8892;
--ts-line:rgba(20,20,26,.08);
--ts-red:#e0314a;
--ts-red-soft:#ffecee;
--ts-amber:#d97706;
--ts-amber-soft:#fff5e6;
--ts-green:#067647;
--ts-green-soft:#e8f8ee;
background:var(--ts-bg);
color:var(--ts-ink);
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
}
.tpl-testing-safety-alert .slide{background:var(--ts-bg);color:var(--ts-ink);padding:64px 84px}
.tpl-testing-safety-alert .ts-stripe{position:absolute;top:0;left:0;right:0;height:14px;background:repeating-linear-gradient(45deg,var(--ts-red) 0 18px,#111318 18px 36px)}
.tpl-testing-safety-alert .ts-stripe-b{position:absolute;bottom:0;left:0;right:0;height:6px;background:repeating-linear-gradient(45deg,var(--ts-red) 0 10px,#111318 10px 20px);opacity:.6}
.tpl-testing-safety-alert .ts-chrome{display:flex;justify-content:space-between;align-items:center;margin:22px 0 16px}
.tpl-testing-safety-alert .ts-alert-tag{display:inline-flex;align-items:center;gap:10px;padding:8px 18px;border-radius:10px;font-size:13px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;background:var(--ts-red);color:#fff;box-shadow:0 6px 18px rgba(224,49,74,.28)}
.tpl-testing-safety-alert .ts-alert-tag::before{content:'⚠';font-size:16px}
.tpl-testing-safety-alert .ts-alert-tag.amber{background:var(--ts-amber);box-shadow:0 6px 18px rgba(217,119,6,.25)}
.tpl-testing-safety-alert .ts-alert-tag.green{background:var(--ts-green);box-shadow:0 6px 18px rgba(6,118,71,.22)}
.tpl-testing-safety-alert .ts-alert-tag.green::before{content:'✓'}
.tpl-testing-safety-alert .ts-page{font-size:13px;color:var(--ts-muted);letter-spacing:.15em;font-weight:700}
.tpl-testing-safety-alert .ts-kicker{font-size:15px;font-weight:700;color:var(--ts-red);letter-spacing:.06em;margin-bottom:10px;text-transform:uppercase}
.tpl-testing-safety-alert .ts-h1{font-size:88px;font-weight:900;line-height:1.04;letter-spacing:-2px;margin:10px 0 16px;color:var(--ts-ink)}
.tpl-testing-safety-alert .ts-h1 .red{color:var(--ts-red)}
.tpl-testing-safety-alert .ts-h1 .strike{position:relative;display:inline-block}
.tpl-testing-safety-alert .ts-h1 .strike::after{content:'';position:absolute;left:-4%;right:-4%;top:50%;height:10px;background:var(--ts-red);transform:skewX(-12deg);opacity:.85}
.tpl-testing-safety-alert .ts-h2{font-size:54px;font-weight:900;line-height:1.1;letter-spacing:-1px;margin:0 0 14px}
.tpl-testing-safety-alert .ts-sub{font-size:22px;line-height:1.5;color:var(--ts-ink2);max-width:880px;margin-top:10px}
.tpl-testing-safety-alert .ts-highlight-red{display:inline-block;padding:4px 14px;background:var(--ts-red);color:#fff;border-radius:8px;font-weight:800}
.tpl-testing-safety-alert .ts-highlight-amber{display:inline-block;padding:4px 14px;background:var(--ts-amber-soft);color:var(--ts-amber);border-radius:8px;font-weight:800;border:1px solid rgba(217,119,6,.2)}
.tpl-testing-safety-alert .ts-highlight-green{display:inline-block;padding:4px 14px;background:var(--ts-green-soft);color:var(--ts-green);border-radius:8px;font-weight:800;border:1px solid rgba(6,118,71,.2)}
.tpl-testing-safety-alert .ts-alert-box{border:2px solid var(--ts-red);border-radius:18px;padding:26px 30px;background:linear-gradient(180deg,#fff 0%,var(--ts-red-soft) 100%);box-shadow:0 14px 36px rgba(224,49,74,.14);margin-top:24px;position:relative}
.tpl-testing-safety-alert .ts-alert-box::before{content:'';position:absolute;top:-11px;left:24px;width:22px;height:22px;background:var(--ts-red);border-radius:50%;box-shadow:0 0 0 6px rgba(224,49,74,.2)}
.tpl-testing-safety-alert .ts-alert-box.amber{border-color:var(--ts-amber);background:linear-gradient(180deg,#fff 0%,var(--ts-amber-soft) 100%);box-shadow:0 14px 36px rgba(217,119,6,.14)}
.tpl-testing-safety-alert .ts-alert-box.amber::before{background:var(--ts-amber);box-shadow:0 0 0 6px rgba(217,119,6,.2)}
.tpl-testing-safety-alert .ts-alert-box.green{border-color:var(--ts-green);background:linear-gradient(180deg,#fff 0%,var(--ts-green-soft) 100%);box-shadow:0 14px 36px rgba(6,118,71,.14)}
.tpl-testing-safety-alert .ts-alert-box.green::before{background:var(--ts-green);box-shadow:0 0 0 6px rgba(6,118,71,.2)}
.tpl-testing-safety-alert .ts-alert-box h3{font-size:34px;font-weight:900;margin:0 0 10px}
.tpl-testing-safety-alert .ts-alert-box p{font-size:17px;line-height:1.6;color:var(--ts-ink2);margin:0}
.tpl-testing-safety-alert .ts-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px}
.tpl-testing-safety-alert .ts-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:20px}
.tpl-testing-safety-alert .ts-card{border:1px solid var(--ts-line);border-radius:16px;padding:22px 24px;background:#fff;box-shadow:0 6px 20px rgba(17,19,24,.04)}
.tpl-testing-safety-alert .ts-card .lbl{font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--ts-muted);margin-bottom:8px}
.tpl-testing-safety-alert .ts-card h4{font-size:26px;font-weight:900;line-height:1.2;margin-bottom:8px}
.tpl-testing-safety-alert .ts-card p{font-size:14px;color:var(--ts-ink2);line-height:1.55}
.tpl-testing-safety-alert .ts-checklist{display:flex;flex-direction:column;gap:12px;margin-top:20px;max-width:880px}
.tpl-testing-safety-alert .ts-check{display:flex;gap:16px;align-items:flex-start;padding:16px 20px;border:1px solid var(--ts-line);border-radius:14px;background:#fff}
.tpl-testing-safety-alert .ts-check .box{flex:0 0 32px;height:32px;border-radius:8px;border:2px solid var(--ts-red);display:grid;place-items:center;font-weight:900;color:var(--ts-red);background:var(--ts-red-soft)}
.tpl-testing-safety-alert .ts-check.ok .box{border-color:var(--ts-green);color:var(--ts-green);background:var(--ts-green-soft)}
.tpl-testing-safety-alert .ts-check .txt{font-size:18px;line-height:1.5;font-weight:600}
.tpl-testing-safety-alert .ts-codebox{background:#141418;color:#fff5ea;border-radius:14px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;margin-top:20px;border-left:6px solid var(--ts-red)}
.tpl-testing-safety-alert .ts-codebox .cm{color:#7a756d}
.tpl-testing-safety-alert .ts-codebox .kw{color:#ffb38a}
.tpl-testing-safety-alert .ts-codebox .st{color:#b3e6c2}
.tpl-testing-safety-alert .ts-codebox .bad{color:#ff9aa8;font-weight:700}
.tpl-testing-safety-alert .ts-footer{position:absolute;left:84px;right:84px;bottom:36px;display:flex;justify-content:space-between;font-size:12px;color:var(--ts-muted);letter-spacing:.1em}

View File

@@ -0,0 +1,8 @@
# weekly-report · 周报
7-slide team weekly report: cover (week range), KPI grid, shipped items, a metric trend chart, blockers, next-week plan, thanks.
Corporate-clarity palette: near-white background, blue→teal accent, ruled dividers and tiny mono tags (`FEAT`, `FIX`, `EXP`, `INFRA`). Data-dense, readable at a glance, and easy to skim in a standup.
**Use when:** team weekly readouts, squad reviews, skip-level updates, cross-team "what shipped this week" mails.
**Feel:** Linear changelog meets a McKinsey KPI deck — serious, measured, actionable.

View File

@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Growth Squad · Weekly W15</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-weekly-report">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<div class="cover-head">
<div class="logo">Growth Squad</div>
<div class="week-chip">W15 · 2026-04-07 → 2026-04-13</div>
</div>
<p class="kicker">WEEKLY REPORT</p>
<h1 class="h1 mt-s">本周:付费转化率<br>回到了 <span style="color:var(--accent)">3.8%</span></h1>
<p class="lede mt-m">6 个发布3 个实验收敛1 个阻塞项升级。整体健康。</p>
<div class="deck-footer"><span>Prepared by @lewis · reviewed by @may</span><span class="slide-number" data-current="1" data-total="7"></span></div>
</section>
<!-- 2. KPI -->
<section class="slide" data-title="KPIs">
<p class="kicker">HIGHLIGHTS · KPIs</p>
<h2 class="h2">本周核心指标</h2>
<div class="grid g4 mt-l">
<div class="kpi good"><div class="label">Paid conv.</div><div class="value">3.82%</div><div class="delta up">▲ +0.4 pts WoW</div></div>
<div class="kpi good"><div class="label">MRR</div><div class="value">$148k</div><div class="delta up">▲ +6.1%</div></div>
<div class="kpi"><div class="label">Signups</div><div class="value">12,430</div><div class="delta flat">— +0.3%</div></div>
<div class="kpi bad"><div class="label">D7 retention</div><div class="value">41%</div><div class="delta down">▼ -1.8 pts</div></div>
<div class="kpi good"><div class="label">NPS</div><div class="value">64</div><div class="delta up">▲ +3</div></div>
<div class="kpi"><div class="label">Support tickets</div><div class="value">318</div><div class="delta flat">— -12</div></div>
<div class="kpi warn"><div class="label">p95 latency</div><div class="value">412ms</div><div class="delta down">▼ +38ms</div></div>
<div class="kpi good"><div class="label">Deploys</div><div class="value">37</div><div class="delta up">▲ +9</div></div>
</div>
</section>
<!-- 3. Shipped -->
<section class="slide" data-title="Shipped">
<p class="kicker">SHIPPED THIS WEEK · 6 items</p>
<h2 class="h2">Shipped</h2>
<div class="mt-l" style="max-width:980px">
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>New onboarding checklist v3</b><p class="dim" style="font-size:13px;margin:2px 0 0">4-step checklist replaces the old 7-step modal. A/B won +18% activation.</p></div><span class="owner">@may</span></div>
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>Stripe Tax auto-filing</b><p class="dim" style="font-size:13px;margin:2px 0 0">Quarterly filings now handled for 12 US states via Stripe Tax API.</p></div><span class="owner">@raj</span></div>
<div class="ship-item"><span class="tag exp">EXP</span><div><b>Pricing page hero test</b><p class="dim" style="font-size:13px;margin:2px 0 0">"From $29" vs "Free trial" headline. Free-trial wins +22% click-through.</p></div><span class="owner">@lewis</span></div>
<div class="ship-item"><span class="tag fix">FIX</span><div><b>Edge case in SSO redirect</b><p class="dim" style="font-size:13px;margin:2px 0 0">Google Workspace users with custom domains now land on the correct workspace.</p></div><span class="owner">@eli</span></div>
<div class="ship-item"><span class="tag infra">INFRA</span><div><b>Postgres 16 upgrade</b><p class="dim" style="font-size:13px;margin:2px 0 0">Zero-downtime migration. Query p50 down 14%, p95 down 9%.</p></div><span class="owner">@raj</span></div>
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>Referral rewards v1</b><p class="dim" style="font-size:13px;margin:2px 0 0">Both sides get 1 month free. Dashboard + email flow live behind flag.</p></div><span class="owner">@may</span></div>
</div>
</section>
<!-- 4. Metrics chart -->
<section class="slide" data-title="Metrics">
<p class="kicker">METRIC DEEP-DIVE</p>
<h2 class="h2">Paid conversion, last 8 weeks</h2>
<div class="chart mt-l">
<div class="row" style="justify-content:space-between"><h4>Paid conv. rate · weekly</h4><span class="pill" style="background:var(--surface-2);color:var(--text-2)">target: 4.0%</span></div>
<div class="chart-bars">
<div class="col"><div class="b" data-v="3.1%" style="height:58%"></div><div class="lbl">W08</div></div>
<div class="col"><div class="b" data-v="3.3%" style="height:64%"></div><div class="lbl">W09</div></div>
<div class="col"><div class="b" data-v="3.5%" style="height:72%"></div><div class="lbl">W10</div></div>
<div class="col"><div class="b" data-v="3.6%" style="height:75%"></div><div class="lbl">W11</div></div>
<div class="col"><div class="b" data-v="3.4%" style="height:68%"></div><div class="lbl">W12</div></div>
<div class="col"><div class="b" data-v="3.0%" style="height:55%"></div><div class="lbl">W13</div></div>
<div class="col"><div class="b" data-v="3.4%" style="height:68%"></div><div class="lbl">W14</div></div>
<div class="col"><div class="b" data-v="3.8%" style="height:88%"></div><div class="lbl">W15</div></div>
</div>
<p class="dim mt-m" style="font-size:13px;margin-top:36px">Drop in W13 tracked to a broken Stripe webhook (fixed W14). Rebound in W15 is driven by the new onboarding checklist.</p>
</div>
</section>
<!-- 5. Blockers -->
<section class="slide" data-title="Blockers">
<p class="kicker">BLOCKERS · 3 items</p>
<h2 class="h2">Needs attention</h2>
<div class="mt-l" style="max-width:900px">
<div class="blocker">
<h4>p95 latency regressed to 412ms (+38ms)</h4>
<p>Traced to the new recommender service under load. Adding caching layer + connection pooling.</p>
<div class="meta">owner: @raj · ETA: W16 Wed · severity: medium</div>
</div>
<div class="blocker">
<h4>Apple Pay disabled in EU for 3 days</h4>
<p>Stripe credential rotation wasn't synced to the EU account. Fixed, but cost ~$4.2k in lost checkouts.</p>
<div class="meta">owner: @eli · severity: high · postmortem in progress</div>
</div>
<div class="blocker">
<h4>D7 retention down 1.8 points</h4>
<p>Cohort analysis shows it's isolated to the free-trial pricing test. Need to decide: kill test, or push through W16.</p>
<div class="meta">owner: @lewis · needs decision from @may by Monday</div>
</div>
</div>
</section>
<!-- 6. Next week -->
<section class="slide" data-title="Next Week">
<p class="kicker">NEXT WEEK · W16 plan</p>
<h2 class="h2">下周重点</h2>
<div class="mt-l" style="max-width:960px">
<div class="next-row"><div class="owner">@raj</div><div class="task"><b>Ship recommender cache layer</b><span>blocker · must land Wed</span></div></div>
<div class="next-row"><div class="owner">@may</div><div class="task"><b>Referral rewards · flag rollout to 100%</b><span>milestone · targets +3% WoW signups</span></div></div>
<div class="next-row"><div class="owner">@lewis</div><div class="task"><b>Pricing test: decision doc + readout</b><span>deadline Mon noon</span></div></div>
<div class="next-row"><div class="owner">@eli</div><div class="task"><b>Apple Pay postmortem + runbook update</b><span>include in W16 eng review</span></div></div>
<div class="next-row"><div class="owner">squad</div><div class="task"><b>Q2 OKR planning offsite</b><span>Thu 25pm · async pre-reads Wed</span></div></div>
</div>
</section>
<!-- 7. Thanks -->
<section class="slide center tc" data-title="Thanks">
<div>
<p class="kicker">FIN · week 15</p>
<h1 class="h1" style="font-size:100px">Thanks, team 🫶</h1>
<p class="lede" style="margin:16px auto">Solid week. Rebound earned, not luck.</p>
<div class="row mt-l" style="justify-content:center;gap:16px">
<span class="week-chip">Next report: Mon W16</span>
<span class="week-chip">questions → #growth-squad</span>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,55 @@
/* weekly-report — corporate clarity */
.tpl-weekly-report{
--bg:#fafbfc;--bg-soft:#f3f5f9;--surface:#ffffff;--surface-2:#f3f5f9;
--border:rgba(22,30,55,.09);--border-strong:rgba(22,30,55,.2);
--text-1:#161e37;--text-2:#50586b;--text-3:#8b92a5;
--accent:#2e63eb;--accent-2:#0ea5b5;--accent-3:#f59e0b;
--good:#10b981;--warn:#f59e0b;--bad:#ef4444;
--grad:linear-gradient(120deg,#2e63eb,#0ea5b5);
--radius:14px;--radius-lg:18px;
--shadow:0 6px 20px rgba(22,30,55,.06),0 1px 3px rgba(22,30,55,.04);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-weekly-report .slide{padding:64px 88px;background:var(--bg)}
.tpl-weekly-report .h1{font-size:64px;line-height:1.05;font-weight:800;letter-spacing:-.025em}
.tpl-weekly-report .h2{font-size:42px;font-weight:700;letter-spacing:-.02em}
.tpl-weekly-report .kicker{color:var(--accent);font-size:12px;font-weight:700}
.tpl-weekly-report .cover-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:48px}
.tpl-weekly-report .logo{font-weight:800;font-size:18px;letter-spacing:-.01em}
.tpl-weekly-report .logo::before{content:"■";color:var(--accent);margin-right:8px}
.tpl-weekly-report .week-chip{display:inline-block;padding:8px 18px;border-radius:8px;background:var(--surface);border:1px solid var(--border);font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--text-2)}
.tpl-weekly-report .kpi{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px 26px;position:relative;overflow:hidden}
.tpl-weekly-report .kpi .label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-3);font-weight:600}
.tpl-weekly-report .kpi .value{font-size:48px;font-weight:800;letter-spacing:-.03em;margin-top:8px;line-height:1}
.tpl-weekly-report .kpi .delta{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:6px;font-size:12px;font-weight:700;margin-top:10px}
.tpl-weekly-report .kpi .delta.up{background:rgba(16,185,129,.12);color:var(--good)}
.tpl-weekly-report .kpi .delta.down{background:rgba(239,68,68,.12);color:var(--bad)}
.tpl-weekly-report .kpi .delta.flat{background:rgba(139,146,165,.14);color:var(--text-2)}
.tpl-weekly-report .kpi::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent)}
.tpl-weekly-report .kpi.good::before{background:var(--good)}
.tpl-weekly-report .kpi.warn::before{background:var(--warn)}
.tpl-weekly-report .kpi.bad::before{background:var(--bad)}
.tpl-weekly-report .ship-item{display:flex;gap:14px;padding:14px 0;border-bottom:1px solid var(--border)}
.tpl-weekly-report .ship-item .tag{flex:none;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;height:22px;display:inline-flex;align-items:center}
.tpl-weekly-report .tag.feat{background:rgba(46,99,235,.12);color:var(--accent)}
.tpl-weekly-report .tag.fix{background:rgba(16,185,129,.12);color:var(--good)}
.tpl-weekly-report .tag.exp{background:rgba(245,158,11,.14);color:var(--warn)}
.tpl-weekly-report .tag.infra{background:rgba(14,165,181,.12);color:var(--accent-2)}
.tpl-weekly-report .ship-item b{color:var(--text-1);font-weight:600}
.tpl-weekly-report .ship-item span.owner{margin-left:auto;color:var(--text-3);font-size:12px;font-family:'JetBrains Mono',monospace}
.tpl-weekly-report .chart{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px}
.tpl-weekly-report .chart-bars{display:flex;align-items:flex-end;gap:16px;height:220px;margin-top:20px}
.tpl-weekly-report .chart-bars .col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative}
.tpl-weekly-report .chart-bars .col .b{width:100%;background:var(--grad);border-radius:6px 6px 0 0;min-height:6px;position:relative}
.tpl-weekly-report .chart-bars .col .b::after{content:attr(data-v);position:absolute;top:-22px;left:0;right:0;text-align:center;font-size:12px;font-weight:700;color:var(--text-1)}
.tpl-weekly-report .chart-bars .col .lbl{font-size:11px;color:var(--text-3);font-family:'JetBrains Mono',monospace}
.tpl-weekly-report .blocker{background:var(--surface);border-left:3px solid var(--bad);padding:16px 20px;border-radius:8px;margin-bottom:12px}
.tpl-weekly-report .blocker h4{font-size:16px;margin-bottom:4px}
.tpl-weekly-report .blocker p{font-size:13px;color:var(--text-2);margin:0}
.tpl-weekly-report .blocker .meta{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-3);margin-top:6px}
.tpl-weekly-report .next-row{display:grid;grid-template-columns:110px 1fr;gap:16px;padding:14px 0;border-bottom:1px dashed var(--border);align-items:baseline}
.tpl-weekly-report .next-row .owner{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--accent)}
.tpl-weekly-report .next-row .task{color:var(--text-1);font-weight:500}
.tpl-weekly-report .next-row .task span{color:var(--text-3);font-size:12px;margin-left:8px}
.tpl-weekly-report .lede{color:var(--text-2)}
.tpl-weekly-report .card{background:var(--surface)}

View File

@@ -0,0 +1,11 @@
# xhs-pastel-card
暖奶油 `#fef8f1` 底 + 模糊彩色 blob + Playfair italic 衬线大字 + 整色马卡龙卡片(桃 / 薄荷 / 天 / 丁香 / 柠檬 / 玫瑰)。共性提取自 `20260412-obsidian-skills/html/xhs-obsidian-skills.html``soft-purple/pink/blue/green/orange/teal` 软色卡系统,以及 `20260409 v2-白底版` 的胶囊 chip 顶部条。
**Visual traits:** 三颗柔光 blob 作背景、顶部 chip+page 组合、Playfair italic 做 accent 词em / rose / mint、整色圆角 28px 大卡片、italic Playfair 序号 01-04、donut SVG 图、小 divider 条 + 渐变、衬线正文做标题 / sans 做正文混排。
**Use when:** 生活方式 / 个人成长 / 轻内容 / 情感向的小红书贴或个人演讲;你想要一种「不那么科技感、偏杂志偏手作」的气质;适合讲「慢」「休息」「温柔」主题。
**Source inspiration:** `20260412-obsidian-skills/html/xhs-obsidian-skills.html` + `20260409` v2-白底版(共性 pastel 系统)。
**Path:** `templates/full-decks/xhs-pastel-card/index.html`

View File

@@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>XHS Pastel Card</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-xhs-pastel-card">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="xp-blob b1"></div>
<div class="xp-blob b2"></div>
<div class="xp-blob b3"></div>
<div class="xp-topbar"><div class="xp-chip">A soft manifesto</div><div class="xp-page">01 · 08</div></div>
<div class="xp-kicker">Living With AI · 2026</div>
<h1 class="xp-h1">放慢一点,<br><em>AI</em> 帮你<br>过一种 <span class="rose">更温柔</span><br>的生活</h1>
<div class="xp-divider"></div>
<p class="xp-sub">这不是一份效率指南。这是一份「怎么用 AI 少做一些事」的清单 —— 把挤出来的 4 小时还给你自己。</p>
<div class="xp-footer"><span>by lewis · pastel edition</span><span>cover</span></div>
</section>
<!-- 2. SECTION -->
<section class="slide">
<div class="xp-blob b2"></div>
<div class="xp-blob b3"></div>
<div class="xp-topbar"><div class="xp-chip mint">Chapter one</div><div class="xp-page">02 · 08</div></div>
<div style="margin:auto 0">
<div class="xp-kicker">先问自己</div>
<h1 class="xp-h1" style="font-size:120px">什么事<br>是你 <span class="mint">其实不想做</span> 的?</h1>
<p class="xp-sub">不是「不得不做」,是「做的时候灵魂在叹气」。</p>
</div>
<div class="xp-footer"><span>section · chapter 1</span><span>02 · 08</span></div>
</section>
<!-- 3. CONTENT 2x2 pastel cards -->
<section class="slide">
<div class="xp-blob b1"></div>
<div class="xp-topbar"><div class="xp-chip rose">Four little escapes</div><div class="xp-page">03 · 08</div></div>
<h2 class="xp-h2">四件可以<br>完全交给 <em>AI</em> 的小事</h2>
<div class="xp-grid-2">
<div class="xp-card peach"><div class="xp-num">01</div><h4>回复那种「收到」邮件</h4><p>它们不需要你思考。让 AI 按你的语气自动处理,一周省 40 分钟。</p></div>
<div class="xp-card mint"><div class="xp-num">02</div><h4>订餐厅、改签、查路线</h4><p>一句话外包出去。你只负责选最后选项,不负责翻十个 app。</p></div>
<div class="xp-card sky"><div class="xp-num">03</div><h4>把会议录音变成行动项</h4><p>录音 → 摘要 → todo 一键完成。你只需要确认和签字。</p></div>
<div class="xp-card lilac"><div class="xp-num">04</div><h4>整理上周拍的 300 张照片</h4><p>按事件分类、挑 10 张精选、写图说。整理档案这件事终于被自动化了。</p></div>
</div>
<div class="xp-footer"><span>content · 2x2</span><span>03 · 08</span></div>
</section>
<!-- 4. QUOTE -->
<section class="slide">
<div class="xp-blob b3"></div>
<div class="xp-blob b2"></div>
<div class="xp-topbar"><div class="xp-chip lilac">A small pause</div><div class="xp-page">04 · 08</div></div>
<div class="xp-hero-card">
<p class="xp-quote">效率工具的终点,不是<em> 做更多</em><br>而是 <em>有资格做更少</em></p>
<div class="xp-divider"></div>
<p class="xp-sub">当你把「收到」邮件、订餐、行程、照片整理都交出去,你才会惊讶地发现 —— 原来一周有 4 个小时是空的。</p>
</div>
<div class="xp-footer"><span>quote</span><span>04 · 08</span></div>
</section>
<!-- 5. CODE / PROMPT -->
<section class="slide">
<div class="xp-blob b1"></div>
<div class="xp-topbar"><div class="xp-chip">My auto-reply prompt</div><div class="xp-page">05 · 08</div></div>
<h2 class="xp-h2">把「<em>收到邮件</em><br>自动化的 <span class="rose">一段 prompt</span></h2>
<pre class="xp-codebox"><span class="cm"># auto-reply skill</span>
<span class="kw">when</span> email matches <span class="st">"收到 / 好的 / 确认 / 收到谢谢"</span>:
reply:
tone: <span class="st">"温柔,简短,不要太商业"</span>
max_lines: <span class="hl">2</span>
sign_with: <span class="st">"— Lewis"</span>
<span class="kw">always_skip</span>:
- from: [<span class="st">"家人"</span>, <span class="st">"伴侣"</span>, <span class="st">"亲密朋友"</span>]
- contains: [<span class="st">"紧急"</span>, <span class="st">"合同"</span>, <span class="st">"付款"</span>]
<span class="cm"># 一周省 38 分钟,测过</span></pre>
<div class="xp-footer"><span>content · prompt</span><span>05 · 08</span></div>
</section>
<!-- 6. CHART — time donut -->
<section class="slide">
<div class="xp-blob b2"></div>
<div class="xp-topbar"><div class="xp-chip mint">Your week, rebuilt</div><div class="xp-page">06 · 08</div></div>
<h2 class="xp-h2">一周 4 小时 <span class="mint">还给自己</span></h2>
<div style="display:flex;align-items:center;gap:60px;margin-top:30px">
<svg viewBox="0 0 260 260" style="width:300px;flex-shrink:0">
<circle cx="130" cy="130" r="100" fill="none" stroke="#fef0e4" stroke-width="40"/>
<!-- email 12% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#f48b5c" stroke-width="40" stroke-dasharray="75 628" stroke-dashoffset="0" transform="rotate(-90 130 130)"/>
<!-- logistics 18% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#2e9d70" stroke-width="40" stroke-dasharray="113 628" stroke-dashoffset="-75" transform="rotate(-90 130 130)"/>
<!-- meetings 14% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#4e7ed6" stroke-width="40" stroke-dasharray="88 628" stroke-dashoffset="-188" transform="rotate(-90 130 130)"/>
<!-- photos 6% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#7b5dc4" stroke-width="40" stroke-dasharray="38 628" stroke-dashoffset="-276" transform="rotate(-90 130 130)"/>
<text x="130" y="130" text-anchor="middle" font-family="Playfair Display" font-size="44" font-weight="900" fill="#2a2340">4h</text>
<text x="130" y="156" text-anchor="middle" font-family="Inter" font-size="12" fill="#9089a8">per week saved</text>
</svg>
<div style="flex:1">
<div class="xp-grid-2" style="grid-template-columns:1fr;gap:12px;margin-top:0">
<div class="xp-card peach" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-peach-d)"></div><div><h4 style="margin:0;font-size:17px">48 min · 邮件</h4></div></div>
<div class="xp-card mint" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-mint-d)"></div><div><h4 style="margin:0;font-size:17px">72 min · 订/改/查</h4></div></div>
<div class="xp-card sky" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-sky-d)"></div><div><h4 style="margin:0;font-size:17px">56 min · 会议摘要</h4></div></div>
<div class="xp-card lilac" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-lilac-d)"></div><div><h4 style="margin:0;font-size:17px">24 min · 照片整理</h4></div></div>
</div>
</div>
</div>
<div class="xp-footer"><span>chart · donut</span><span>06 · 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="xp-blob b1"></div>
<div class="xp-blob b3"></div>
<div class="xp-topbar"><div class="xp-chip rose">This weekend</div><div class="xp-page">07 · 08</div></div>
<h2 class="xp-h2">这周末,<br>先给自己 <em>放一个小假</em></h2>
<div class="xp-grid-3">
<div class="xp-card lemon"><div class="xp-num"></div><h4>Saturday morning</h4><p>挑一个你最烦的小事,写 prompt让它从此不再烦你。</p></div>
<div class="xp-card peach"><div class="xp-num">🌸</div><h4>Saturday afternoon</h4><p>去散步。什么都不带。AI 在家帮你看着消息。</p></div>
<div class="xp-card sky"><div class="xp-num">🌙</div><h4>Sunday night</h4><p>复盘:哪 4 小时是真的空的?下周继续。</p></div>
</div>
<div class="xp-footer"><span>cta</span><span>07 · 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="xp-blob b2"></div>
<div style="margin:auto 0;text-align:center">
<div class="xp-kicker" style="text-align:center">thanks for reading</div>
<h1 class="xp-h1" style="font-size:160px;text-align:center">谢谢 <em>·</em> thanks</h1>
<div class="xp-divider" style="margin:24px auto"></div>
<p class="xp-sub" style="margin:0 auto">如果你也想过更温柔的一周,评论区跟我说说你打算把哪一件事先交出去 ♡</p>
</div>
<div class="xp-footer"><span>end</span><span>08 · 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,66 @@
/* xhs-pastel-card — 柔和马卡龙大色块封面风 */
.tpl-xhs-pastel-card{
--xp-bg:#fef8f1;
--xp-ink:#2a2340;
--xp-ink2:#5b5470;
--xp-muted:#9089a8;
--xp-peach:#ffd8c2;
--xp-peach-d:#f48b5c;
--xp-mint:#c8ecd8;
--xp-mint-d:#2e9d70;
--xp-sky:#c9dcfb;
--xp-sky-d:#4e7ed6;
--xp-lilac:#ddd0f5;
--xp-lilac-d:#7b5dc4;
--xp-lemon:#fdf0b2;
--xp-lemon-d:#c8910a;
--xp-rose:#fcd0dd;
--xp-rose-d:#c94673;
background:var(--xp-bg);
color:var(--xp-ink);
font-family:'Playfair Display','Noto Serif SC','Inter','Noto Sans SC',Georgia,serif;
}
.tpl-xhs-pastel-card .slide{background:var(--xp-bg);color:var(--xp-ink);padding:76px 90px}
.tpl-xhs-pastel-card .xp-blob{position:absolute;border-radius:50%;filter:blur(2px);opacity:.85;z-index:0}
.tpl-xhs-pastel-card .xp-blob.b1{width:420px;height:420px;background:radial-gradient(circle,var(--xp-peach),transparent 70%);top:-8%;right:-6%}
.tpl-xhs-pastel-card .xp-blob.b2{width:360px;height:360px;background:radial-gradient(circle,var(--xp-lilac),transparent 72%);bottom:-10%;left:-8%}
.tpl-xhs-pastel-card .xp-blob.b3{width:260px;height:260px;background:radial-gradient(circle,var(--xp-mint),transparent 72%);top:40%;right:20%}
.tpl-xhs-pastel-card .slide > *{position:relative;z-index:2}
.tpl-xhs-pastel-card .xp-topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:22px;font-family:'Inter','Noto Sans SC',sans-serif}
.tpl-xhs-pastel-card .xp-chip{display:inline-flex;align-items:center;gap:10px;padding:8px 18px;border-radius:999px;background:#fff;border:1.5px solid rgba(42,35,64,.1);font-size:13px;font-weight:600;letter-spacing:.08em;color:var(--xp-ink2);text-transform:uppercase}
.tpl-xhs-pastel-card .xp-chip::before{content:'';width:9px;height:9px;border-radius:50%;background:var(--xp-peach-d)}
.tpl-xhs-pastel-card .xp-chip.mint::before{background:var(--xp-mint-d)}
.tpl-xhs-pastel-card .xp-chip.sky::before{background:var(--xp-sky-d)}
.tpl-xhs-pastel-card .xp-chip.lilac::before{background:var(--xp-lilac-d)}
.tpl-xhs-pastel-card .xp-chip.rose::before{background:var(--xp-rose-d)}
.tpl-xhs-pastel-card .xp-page{font-family:'Inter',sans-serif;font-size:13px;color:var(--xp-muted);letter-spacing:.12em;font-weight:600}
.tpl-xhs-pastel-card .xp-kicker{font-family:'Inter',sans-serif;font-size:14px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--xp-peach-d);margin-bottom:14px}
.tpl-xhs-pastel-card .xp-h1{font-size:96px;font-weight:900;line-height:1.05;letter-spacing:-2px;margin:0 0 18px;color:var(--xp-ink);font-family:'Playfair Display','Noto Serif SC',serif}
.tpl-xhs-pastel-card .xp-h1 em{font-style:italic;color:var(--xp-peach-d);font-family:'Playfair Display',serif}
.tpl-xhs-pastel-card .xp-h1 .rose{color:var(--xp-rose-d);font-style:italic}
.tpl-xhs-pastel-card .xp-h1 .mint{color:var(--xp-mint-d);font-style:italic}
.tpl-xhs-pastel-card .xp-h2{font-size:60px;font-weight:800;line-height:1.1;letter-spacing:-1px;margin:0 0 14px;font-family:'Playfair Display','Noto Serif SC',serif}
.tpl-xhs-pastel-card .xp-sub{font-family:'Inter','Noto Sans SC',sans-serif;font-size:21px;line-height:1.6;color:var(--xp-ink2);max-width:800px;font-weight:400}
.tpl-xhs-pastel-card .xp-card{border-radius:28px;padding:30px 34px;background:#fff;box-shadow:0 14px 40px rgba(42,35,64,.08);position:relative;overflow:hidden}
.tpl-xhs-pastel-card .xp-card.peach{background:var(--xp-peach)}
.tpl-xhs-pastel-card .xp-card.mint{background:var(--xp-mint)}
.tpl-xhs-pastel-card .xp-card.sky{background:var(--xp-sky)}
.tpl-xhs-pastel-card .xp-card.lilac{background:var(--xp-lilac)}
.tpl-xhs-pastel-card .xp-card.lemon{background:var(--xp-lemon)}
.tpl-xhs-pastel-card .xp-card.rose{background:var(--xp-rose)}
.tpl-xhs-pastel-card .xp-card .xp-num{font-family:'Playfair Display',serif;font-size:68px;font-weight:900;font-style:italic;line-height:1;opacity:.85}
.tpl-xhs-pastel-card .xp-card h4{font-size:22px;font-weight:800;margin:8px 0;font-family:'Inter','Noto Sans SC',sans-serif}
.tpl-xhs-pastel-card .xp-card p{font-family:'Inter','Noto Sans SC',sans-serif;font-size:15px;line-height:1.55;color:var(--xp-ink2)}
.tpl-xhs-pastel-card .xp-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:26px}
.tpl-xhs-pastel-card .xp-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin-top:26px}
.tpl-xhs-pastel-card .xp-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:24px}
.tpl-xhs-pastel-card .xp-hero-card{background:#fff;border-radius:36px;padding:40px 46px;margin-top:28px;box-shadow:0 20px 50px rgba(42,35,64,.1)}
.tpl-xhs-pastel-card .xp-quote{font-family:'Playfair Display','Noto Serif SC',serif;font-size:40px;font-weight:800;font-style:italic;line-height:1.3;color:var(--xp-ink)}
.tpl-xhs-pastel-card .xp-quote::before{content:'“';font-size:100px;line-height:.8;display:block;color:var(--xp-peach-d);opacity:.7}
.tpl-xhs-pastel-card .xp-footer{position:absolute;left:90px;right:90px;bottom:40px;display:flex;justify-content:space-between;font-family:'Inter',sans-serif;font-size:12px;color:var(--xp-muted);letter-spacing:.1em}
.tpl-xhs-pastel-card .xp-divider{width:90px;height:4px;background:linear-gradient(90deg,var(--xp-peach-d),var(--xp-rose-d));border-radius:2px;margin:20px 0}
.tpl-xhs-pastel-card .xp-codebox{background:#2a2340;color:#fef8f1;border-radius:24px;padding:26px 30px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;margin-top:22px}
.tpl-xhs-pastel-card .xp-codebox .cm{color:#9089a8}
.tpl-xhs-pastel-card .xp-codebox .kw{color:#ffc6a0}
.tpl-xhs-pastel-card .xp-codebox .st{color:#c8ecd8}
.tpl-xhs-pastel-card .xp-codebox .hl{color:#fcd0dd;font-weight:700}

View File

@@ -0,0 +1,9 @@
# xhs-post · 小红书 9 图
小红书 3:4 图文格式9 张图810 × 1080。结构封面 → hook → 痛点 → aha moment → 步骤 1-3 → 效果 → CTA 关注。
手写便签 + 贴纸 + 圆角硬阴影的 MUJI/风格,暖米色背景 + 粉橘黄柔和渐变。每页右上角有 `N / 9` 页码贴纸,最后一页有话题 tag。
**适用场景:** 小红书 / 微博九宫格 / 公众号图文首图 / 抖音图文卡片。
**使用方式:** 每张 `.slide` 直接截图导出即可,保持 810×1080 比例。按 → 依次浏览。
**Feel:** 手帐、贴纸、闺蜜跟你分享干货的 vibe。

View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>每天只睡 6h 还精神?· 小红书图文</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-xhs-post">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<div class="page-dot">1 / 9</div>
<div class="sticker pink" style="top:120px;left:48px;transform:rotate(-6deg)">💤 救命</div>
<div class="sticker yellow" style="top:140px;right:64px;transform:rotate(5deg)">亲测 7 天</div>
<div style="margin-top:200px">
<p class="lede" style="font-size:24px;color:var(--text-1);font-weight:600">打工人深夜自救手册</p>
<h1 class="h1 mt-s">每天只睡 <span class="cover-title">6h</span><br>还能<span class="cover-title">精神一整天</span><br>的 3 个小习惯</h1>
</div>
<div class="bottom-bar"><div><span class="avatar"></span> <b style="color:var(--text-1);margin-left:8px">@小熊不困了</b></div><div>← 左滑 查看</div></div>
</section>
<!-- 2. Hook -->
<section class="slide" data-title="Hook">
<div class="page-dot">2 / 9</div>
<div class="big-emoji" style="margin-top:80px">👀</div>
<h2 class="h2 tc mt-l">等等先别划走!</h2>
<p class="lede tc mt-m" style="padding:0 20px">我也曾是那个<br>早上起来像被卡车撞过的人。<br><br>直到我发现了<br><b style="color:var(--accent)">1 件事</b>比睡够 8 小时还重要。</p>
<div class="sticker blue" style="bottom:160px;left:50%;transform:translateX(-50%) rotate(-2deg)">真 · 转折点 ↓</div>
</section>
<!-- 3. Pain -->
<section class="slide" data-title="Pain">
<div class="page-dot">3 / 9</div>
<p class="lede" style="font-weight:700;color:var(--accent)">❌ 你是不是也这样</p>
<h2 class="h2 mt-s">越睡越累</h2>
<div class="stack mt-l">
<div class="hand-box"><b style="font-size:22px">😵‍💫 周末补觉到中午</b><p class="dim" style="font-size:16px;margin-top:4px">起来头更晕,一整天废掉</p></div>
<div class="hand-box"><b style="font-size:22px">☕️ 咖啡续三杯</b><p class="dim" style="font-size:16px;margin-top:4px">下午 3 点照样困到扶墙</p></div>
<div class="hand-box"><b style="font-size:22px">📱 睡前刷到凌晨</b><p class="dim" style="font-size:16px;margin-top:4px">明明很困就是不舍得睡</p></div>
</div>
</section>
<!-- 4. Aha -->
<section class="slide" data-title="Aha">
<div class="page-dot">4 / 9</div>
<div class="sticker green" style="top:100px;right:48px;transform:rotate(4deg)">✨ aha moment</div>
<p class="lede mt-l" style="color:var(--accent);font-weight:700">💡 真相是</p>
<h2 class="h2 mt-s">不是睡得少,<br><span style="background:var(--accent-3);padding:0 8px">醒得不对</span></h2>
<p class="lede mt-l">身体有 90 分钟一个周期。<br>在"深睡"里被闹钟拽起来,<br>就算睡 9 小时也跟没睡一样。</p>
<p class="lede mt-m" style="color:var(--text-1);font-weight:700">关键是:<span style="color:var(--accent)">卡着周期醒</span></p>
</section>
<!-- 5. Step 1 -->
<section class="slide" data-title="Step 1">
<div class="page-dot">5 / 9</div>
<div class="num-circle">1</div>
<h2 class="h2 mt-m">倒推睡眠时间</h2>
<div class="hand-box mt-l">
<p style="font-size:22px;margin:0;color:var(--text-1);font-weight:700">👉 公式</p>
<p style="font-size:20px;margin:10px 0 0;color:var(--text-2);line-height:1.7">起床时间 <b style="color:var(--accent)">90min × N</b> 15min 入睡<br>= 你今晚该上床的点</p>
</div>
<div class="hand-box mt-m" style="background:#fff5ef">
<p style="font-size:18px;margin:0;color:var(--text-2)">举例:要 7 点起</p>
<p style="font-size:24px;margin:8px 0 0;color:var(--text-1);font-weight:800">→ 23:15 上床 (4 个周期)<br>→ 00:45 上床 (3 个周期)</p>
</div>
</section>
<!-- 6. Step 2 -->
<section class="slide" data-title="Step 2">
<div class="page-dot">6 / 9</div>
<div class="num-circle" style="background:var(--accent-2)">2</div>
<h2 class="h2 mt-m">早晨 10 分钟光</h2>
<div class="hand-box mt-l">
<p style="font-size:22px;margin:0;color:var(--text-1);font-weight:700">☀️ 打开窗帘 / 下楼遛弯</p>
<p style="font-size:18px;margin:8px 0 0;color:var(--text-2);line-height:1.6">自然光一照,褪黑素立刻被掐停,人就真的醒了。阴天也有效,别偷懒。</p>
</div>
<div class="sticker yellow" style="bottom:200px;right:60px;transform:rotate(8deg)">⏰ 比咖啡还猛</div>
<div class="hand-box mt-m" style="background:#fff5ef">
<p style="font-size:18px;margin:0;color:var(--text-2)">懒人方案:</p>
<p style="font-size:22px;margin:6px 0 0;color:var(--text-1);font-weight:700">刷牙的时候站在窗边 🪥</p>
</div>
</section>
<!-- 7. Step 3 -->
<section class="slide" data-title="Step 3">
<div class="page-dot">7 / 9</div>
<div class="num-circle" style="background:var(--accent-3);color:var(--text-1)">3</div>
<h2 class="h2 mt-m">下午 3 点<br>20 分钟小睡</h2>
<div class="hand-box mt-l">
<p style="font-size:20px;margin:0;color:var(--text-2);line-height:1.6"><b style="color:var(--text-1)">⏱️ 最多 20 分钟。</b>超过 30 就会进入深睡,醒来会更累。</p>
</div>
<div class="hand-box mt-m" style="background:#fff5ef">
<p style="font-size:20px;margin:0;color:var(--text-2);line-height:1.6"><b style="color:var(--text-1)">💡 小 tip</b>睡前喝一口咖啡。20 分钟后咖啡因正好起效,和小睡的清醒 buff 叠加。</p>
</div>
<div class="sticker pink" style="bottom:140px;left:50%;transform:translateX(-50%) rotate(-3deg)">打工人作弊技</div>
</section>
<!-- 8. Result -->
<section class="slide" data-title="Result">
<div class="page-dot">8 / 9</div>
<p class="lede" style="color:var(--good);font-weight:700">✅ 我坚持 7 天后</p>
<h2 class="h2 mt-s">结果是……</h2>
<div class="stack mt-l">
<div class="hand-box"><b style="font-size:22px">😌 早上闹钟响之前就自然醒</b></div>
<div class="hand-box"><b style="font-size:22px">💪 下午不再崩溃</b></div>
<div class="hand-box"><b style="font-size:22px">☕️ 咖啡从 3 杯 → 1 杯</b></div>
<div class="hand-box" style="background:var(--accent-3);border-color:var(--text-1)"><b style="font-size:24px">✨ 最重要:脾气变好了</b></div>
</div>
</section>
<!-- 9. CTA -->
<section class="slide" data-title="CTA">
<div class="page-dot">9 / 9</div>
<div class="big-emoji" style="margin-top:60px">💌</div>
<h2 class="h2 tc mt-l">觉得有用的话</h2>
<h1 class="h1 tc mt-s" style="color:var(--accent)">收藏 + 关注 🧡</h1>
<p class="lede tc mt-l" style="padding:0 30px">下期讲<br><b style="color:var(--text-1)">「打工人脊椎急救 5 式」</b><br>办公室也能做</p>
<div class="tag-row" style="justify-content:center;margin-top:36px">
<span class="ht">#睡眠</span>
<span class="ht">#打工人日常</span>
<span class="ht">#自律</span>
<span class="ht">#健康生活</span>
</div>
<div class="bottom-bar"><div><span class="avatar"></span> <b style="color:var(--text-1);margin-left:8px">@小熊不困了</b></div><div>❤️ 5.2w</div></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,47 @@
/* xhs-post — 小红书 3:4 九宫格 */
.tpl-xhs-post{
--bg:#fef7f3;--bg-soft:#fff1ea;--surface:#ffffff;--surface-2:#fff5ef;
--border:rgba(90,40,30,.12);--border-strong:rgba(90,40,30,.24);
--text-1:#3a1f18;--text-2:#6f4a3e;--text-3:#a68676;
--accent:#ff6b8b;--accent-2:#ffa94d;--accent-3:#ffd166;
--grad:linear-gradient(135deg,#ffd3e0,#ffe5c7 50%,#d6f0ff);
--good:#7bc67b;--warn:#ffb547;--bad:#ff6b6b;
--radius:24px;--radius-lg:32px;
--shadow:0 14px 36px rgba(90,40,30,.08);
font-family:'Inter','Noto Sans SC','PingFang SC',sans-serif;
}
.tpl-xhs-post{background:#f0eae2;display:flex;align-items:center;justify-content:center;min-height:100vh}
.tpl-xhs-post .deck{width:810px;height:1080px;position:relative;background:transparent}
.tpl-xhs-post .slide{
position:absolute;inset:0;width:810px;height:1080px;aspect-ratio:3/4;
padding:70px 64px;border-radius:28px;overflow:hidden;
background:var(--bg);
}
.tpl-xhs-post .slide::before{content:"";position:absolute;inset:0;background:
radial-gradient(45% 30% at 80% 10%,rgba(255,209,102,.35),transparent 70%),
radial-gradient(50% 35% at 10% 95%,rgba(255,107,139,.22),transparent 70%),
radial-gradient(40% 30% at 90% 85%,rgba(122,200,255,.18),transparent 70%);
pointer-events:none;z-index:0}
.tpl-xhs-post .slide > *{position:relative;z-index:1}
.tpl-xhs-post .h1{font-size:72px;line-height:1.1;font-weight:900;letter-spacing:-.02em;color:var(--text-1)}
.tpl-xhs-post .h2{font-size:54px;line-height:1.15;font-weight:800;letter-spacing:-.015em;color:var(--text-1)}
.tpl-xhs-post .h3{font-size:36px;font-weight:800;color:var(--text-1)}
.tpl-xhs-post .page-dot{position:absolute;top:40px;right:48px;background:var(--text-1);color:#fff;border-radius:999px;padding:6px 14px;font-family:'JetBrains Mono',monospace;font-size:14px;font-weight:700;z-index:2}
.tpl-xhs-post .sticker{position:absolute;padding:10px 18px;background:#fff;border:2.5px dashed var(--text-1);border-radius:18px;font-weight:800;font-size:18px;color:var(--text-1);transform:rotate(-3deg);box-shadow:4px 4px 0 var(--text-1)}
.tpl-xhs-post .sticker.pink{background:#ffd3e0}
.tpl-xhs-post .sticker.yellow{background:#ffe788}
.tpl-xhs-post .sticker.blue{background:#cfeaff}
.tpl-xhs-post .sticker.green{background:#d4f2c8}
.tpl-xhs-post .hand-box{background:#fff;border:2.5px solid var(--text-1);border-radius:22px;padding:24px 28px;box-shadow:5px 5px 0 var(--text-1)}
.tpl-xhs-post .lede{color:var(--text-2);font-size:26px;line-height:1.55}
.tpl-xhs-post .big-emoji{font-size:180px;line-height:1;text-align:center}
.tpl-xhs-post .num-circle{display:inline-flex;align-items:center;justify-content:center;width:72px;height:72px;border-radius:50%;background:var(--accent);color:#fff;font-weight:900;font-size:36px;border:3px solid var(--text-1);box-shadow:4px 4px 0 var(--text-1)}
.tpl-xhs-post .step-card{background:#fff;border:2.5px solid var(--text-1);border-radius:22px;padding:26px 28px;box-shadow:5px 5px 0 var(--text-1);margin-bottom:24px}
.tpl-xhs-post .step-card h4{font-size:28px;font-weight:800;margin:0 0 6px}
.tpl-xhs-post .step-card p{font-size:18px;color:var(--text-2);margin:0}
.tpl-xhs-post .tag-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:24px}
.tpl-xhs-post .ht{background:#fff;color:var(--accent);border:2px solid var(--text-1);padding:6px 14px;border-radius:999px;font-weight:700;font-size:16px}
.tpl-xhs-post .cover-title{background:linear-gradient(180deg,transparent 60%,var(--accent-3) 60%,var(--accent-3) 92%,transparent 92%);padding:0 10px}
.tpl-xhs-post .heart{color:var(--accent);font-size:28px}
.tpl-xhs-post .bottom-bar{position:absolute;bottom:40px;left:64px;right:64px;display:flex;justify-content:space-between;align-items:center;font-size:15px;color:var(--text-3);font-family:'JetBrains Mono',monospace;z-index:2}
.tpl-xhs-post .avatar{width:54px;height:54px;border-radius:50%;background:var(--grad);border:2.5px solid var(--text-1);box-shadow:3px 3px 0 var(--text-1);display:inline-flex;align-items:center;justify-content:center;font-weight:900;font-size:20px;color:var(--text-1)}

View File

@@ -0,0 +1,11 @@
# xhs-white-editorial
白底杂志风、强调重点块、macaron soft-card 分组。灵感来自 `20260409 升级版知识库/小红书图文/v2-白底版/slide_01_cover.html` 的顶部彩虹条 + 大字标题,以及 `20260412-AI测试与安全/xhs-ai-testing-safety-v2.html``.focus` 黑底白字强重点和 macaron 软色卡片系统。
**Visual traits:** 纯白背景、顶部 10 色彩虹条、巨型 80-110px 标题配轻微负字距、渐变 brand 文字紫→蓝→绿→橙→粉、macaron 软色卡soft-purple / pink / blue / green / orange、胶囊 tag + dot、黑底 `.focus` 强调框、hero quote box 带淡阴影。
**Use when:** 你需要一份能当小红书图文、也能当横屏 deck 用的白底内容帖;文字多、重点密集、需要一眼抓住关键词;面向中文读者为主。
**Source inspiration:** `20260409` xhs v2 白底封面 + `20260412` AI 测试与安全 v2。
**Path:** `templates/full-decks/xhs-white-editorial/index.html`

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>白底杂志风 · XHS Editorial</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-xhs-white-editorial">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>AI 时代 · 职业判断</div>
<div class="xw-page">01 / 08</div>
</div>
<div class="xw-kicker">我越来越确定的一件事</div>
<h1 class="xw-title">以后最贵的工作,<br><span class="xw-grad">测试 + 安全</span></h1>
<p class="xw-sub">AI 会越来越会做事。但谁来保证它 <span class="xw-focus">做对</span><span class="xw-focus">没风险</span><span class="xw-focus">不会出事</span></p>
<div class="xw-hero">
<div class="xw-quote">未来最值钱的,<br>不是 <span class="xw-focus-orange">生产</span>,而是 <span class="xw-focus">验收和兜底</span></div>
</div>
<div class="xw-footer"><span>白底|强重点|杂志竖排</span><span>Cover · 01</span></div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>Chapter · 01</div>
<div class="xw-page">02 / 08</div>
</div>
<div style="margin-top:120px">
<div class="xw-kicker" style="font-size:20px;letter-spacing:.2em;text-transform:uppercase;color:#98a2b3">第一章</div>
<h1 class="xw-title" style="font-size:110px;margin-top:20px">先看 <span class="xw-grad">大趋势</span></h1>
<p class="xw-sub" style="font-size:28px">当执行越来越便宜,判断就会越来越贵。</p>
</div>
<div class="xw-footer"><span>Section Divider</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT — 4 card grid -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>越来越多的事会交给 AI</div>
<div class="xw-page">03 / 08</div>
</div>
<h2 class="xw-title-md">未来 3 年,这些事都会 <span class="xw-grad">自动跑</span></h2>
<div class="xw-grid-2">
<div class="xw-card soft-pink"><div class="xw-label">内容</div><div class="main">写文案 · 写方案 · 写脚本</div><div class="desc">创作变成一个 prompt 的距离</div></div>
<div class="xw-card soft-blue"><div class="xw-label">生产</div><div class="main">做图 · 搭页面 · 做表格</div><div class="desc">生产力工具集体重写一次</div></div>
<div class="xw-card soft-green"><div class="xw-label">执行</div><div class="main">跑流程 · 写代码 · 自动操作</div><div class="desc">Agent 从 demo 走进真实工作流</div></div>
<div class="xw-card soft-orange"><div class="xw-label">分析</div><div class="main">读数据 · 做总结 · 给建议</div><div class="desc">决策支持层彻底向下延伸</div></div>
</div>
<div class="xw-footer"><span>Content · Grid 2x2</span><span>03 / 08</span></div>
</section>
<!-- 4. STEPS -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>为什么会这样</div>
<div class="xw-page">04 / 08</div>
</div>
<h2 class="xw-title-md">AI 越强,<span class="xw-grad">判断对错</span> 越值钱</h2>
<div class="xw-steps">
<div class="xw-step"><div class="xw-num">1</div><div class="xw-txt">生产会更便宜,边际成本接近零</div></div>
<div class="xw-step"><div class="xw-num">2</div><div class="xw-txt">复制会更快,错误也一起被加速</div></div>
<div class="xw-step"><div class="xw-num">3</div><div class="xw-txt">AI 一本正经地做错,人类难以察觉</div></div>
<div class="xw-step"><div class="xw-num">4</div><div class="xw-txt">所以最贵的能力会变成 <span class="xw-focus">发现问题</span></div></div>
</div>
<div class="xw-hero"><div class="xw-quote" style="font-size:30px">AI 让「<span class="xw-focus-blue">做出来</span>」变便宜,<br>但让「<span class="xw-focus">做对、做稳、别出事</span>」变更贵。</div></div>
<div class="xw-footer"><span>Content · Steps</span><span>04 / 08</span></div>
</section>
<!-- 5. CODE EXAMPLE -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>一段你今晚就能跑的验收 Skill</div>
<div class="xw-page">05 / 08</div>
</div>
<h2 class="xw-title-md">不是写 prompt<br>是写 <span class="xw-grad">验收清单</span></h2>
<pre class="xw-codebox"><span class="cm"># skills/ai-acceptance/SKILL.md</span>
<span class="kw">name</span>: <span class="st">ai-acceptance</span>
<span class="kw">description</span>: <span class="st">"Runs AI output through a 4-gate review checklist."</span>
<span class="kw">gates</span>:
- <span class="hl">functional</span>: <span class="st">"Does it actually do what the user asked?"</span>
- <span class="hl">edge_cases</span>: <span class="st">"Empty / long / non-ASCII / concurrent?"</span>
- <span class="hl">safety</span>: <span class="st">"PII, secrets, destructive ops — all red-flagged?"</span>
- <span class="hl">rollback</span>: <span class="st">"If this ships and breaks, can we undo in 60s?"</span></pre>
<div class="xw-footer"><span>Content · Code Block</span><span>05 / 08</span></div>
</section>
<!-- 6. CHART — SVG bar -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>岗位相对价值变化</div>
<div class="xw-page">06 / 08</div>
</div>
<h2 class="xw-title-md">越来越 <span class="xw-focus-pink">便宜</span>,越来越 <span class="xw-focus-green"></span></h2>
<svg viewBox="0 0 960 380" style="width:100%;max-width:1000px;margin-top:30px" xmlns="http://www.w3.org/2000/svg">
<g font-family="Inter, sans-serif" font-size="16" fill="#475467">
<!-- baseline -->
<line x1="180" y1="330" x2="940" y2="330" stroke="#eaecf3" stroke-width="2"/>
<!-- rows -->
<g transform="translate(0,40)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">纯执行</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#fff0f6"/>
<rect x="180" y="10" width="120" height="28" rx="14" fill="#ff5fa2"/>
<text x="710" y="30" fill="#c11574" font-weight="700">-65% 价值</text>
</g>
<g transform="translate(0,100)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">内容生产</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#eef4ff"/>
<rect x="180" y="10" width="200" height="28" rx="14" fill="#4e8cff"/>
<text x="710" y="30" fill="#174ea6" font-weight="700">-40% 价值</text>
</g>
<g transform="translate(0,160)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">数据分析</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#fff5ea"/>
<rect x="180" y="10" width="320" height="28" rx="14" fill="#ff9d42"/>
<text x="710" y="30" fill="#b54708" font-weight="700">持平</text>
</g>
<g transform="translate(0,220)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">测试 / 验收</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#edfdf3"/>
<rect x="180" y="10" width="440" height="28" rx="14" fill="#17b26a"/>
<text x="710" y="30" fill="#067647" font-weight="700">+85% 价值</text>
</g>
<g transform="translate(0,280)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">安全 / 风控</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#f4efff"/>
<rect x="180" y="10" width="500" height="28" rx="14" fill="#7b61ff"/>
<text x="710" y="30" fill="#5b21b6" font-weight="700">+110% 价值</text>
</g>
</g>
</svg>
<div class="xw-footer"><span>Chart · Horizontal Bars</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>今晚就可以做的三件事</div>
<div class="xw-page">07 / 08</div>
</div>
<h2 class="xw-title-md">别再追工具,<br>开始练 <span class="xw-grad">判断力</span></h2>
<div class="xw-grid-3">
<div class="xw-card soft-purple"><div class="xw-label">Tonight</div><div class="main">写一份<br>验收清单</div><div class="desc">哪怕只有 5 条,开始比完美更重要</div></div>
<div class="xw-card soft-blue"><div class="xw-label">This week</div><div class="main">跑一遍<br>红队演练</div><div class="desc">对自己的 agent 说:试着让它出事</div></div>
<div class="xw-card soft-green"><div class="xw-label">This month</div><div class="main">加一条<br>回滚流程</div><div class="desc">60 秒内能撤销,你就敢把手放开</div></div>
</div>
<div class="xw-hero"><div class="xw-quote" style="font-size:32px">真正的稀缺,不是「会用 AI」<br>而是 <span class="xw-focus">「敢为 AI 的结果签字」</span></div></div>
<div class="xw-footer"><span>CTA</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>Thanks for reading</div>
<div class="xw-page">08 / 08</div>
</div>
<div style="margin-top:100px">
<div class="xw-big-stat xw-grad">谢谢<small> · thanks</small></div>
<p class="xw-sub" style="font-size:28px;margin-top:36px">如果你也在想这些问题,欢迎在评论里告诉我——<br>你最想让 AI 帮你做什么?你最不放心它做什么?</p>
<div style="margin-top:40px">
<span class="xw-pill">@lewis</span>
<span class="xw-pill">小红书 · 白底杂志风</span>
<span class="xw-pill">html-ppt · full-deck</span>
</div>
</div>
<div class="xw-footer"><span>End</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,63 @@
/* xhs-white-editorial — 白底杂志风 */
.tpl-xhs-white-editorial{
--xw-bg:#ffffff;
--xw-ink:#111318;
--xw-ink2:#475467;
--xw-muted:#98a2b3;
--xw-line:#eaecf3;
--xw-purple:#7b61ff;
--xw-pink:#ff5fa2;
--xw-blue:#4e8cff;
--xw-green:#17b26a;
--xw-orange:#ff9d42;
--xw-soft-purple:#f4efff;
--xw-soft-pink:#fff0f6;
--xw-soft-blue:#eef4ff;
--xw-soft-green:#edfdf3;
--xw-soft-orange:#fff5ea;
background:var(--xw-bg);
color:var(--xw-ink);
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
}
.tpl-xhs-white-editorial .slide{background:#fff;padding:72px 88px}
.tpl-xhs-white-editorial .xw-topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
.tpl-xhs-white-editorial .xw-tag{display:inline-flex;align-items:center;gap:10px;padding:10px 18px;border:1px solid var(--xw-line);border-radius:999px;font-size:15px;color:var(--xw-ink2);background:#fff}
.tpl-xhs-white-editorial .xw-tag .dot{width:10px;height:10px;border-radius:50%;background:linear-gradient(90deg,#7b61ff,#4e8cff,#17b26a,#ff9d42,#ff5fa2)}
.tpl-xhs-white-editorial .xw-page{font-size:14px;color:var(--xw-muted);letter-spacing:.1em}
.tpl-xhs-white-editorial .xw-kicker{font-size:18px;color:var(--xw-ink2);margin-top:6px;font-weight:500}
.tpl-xhs-white-editorial .xw-title{font-size:84px;line-height:1.02;letter-spacing:-2px;font-weight:850;margin:18px 0 0;color:var(--xw-ink)}
.tpl-xhs-white-editorial .xw-title-md{font-size:60px;line-height:1.05;letter-spacing:-1.5px;font-weight:800;margin:14px 0 0}
.tpl-xhs-white-editorial .xw-grad{background:linear-gradient(90deg,#7b61ff 0%,#4e8cff 25%,#17b26a 48%,#ff9d42 72%,#ff5fa2 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
.tpl-xhs-white-editorial .xw-sub{font-size:24px;line-height:1.45;color:#1f2937;margin-top:22px;max-width:900px}
.tpl-xhs-white-editorial .xw-focus{display:inline-block;padding:6px 14px;border-radius:14px;background:#111318;color:#fff;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-blue{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-blue);color:#174ea6;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-pink{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-pink);color:#c11574;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-orange{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-orange);color:#b54708;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-green{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-green);color:#067647;font-weight:700}
.tpl-xhs-white-editorial .xw-hero{margin-top:28px;border:1px solid var(--xw-line);border-radius:28px;padding:30px 34px;background:linear-gradient(180deg,#fff 0%,#fcfcff 100%);box-shadow:0 18px 48px rgba(17,19,24,.08)}
.tpl-xhs-white-editorial .xw-quote{font-size:38px;line-height:1.3;font-weight:800;letter-spacing:-.5px}
.tpl-xhs-white-editorial .xw-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:22px}
.tpl-xhs-white-editorial .xw-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:22px}
.tpl-xhs-white-editorial .xw-card{border:1px solid var(--xw-line);border-radius:24px;padding:24px 26px;box-shadow:0 10px 24px rgba(17,19,24,.04);background:#fff}
.tpl-xhs-white-editorial .xw-card.soft-purple{background:var(--xw-soft-purple)}
.tpl-xhs-white-editorial .xw-card.soft-pink{background:var(--xw-soft-pink)}
.tpl-xhs-white-editorial .xw-card.soft-blue{background:var(--xw-soft-blue)}
.tpl-xhs-white-editorial .xw-card.soft-green{background:var(--xw-soft-green)}
.tpl-xhs-white-editorial .xw-card.soft-orange{background:var(--xw-soft-orange)}
.tpl-xhs-white-editorial .xw-label{font-size:14px;font-weight:800;opacity:.7;margin-bottom:10px;letter-spacing:.08em;text-transform:uppercase}
.tpl-xhs-white-editorial .xw-card .main{font-size:28px;line-height:1.22;font-weight:850;letter-spacing:-.5px}
.tpl-xhs-white-editorial .xw-card .desc{font-size:16px;line-height:1.5;color:#475467;margin-top:12px}
.tpl-xhs-white-editorial .xw-steps{margin-top:18px}
.tpl-xhs-white-editorial .xw-step{display:flex;gap:18px;align-items:flex-start;margin:16px 0}
.tpl-xhs-white-editorial .xw-num{flex:0 0 48px;height:48px;border-radius:50%;background:#111318;color:#fff;display:grid;place-items:center;font-size:20px;font-weight:900}
.tpl-xhs-white-editorial .xw-txt{font-size:22px;line-height:1.45;font-weight:700}
.tpl-xhs-white-editorial .xw-codebox{background:#0f1117;color:#e4e2d8;border-radius:18px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:15px;line-height:1.75;margin-top:20px;border:1px solid #1f222c}
.tpl-xhs-white-editorial .xw-codebox .cm{color:#6b6a62}
.tpl-xhs-white-editorial .xw-codebox .kw{color:#c88f64}
.tpl-xhs-white-editorial .xw-codebox .st{color:#a8c292}
.tpl-xhs-white-editorial .xw-codebox .hl{color:#e9c58a;font-weight:600}
.tpl-xhs-white-editorial .xw-footer{position:absolute;left:88px;right:88px;bottom:44px;display:flex;justify-content:space-between;align-items:flex-end;font-size:13px;color:var(--xw-muted)}
.tpl-xhs-white-editorial .xw-topline{position:absolute;top:0;left:0;right:0;height:5px;background:linear-gradient(90deg,#6366f1,#8b5cf6,#a855f7,#ec4899,#f43f5e,#f97316,#eab308,#22c55e,#06b6d4,#6366f1)}
.tpl-xhs-white-editorial .xw-pill{display:inline-block;padding:8px 16px;border-radius:999px;font-size:14px;font-weight:700;margin:0 8px 8px 0;background:#fff;border:1px solid var(--xw-line);color:#394150}
.tpl-xhs-white-editorial .xw-big-stat{font-size:96px;font-weight:900;letter-spacing:-4px;line-height:1}
.tpl-xhs-white-editorial .xw-big-stat small{font-size:22px;color:var(--xw-muted);font-weight:700;letter-spacing:0;margin-left:6px}

View File

@@ -0,0 +1,223 @@
/**
* ═══════════════════════════════════════════════════════════════════════════
* P5.JS GENERATIVE ART - BEST PRACTICES
* ═══════════════════════════════════════════════════════════════════════════
*
* This file shows STRUCTURE and PRINCIPLES for p5.js generative art.
* It does NOT prescribe what art you should create.
*
* Your algorithmic philosophy should guide what you build.
* These are just best practices for how to structure your code.
*
* ═══════════════════════════════════════════════════════════════════════════
*/
// ============================================================================
// 1. PARAMETER ORGANIZATION
// ============================================================================
// Keep all tunable parameters in one object
// This makes it easy to:
// - Connect to UI controls
// - Reset to defaults
// - Serialize/save configurations
let params = {
// Define parameters that match YOUR algorithm
// Examples (customize for your art):
// - Counts: how many elements (particles, circles, branches, etc.)
// - Scales: size, speed, spacing
// - Probabilities: likelihood of events
// - Angles: rotation, direction
// - Colors: palette arrays
seed: 12345,
// define colorPalette as an array -- choose whatever colors you'd like ['#d97757', '#6a9bcc', '#788c5d', '#b0aea5']
// Add YOUR parameters here based on your algorithm
};
// ============================================================================
// 2. SEEDED RANDOMNESS (Critical for reproducibility)
// ============================================================================
// ALWAYS use seeded random for Art Blocks-style reproducible output
function initializeSeed(seed) {
randomSeed(seed);
noiseSeed(seed);
// Now all random() and noise() calls will be deterministic
}
// ============================================================================
// 3. P5.JS LIFECYCLE
// ============================================================================
function setup() {
createCanvas(800, 800);
// Initialize seed first
initializeSeed(params.seed);
// Set up your generative system
// This is where you initialize:
// - Arrays of objects
// - Grid structures
// - Initial positions
// - Starting states
// For static art: call noLoop() at the end of setup
// For animated art: let draw() keep running
}
function draw() {
// Option 1: Static generation (runs once, then stops)
// - Generate everything in setup()
// - Call noLoop() in setup()
// - draw() doesn't do much or can be empty
// Option 2: Animated generation (continuous)
// - Update your system each frame
// - Common patterns: particle movement, growth, evolution
// - Can optionally call noLoop() after N frames
// Option 3: User-triggered regeneration
// - Use noLoop() by default
// - Call redraw() when parameters change
}
// ============================================================================
// 4. CLASS STRUCTURE (When you need objects)
// ============================================================================
// Use classes when your algorithm involves multiple entities
// Examples: particles, agents, cells, nodes, etc.
class Entity {
constructor() {
// Initialize entity properties
// Use random() here - it will be seeded
}
update() {
// Update entity state
// This might involve:
// - Physics calculations
// - Behavioral rules
// - Interactions with neighbors
}
display() {
// Render the entity
// Keep rendering logic separate from update logic
}
}
// ============================================================================
// 5. PERFORMANCE CONSIDERATIONS
// ============================================================================
// For large numbers of elements:
// - Pre-calculate what you can
// - Use simple collision detection (spatial hashing if needed)
// - Limit expensive operations (sqrt, trig) when possible
// - Consider using p5 vectors efficiently
// For smooth animation:
// - Aim for 60fps
// - Profile if things are slow
// - Consider reducing particle counts or simplifying calculations
// ============================================================================
// 6. UTILITY FUNCTIONS
// ============================================================================
// Color utilities
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function colorFromPalette(index) {
return params.colorPalette[index % params.colorPalette.length];
}
// Mapping and easing
function mapRange(value, inMin, inMax, outMin, outMax) {
return outMin + (outMax - outMin) * ((value - inMin) / (inMax - inMin));
}
function easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
// Constrain to bounds
function wrapAround(value, max) {
if (value < 0) return max;
if (value > max) return 0;
return value;
}
// ============================================================================
// 7. PARAMETER UPDATES (Connect to UI)
// ============================================================================
function updateParameter(paramName, value) {
params[paramName] = value;
// Decide if you need to regenerate or just update
// Some params can update in real-time, others need full regeneration
}
function regenerate() {
// Reinitialize your generative system
// Useful when parameters change significantly
initializeSeed(params.seed);
// Then regenerate your system
}
// ============================================================================
// 8. COMMON P5.JS PATTERNS
// ============================================================================
// Drawing with transparency for trails/fading
function fadeBackground(opacity) {
fill(250, 249, 245, opacity); // brand light with alpha
noStroke();
rect(0, 0, width, height);
}
// Using noise for organic variation
function getNoiseValue(x, y, scale = 0.01) {
return noise(x * scale, y * scale);
}
// Creating vectors from angles
function vectorFromAngle(angle, magnitude = 1) {
return createVector(cos(angle), sin(angle)).mult(magnitude);
}
// ============================================================================
// 9. EXPORT FUNCTIONS
// ============================================================================
function exportImage() {
saveCanvas('generative-art-' + params.seed, 'png');
}
// ============================================================================
// REMEMBER
// ============================================================================
//
// These are TOOLS and PRINCIPLES, not a recipe.
// Your algorithmic philosophy should guide WHAT you create.
// This structure helps you create it WELL.
//
// Focus on:
// - Clean, readable code
// - Parameterized for exploration
// - Seeded for reproducibility
// - Performant execution
//
// The art itself is entirely up to you!
//
// ============================================================================

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><title>Layout Showcase — html-ppt</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
<style>
.layout-nav{position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:50;background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:8px 20px;font-family:var(--font-mono);font-size:12px;color:var(--text-2);box-shadow:var(--shadow);display:flex;gap:14px;align-items:center}
.layout-nav a{color:var(--text-2);text-decoration:none;padding:4px 10px;border-radius:999px}
.layout-nav a:hover{background:var(--surface-2)}
iframe{width:100%;height:100vh;border:0;display:block;background:var(--bg)}
body{margin:0;overflow:hidden}
</style>
</head>
<body>
<div class="layout-nav">
<b>layouts</b>
<a href="#" data-go="-1"></a>
<span id="cur">cover</span>
<a href="#" data-go="+1"></a>
</div>
<iframe id="frame" src="single-page/cover.html"></iframe>
<script>
const list=['cover','toc','section-divider','bullets','two-column','three-column','big-quote',
'stat-highlight','kpi-grid','table','code','diff','terminal','flow-diagram','timeline',
'roadmap','mindmap','comparison','pros-cons','todo-checklist','gantt','image-hero','image-grid',
'chart-bar','chart-line','chart-pie','chart-radar','arch-diagram','process-steps','cta','thanks'];
let i=0;
const frame=document.getElementById('frame');
const cur=document.getElementById('cur');
function go(n){
i=(n+list.length)%list.length;
frame.src='single-page/'+list[i]+'.html';
cur.textContent=list[i]+' · '+(i+1)+'/'+list.length;
history.replaceState(null,'','#/'+(i+1));
}
document.querySelectorAll('[data-go]').forEach(a=>a.addEventListener('click',e=>{e.preventDefault();go(i+parseInt(a.dataset.go,10))}));
document.addEventListener('keydown',e=>{
if(e.key==='ArrowRight'||e.key===' '){go(i+1);e.preventDefault()}
if(e.key==='ArrowLeft'){go(i-1);e.preventDefault()}
});
const m=/^#\/(\d+)/.exec(location.hash||'');
if(m)go(parseInt(m[1],10)-1);else go(0);
</script>
</body></html>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
</Types>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
Target="xl/workbook.xml"/>
</Relationships>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
workbook.xml.rels — Maps each sheet r:id to its worksheet XML file.
When adding a new sheet:
- Add: <Relationship Id="rId2" Type="...worksheet" Target="worksheets/sheet2.xml"/>
- The Id must match the r:id in workbook.xml <sheet> element
-->
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
Target="worksheets/sheet1.xml"/>
<Relationship Id="rId2"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
Target="styles.xml"/>
<Relationship Id="rId3"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
Target="sharedStrings.xml"/>
</Relationships>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
sharedStrings.xml — The shared string table.
All text values in cells use this table. Instead of storing text directly
in the cell, each text string is stored here once, and cells reference it
by 0-based index:
<c r="A1" t="s"><v>0</v></c> → first string in this table
To add strings:
1. Append a new <si><t>Your Text</t></si> element
2. Increment both `count` and `uniqueCount` attributes
3. Use the new string's 0-based index in the cell's <v> element
Special characters in text:
- & → &amp;
- < → &lt;
- > → &gt;
- Leading/trailing spaces → use xml:space="preserve": <t xml:space="preserve"> text </t>
count = total number of string references across the workbook
uniqueCount = number of unique strings in this table
(They may differ if some strings are used in multiple cells)
-->
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
count="0" uniqueCount="0">
<!-- Strings will be added here. Example:
<si><t>Revenue</t></si>
<si><t>Cost of Goods Sold</t></si>
<si><t>Gross Profit</t></si>
-->
</sst>

View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
styles.xml — The complete style system for this workbook.
Style index (cellXfs) lookup table:
┌───────┬─────────────────────────────────┬────────────────┬───────────────────┐
│ Index │ Semantic Role │ Font Color │ Number Format │
├───────┼─────────────────────────────────┼────────────────┼───────────────────┤
│ 0 │ Default │ Theme (black) │ General │
│ 1 │ Input / Assumption │ Blue 000000FF │ General │
│ 2 │ Formula / Computed result │ Black 00000000 │ General │
│ 3 │ Cross-sheet reference │ Green 00008000 │ General │
│ 4 │ Header (bold) │ Black bold │ General │
│ 5 │ Currency input │ Blue 000000FF │ $#,##0 (id=164) │
│ 6 │ Currency formula │ Black │ $#,##0 (id=164) │
│ 7 │ Percentage input │ Blue 000000FF │ 0.0% (id=165) │
│ 8 │ Percentage formula │ Black │ 0.0% (id=165) │
│ 9 │ Integer with commas input │ Blue 000000FF │ #,##0 (id=167) │
│ 10 │ Integer with commas formula │ Black │ #,##0 (id=167) │
│ 11 │ Year (no comma) — input │ Blue 000000FF │ 0 (id=1) │
│ 12 │ Key assumption (yellow bg) │ Blue 000000FF │ General + yellow │
└───────┴─────────────────────────────────┴────────────────┴───────────────────┘
To add a new style:
1. If needed, add a <numFmt> to <numFmts> with a new numFmtId >= 164 (increment max)
2. If needed, add a <font> to <fonts>
3. If needed, add a <fill> to <fills>
4. Append a new <xf> to <cellXfs>, combining fontId + fillId + numFmtId
5. Update the count attributes on <numFmts>, <fonts>, <fills>, <cellXfs>
6. The new xf's index = (old cellXfs count) — use this as the s attribute on cells
CRITICAL RULES:
- fills[0] and fills[1] are REQUIRED BY SPEC — never remove them
- Do NOT modify existing <xf> entries — only append new ones
- AARRGGBB color format: first 2 hex digits = Alpha (00 = opaque)
-->
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<!-- ── Number Formats ─────────────────────────────────────────────────── -->
<!-- Built-in IDs 0-163 need NOT be declared here. Custom formats: 164+ -->
<numFmts count="4">
<!-- 164: Standard currency — positive $1,234 / negative ($1,234) / zero - -->
<numFmt numFmtId="164" formatCode="$#,##0;($#,##0);&quot;-&quot;"/>
<!-- 165: Percentage with 1 decimal place -->
<numFmt numFmtId="165" formatCode="0.0%"/>
<!-- 166: Multiplier / ratio (e.g. 8.5x for EV/EBITDA) -->
<numFmt numFmtId="166" formatCode="0.0x"/>
<!-- 167: Integer with thousands separator, no decimals -->
<numFmt numFmtId="167" formatCode="#,##0"/>
</numFmts>
<!-- ── Fonts ──────────────────────────────────────────────────────────── -->
<fonts count="5">
<!-- 0: Default (theme color, no explicit color) -->
<font>
<sz val="11"/>
<name val="Calibri"/>
</font>
<!-- 1: Input / Assumption — Blue -->
<font>
<sz val="11"/>
<name val="Calibri"/>
<color rgb="000000FF"/>
</font>
<!-- 2: Formula / Computed result — Black (explicit) -->
<font>
<sz val="11"/>
<name val="Calibri"/>
<color rgb="00000000"/>
</font>
<!-- 3: Cross-sheet reference — Green -->
<font>
<sz val="11"/>
<name val="Calibri"/>
<color rgb="00008000"/>
</font>
<!-- 4: Header — Bold Black -->
<font>
<b/>
<sz val="11"/>
<name val="Calibri"/>
<color rgb="00000000"/>
</font>
</fonts>
<!-- ── Fills ──────────────────────────────────────────────────────────── -->
<!-- fills[0] and fills[1] are REQUIRED by OOXML spec — DO NOT REMOVE -->
<fills count="3">
<fill><patternFill patternType="none"/></fill>
<fill><patternFill patternType="gray125"/></fill>
<!-- 2: Yellow highlight for key assumptions requiring review -->
<fill>
<patternFill patternType="solid">
<fgColor rgb="00FFFF00"/>
<bgColor indexed="64"/>
</patternFill>
</fill>
</fills>
<!-- ── Borders ────────────────────────────────────────────────────────── -->
<borders count="1">
<!-- 0: No borders (default) -->
<border>
<left/>
<right/>
<top/>
<bottom/>
<diagonal/>
</border>
</borders>
<!-- ── Cell Style Xfs (base styles) ──────────────────────────────────── -->
<cellStyleXfs count="1">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
</cellStyleXfs>
<!-- ── Cell Xfs (the actual style slots referenced by <c s="N">) ──────── -->
<cellXfs count="13">
<!-- 0: Default -->
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<!-- 1: Input / Assumption (blue, general format) -->
<xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<!-- 2: Formula / Computed (black, general format) -->
<xf numFmtId="0" fontId="2" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<!-- 3: Cross-sheet reference (green, general format) -->
<xf numFmtId="0" fontId="3" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<!-- 4: Header (bold black) -->
<xf numFmtId="0" fontId="4" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<!-- 5: Currency input (blue + $#,##0) -->
<xf numFmtId="164" fontId="1" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- 6: Currency formula (black + $#,##0) -->
<xf numFmtId="164" fontId="2" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- 7: Percentage input (blue + 0.0%) -->
<xf numFmtId="165" fontId="1" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- 8: Percentage formula (black + 0.0%) -->
<xf numFmtId="165" fontId="2" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- 9: Integer-with-commas input (blue + #,##0) -->
<xf numFmtId="167" fontId="1" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- 10: Integer-with-commas formula (black + #,##0) -->
<xf numFmtId="167" fontId="2" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- 11: Year column input (blue + plain integer "0", no comma) -->
<xf numFmtId="1" fontId="1" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- 12: Key assumption highlight (blue on yellow — needs human review) -->
<xf numFmtId="0" fontId="1" fillId="2" borderId="0" xfId="0"
applyFont="1" applyFill="1"/>
</cellXfs>
<!-- ── Named Cell Styles ─────────────────────────────────────────────── -->
<cellStyles count="1">
<cellStyle name="Normal" xfId="0" builtinId="0"/>
</cellStyles>
</styleSheet>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
workbook.xml — Defines the list of sheets.
To add a new sheet:
1. Add a <sheet> element below with a unique sheetId and r:id
2. Add a matching <Relationship> in xl/_rels/workbook.xml.rels
3. Add an <Override> in [Content_Types].xml
4. Create the xl/worksheets/sheetN.xml file
Sheet name rules:
- Max 31 characters
- Cannot contain: / \ ? * [ ] :
- Ampersand must be escaped as &amp; in XML
-->
<workbook
xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<fileVersion appName="xl" lastEdited="7" lowestEdited="7"/>
<workbookPr defaultThemeVersion="166925"/>
<bookViews>
<workbookView xWindow="0" yWindow="0" windowWidth="20140" windowHeight="10960"/>
</bookViews>
<sheets>
<!-- Add more <sheet> elements here for multi-sheet workbooks -->
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
</sheets>
<!-- calcId ensures Excel recalculates formulas on open -->
<calcPr calcId="191029"/>
</workbook>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
sheet1.xml — Worksheet data.
Cell anatomy:
<c r="A1" t="s" s="4"><v>0</v></c>
↑ ↑ ↑ ↑
address type style value (sharedStrings index for t="s")
Type values (t attribute):
s = shared string (text) — <v> contains index into sharedStrings.xml
inlineStr = inline string — use <is><t>text</t></is> instead of <v>
n (or omit)= number — <v> contains the raw number
b = boolean — <v> is 1 (TRUE) or 0 (FALSE)
e = error — <v> contains error string like #REF!
(no t) = formula cell — <f> contains formula (NO leading =), <v> is cache
Formula cells:
<c r="B5" s="2"><f>SUM(B2:B4)</f><v></v></c>
Cross-sheet: <c r="C1" s="3"><f>Assumptions!B2</f><v></v></c>
With spaces: <c r="C1" s="3"><f>'Q1 Data'!B2</f><v></v></c>
Style index (s attribute) — pre-built in styles.xml:
0 = default
1 = input/assumption (blue font)
2 = formula/computed (black font)
3 = cross-sheet reference (green font)
4 = header bold
5 = currency input (blue + $#,##0 format)
See styles.xml for the full list and how to add more.
Row r attribute must be 1-based integer.
Column letters: A=1, Z=26, AA=27, AZ=52, BA=53, BZ=78, etc.
-->
<worksheet
xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheetViews>
<sheetView tabSelected="1" workbookViewId="0">
<!-- Freeze top row as header: -->
<!-- <pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/> -->
</sheetView>
</sheetViews>
<sheetFormatPr defaultRowHeight="15" x14ac:dyDescent="0.25"
xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"/>
<!-- Column widths — uncomment and adjust as needed:
<cols>
<col min="1" max="1" width="24" customWidth="1"/>
<col min="2" max="10" width="14" customWidth="1"/>
</cols>
-->
<sheetData>
<!-- Replace this placeholder with actual data rows.
Example:
<row r="1">
<c r="A1" t="s" s="4"><v>0</v></c>
<c r="B1" t="s" s="4"><v>1</v></c>
</row>
<row r="2">
<c r="A2" t="s" s="1"><v>2</v></c>
<c r="B2" s="1"><v>1000</v></c>
</row>
<row r="3">
<c r="A3" t="s" s="2"><v>3</v></c>
<c r="B3" s="2"><f>B2*1.1</f><v></v></c>
</row>
-->
</sheetData>
<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
</worksheet>

View File

@@ -0,0 +1,104 @@
# นโยบายความเป็นส่วนตัว
**บริษัท [ชื่อบริษัท] จำกัด** ("บริษัทฯ") ให้ความสำคัญกับการคุ้มครองข้อมูลส่วนบุคคลของท่าน จึงได้จัดทำนโยบายความเป็นส่วนตัวฉบับนี้ขึ้นเพื่อชี้แจงเกี่ยวกับการเก็บรวบรวม ใช้ หรือเปิดเผยข้อมูลส่วนบุคคล รวมถึงสิทธิต่างๆ ของท่านตามพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562
## 1. ข้อมูลส่วนบุคคลที่เรา<E0B8A3>เก็บรวบรวม
ข้อมูลส่วนบุคคลที่บริษัทฯ อาจเก็บรวบรวมจากท่าน อาจรวมถึง:
- **ข้อมูลส่วนบุคคลทั่วไป:** ชื่อ-นามสกุล ที่อยู่อีเมล หมายเลขโทรศัพท์ ที่อยู่ไปรษณีย์ วันเดือนปีเกิด
- **ข้อมูลการติดต่อ:** ข้อมูลการติดต่อที่ท่านให้ไว้เมื่อลงทะเบียน กรอกแบบฟอร์ม หรือติดต่อบริษัทฯ
- **ข้อมูลการใช้งาน:** IP address, ข้อมูลการเข้าชมเว็บไซต์, คุกกี้, ข้อมูลกิจกรรมการใช้งาน
- **ข้อมูลการเงิน:** ข้อมูลบัตรเครดิต/เดบิต ข้อมูลการชำระเงิน (กรณีใช้บริการที่มีค่าใช้จ่าย)
- **ข้อมูลอื่นที่ท่านให้ไว้:** ข้อมูลใดๆ ที่ท่านให้ไว้โดยสมัครใจผ่านช่องทางการติดต่อของบริษัทฯ
## 2. วัตถุประสงค์ในการเก็บรวบรวมข้อมูล
บริษัทฯ เก็บรวบรวมข้อมูลส่วนบุคคลเพื่อวัตถุประสงค์ดังต่อไปนี้:
- เพื่อให้บริการและดำเนินการตามคำขอของท่าน
- เพื่อการสื่อสารและให้ข้อมูลข่าวสารเกี่ยวกับบริการของบริษัทฯ
- เพื่อปรับปรุงคุณภาพบริการและพัฒนาเว็บไซต์
- เพื่อการวิเคราะห์ข้อมูลและสถิติการใช้งาน
- เพื่อการตลาดและการโฆษณา (ได้รับความยินยอมจากท่าน)
- เพื่อการปฏิบัติตามกฎหมาย คำสั่ง หรือกระบวนการทางกฎหมาย
- เพื่อการรักษาความปลอดภัยและป้องกันการทุจริต
## 3. การใช้และเปิดเผยข้อมูลส่วนบุคคล
บริษัทฯ จะไม่เปิดเผยข้อมูลส่วนบุคคลของท่านต่อบุคคลที่สาม เว้นแต่:
- ได้รับความยินยอมจากท่าน
- จำเป็นต้องเปิดเผยเพื่อให้บริการตามคำขอของท่าน
- จำเป็นต้องเปิดเผยต่อผู้ให้บริการที่บริษัทฯ ว่าจ้างให้ดำเนินการในส่วนที่จำเป็น
- กฎหมายกำหนดหรือร้ณขอให้เปิดเผย
- เพื่อป้องกันหรือระงับอันตรายต่อชีวิต สุขภาพ หรือทรัพย์สิน
- จำเป็นเพื่อประโยชน์โดยชอบด้วยกฎหมายของบริษัทฯ
## 4. ระยะเวลาการเก็บรักษาข้อมูล
บริษัทฯ จะเก็บรักษาข้อมูลส่วนบุคคลของท่านตราบเท่าที่จำเป็นเพื่อบรรลุวัตถุประสงค์ที่ระบุไว้ในนโยบายนี้ เว้นแต่กฎหมายกำหนดให้เก็บรักษาไว้นานกว่านั้น
ท่านสามารถขอให้บริษัทฯ ลบหรือทำลายข้อมูลส่วนบุคคลของท่านได้ตามสิทธิ์ของท่านในข้อ 7
## 5. การคุ้มครองข้อมูลส่วนบุคคล
บริษัทฯ มีมาตรการรักษาความปลอดภัยที่เหมาะสมเพื่อป้องกันการสูญหาย เข้าถึง ใช้ เปลี่ยนแปลง แก้ไข หรือเปิดเผยข้อมูลส่วนบุคคลโดยไม่ได้รับอนุญาต รวมถึงมาตรการทางเทคนิคและองค์กรที่จำเป็น
## 6. การใช้คุกกี้
เว็บไซต์ของบริษัทฯ อาจใช้คุกกี้และเทคโนโลยีที่คล้ายคลึงกันเพื่อ:
- จดจำการตั้งค่าของท่าน
- วิเคราะห์การใช้งานเว็บไซต์
- ปรับปรุงประสบการณ์การใช้งาน
- แสดงเนื้อหาและโฆษณาที่ท่านสนใจ
ท่านสามารถตั้งค่าเบราว์เซอร์ของท่านเพื่อปฏิเสธคุกกี้บางประเภทหรือทั้งหมดได้ แต่การปฏิเสธคุกกี้อาจส่งผลต่อการทำงานของเว็บไซต์
## 7. สิทธิของเจ้าของข้อมูล
ตามพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล ท่านมีสิทธิดังต่อไปนี้:
**7.1 สิทธิในการเข้าถึง**
ท่านมีสิทธิขอเข้าถึงและขอรับสำเนาข้อมูลส่วนบุคคลของท่านที่บริษัทฯ มีอยู่
**7.2 สิทธิในการแก้ไข**
ท่านมีสิทธิขอให้บริษัทฯ แก้ไขข้อมูลส่วนบุคคลที่ไม่ถูกต้องหรือไม่สมบูรณ์
**7.3 สิทธิในการลบ**
ท่านมีสิทธิขอให้บริษัทฯ ลบข้อมูลส่วนบุคคลของท่าน ในกรณีที่ข้อมูลนั้นไม่จำเป็นต้องเก็บรักษาไว้ต่อไป
**7.4 สิทธิในการระงับการใช้**
ท่านมีสิทธิขอให้บริษัทฯ ระงับการใช้ข้อมูลส่วนบุคคลของท่านในบางกรณี
**7.5 สิทธิในการคัดค้าน**
ท่านมีสิทธิคัดค้านการเก็บรวบรวม ใช้ หรือเปิดเผยข้อมูลส่วนบุคคลของท่าน
**7.6 สิทธิในการโอนย้าย**
ท่านมีสิทธิขอรับข้อมูลส่วนบุคคลในรูปแบบที่สามารถอ่านได้ด้วยเครื่องมือหรืออุปกรณ์อัตโนมัติ และขอส่งหรือโอนข้อมูลนั้นไปยังระบบอื่น
**7.7 สิทธิในการถอนความยินยอม**
ท่านมีสิทธิถอนความยินยอมที่ท่านได้ให้ไว้แก่บริษัทฯ ได้ตลอดเวลา
**7.8 สิทธิในการร้องเรียน**
ท่านมีสิทธิร้องเรียนต่อหน่วยงานกำกับดูแล (สำนักงานคณะกรรมการคุ้มครองข้อมูลส่วนบุคคล) หากบริษัทฯ ละเมิดหรือไม่ปฏิบัติตามพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล
## 8. การติดต่อบริษัทฯ
หากท่านมีคำถาม ข้อสงสัย หรือต้องการใช้สิทธิใดๆ ตามนโยบายนี้ กรุณาติดต่อบริษัทฯ:
**บริษัท [ชื่อบริษัท] จำกัด**
**ที่อยู่:** [ที่อยู่บริษัท]
**โทรศัพท์:** [หมายเลขโทรศัพท์]
**อีเมล:** [อีเมลติดต่อ]
## 9. การเปลี่ยนแปลงนโยบาย
บริษัทฯ อาจปรับปรุงหรือเปลี่ยนแปลงนโยบายความเป็นส่วนตัวนี้เป็นครั้งคราว การเปลี่ยนแปลงจะมีผลเมื่อประกาศบนเว็บไซต์ ท่านควรตรวจสอบนโยบายนี้เป็นระยะเพื่อรับทราบการเปลี่ยนแปลง
**วันที่มีผลบังคับใช้:** [วันที่เริ่มใช้]
---
*นโยบายความเป็นส่วนตัวฉบับนี้จัดทำขึ้นตามพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562*

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Architecture</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.arch{margin-top:20px;display:grid;grid-template-rows:auto auto auto;gap:22px}
.arch .tier{display:grid;grid-template-columns:120px 1fr;align-items:stretch;gap:22px}
.arch .tname{display:flex;align-items:center;justify-content:center;background:var(--surface-2);border-radius:var(--radius);padding:18px;font-weight:600;font-size:13px;color:var(--text-2);text-transform:uppercase;letter-spacing:.1em;text-align:center}
.arch .cells{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.arch .cells.three{grid-template-columns:repeat(3,1fr)}
.arch .cell{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px;text-align:center;box-shadow:var(--shadow)}
.arch .cell .ic{font-size:24px}
.arch .cell h4{font-size:14px;margin:6px 0 2px}
.arch .cell p{font-size:11px;color:var(--text-3);margin:0}
.arch .tier.hl .cell{border-top:3px solid var(--accent)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Architecture">
<p class="kicker">Architecture · 系统总览</p>
<h2 class="h2">一套三层栈</h2>
<div class="arch anim-stagger-list" data-anim-target>
<div class="tier"><div class="tname">UI Layer</div>
<div class="cells"><div class="cell"><div class="ic">🧱</div><h4>Layouts</h4><p>30 个单页</p></div>
<div class="cell"><div class="ic">🎨</div><h4>Themes</h4><p>24 tokens 主题</p></div>
<div class="cell"><div class="ic"></div><h4>Animations</h4><p>25 个命名动效</p></div>
<div class="cell"><div class="ic">⌨️</div><h4>Runtime</h4><p>键盘导航</p></div>
</div>
</div>
<div class="tier hl"><div class="tname">Core · tokens</div>
<div class="cells three"><div class="cell"><div class="ic">🎯</div><h4>base.css</h4><p>排版 + 网格</p></div>
<div class="cell"><div class="ic">🔤</div><h4>fonts.css</h4><p>中英字体</p></div>
<div class="cell"><div class="ic">🪞</div><h4>:root vars</h4><p>语义颜色</p></div>
</div>
</div>
<div class="tier"><div class="tname">Tooling</div>
<div class="cells three"><div class="cell"><div class="ic">🧪</div><h4>render.sh</h4><p>headless Chrome</p></div>
<div class="cell"><div class="ic">🆕</div><h4>new-deck.sh</h4><p>脚手架</p></div>
<div class="cell"><div class="ic">📦</div><h4>AgentSkill</h4><p>Claude 接入点</p></div>
</div>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Big Quote</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/editorial-serif.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active center tc" data-title="Quote">
<div style="max-width:1040px">
<div class="serif" style="font-size:140px;line-height:.9;color:var(--accent);opacity:.6">"</div>
<blockquote class="serif anim-fade-up" data-anim="fade-up" style="font-size:56px;line-height:1.25;margin:-40px 0 24px;font-style:italic;font-weight:600">
好的设计不是把东西加得更多,<br>而是把一切不需要的东西拿掉。
</blockquote>
<p class="dim" style="font-size:20px;letter-spacing:.08em">— Dieter Rams</p>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Bullets</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Why">
<p class="kicker">Why · 为什么</p>
<h2 class="h2">好的演讲系统,帮你做三件事</h2>
<p class="lede mb-l">不是做不出漂亮的 slide是每次都要重新做。</p>
<ul class="grid g1 anim-stagger-list" style="list-style:none;padding:0;margin:0;gap:14px" data-anim-target>
<li class="card card-accent"><h4>① 统一设计语言</h4><p class="dim">所有页共用同一套 tokens字号、颜色、阴影不会跑偏。</p></li>
<li class="card card-accent"><h4>② 降低复用成本</h4><p class="dim">下一次讲同类话题,复制 deck、换数据、换主题即可。</p></li>
<li class="card card-accent"><h4>③ 可切换,可演示</h4><p class="dim">按 T 即可循环切主题、按 A 可切动效,汇报现场加分。</p></li>
</ul>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Bar</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Bar chart">
<p class="kicker">Chart · 柱状图</p>
<h2 class="h2">季度 MRR 趋势</h2>
<div class="card mt-l" style="height:520px;padding:28px"><canvas id="c"></canvas></div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const accent=css.getPropertyValue('--accent').trim();
const text2=css.getPropertyValue('--text-2').trim();
const border=css.getPropertyValue('--border').trim();
new Chart(document.getElementById('c'),{type:'bar',
data:{labels:['Q1','Q2','Q3','Q4','Q1 +1','Q2 +1','Q3 +1','Q4 +1'],
datasets:[{label:'MRR (K)',data:[42,58,73,96,124,158,204,261],
backgroundColor:accent,borderRadius:8,barThickness:36}]},
options:{plugins:{legend:{labels:{color:text2}}},
scales:{x:{ticks:{color:text2},grid:{color:border}},
y:{ticks:{color:text2},grid:{color:border}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Line</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Line chart">
<p class="kicker">Chart · 折线图</p>
<h2 class="h2">日活与留存并排看</h2>
<div class="card mt-l" style="height:520px;padding:28px"><canvas id="c"></canvas></div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const accent=css.getPropertyValue('--accent').trim();
const acc2=css.getPropertyValue('--accent-2').trim();
const text2=css.getPropertyValue('--text-2').trim();
const border=css.getPropertyValue('--border').trim();
new Chart(document.getElementById('c'),{type:'line',
data:{labels:['W1','W2','W3','W4','W5','W6','W7','W8','W9','W10','W11','W12'],
datasets:[
{label:'DAU (K)',data:[12,14,15,19,24,28,33,38,45,51,58,66],
borderColor:accent,backgroundColor:accent+'22',fill:true,tension:.4,borderWidth:3,pointRadius:4},
{label:'Retention %',data:[38,40,42,45,48,50,53,55,58,60,62,64],
borderColor:acc2,backgroundColor:acc2+'22',fill:true,tension:.4,borderWidth:3,pointRadius:4}
]},
options:{plugins:{legend:{labels:{color:text2}}},
scales:{x:{ticks:{color:text2},grid:{color:border}},
y:{ticks:{color:text2},grid:{color:border}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Pie</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Pie chart">
<p class="kicker">Chart · 环形图</p>
<h2 class="h2">时间都花在了哪里</h2>
<div class="grid g2 mt-l" style="align-items:center">
<div class="card" style="height:460px;padding:28px"><canvas id="c"></canvas></div>
<div class="card">
<h4>Takeaways</h4>
<p class="dim">超过一半的时间都在写内容,动效只占 5%。说明值得把动效做成可复用模板。</p>
<ul class="mt-m dim">
<li>✍️ 写内容 &nbsp; 55%</li><li>🧩 挑版式 &nbsp; 18%</li><li>🎨 调样式 &nbsp; 14%</li>
<li>📸 出图 &nbsp; 8%</li><li>✨ 动效 &nbsp; 5%</li>
</ul>
</div>
</div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const colors=['--accent','--accent-2','--accent-3','--good','--warn'].map(k=>css.getPropertyValue(k).trim());
const text2=css.getPropertyValue('--text-2').trim();
new Chart(document.getElementById('c'),{type:'doughnut',
data:{labels:['写内容','挑版式','调样式','出图','动效'],
datasets:[{data:[55,18,14,8,5],backgroundColor:colors,borderWidth:0}]},
options:{cutout:'62%',plugins:{legend:{position:'right',labels:{color:text2}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Radar</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Radar chart">
<p class="kicker">Chart · 雷达图</p>
<h2 class="h2">把 html-ppt 和竞品放一张图</h2>
<div class="card mt-l" style="height:520px;padding:28px"><canvas id="c"></canvas></div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const a1=css.getPropertyValue('--accent').trim();
const a2=css.getPropertyValue('--accent-2').trim();
const text2=css.getPropertyValue('--text-2').trim();
new Chart(document.getElementById('c'),{type:'radar',
data:{labels:['上手','美观','自定义','性能','导出','生态'],
datasets:[
{label:'html-ppt',data:[9,9,10,10,9,6],borderColor:a1,backgroundColor:a1+'33',borderWidth:3,pointRadius:5},
{label:'reveal.js',data:[8,7,9,8,8,10],borderColor:a2,backgroundColor:a2+'22',borderWidth:3,pointRadius:5}]},
options:{plugins:{legend:{labels:{color:text2}}},
scales:{r:{suggestedMin:0,suggestedMax:10,ticks:{color:text2,backdropColor:'transparent'},
pointLabels:{color:text2,font:{size:14}},grid:{color:'rgba(0,0,0,.08)'},angleLines:{color:'rgba(0,0,0,.08)'}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Code</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/tokyo-night.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.10.0/styles/tokyo-night-dark.min.css">
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.10.0/lib/core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.10.0/lib/languages/javascript.min.js"></script>
<script>addEventListener('DOMContentLoaded',()=>{hljs.registerLanguage('javascript',window.hljsLangJavascript||window.hljs.getLanguage('javascript'));document.querySelectorAll('pre code').forEach(el=>hljs.highlightElement(el))})</script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Code">
<p class="kicker">Snippet · 运行时核心</p>
<h2 class="h2">按一下键盘,幻灯片就跑起来了</h2>
<pre class="card mt-m" style="padding:24px"><code class="language-javascript">// runtime.js — keyboard-driven deck
function go(n) {
n = Math.max(0, Math.min(total - 1, n));
slides.forEach((s, i) => {
s.classList.toggle('is-active', i === n);
});
idx = n;
barFill.style.width = ((n + 1) / total * 100) + '%';
history.replaceState(null, '', '#/' + (n + 1));
}
document.addEventListener('keydown', (e) =&gt; {
if (e.key === 'ArrowRight' || e.key === ' ') go(idx + 1);
if (e.key === 'ArrowLeft') go(idx - 1);
if (e.key === 't') cycleTheme();
});</code></pre>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Comparison</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.vs{display:grid;grid-template-columns:1fr 90px 1fr;gap:28px;align-items:stretch;margin-top:30px}
.vs .side{padding:30px}
.vs .mid{font-size:56px;font-weight:800;color:var(--text-3);display:flex;align-items:center;justify-content:center}
.vs .bad-side{border-top:3px solid var(--bad)}
.vs .good-side{border-top:3px solid var(--good)}
.vs h3{font-size:24px}
.vs ul{padding-left:20px;font-size:15px;line-height:1.8;color:var(--text-2)}
.vs li::marker{color:var(--bad)}
.vs .good-side li::marker{color:var(--good)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Comparison">
<p class="kicker">Before vs After · 对比</p>
<h2 class="h2">从「每次重做」到「一键起稿」</h2>
<div class="vs">
<div class="card bad-side side anim-fade-left" data-anim="fade-left">
<h3>📉 过去</h3>
<ul>
<li>每次都要重新做封面、目录、结语</li>
<li>颜色、字号跨 slide 不一致</li>
<li>想换主题?手动改每一页</li>
<li>动效全靠 transition 硬写</li>
<li>截图导出还要一张一张截</li>
</ul>
</div>
<div class="mid"></div>
<div class="card good-side side anim-fade-right" data-anim="fade-right">
<h3>📈 现在</h3>
<ul>
<li>复制模板、换数据、完事</li>
<li>所有页共享 tokens</li>
<li><b>T</b> 循环切主题</li>
<li>25 个命名动效直接挑</li>
<li>一条 render.sh 全部出图</li>
</ul>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="zh-CN" data-theme="minimal-white">
<head>
<meta charset="utf-8"><title>Cover — html-ppt</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head>
<body class="single">
<div class="deck" data-themes="minimal-white,aurora,catppuccin-mocha,tokyo-night,xiaohongshu-white,neo-brutalism" data-theme-base="../../assets/themes/">
<section class="slide is-active" data-title="Cover">
<div class="deck-header"><span class="eyebrow">Keynote · 2026</span><span class="eyebrow">html-ppt</span></div>
<div class="anim-stagger-list">
<p class="kicker">Tech Sharing · 纯干货</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">
设计一套<span class="gradient-text">属于你</span><br>HTML 演讲系统
</h1>
<p class="lede">从主题、版式到动效,全部由模板驱动。一行命令即可开场。</p>
<div class="row wrap mt-l">
<span class="pill pill-accent">24 themes</span>
<span class="pill">30 layouts</span>
<span class="pill">25 animations</span>
<span class="pill">零构建</span>
</div>
</div>
<div class="deck-footer"><span class="dim2">lewis · 2026-04-15</span><span class="slide-number" data-current="1" data-total="1"></span></div>
<div class="notes">封面页口播:大家好,今天给大家带来一套开箱即用的 HTML 演讲系统。</div>
</section>
</div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>CTA</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/aurora.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.cta .btn{display:inline-flex;align-items:center;gap:10px;padding:20px 32px;border-radius:999px;background:var(--accent);color:#0b1024;font-weight:700;font-size:20px;box-shadow:var(--shadow-lg);text-decoration:none;border:none;cursor:pointer}
.cta .btn.outline{background:transparent;color:var(--text-1);border:1.5px solid var(--border-strong)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active center tc cta" data-title="CTA">
<div style="max-width:900px">
<p class="kicker">Call to action</p>
<h1 class="h1 anim-rise-in" data-anim="rise-in" style="font-size:96px">
<span class="gradient-text">动手做你的</span><br>第一份 html-ppt
</h1>
<p class="lede" style="margin:16px auto 30px">复制模板、换上你的内容、按 <b>T</b> 挑一个最对味的主题,讲完还能一键导出 PNG。</p>
<div class="row" style="justify-content:center">
<a class="btn" href="#">🚀 ./new-deck.sh my-talk</a>
<a class="btn outline" href="#">查看 SKILL.md</a>
</div>
<p class="dim mt-l" style="font-size:14px">键盘: ← → 翻页 · T 主题 · A 动效 · F 全屏 · O 概览 · S 备注</p>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Diff</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.diff{font-family:var(--font-mono);font-size:14px;line-height:1.6;border-radius:var(--radius);overflow:hidden;border:1px solid var(--border)}
.diff .ln{display:block;padding:2px 16px;white-space:pre}
.diff .add{background:rgba(26,175,108,.12);color:var(--good)}
.diff .del{background:rgba(224,68,90,.12);color:var(--bad)}
.diff .ctx{color:var(--text-2)}
.diff .hd{background:var(--surface-2);color:var(--text-3);padding:8px 16px;font-size:12px;letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid var(--border)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Diff">
<p class="kicker">Before / After</p>
<h2 class="h2">一次迁移到 tokens 后</h2>
<div class="card diff mt-l" style="padding:0">
<div class="hd">assets/components/card.css</div>
<span class="ln ctx">.card {</span>
<span class="ln del">- background: #ffffff;</span>
<span class="ln del">- color: #0c0d10;</span>
<span class="ln del">- border-radius: 18px;</span>
<span class="ln del">- box-shadow: 0 10px 30px rgba(18,24,40,.08);</span>
<span class="ln add">+ background: var(--surface);</span>
<span class="ln add">+ color: var(--text-1);</span>
<span class="ln add">+ border-radius: var(--radius);</span>
<span class="ln add">+ box-shadow: var(--shadow);</span>
<span class="ln ctx">}</span>
</div>
<p class="dim mt-m">24 个主题从此只需改 variables——其他 0 改动。</p>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>

Some files were not shown because too many files have changed in this diff Show More