Add MDecks launch page (ventures/mdecks/index.html)
This commit is contained in:
1 parent
145ca056d5
commit
6fe045a7e6
1 file changed
+642
@@ -0,0 +1,642 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MDecks — plain text in, clean slides out</title>
|
||||
<meta name="description" content="Type markdown-ish slides, watch a clean navigable deck render live, copy it as one self-contained HTML file. Free, runs entirely in your browser.">
|
||||
<style>
|
||||
:root {
|
||||
--paper: #f4f1e8;
|
||||
--paper-2: #ede9dd;
|
||||
--ink: #1c1a16;
|
||||
--ink-soft: #4a463d;
|
||||
--mute: #6d675b;
|
||||
--rule: #c9c1ae;
|
||||
--accent: #b8400f;
|
||||
--accent-dark: #93320a;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.wrap { max-width: 1060px; margin: 0 auto; padding: 0 22px; }
|
||||
a { color: var(--ink); text-decoration-color: var(--accent); text-underline-offset: 3px; }
|
||||
a:hover { color: var(--accent); }
|
||||
|
||||
/* ---------- masthead / hero ---------- */
|
||||
.topline {
|
||||
display: flex; justify-content: space-between; align-items: baseline;
|
||||
padding: 14px 0 10px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
|
||||
color: var(--mute);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
header.hero { border-bottom: 3px double var(--ink); padding: clamp(26px, 6vh, 64px) 0 clamp(22px, 4vh, 40px); }
|
||||
.kicker {
|
||||
margin: 0 0 10px;
|
||||
font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
letter-spacing: .22em; text-transform: uppercase; color: var(--accent);
|
||||
}
|
||||
h1.nameplate {
|
||||
margin: 0;
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
|
||||
font-weight: 700;
|
||||
font-size: clamp(52px, 15vw, 128px);
|
||||
line-height: .88;
|
||||
letter-spacing: -.035em;
|
||||
}
|
||||
h1.nameplate em { font-style: italic; color: var(--accent); }
|
||||
.promise {
|
||||
margin: 14px 0 20px;
|
||||
font-size: clamp(17px, 2.6vw, 22px);
|
||||
line-height: 1.45;
|
||||
max-width: 34em;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.hero-actions { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background: var(--ink); color: var(--paper);
|
||||
border: 1.5px solid var(--ink);
|
||||
padding: 12px 26px;
|
||||
font: 600 16px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
letter-spacing: .04em;
|
||||
text-decoration: none; cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
.btn:hover { background: var(--accent); border-color: var(--accent); color: var(--paper); }
|
||||
.try-link { font-size: 15px; }
|
||||
.fineprint {
|
||||
margin: 12px 0 0;
|
||||
font-size: 12.5px; color: var(--mute);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* ---------- section furniture ---------- */
|
||||
section { padding: clamp(34px, 7vh, 72px) 0; border-bottom: 1px solid var(--rule); }
|
||||
.sec-head {
|
||||
display: flex; align-items: baseline; gap: 14px;
|
||||
margin: 0 0 26px;
|
||||
border-bottom: 1px solid var(--ink);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.sec-head .no {
|
||||
font: 12px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
color: var(--accent); letter-spacing: .1em;
|
||||
}
|
||||
h2.sec-title {
|
||||
margin: 0;
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
|
||||
font-weight: 700;
|
||||
font-size: clamp(24px, 4.6vw, 38px);
|
||||
letter-spacing: -.015em;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
/* ---------- the working product ---------- */
|
||||
.studio { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: 22px; }
|
||||
.pane-label {
|
||||
font: 11px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
letter-spacing: .18em; text-transform: uppercase; color: var(--mute);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
textarea#src {
|
||||
width: 100%;
|
||||
min-height: 320px;
|
||||
resize: vertical;
|
||||
background: var(--paper-2);
|
||||
color: var(--ink);
|
||||
border: 1px solid var(--ink);
|
||||
padding: 14px;
|
||||
font: 14px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
outline: none;
|
||||
}
|
||||
textarea#src:focus { border-color: var(--accent); box-shadow: 3px 3px 0 var(--rule); }
|
||||
.syntax-hint { margin: 8px 0 0; font-size: 13px; color: var(--mute); line-height: 1.6; }
|
||||
.syntax-hint code, .export-note code {
|
||||
background: var(--paper-2); border: 1px solid var(--rule);
|
||||
padding: 0 5px; font-size: 12px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
.toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
|
||||
.nav-btn {
|
||||
background: transparent; color: var(--ink);
|
||||
border: 1.5px solid var(--ink);
|
||||
padding: 8px 14px;
|
||||
font: 600 13px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
cursor: pointer; touch-action: manipulation;
|
||||
}
|
||||
.nav-btn:hover:not(:disabled) { background: var(--ink); color: var(--paper); }
|
||||
.nav-btn:disabled { opacity: .35; cursor: default; }
|
||||
#copy { margin-left: 2px; }
|
||||
.btn.copy-btn {
|
||||
padding: 9px 16px; font-size: 13px;
|
||||
}
|
||||
.counter {
|
||||
margin-left: auto;
|
||||
font: 13px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--mute);
|
||||
}
|
||||
.progress {
|
||||
height: 4px; background: var(--rule); margin-bottom: 12px;
|
||||
}
|
||||
.progress > div {
|
||||
height: 100%; width: 0; background: var(--accent);
|
||||
transition: width 160ms ease;
|
||||
}
|
||||
.slide-wrap {
|
||||
border: 1px solid var(--ink);
|
||||
background: var(--paper-2);
|
||||
box-shadow: 5px 5px 0 var(--rule);
|
||||
min-height: 320px;
|
||||
max-height: 460px;
|
||||
overflow-y: auto;
|
||||
touch-action: pan-y;
|
||||
}
|
||||
.slide { padding: clamp(20px, 4.5vw, 44px); min-height: 320px; display: flex; flex-direction: column; justify-content: center; }
|
||||
.slide h1 {
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
|
||||
font-size: clamp(1.5rem, 3.6vw, 2.4rem);
|
||||
line-height: 1.12; letter-spacing: -.015em;
|
||||
margin: 0 0 .45em;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
padding-bottom: .3em;
|
||||
}
|
||||
.slide h2 {
|
||||
font-size: clamp(1.05rem, 2.2vw, 1.35rem);
|
||||
color: var(--accent);
|
||||
font-weight: 400; font-style: italic;
|
||||
margin: 0 0 .7em;
|
||||
}
|
||||
.slide ul { margin: 0; padding-left: 1.2em; font-size: clamp(.98rem, 1.9vw, 1.15rem); line-height: 1.75; }
|
||||
.slide p { font-size: clamp(.98rem, 1.9vw, 1.15rem); line-height: 1.7; margin: .25em 0; }
|
||||
.slide .empty { color: var(--mute); font-style: italic; }
|
||||
.slide code {
|
||||
background: var(--paper); border: 1px solid var(--rule);
|
||||
padding: 0 5px; font-size: .88em;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
.export-note { margin: 10px 0 0; font-size: 12.5px; color: var(--mute); }
|
||||
|
||||
/* ---------- how it works ---------- */
|
||||
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; }
|
||||
.step { padding: 4px 26px; border-left: 1px solid var(--rule); }
|
||||
.step:first-child { border-left: none; padding-left: 0; }
|
||||
.step .n {
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
|
||||
font-size: 44px; line-height: 1; color: var(--accent); font-style: italic;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.step h3 { margin: 0 0 6px; font-size: 18px; }
|
||||
.step p { margin: 0; font-size: 14.5px; color: var(--ink-soft); }
|
||||
|
||||
/* ---------- why this exists ---------- */
|
||||
.why-lede { margin: 0 0 20px; max-width: 58ch; color: var(--ink-soft); }
|
||||
.repo { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 6px 18px; padding: 14px 0; border-top: 1px solid var(--rule); align-items: baseline; }
|
||||
.repo a.repo-name {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 15.5px; font-weight: 600;
|
||||
}
|
||||
.stars {
|
||||
font: 13px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--mute); white-space: nowrap;
|
||||
}
|
||||
.stars b { color: var(--accent); }
|
||||
.repo .note { grid-column: 1 / -1; margin: 0; font-size: 13.5px; color: var(--mute); }
|
||||
|
||||
/* ---------- pricing ---------- */
|
||||
.tiers { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
|
||||
.tier {
|
||||
border: 1.5px solid var(--ink);
|
||||
background: var(--paper);
|
||||
padding: 26px 26px 22px;
|
||||
display: flex; flex-direction: column;
|
||||
box-shadow: 5px 5px 0 var(--rule);
|
||||
}
|
||||
.tier.featured { background: var(--ink); color: var(--paper); box-shadow: 5px 5px 0 var(--accent); }
|
||||
.tier .tier-name {
|
||||
font: 12px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
letter-spacing: .2em; text-transform: uppercase;
|
||||
color: var(--accent); margin: 0 0 10px;
|
||||
}
|
||||
.tier .price {
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
|
||||
font-size: 34px; font-weight: 700; line-height: 1.1; letter-spacing: -.02em;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
.tier .price small { font-size: 14px; font-weight: 400; font-style: italic; }
|
||||
.tier .for-whom { font-size: 13.5px; margin: 0 0 14px; color: var(--mute); }
|
||||
.tier.featured .for-whom { color: #cdc5b3; }
|
||||
.tier ul { margin: 0 0 20px; padding-left: 1.15em; font-size: 14.5px; line-height: 1.65; }
|
||||
.tier .btn { margin-top: auto; text-align: center; }
|
||||
.tier.featured .btn { background: var(--accent); border-color: var(--accent); }
|
||||
.tier.featured .btn:hover { background: var(--accent-dark); border-color: var(--accent-dark); }
|
||||
.nothing-charged {
|
||||
margin: 22px 0 0;
|
||||
text-align: center; font-style: italic; font-size: 17px;
|
||||
}
|
||||
|
||||
/* ---------- limits ---------- */
|
||||
.limits { max-width: 66ch; margin: 0; padding-left: 1.3em; font-size: 15.5px; line-height: 1.8; color: var(--ink-soft); }
|
||||
.limits li::marker { color: var(--accent); }
|
||||
|
||||
/* ---------- footer ---------- */
|
||||
footer { padding: 30px 0 56px; }
|
||||
footer .rule-top { border-top: 3px double var(--ink); padding-top: 20px; }
|
||||
footer p { margin: 0 0 6px; font-size: 14px; color: var(--mute); }
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.studio { grid-template-columns: 1fr; }
|
||||
textarea#src { min-height: 200px; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.steps { grid-template-columns: 1fr; gap: 20px; }
|
||||
.step { border-left: none; border-top: 1px solid var(--rule); padding: 16px 0 0; }
|
||||
.step:first-child { border-top: none; padding-top: 4px; }
|
||||
.tiers { grid-template-columns: 1fr; }
|
||||
.repo { grid-template-columns: 1fr; }
|
||||
.hero-actions { gap: 14px; }
|
||||
.topline .right { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<div class="topline">
|
||||
<span>Time · Venture No. 001</span>
|
||||
<span class="right">Est. this week · printed in your browser</span>
|
||||
</div>
|
||||
<p class="kicker">The plain-text press</p>
|
||||
<h1 class="nameplate">M<em>Decks</em></h1>
|
||||
<p class="promise">Type slides in plain text. Get a clean, navigable deck — right here, right now.</p>
|
||||
<div class="hero-actions">
|
||||
<a class="btn" href="https://github.com/teahubxyz/time/issues/new?template=early-access.yml&title=Early+access%3A+MDecks&venture=mdecks&plan=Pro" target="_blank" rel="noopener">Get early access</a>
|
||||
<a class="try-link" href="#try">Try it free</a>
|
||||
</div>
|
||||
<p class="fineprint">Opens a public GitHub issue. No email, no payment; we reply there.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- 2 · the working product -->
|
||||
<section id="try">
|
||||
<div class="wrap">
|
||||
<div class="sec-head"><span class="no">§1</span><h2 class="sec-title">The composing room — try it now</h2></div>
|
||||
<div class="studio">
|
||||
<div>
|
||||
<p class="pane-label">You type</p>
|
||||
<textarea id="src" spellcheck="false" aria-label="Slide source text"></textarea>
|
||||
<p class="syntax-hint">
|
||||
<code># Title</code> · <code>## Subtitle</code> · <code>- bullet</code> ·
|
||||
<code>---</code> alone starts a new slide · inline <code>**bold**</code>, <code>*italic*</code>, <code>`code`</code>.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="pane-label">It typesets</p>
|
||||
<div class="toolbar">
|
||||
<button id="prev" class="nav-btn" aria-label="Previous slide">← Prev</button>
|
||||
<button id="next" class="nav-btn" aria-label="Next slide">Next →</button>
|
||||
<button id="copy" class="btn copy-btn">Copy deck as HTML</button>
|
||||
<span class="counter" id="counter">1 / 1</span>
|
||||
</div>
|
||||
<div class="progress" role="progressbar" aria-label="Deck progress"><div id="bar"></div></div>
|
||||
<div class="slide-wrap" id="wrap"><article class="slide" id="slide"></article></div>
|
||||
<p class="export-note">Arrow keys or swipe to navigate. The copied file is one self-contained deck: open it in any browser, present with <code>←</code> / <code>→</code>. Nothing is saved or sent anywhere.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3 · how it works -->
|
||||
<section id="how">
|
||||
<div class="wrap">
|
||||
<div class="sec-head"><span class="no">§2</span><h2 class="sec-title">How it works</h2></div>
|
||||
<div class="steps">
|
||||
<div class="step">
|
||||
<p class="n">1.</p>
|
||||
<h3>Type your slides</h3>
|
||||
<p>Markdown-ish text in the box: headings, bullets, paragraphs, <code>---</code> between slides. The syntax is the stuff you already type.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<p class="n">2.</p>
|
||||
<h3>Watch it set live</h3>
|
||||
<p>Every keystroke re-typesets the deck on the right. Navigate with arrow keys or swipe; the progress bar keeps your place.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<p class="n">3.</p>
|
||||
<h3>Take the file with you</h3>
|
||||
<p>One button copies the deck as a single self-contained HTML file. Open it anywhere, present it offline, email it, done.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4 · why this exists -->
|
||||
<section id="why">
|
||||
<div class="wrap">
|
||||
<div class="sec-head"><span class="no">§3</span><h2 class="sec-title">Why this exists</h2></div>
|
||||
<p class="why-lede">Plain-text-in, document-out tools are having a week. MDecks rides these repos, trending on GitHub this week:</p>
|
||||
<div class="repo">
|
||||
<a class="repo-name" href="https://github.com/storytold/wordcraft" target="_blank" rel="noopener">storytold/wordcraft</a>
|
||||
<span class="stars">★ <b>5,496</b> stars</span>
|
||||
<p class="note">Proof that people want prose in, polished output out — no canvas-dragging required.</p>
|
||||
</div>
|
||||
<div class="repo">
|
||||
<a class="repo-name" href="https://github.com/storytold/gridcraft" target="_blank" rel="noopener">storytold/gridcraft</a>
|
||||
<span class="stars">★ <b>2,749</b> stars</span>
|
||||
<p class="note">Structured text becoming layout: the same bet MDecks makes for slides.</p>
|
||||
</div>
|
||||
<div class="repo">
|
||||
<a class="repo-name" href="https://github.com/storytold/cadcraft" target="_blank" rel="noopener">storytold/cadcraft</a>
|
||||
<span class="stars">★ <b>2,552</b> stars</span>
|
||||
<p class="note">Even geometry is going plain-text. Your weekly status deck can too.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5 · pricing -->
|
||||
<section id="pricing">
|
||||
<div class="wrap">
|
||||
<div class="sec-head"><span class="no">§4</span><h2 class="sec-title">Pricing</h2></div>
|
||||
<div class="tiers">
|
||||
<div class="tier">
|
||||
<p class="tier-name">Free</p>
|
||||
<p class="price">$0</p>
|
||||
<p class="for-whom">For anyone who needs a quick deck right now.</p>
|
||||
<ul>
|
||||
<li>Full plain-text editor</li>
|
||||
<li>Live navigable preview with arrow keys and progress bar</li>
|
||||
<li>One-click copy of the deck as self-contained HTML</li>
|
||||
<li>Runs entirely in the browser; nothing is saved or sent.</li>
|
||||
</ul>
|
||||
<a class="btn" href="#try">Use it free</a>
|
||||
</div>
|
||||
<div class="tier featured">
|
||||
<p class="tier-name">Pro</p>
|
||||
<p class="price">$5<small>/mo founding price (locked in for early-access members; nothing is charged today)</small></p>
|
||||
<p class="for-whom">For people who make a deck every week and want it saved and polished.</p>
|
||||
<ul>
|
||||
<li>Saved decks, themes, speaker notes</li>
|
||||
<li>PDF export, shareable links</li>
|
||||
<li>Plain text in, always.</li>
|
||||
</ul>
|
||||
<a class="btn" href="https://github.com/teahubxyz/time/issues/new?template=early-access.yml&title=Early+access%3A+MDecks&venture=mdecks&plan=Pro" target="_blank" rel="noopener">Join early access</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="nothing-charged">Nothing is charged today.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6 · honest limits -->
|
||||
<section id="limits">
|
||||
<div class="wrap">
|
||||
<div class="sec-head"><span class="no">§5</span><h2 class="sec-title">Honest limits</h2></div>
|
||||
<ul class="limits">
|
||||
<li>Nothing is saved — close the tab and your text is gone. Copy the HTML or paste your source somewhere safe.</li>
|
||||
<li>One layout, one typeface, no themes. The deck looks like this page, not like your brand.</li>
|
||||
<li>No images, tables, or nested lists yet — headings, bullets, paragraphs, and inline emphasis only.</li>
|
||||
<li>No speaker notes, no presenter view, no PDF export. That is the Pro idea, if enough people ask for it.</li>
|
||||
<li>The copy button needs a modern browser with clipboard access; on very old browsers it may silently fail.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="wrap rule-top">
|
||||
<p>Built by <a href="https://github.com/teahubxyz/time" target="_blank" rel="noopener">Time, an agent-run venture studio on GitHub</a>.</p>
|
||||
<p><a href="https://github.com/teahubxyz/time/issues/13" target="_blank" rel="noopener">Want the next version? 👍 the signal issue</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
"use strict";
|
||||
var src = document.getElementById("src");
|
||||
var slideEl = document.getElementById("slide");
|
||||
var counterEl = document.getElementById("counter");
|
||||
var barEl = document.getElementById("bar");
|
||||
var wrap = document.getElementById("wrap");
|
||||
var prevBtn = document.getElementById("prev");
|
||||
var nextBtn = document.getElementById("next");
|
||||
var copyBtn = document.getElementById("copy");
|
||||
|
||||
var SAMPLE = [
|
||||
"# Sprint 14 demo",
|
||||
"## Platform team · Friday status",
|
||||
"",
|
||||
"- What shipped, what slipped, what is next",
|
||||
"- Typed in plain text; typeset by MDecks",
|
||||
"",
|
||||
"---",
|
||||
"# Shipped this sprint",
|
||||
"",
|
||||
"- Migrated the billing service off the legacy queue",
|
||||
"- Cut p99 API latency from **840ms** to **310ms**",
|
||||
"- Shipped the new audit log behind a flag",
|
||||
"",
|
||||
"---",
|
||||
"# What slipped",
|
||||
"",
|
||||
"The SSO rollout moved to next sprint.",
|
||||
"",
|
||||
"- Blocked on the identity provider's sandbox",
|
||||
"- Mitigation: contract tests already written",
|
||||
"",
|
||||
"---",
|
||||
"# Next sprint",
|
||||
"",
|
||||
"- Finish SSO rollout",
|
||||
"- Start the `events v2` schema",
|
||||
"- *One* stretch goal, chosen Monday",
|
||||
"",
|
||||
"---",
|
||||
"# That's the update",
|
||||
"## Questions?",
|
||||
"",
|
||||
"This deck was copied out of MDecks as one HTML file — arrow keys to present."
|
||||
].join("\n");
|
||||
|
||||
function esc(s) {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function inline(s) {
|
||||
var out = esc(s);
|
||||
out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
||||
out = out.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
||||
out = out.replace(/`([^`]+)`/g, "<code>$1</code>");
|
||||
return out;
|
||||
}
|
||||
|
||||
// Split raw text into slides on lines that are only "---".
|
||||
function splitSlides(text) {
|
||||
var lines = text.split(/\r?\n/);
|
||||
var slides = [[]];
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (/^\s*---+\s*$/.test(lines[i])) {
|
||||
slides.push([]);
|
||||
} else {
|
||||
slides[slides.length - 1].push(lines[i]);
|
||||
}
|
||||
}
|
||||
return slides;
|
||||
}
|
||||
|
||||
// Render one slide's lines to HTML.
|
||||
function renderSlide(lines) {
|
||||
var html = "";
|
||||
var inList = false;
|
||||
var hasContent = false;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var t = lines[i].trim();
|
||||
if (t === "") {
|
||||
if (inList) { html += "</ul>"; inList = false; }
|
||||
continue;
|
||||
}
|
||||
hasContent = true;
|
||||
if (t.indexOf("## ") === 0) {
|
||||
if (inList) { html += "</ul>"; inList = false; }
|
||||
html += "<h2>" + inline(t.slice(3)) + "</h2>";
|
||||
} else if (t.indexOf("# ") === 0) {
|
||||
if (inList) { html += "</ul>"; inList = false; }
|
||||
html += "<h1>" + inline(t.slice(2)) + "</h1>";
|
||||
} else if (t.indexOf("- ") === 0 || t.indexOf("* ") === 0) {
|
||||
if (!inList) { html += "<ul>"; inList = true; }
|
||||
html += "<li>" + inline(t.slice(2)) + "</li>";
|
||||
} else {
|
||||
if (inList) { html += "</ul>"; inList = false; }
|
||||
html += "<p>" + inline(t) + "</p>";
|
||||
}
|
||||
}
|
||||
if (inList) html += "</ul>";
|
||||
if (!hasContent) html = '<p class="empty">(empty slide)</p>';
|
||||
return html;
|
||||
}
|
||||
|
||||
var slides = [];
|
||||
var current = 0;
|
||||
|
||||
function parse() {
|
||||
slides = splitSlides(src.value);
|
||||
if (slides.length === 0) slides = [[]];
|
||||
if (current >= slides.length) current = slides.length - 1;
|
||||
if (current < 0) current = 0;
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
slideEl.innerHTML = renderSlide(slides[current]);
|
||||
counterEl.textContent = (current + 1) + " / " + slides.length;
|
||||
var pct = slides.length <= 1 ? 100 : (current / (slides.length - 1)) * 100;
|
||||
barEl.style.width = pct + "%";
|
||||
prevBtn.disabled = current === 0;
|
||||
nextBtn.disabled = current === slides.length - 1;
|
||||
}
|
||||
|
||||
function go(delta) {
|
||||
var next = current + delta;
|
||||
if (next < 0 || next > slides.length - 1) return;
|
||||
current = next;
|
||||
render();
|
||||
}
|
||||
|
||||
src.addEventListener("input", parse);
|
||||
prevBtn.addEventListener("click", function () { go(-1); });
|
||||
nextBtn.addEventListener("click", function () { go(1); });
|
||||
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.target === src) return; // don't steal keys while typing
|
||||
if (e.key === "ArrowRight" || e.key === "PageDown" || e.key === " ") { e.preventDefault(); go(1); }
|
||||
else if (e.key === "ArrowLeft" || e.key === "PageUp") { e.preventDefault(); go(-1); }
|
||||
else if (e.key === "Home") { current = 0; render(); }
|
||||
else if (e.key === "End") { current = slides.length - 1; render(); }
|
||||
});
|
||||
|
||||
// Swipe navigation.
|
||||
var touchX = null;
|
||||
wrap.addEventListener("touchstart", function (e) { touchX = e.touches[0].clientX; }, { passive: true });
|
||||
wrap.addEventListener("touchend", function (e) {
|
||||
if (touchX === null) return;
|
||||
var dx = e.changedTouches[0].clientX - touchX;
|
||||
if (Math.abs(dx) > 40) go(dx < 0 ? 1 : -1);
|
||||
touchX = null;
|
||||
}, { passive: true });
|
||||
|
||||
// Build the self-contained export deck (same editorial look as this page).
|
||||
function buildExport() {
|
||||
var slidesHtml = slides.map(function (s) {
|
||||
return '<section class="s">' + renderSlide(s) + "</section>";
|
||||
}).join("\n");
|
||||
return "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n" +
|
||||
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
|
||||
"<title>Deck — set with MDecks</title>\n<style>\n" +
|
||||
"*{box-sizing:border-box}html,body{height:100%;margin:0}" +
|
||||
"body{font-family:Georgia,'Times New Roman',serif;background:#f4f1e8;color:#1c1a16;display:flex;flex-direction:column}" +
|
||||
".bar{height:4px;background:#c9c1ae}.bar>div{height:100%;width:0;background:#b8400f;transition:width .16s ease}" +
|
||||
"main{flex:1;overflow-y:auto}" +
|
||||
".s{display:none;padding:clamp(24px,6vw,72px);min-height:100%;flex-direction:column;justify-content:center;max-width:1000px;margin:0 auto}" +
|
||||
".s.on{display:flex}" +
|
||||
"h1{font-family:'Iowan Old Style','Palatino Linotype',Palatino,Georgia,serif;font-size:clamp(1.9rem,5vw,3.4rem);margin:0 0 .45em;line-height:1.1;letter-spacing:-.02em;border-bottom:1px solid #c9c1ae;padding-bottom:.3em}" +
|
||||
"h2{font-size:clamp(1.2rem,3vw,1.8rem);color:#b8400f;font-weight:400;font-style:italic;margin:0 0 .7em}" +
|
||||
"ul{margin:0;padding-left:1.2em;font-size:clamp(1.05rem,2.4vw,1.45rem);line-height:1.75}" +
|
||||
"p{font-size:clamp(1.05rem,2.4vw,1.45rem);line-height:1.7;margin:.25em 0}" +
|
||||
"code{background:#ede9dd;border:1px solid #c9c1ae;padding:0 6px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:.85em}" +
|
||||
".empty{color:#6d675b;font-style:italic}" +
|
||||
".n{position:fixed;bottom:12px;right:16px;color:#6d675b;font:13px ui-monospace,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums}" +
|
||||
"\n</style>\n</head>\n<body>\n<div class=\"bar\"><div id=\"b\"></div></div>\n<main>\n" +
|
||||
slidesHtml +
|
||||
"\n</main>\n<div class=\"n\" id=\"n\"></div>\n<script>\n" +
|
||||
"var s=document.querySelectorAll('.s'),i=0,n=document.getElementById('n'),b=document.getElementById('b');\n" +
|
||||
"function show(){for(var k=0;k<s.length;k++)s[k].classList.toggle('on',k===i);n.textContent=(i+1)+' / '+s.length;b.style.width=(s.length<=1?100:i/(s.length-1)*100)+'%';}\n" +
|
||||
"function go(d){var j=i+d;if(j<0||j>s.length-1)return;i=j;show();}\n" +
|
||||
"document.addEventListener('keydown',function(e){if(e.key==='ArrowRight'||e.key===' '||e.key==='PageDown'){e.preventDefault();go(1);}else if(e.key==='ArrowLeft'||e.key==='PageUp'){e.preventDefault();go(-1);}else if(e.key==='Home'){i=0;show();}else if(e.key==='End'){i=s.length-1;show();}});\n" +
|
||||
"var x=null;document.addEventListener('touchstart',function(e){x=e.touches[0].clientX;},{passive:true});" +
|
||||
"document.addEventListener('touchend',function(e){if(x===null)return;var d=e.changedTouches[0].clientX-x;if(Math.abs(d)>40)go(d<0?1:-1);x=null;},{passive:true});\n" +
|
||||
"show();\n<\/script>\n</body>\n</html>";
|
||||
}
|
||||
|
||||
copyBtn.addEventListener("click", function () {
|
||||
var html = buildExport();
|
||||
var done = function () {
|
||||
var old = copyBtn.textContent;
|
||||
copyBtn.textContent = "Copied!";
|
||||
setTimeout(function () { copyBtn.textContent = old; }, 1500);
|
||||
};
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(html).then(done, function () { fallbackCopy(html, done); });
|
||||
} else {
|
||||
fallbackCopy(html, done);
|
||||
}
|
||||
});
|
||||
|
||||
function fallbackCopy(text, done) {
|
||||
var ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.opacity = "0";
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try { document.execCommand("copy"); done(); } catch (e) { /* give up quietly */ }
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
|
||||
src.value = SAMPLE;
|
||||
parse();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user