| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055 |
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <title>c2-slides-pptx · English · v2</title>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,300..700;1,8..60,300..700&family=Noto+Serif+SC:wght@300;400;500;600&family=Inter:wght@100;200;300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
- <style>
- :root {
- --bg: #000000;
- --ink: #FFFFFF;
- --ink-80: rgba(255,255,255,0.82);
- --ink-60: rgba(255,255,255,0.58);
- --muted: rgba(255,255,255,0.40);
- --dim: rgba(255,255,255,0.18);
- --hairline: rgba(255,255,255,0.12);
- --accent: #D97757;
- --accent-deep: #B85D3D;
- --cd-bg: #F5F4F0;
- --cd-panel: #FFFFFF;
- --cd-ink: #1A1918;
- --cd-dim: #8B867E;
- --cd-hair: rgba(0,0,0,0.08);
- --serif-cn: "Source Serif 4", Georgia, serif;
- --serif-en: "Source Serif 4", Georgia, serif;
- --sans: "Inter", -apple-system, system-ui, sans-serif;
- --mono: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
- }
- html, body {
- margin: 0; padding: 0;
- background: #000;
- overflow: hidden;
- font-family: var(--sans);
- color: var(--ink);
- -webkit-font-smoothing: antialiased;
- }
- * { box-sizing: border-box; }
- .stage {
- position: fixed;
- top: 50%; left: 50%;
- width: 1920px; height: 1080px;
- transform-origin: center center;
- background: var(--bg);
- overflow: hidden;
- }
- /* Film grain (2% opacity) */
- .stage::after {
- content: '';
- position: absolute; inset: 0;
- background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
- opacity: 0.025;
- pointer-events: none;
- mix-blend-mode: overlay;
- z-index: 200;
- }
- .watermark-tl {
- position: absolute;
- top: 40px; left: 56px;
- font-family: var(--mono);
- font-size: 14px;
- letter-spacing: 0.2em;
- text-transform: uppercase;
- color: rgba(255,255,255,0.16);
- z-index: 180;
- pointer-events: none;
- }
- /* ====== Beat 1: browser-fullscreen deck ====== */
- .beat1 {
- position: absolute; inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- opacity: 1;
- }
- .deck-window {
- width: 1400px;
- height: 788px;
- border-radius: 14px;
- background: #101010;
- border: 1px solid var(--hairline);
- box-shadow: 0 40px 120px -30px rgba(217,119,87,0.18),
- 0 0 0 1px rgba(255,255,255,0.03);
- position: relative;
- will-change: transform, opacity;
- }
- .deck-window .deck-body-wrap {
- position: absolute;
- top: 44px; left: 0; right: 0; bottom: 0;
- border-radius: 0 0 14px 14px;
- overflow: hidden;
- background: #0A0A0A;
- }
- .deck-chrome {
- height: 44px;
- background: #161616;
- border-bottom: 1px solid var(--hairline);
- display: flex;
- align-items: center;
- padding: 0 18px;
- gap: 14px;
- }
- .deck-chrome .traffic {
- display: flex; gap: 8px;
- }
- .deck-chrome .traffic .d {
- width: 11px; height: 11px; border-radius: 50%;
- background: var(--hairline);
- }
- .deck-chrome .url {
- flex: 1;
- text-align: center;
- font-family: var(--mono);
- font-size: 12px;
- color: var(--muted);
- letter-spacing: 0.02em;
- }
- .deck-chrome .page-count {
- font-family: var(--mono);
- font-size: 13px;
- color: var(--accent);
- letter-spacing: 0.08em;
- min-width: 60px;
- text-align: right;
- }
- .deck-slide {
- position: absolute;
- top: 0; left: 0;
- width: 100%;
- height: 100%;
- background: #0A0A0A;
- display: flex;
- flex-direction: column;
- justify-content: center;
- padding: 96px 120px;
- will-change: transform, opacity;
- }
- .deck-slide .eyebrow {
- font-family: var(--mono);
- font-size: 14px;
- color: var(--accent);
- letter-spacing: 0.24em;
- text-transform: uppercase;
- margin-bottom: 24px;
- }
- .deck-slide h1 {
- font-family: var(--serif-cn);
- font-size: 92px;
- font-weight: 500;
- line-height: 1.08;
- color: var(--ink);
- margin: 0 0 28px 0;
- letter-spacing: -0.01em;
- }
- .deck-slide .sub {
- font-family: var(--sans);
- font-size: 22px;
- color: var(--ink-60);
- line-height: 1.5;
- max-width: 780px;
- }
- .deck-slide .hairline {
- margin-top: 48px;
- width: 80px;
- height: 2px;
- background: var(--accent);
- }
- /* Key press indicator — sits below the window */
- .key-hint {
- position: absolute;
- top: calc(50% + 440px);
- left: 50%;
- transform: translateX(-50%);
- display: flex;
- align-items: center;
- gap: 14px;
- font-family: var(--mono);
- font-size: 13px;
- color: var(--muted);
- letter-spacing: 0.14em;
- opacity: 0;
- will-change: opacity;
- z-index: 30;
- }
- .key-hint .kbd {
- display: inline-flex;
- align-items: center; justify-content: center;
- width: 36px; height: 36px;
- border: 1px solid var(--hairline);
- border-radius: 6px;
- background: rgba(255,255,255,0.04);
- color: var(--ink-80);
- font-size: 14px;
- will-change: background, color, transform;
- }
- /* ====== Beat 2: split screen — HTML left, PowerPoint right ====== */
- .beat2 {
- position: absolute; inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 56px;
- opacity: 0;
- padding: 0 96px;
- will-change: opacity;
- }
- .split-window {
- width: 820px;
- height: 580px;
- border-radius: 12px;
- overflow: hidden;
- position: relative;
- will-change: transform, opacity;
- }
- /* Left: HTML deck shrunk */
- .split-left {
- background: #0A0A0A;
- border: 1px solid var(--hairline);
- box-shadow: 0 30px 80px -30px rgba(0,0,0,0.6);
- }
- .split-left .mini-chrome {
- height: 30px;
- background: #161616;
- border-bottom: 1px solid var(--hairline);
- display: flex;
- align-items: center;
- padding: 0 12px;
- gap: 8px;
- }
- .split-left .mini-chrome .d {
- width: 8px; height: 8px; border-radius: 50%;
- background: var(--hairline);
- }
- .split-left .mini-chrome .label {
- margin-left: 10px;
- font-family: var(--mono);
- font-size: 11px;
- color: var(--muted);
- letter-spacing: 0.08em;
- }
- .split-left .mini-slide {
- padding: 56px 64px;
- height: calc(100% - 30px);
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
- .split-left .mini-eye {
- font-family: var(--mono);
- font-size: 11px;
- color: var(--accent);
- letter-spacing: 0.22em;
- text-transform: uppercase;
- margin-bottom: 16px;
- }
- .split-left .mini-title {
- font-family: var(--serif-cn);
- font-size: 54px;
- font-weight: 500;
- line-height: 1.1;
- color: var(--ink);
- letter-spacing: -0.01em;
- }
- .split-left .mini-sub {
- margin-top: 20px;
- font-family: var(--sans);
- font-size: 15px;
- color: var(--ink-60);
- line-height: 1.5;
- }
- .split-left .mini-hair {
- margin-top: 28px;
- width: 52px; height: 2px;
- background: var(--accent);
- }
- /* Right: PowerPoint chrome */
- .split-right {
- background: #F3F2EE;
- border: 1px solid rgba(0,0,0,0.2);
- box-shadow: 0 30px 80px -30px rgba(0,0,0,0.6);
- }
- .ppt-titlebar {
- height: 32px;
- background: #C44A36;
- display: flex;
- align-items: center;
- padding: 0 14px;
- gap: 10px;
- color: #fff;
- font-family: var(--sans);
- font-size: 12px;
- font-weight: 500;
- letter-spacing: 0.02em;
- }
- .ppt-titlebar .pp-logo {
- width: 18px; height: 18px;
- background: #fff;
- border-radius: 2px;
- display: inline-flex;
- align-items: center; justify-content: center;
- color: #C44A36;
- font-weight: 700;
- font-size: 11px;
- font-family: var(--sans);
- }
- .ppt-titlebar .title-text { opacity: 0.92; }
- .ppt-titlebar .win-dots {
- margin-left: auto;
- display: flex; gap: 10px;
- opacity: 0.7;
- }
- .ppt-titlebar .win-dots span {
- width: 10px; height: 10px; border: 1px solid rgba(255,255,255,0.7);
- border-radius: 1px;
- }
- .ppt-toolbar {
- height: 40px;
- background: #EAE8E3;
- border-bottom: 1px solid rgba(0,0,0,0.08);
- display: flex;
- align-items: center;
- padding: 0 14px;
- gap: 14px;
- font-family: var(--sans);
- font-size: 12px;
- color: #4A4843;
- }
- .ppt-toolbar .tool {
- display: flex; align-items: center; gap: 6px;
- padding: 4px 10px;
- border-radius: 4px;
- }
- .ppt-toolbar .tool.active {
- background: #fff;
- border: 1px solid rgba(0,0,0,0.08);
- color: var(--cd-ink);
- }
- .ppt-toolbar .tool .ico {
- width: 14px; height: 14px;
- border: 1px solid currentColor;
- border-radius: 2px;
- opacity: 0.7;
- }
- .ppt-toolbar .font-name {
- padding: 4px 10px;
- background: #fff;
- border: 1px solid rgba(0,0,0,0.12);
- border-radius: 3px;
- min-width: 140px;
- font-size: 12px;
- color: var(--cd-ink);
- display: flex; align-items: center; justify-content: space-between;
- }
- .ppt-toolbar .divider {
- width: 1px; height: 20px;
- background: rgba(0,0,0,0.08);
- }
- /* PPT canvas (the actual slide) */
- .ppt-canvas {
- height: calc(100% - 32px - 40px);
- background: #D8D4CB;
- padding: 24px;
- position: relative;
- overflow: hidden;
- }
- .ppt-slide {
- background: #0A0A0A;
- border-radius: 3px;
- width: 100%;
- height: 100%;
- padding: 56px 64px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- position: relative;
- box-shadow: 0 4px 16px rgba(0,0,0,0.18);
- }
- .ppt-slide .ppt-eye {
- font-family: var(--mono);
- font-size: 11px;
- color: var(--accent);
- letter-spacing: 0.22em;
- text-transform: uppercase;
- margin-bottom: 16px;
- }
- .ppt-slide .ppt-title-frame {
- position: relative;
- display: inline-block;
- padding: 6px 10px;
- margin: -6px -10px;
- border-radius: 2px;
- transition: box-shadow 0.12s ease;
- align-self: flex-start;
- max-width: fit-content;
- min-width: 160px;
- }
- .ppt-slide .ppt-title-frame.selected {
- box-shadow:
- 0 0 0 1px rgba(217,119,87,0.0),
- inset 0 0 0 0 rgba(217,119,87,0.0);
- }
- .ppt-slide .ppt-title-frame.editing {
- box-shadow:
- 0 0 0 1.5px var(--accent),
- 0 0 0 3px rgba(217,119,87,0.2);
- }
- .ppt-slide .ppt-title {
- font-family: var(--serif-cn);
- font-size: 54px;
- font-weight: 500;
- line-height: 1.1;
- color: var(--ink);
- letter-spacing: -0.01em;
- display: inline;
- position: relative;
- }
- .ppt-slide .edit-caret {
- display: inline-block;
- width: 2px;
- height: 52px;
- background: var(--accent);
- vertical-align: -8px;
- margin: 0 2px;
- opacity: 0;
- }
- .ppt-slide .ppt-sub {
- margin-top: 20px;
- font-family: var(--sans);
- font-size: 15px;
- color: var(--ink-60);
- line-height: 1.5;
- }
- .ppt-slide .ppt-hair {
- margin-top: 28px;
- width: 52px; height: 2px;
- background: var(--accent);
- }
- /* Selection handles (corners) */
- .ppt-slide .ppt-title-frame .handle {
- position: absolute;
- width: 8px; height: 8px;
- background: var(--accent);
- border: 1.5px solid #fff;
- border-radius: 1px;
- opacity: 0;
- pointer-events: none;
- }
- .ppt-slide .ppt-title-frame .handle.tl { top: -4px; left: -4px; }
- .ppt-slide .ppt-title-frame .handle.tr { top: -4px; right: -4px; }
- .ppt-slide .ppt-title-frame .handle.bl { bottom: -4px; left: -4px; }
- .ppt-slide .ppt-title-frame .handle.br { bottom: -4px; right: -4px; }
- .ppt-slide .ppt-title-frame.selected .handle { opacity: 1; }
- .ppt-slide .ppt-title-frame.editing .handle { opacity: 0; }
- /* Mouse cursor */
- .cursor {
- position: absolute;
- top: 0; left: 0;
- width: 22px; height: 30px;
- pointer-events: none;
- z-index: 50;
- opacity: 0;
- will-change: transform, opacity;
- filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
- }
- .cursor svg { width: 100%; height: 100%; }
- /* Double-click ripple */
- .dblclick-ripple {
- position: absolute;
- top: 0; left: 0;
- width: 20px; height: 20px;
- border: 2px solid var(--accent);
- border-radius: 50%;
- pointer-events: none;
- z-index: 45;
- opacity: 0;
- will-change: transform, opacity;
- }
- /* Connection line between two windows */
- .connector {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 56px;
- height: 120px;
- display: flex;
- align-items: center;
- justify-content: center;
- opacity: 0;
- will-change: opacity;
- z-index: 10;
- }
- .connector svg { width: 100%; height: 100%; }
- .connector-label {
- position: absolute;
- top: calc(50% + 72px);
- left: 50%;
- transform: translateX(-50%);
- font-family: var(--mono);
- font-size: 12px;
- color: var(--accent);
- letter-spacing: 0.12em;
- white-space: nowrap;
- opacity: 0;
- will-change: opacity;
- }
- /* Stage labels above windows */
- .split-label {
- position: absolute;
- top: -48px;
- left: 0;
- font-family: var(--mono);
- font-size: 16px;
- color: var(--ink-60);
- letter-spacing: 0.18em;
- text-transform: uppercase;
- opacity: 0;
- will-change: opacity;
- white-space: nowrap;
- }
- .split-label .em { color: var(--accent); }
- /* ====== Brand Reveal (米色面板 · hero-v10 系列 signature) ====== */
- .brand-panel {
- position: absolute;
- inset: 0;
- background: var(--cd-bg);
- transform: translateY(100%);
- will-change: transform;
- z-index: 80;
- }
- .brand-reveal {
- position: absolute;
- inset: 0;
- z-index: 81;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- opacity: 0;
- pointer-events: none;
- will-change: opacity;
- }
- .brand-reveal .brand-wordmark {
- font-family: var(--serif-en);
- font-size: 72px;
- font-weight: 100;
- font-variation-settings: "wght" 100;
- letter-spacing: -0.01em;
- color: var(--cd-ink);
- line-height: 1;
- opacity: 0;
- will-change: opacity, transform, font-variation-settings;
- }
- .brand-reveal .brand-wordmark .accent {
- color: var(--accent);
- font-weight: inherit;
- }
- .brand-reveal .brand-line {
- width: 0;
- height: 2px;
- background: var(--accent);
- margin-top: 60px;
- will-change: width;
- }
- </style>
- </head>
- <body>
- <div class="stage" id="stage">
- <div class="watermark-tl">HUASHU · DESIGN</div>
- <!-- ====== Beat 1 ====== -->
- <div class="beat1" id="beat1">
- <div class="deck-window" id="deckWindow">
- <div class="deck-chrome">
- <div class="traffic"><span class="d"></span><span class="d"></span><span class="d"></span></div>
- <div class="url">localhost:8080 / deck · presenting</div>
- <div class="page-count" id="pageCount">3 / 12</div>
- </div>
- <div class="deck-body-wrap">
- <div class="deck-slide" id="slideA">
- <div class="eyebrow">AI PSYCHOLOGY · 03</div>
- <h1>The Mind<br/>is Plastic</h1>
- <div class="sub">Agents aren't tools. They have preferences.</div>
- <div class="hairline"></div>
- </div>
- <div class="deck-slide" id="slideB" style="opacity:0; transform: translateX(60px);">
- <div class="eyebrow">AI PSYCHOLOGY · 04</div>
- <h1>Injection<br/>& Steering</h1>
- <div class="sub">A world hides in the parameters.</div>
- <div class="hairline"></div>
- </div>
- </div>
- </div>
- <div class="key-hint" id="keyHint">
- <span>PRESS</span>
- <span class="kbd" id="kbdKey">→</span>
- </div>
- </div>
- <!-- ====== Beat 2: Split Screen ====== -->
- <div class="beat2" id="beat2">
- <!-- LEFT: HTML deck -->
- <div class="split-col" style="position: relative;">
- <div class="split-label" id="labelLeft">HTML · <span class="em">READ-ONLY</span></div>
- <div class="split-window split-left" id="splitLeft">
- <div class="mini-chrome">
- <span class="d"></span><span class="d"></span><span class="d"></span>
- <span class="label">localhost:8080/deck</span>
- </div>
- <div class="mini-slide">
- <div class="mini-eye">AI PSYCHOLOGY · 03</div>
- <div class="mini-title">The Mind<br/>is Plastic</div>
- <div class="mini-sub">Agents aren't tools. They have preferences.</div>
- <div class="mini-hair"></div>
- </div>
- </div>
- </div>
- <!-- Connector -->
- <div class="connector" id="connector">
- <svg viewBox="0 0 56 120" fill="none">
- <line x1="4" y1="60" x2="52" y2="60" stroke="#D97757" stroke-width="1.5" stroke-dasharray="4 4"/>
- <polygon points="44,54 54,60 44,66" fill="#D97757"/>
- </svg>
- </div>
- <div class="connector-label" id="connectorLabel">html2pptx.js</div>
- <!-- RIGHT: PowerPoint -->
- <div class="split-col" style="position: relative;">
- <div class="split-label" id="labelRight">PowerPoint · <span class="em">EDITABLE TEXT</span></div>
- <div class="split-window split-right" id="splitRight">
- <div class="ppt-titlebar">
- <div class="pp-logo">P</div>
- <div class="title-text">ai-psychology-talk.pptx - PowerPoint</div>
- <div class="win-dots"><span></span><span></span><span></span></div>
- </div>
- <div class="ppt-toolbar">
- <div class="tool">
- <span class="ico"></span>
- <span class="font-name"><span id="fontName">Source Serif 4</span><span style="opacity:0.5">▾</span></span>
- </div>
- <div class="divider"></div>
- <div class="tool"><span style="font-weight:700">B</span></div>
- <div class="tool" style="font-style:italic">I</div>
- <div class="tool" style="text-decoration:underline">U</div>
- <div class="divider"></div>
- <div class="tool active"><span class="ico" style="background:#D97757;border-color:#D97757"></span></div>
- </div>
- <div class="ppt-canvas">
- <div class="ppt-slide">
- <div class="ppt-eye">AI PSYCHOLOGY · 03</div>
- <div class="ppt-title-frame" id="titleFrame">
- <span class="handle tl"></span>
- <span class="handle tr"></span>
- <span class="handle bl"></span>
- <span class="handle br"></span>
- <span class="ppt-title" id="titleText">The Mind is Plastic</span><span class="edit-caret" id="caret"></span>
- </div>
- <div class="ppt-sub">Agents aren't tools. They have preferences.</div>
- <div class="ppt-hair"></div>
- </div>
- <!-- Cursor arrow -->
- <div class="cursor" id="cursor">
- <svg viewBox="0 0 22 30" fill="none">
- <path d="M2 2 L2 22 L8 17 L12 26 L16 24 L12 15 L20 14 Z"
- fill="#1A1918" stroke="#fff" stroke-width="1.2" stroke-linejoin="round"/>
- </svg>
- </div>
- <!-- Double-click ripple -->
- <div class="dblclick-ripple" id="ripple"></div>
- </div>
- </div>
- </div>
- </div>
- <!-- ====== Brand Reveal (米色面板 · hero-v10 signature) ====== -->
- <div class="brand-panel" id="brandPanel"></div>
- <div class="brand-reveal" id="brandReveal">
- <div class="brand-wordmark" id="wordmark">huashu<span class="accent">-</span>design</div>
- <div class="brand-line" id="brandLine"></div>
- </div>
- </div>
- <script>
- (function() {
- // ---------- Fit stage ----------
- const stage = document.getElementById('stage');
- function rescale() {
- const s = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
- stage.style.transform = `translate(-50%, -50%) scale(${s})`;
- }
- rescale();
- window.addEventListener('resize', rescale);
- // ---------- Easings ----------
- const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
- const expoOut = t => (t <= 0) ? 0 : (t >= 1) ? 1 : 1 - Math.pow(2, -10 * t);
- const expoIn = t => (t <= 0) ? 0 : (t >= 1) ? 1 : Math.pow(2, 10 * (t - 1));
- const easeOut = t => 1 - Math.pow(1 - t, 3);
- const easeInOut = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2, 3)/2;
- function lerp(time, start, end, fromV, toV, ease) {
- if (time <= start) return fromV;
- if (time >= end) return toV;
- let p = (time - start) / (end - start);
- if (ease) p = ease(p);
- return fromV + (toV - fromV) * p;
- }
- function clampLerp(time, start, end) {
- if (time <= start) return 0;
- if (time >= end) return 1;
- return (time - start) / (end - start);
- }
- // ---------- Timeline (10s total) ----------
- const T = {
- DURATION: 10.0,
- // Beat 1: 0 - 2s
- deckIn: [0.15, 0.9], // browser fade+rise
- keyHintIn: [0.6, 1.1],
- keyPress: [1.25, 1.4], // arrow key highlight
- slideFlip: [1.3, 1.9], // slide A→B
- beat1Out: [2.0, 2.4],
- // Beat 2: split screen: 2.2 - 8.0s
- beat2In: [2.3, 2.9],
- labelsIn: [3.0, 3.5],
- cursorIn: [3.1, 3.4], // cursor arrives on right side
- cursorMove1: [3.4, 4.1], // cursor moves to title
- dblclick: [4.1, 4.3], // double click
- frameSelect: [4.15, 4.35], // frame shows handles
- frameEdit: [4.4, 4.55], // frame enters edit mode
- caretShowStart: 4.5,
- textDelete: [4.6, 5.4], // delete original text char by char
- textRetype: [5.5, 7.2], // type new text char by char
- commitEdit: [7.3, 7.5], // exit edit mode
- connectorIn: [3.3, 3.9],
- beat2Out: [8.0, 8.3], // main scene fades to 0 (0.3s)
- // Brand Reveal (米色面板 · hero-v10 signature): 8.3 - 10s
- // panelRise 与 beat2Out 微重叠 0.05s,避免黑屏间隙
- panelRise: [8.25, 8.7], // 米色面板 translateY 100%→0 (expoOut)
- wordmarkIn: [8.7, 9.3], // wordmark opacity 0→1 + translateY 20→0 + weight 100→500 (0.6s, expoOut)
- brandLineIn: [9.3, 9.7], // brand-line expand 0→280px (0.4s, cubicOut)
- brandHold: [9.7, 10.0], // hold (0.3s)
- };
- // ---------- Elements ----------
- const beat1 = document.getElementById('beat1');
- const beat2 = document.getElementById('beat2');
- const brandReveal = document.getElementById('brandReveal');
- const deckWindow = document.getElementById('deckWindow');
- const pageCount = document.getElementById('pageCount');
- const slideA = document.getElementById('slideA');
- const slideB = document.getElementById('slideB');
- const keyHint = document.getElementById('keyHint');
- const kbdKey = document.getElementById('kbdKey');
- const splitLeft = document.getElementById('splitLeft');
- const splitRight = document.getElementById('splitRight');
- const labelLeft = document.getElementById('labelLeft');
- const labelRight = document.getElementById('labelRight');
- const connector = document.getElementById('connector');
- const connectorLabel = document.getElementById('connectorLabel');
- const cursor = document.getElementById('cursor');
- const ripple = document.getElementById('ripple');
- const titleFrame = document.getElementById('titleFrame');
- const titleText = document.getElementById('titleText');
- const caret = document.getElementById('caret');
- const panel = document.getElementById('brandPanel');
- const wordmark = document.getElementById('wordmark');
- const brandLine = document.getElementById('brandLine');
- // Text to animate
- const ORIG_TEXT = 'The Mind is Plastic';
- const NEW_TEXT = 'Mind · Plastic';
- // ---------- Render ----------
- function render(t) {
- /* ======= Beat 1 ======= */
- let beat1Op;
- if (t < T.beat1Out[0]) {
- beat1Op = lerp(t, T.deckIn[0], T.deckIn[1], 0, 1, expoOut);
- } else {
- beat1Op = 1 - clampLerp(t, T.beat1Out[0], T.beat1Out[1]);
- }
- beat1.style.opacity = beat1Op;
- beat1.style.visibility = beat1Op > 0.01 ? 'visible' : 'hidden';
- // Deck window rise
- const deckRise = lerp(t, T.deckIn[0], T.deckIn[1], 24, 0, expoOut);
- deckWindow.style.transform = `translate3d(0, ${deckRise}px, 0)`;
- // Key hint appear
- const khOp = clampLerp(t, T.keyHintIn[0], T.keyHintIn[1]);
- keyHint.style.opacity = khOp;
- // Key press flash
- const kpActive = t >= T.keyPress[0] && t < T.keyPress[1] + 0.2;
- if (kpActive) {
- const kp = clampLerp(t, T.keyPress[0], T.keyPress[1]);
- kbdKey.style.background = `rgba(217,119,87,${0.9 * (1 - kp * 0.4)})`;
- kbdKey.style.color = '#fff';
- kbdKey.style.transform = `scale(${1 - 0.08 * kp})`;
- } else {
- kbdKey.style.background = '';
- kbdKey.style.color = '';
- kbdKey.style.transform = '';
- }
- // Slide flip A→B
- if (t >= T.slideFlip[0] && t < T.slideFlip[1] + 0.2) {
- const sp = clampLerp(t, T.slideFlip[0], T.slideFlip[1]);
- const eased = expoOut(sp);
- slideA.style.opacity = 1 - eased;
- slideA.style.transform = `translateX(${-60 * eased}px)`;
- slideB.style.opacity = eased;
- slideB.style.transform = `translateX(${60 * (1 - eased)}px)`;
- // Update page count at midway
- if (sp > 0.5) pageCount.textContent = '4 / 12';
- else pageCount.textContent = '3 / 12';
- } else if (t >= T.slideFlip[1]) {
- slideA.style.opacity = 0;
- slideB.style.opacity = 1;
- slideB.style.transform = 'translateX(0)';
- pageCount.textContent = '4 / 12';
- } else {
- slideA.style.opacity = 1;
- slideA.style.transform = 'translateX(0)';
- slideB.style.opacity = 0;
- pageCount.textContent = '3 / 12';
- }
- /* ======= Beat 2 ======= */
- let beat2Op = 0;
- if (t >= T.beat2In[0] && t < T.beat2Out[1]) {
- if (t < T.beat2In[1]) beat2Op = clampLerp(t, T.beat2In[0], T.beat2In[1]);
- else if (t < T.beat2Out[0]) beat2Op = 1;
- else beat2Op = 1 - clampLerp(t, T.beat2Out[0], T.beat2Out[1]);
- }
- beat2.style.opacity = beat2Op;
- beat2.style.visibility = beat2Op > 0.01 ? 'visible' : 'hidden';
- // Windows rise in
- const splitInP = clampLerp(t, T.beat2In[0], T.beat2In[1]);
- const splitRise = lerp(t, T.beat2In[0], T.beat2In[1], 28, 0, expoOut);
- splitLeft.style.transform = `translate3d(${-8 * (1 - expoOut(splitInP))}px, ${splitRise}px, 0)`;
- splitRight.style.transform = `translate3d(${8 * (1 - expoOut(splitInP))}px, ${splitRise}px, 0)`;
- // Labels
- const labelOp = clampLerp(t, T.labelsIn[0], T.labelsIn[1]);
- labelLeft.style.opacity = labelOp * 0.7;
- labelRight.style.opacity = labelOp * 0.85;
- // Connector
- const connOp = clampLerp(t, T.connectorIn[0], T.connectorIn[1]);
- connector.style.opacity = connOp;
- connectorLabel.style.opacity = connOp * 0.9;
- /* === Cursor movement === */
- // Cursor positions (relative to .ppt-canvas, which is inside split-right)
- // Canvas starts at (0,0), size ~820 × 508 (580 - 32 - 40)
- // Title sits around x=84 y=110 (inside .ppt-slide padding 56/64)
- // We'll place cursor with absolute positioning inside .ppt-canvas.
- // Entry point: off to the right bottom of canvas
- const P_ENTER = { x: 720, y: 420 };
- const P_TITLE = { x: 250, y: 170 }; // on the title
- let cursorOp = 0;
- let cx = P_ENTER.x, cy = P_ENTER.y;
- if (t >= T.cursorIn[0] && t < T.beat2Out[0]) {
- cursorOp = 1;
- // Phase 1: appear (pop in with slight scale)
- const inP = clampLerp(t, T.cursorIn[0], T.cursorIn[1]);
- cursorOp = expoOut(inP);
- // Phase 2: move to title
- if (t >= T.cursorMove1[0]) {
- const mp = clampLerp(t, T.cursorMove1[0], T.cursorMove1[1]);
- const e = easeInOut(mp);
- cx = P_ENTER.x + (P_TITLE.x - P_ENTER.x) * e;
- cy = P_ENTER.y + (P_TITLE.y - P_ENTER.y) * e;
- } else {
- cx = P_ENTER.x;
- cy = P_ENTER.y;
- }
- // After double-click, slight jitter toward caret position during typing
- if (t >= T.textRetype[0] && t < T.textRetype[1]) {
- cx = P_TITLE.x + 6;
- cy = P_TITLE.y - 2;
- }
- } else if (t >= T.beat2Out[0]) {
- cursorOp = 1 - clampLerp(t, T.beat2Out[0], T.beat2Out[1]);
- }
- cursor.style.opacity = cursorOp;
- cursor.style.transform = `translate(${cx}px, ${cy}px)`;
- /* === Double-click ripple === */
- // Ripple pulses twice at T.dblclick start
- let rippleVisible = false;
- if (t >= T.dblclick[0] && t < T.dblclick[0] + 0.7) {
- const dt = t - T.dblclick[0];
- // Two rapid pulses
- const pulse1 = clamp(dt / 0.25, 0, 1);
- const pulse2 = clamp((dt - 0.15) / 0.25, 0, 1);
- const scale1 = 0.4 + pulse1 * 1.4;
- const scale2 = 0.4 + pulse2 * 1.4;
- const op1 = 1 - pulse1;
- const op2 = dt > 0.15 ? (1 - pulse2) : 0;
- // Render as single element: use larger of the two
- const scale = Math.max(scale1, scale2);
- const op = Math.max(op1, op2);
- ripple.style.opacity = op;
- ripple.style.transform = `translate(-50%, -50%) translate(${P_TITLE.x + 6}px, ${P_TITLE.y + 26}px) scale(${scale})`;
- rippleVisible = true;
- }
- if (!rippleVisible) ripple.style.opacity = 0;
- /* === Frame states: selected → editing === */
- titleFrame.classList.remove('selected', 'editing');
- if (t >= T.frameSelect[0] && t < T.frameEdit[0]) {
- titleFrame.classList.add('selected');
- } else if (t >= T.frameEdit[0] && t < T.commitEdit[1]) {
- titleFrame.classList.add('editing');
- }
- /* === Text animation: delete → retype === */
- let displayedText = ORIG_TEXT;
- let caretOp = 0;
- if (t < T.textDelete[0]) {
- displayedText = ORIG_TEXT;
- caretOp = t >= T.caretShowStart ? 1 : 0;
- } else if (t < T.textDelete[1]) {
- // Delete: remove chars from end
- const dp = clampLerp(t, T.textDelete[0], T.textDelete[1]);
- const charsToRemove = Math.floor(dp * ORIG_TEXT.length);
- displayedText = ORIG_TEXT.slice(0, ORIG_TEXT.length - charsToRemove);
- caretOp = 1;
- } else if (t < T.textRetype[0]) {
- displayedText = '';
- caretOp = 1;
- } else if (t < T.textRetype[1]) {
- // Retype new text
- const rp = clampLerp(t, T.textRetype[0], T.textRetype[1]);
- const charsToShow = Math.floor(rp * NEW_TEXT.length);
- displayedText = NEW_TEXT.slice(0, charsToShow);
- caretOp = 1;
- } else if (t < T.commitEdit[1]) {
- displayedText = NEW_TEXT;
- // Caret blinks while still in edit mode
- caretOp = (Math.floor(t * 2) % 2 === 0) ? 1 : 0.3;
- } else {
- displayedText = NEW_TEXT;
- caretOp = 0;
- }
- // Blinking during idle-in-edit phases (when not actively typing/deleting)
- if (t >= T.caretShowStart && t < T.textDelete[0]) {
- caretOp = (Math.floor((t - T.caretShowStart) * 3) % 2 === 0) ? 1 : 0.35;
- }
- titleText.textContent = displayedText;
- caret.style.opacity = caretOp;
- /* ======= Brand Reveal (米色面板 · hero-v10 signature) ======= */
- // Panel rises from bottom (米色面板 #F5F4F0)
- const panelP = clampLerp(t, T.panelRise[0], T.panelRise[1]);
- panel.style.transform = `translateY(${(1 - expoOut(panelP)) * 100}%)`;
- // brand-reveal container visible once panel starts rising
- brandReveal.style.opacity = panelP > 0.01 ? 1 : 0;
- // Wordmark: opacity 0→1 + translateY 20→0 + weight 100→500 (expoOut)
- const wmP = clampLerp(t, T.wordmarkIn[0], T.wordmarkIn[1]);
- const wmEased = expoOut(wmP);
- wordmark.style.opacity = wmEased;
- const wmRise = (1 - wmEased) * 20;
- wordmark.style.transform = `translate3d(0, ${wmRise}px, 0)`;
- const w = 100 + (500 - 100) * wmEased;
- wordmark.style.fontVariationSettings = `"wght" ${w.toFixed(0)}`;
- wordmark.style.fontWeight = Math.round(w);
- // Brand line expand 0→280px (cubicOut)
- const lineP = clampLerp(t, T.brandLineIn[0], T.brandLineIn[1]);
- const cubicOut = x => 1 - Math.pow(1 - x, 3);
- brandLine.style.width = (280 * cubicOut(lineP)) + 'px';
- }
- // ---------- Driver ----------
- let manualT = null;
- let startMs = null;
- let hasFinished = false;
- function tick(now) {
- if (manualT != null) render(manualT);
- else {
- if (startMs == null) startMs = now;
- const elapsed = (now - startMs) / 1000;
- const recording = window.__recording === true;
- let t;
- if (recording) {
- t = Math.min(elapsed, T.DURATION - 0.001);
- if (elapsed >= T.DURATION) hasFinished = true;
- } else {
- t = elapsed % T.DURATION;
- }
- render(t);
- }
- requestAnimationFrame(tick);
- }
- // Force first-frame render synchronously, THEN set ready
- render(0);
- requestAnimationFrame(tick);
- window.__setTime = function(t) { manualT = t; render(t); };
- window.__resume = function() { manualT = null; startMs = null; };
- window.__duration = T.DURATION;
- window.__render = render;
- window.__ready = true;
- })();
- </script>
- </body>
- </html>
|