docs(slides): add slides v1
This commit is contained in:
9
docs/slides/README.md
Normal file
9
docs/slides/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# ارائه WebSocket فارسی
|
||||||
|
|
||||||
|
- فایل `index.html` را در مرورگر باز کنید.
|
||||||
|
- با کلیدهای چپ/راست بین اسلایدها حرکت کنید.
|
||||||
|
- کلید `N` یادداشت سخنران را نشان میدهد.
|
||||||
|
- کلید `F` حالت تمامصفحه را فعال میکند.
|
||||||
|
- فونت اصلی `Vazirmatn` است؛ فایل ارائه از Google Fonts / فونت نصبشده روی سیستم استفاده میکند.
|
||||||
|
- در نمایشگرهای بزرگ، اسلایدها به صورت ۱۶:۹ تا جای ممکن ارتفاع صفحه را پر میکنند.
|
||||||
|
- در نمایشگرهای کوچک، اندازه فونت و چینش اسلایدها کوچکتر و عمودیتر میشود.
|
||||||
49
docs/slides/assets/chatroom-architecture.svg
Normal file
49
docs/slides/assets/chatroom-architecture.svg
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">Chatroom architecture</title>
|
||||||
|
<desc id="desc">A compact architecture diagram for the chatroom project.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<marker id="arrow" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#2563eb"/></marker>
|
||||||
|
<marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#10b981"/></marker>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".12"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<path d="M714 38 L932 104 L802 224 L642 132 Z" fill="#6d8cff" opacity=".14"/>
|
||||||
|
<path d="M36 414 L236 304 L342 478 L96 548 Z" fill="#f4b740" opacity=".14"/>
|
||||||
|
<text x="72" y="76" fill="#f8fafc" font-size="34" font-weight="800">Chatroom Architecture</text>
|
||||||
|
<text x="72" y="112" fill="#a8adbd" font-size="17">Browser Clients - FastAPI - Room Manager</text>
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="82" y="178" width="190" height="82" rx="20" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="82" y="302" width="190" height="82" rx="20" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="368" y="178" width="224" height="206" rx="26" fill="#2563eb"/>
|
||||||
|
<rect x="690" y="162" width="196" height="94" rx="22" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="690" y="308" width="196" height="94" rx="22" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="177" y="214" text-anchor="middle" fill="#f8fafc" font-size="19" font-weight="800">Client A</text>
|
||||||
|
<text x="177" y="240" text-anchor="middle" fill="#a8adbd" font-size="15">Browser tab</text>
|
||||||
|
<text x="177" y="338" text-anchor="middle" fill="#f8fafc" font-size="19" font-weight="800">Client B</text>
|
||||||
|
<text x="177" y="364" text-anchor="middle" fill="#a8adbd" font-size="15">Browser tab</text>
|
||||||
|
<text x="480" y="242" text-anchor="middle" fill="#fff" font-size="25" font-weight="800">FastAPI</text>
|
||||||
|
<text x="480" y="276" text-anchor="middle" fill="#dbeafe" font-size="16">WebSocket Endpoint</text>
|
||||||
|
<text x="480" y="306" text-anchor="middle" fill="#dbeafe" font-size="16">ConnectionManager</text>
|
||||||
|
<text x="480" y="338" text-anchor="middle" fill="#fff" font-size="15" font-weight="800">/ws/{room}/{user}</text>
|
||||||
|
<text x="788" y="202" text-anchor="middle" fill="#f8fafc" font-size="19" font-weight="800">Room: general</text>
|
||||||
|
<text x="788" y="230" text-anchor="middle" fill="#a8adbd" font-size="15">A - B - C</text>
|
||||||
|
<text x="788" y="348" text-anchor="middle" fill="#f8fafc" font-size="19" font-weight="800">Room: class</text>
|
||||||
|
<text x="788" y="376" text-anchor="middle" fill="#a8adbd" font-size="15">D - E</text>
|
||||||
|
<path d="M272 220 C320 220 330 230 368 230" fill="none" stroke="#2563eb" stroke-width="4" marker-end="url(#arrow)"/>
|
||||||
|
<path d="M272 344 C320 344 330 330 368 330" fill="none" stroke="#2563eb" stroke-width="4" marker-end="url(#arrow)"/>
|
||||||
|
<path d="M592 242 C642 220 650 208 690 208" fill="none" stroke="#10b981" stroke-width="4" marker-end="url(#arrow-green)"/>
|
||||||
|
<path d="M592 318 C642 340 650 356 690 356" fill="none" stroke="#10b981" stroke-width="4" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="320" y="178" fill="#f7c75f" font-size="14" font-weight="800">WebSocket Frames</text>
|
||||||
|
<text x="632" y="156" fill="#43d9a3" font-size="14" font-weight="800">Broadcast by room</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/slides/assets/home.png
Normal file
BIN
docs/slides/assets/home.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
40
docs/slides/assets/websocket-handshake.svg
Normal file
40
docs/slides/assets/websocket-handshake.svg
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket handshake</title>
|
||||||
|
<desc id="desc">A compact HTTP Upgrade handshake diagram.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<marker id="arrow-blue" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#2563eb"/></marker>
|
||||||
|
<marker id="arrow-green" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#10b981"/></marker>
|
||||||
|
<marker id="arrow-purple" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto"><path d="M2,2 L10,6 L2,10 Z" fill="#7c3aed"/></marker>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".12"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<path d="M42 420 L236 318 L348 462 L96 548 Z" fill="#f4b740" opacity=".13"/>
|
||||||
|
<path d="M710 38 L934 90 L842 224 L660 162 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<text x="72" y="76" fill="#f8fafc" font-size="34" font-weight="800">HTTP Upgrade Handshake</text>
|
||||||
|
<text x="72" y="112" fill="#a8adbd" font-size="17">Starts with HTTP, continues with WebSocket Frames</text>
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="82" y="190" width="220" height="154" rx="24" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="658" y="190" width="220" height="154" rx="24" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="192" y="252" text-anchor="middle" fill="#f8fafc" font-size="24" font-weight="800">Browser</text>
|
||||||
|
<text x="192" y="286" text-anchor="middle" fill="#a8adbd" font-size="16">JavaScript WebSocket</text>
|
||||||
|
<text x="768" y="252" text-anchor="middle" fill="#f8fafc" font-size="24" font-weight="800">FastAPI Server</text>
|
||||||
|
<text x="768" y="286" text-anchor="middle" fill="#a8adbd" font-size="16">/ws/{room}/{user}</text>
|
||||||
|
<line x1="320" y1="206" x2="638" y2="206" stroke="#2563eb" stroke-width="4" marker-end="url(#arrow-blue)"/>
|
||||||
|
<text x="479" y="186" text-anchor="middle" fill="#f7c75f" font-size="16" font-weight="800">1. GET + Upgrade</text>
|
||||||
|
<line x1="638" y1="268" x2="320" y2="268" stroke="#10b981" stroke-width="4" marker-end="url(#arrow-green)"/>
|
||||||
|
<text x="479" y="250" text-anchor="middle" fill="#43d9a3" font-size="16" font-weight="800">2. 101 Switching Protocols</text>
|
||||||
|
<line x1="320" y1="332" x2="638" y2="332" stroke="#7c3aed" stroke-width="4" stroke-dasharray="10 9" marker-end="url(#arrow-purple)"/>
|
||||||
|
<line x1="638" y1="372" x2="320" y2="372" stroke="#7c3aed" stroke-width="4" stroke-dasharray="10 9" marker-end="url(#arrow-purple)"/>
|
||||||
|
<text x="480" y="414" text-anchor="middle" fill="#aebdff" font-size="16" font-weight="800">3. Full-duplex WebSocket Frames</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
63
docs/slides/assets/websocket-history.svg
Normal file
63
docs/slides/assets/websocket-history.svg
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket history</title>
|
||||||
|
<desc id="desc">A compact Persian timeline with English technical names.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".12"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<path d="M34 112 L238 34 L340 146 L156 238 Z" fill="#f4b740" opacity=".12"/>
|
||||||
|
<path d="M700 410 L930 318 L982 504 L746 548 Z" fill="#6d8cff" opacity=".16"/>
|
||||||
|
<text x="72" y="76" fill="#f8fafc" font-size="34" font-weight="800">WebSocket History</text>
|
||||||
|
<text x="72" y="112" fill="#a8adbd" font-size="17">From Classic HTTP to Real-time communication</text>
|
||||||
|
<line x1="128" y1="282" x2="832" y2="282" stroke="#4a5168" stroke-width="10" stroke-linecap="round"/>
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="76" y="164" width="808" height="238" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<g text-anchor="middle">
|
||||||
|
<g>
|
||||||
|
<circle cx="150" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="150" y="289" fill="#fff" font-size="16" font-weight="800">1</text>
|
||||||
|
<text x="150" y="338" fill="#f8fafc" font-size="18" font-weight="800">Classic HTTP</text>
|
||||||
|
<text x="150" y="365" fill="#a8adbd" font-size="15">Request / Response</text>
|
||||||
|
<text x="150" y="390" fill="#a8adbd" font-size="15">No Server Push</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="330" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="330" y="289" fill="#fff" font-size="16" font-weight="800">2</text>
|
||||||
|
<text x="330" y="338" fill="#f8fafc" font-size="18" font-weight="800">Polling</text>
|
||||||
|
<text x="330" y="365" fill="#a8adbd" font-size="15">Repeated request</text>
|
||||||
|
<text x="330" y="390" fill="#a8adbd" font-size="15">More overhead</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="510" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="510" y="289" fill="#fff" font-size="16" font-weight="800">3</text>
|
||||||
|
<text x="510" y="338" fill="#f8fafc" font-size="18" font-weight="800">Long Polling</text>
|
||||||
|
<text x="510" y="365" fill="#a8adbd" font-size="15">Open request</text>
|
||||||
|
<text x="510" y="390" fill="#a8adbd" font-size="15">Long wait</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="690" cy="282" r="24" fill="#2563eb"/>
|
||||||
|
<text x="690" y="289" fill="#fff" font-size="16" font-weight="800">4</text>
|
||||||
|
<text x="690" y="338" fill="#f8fafc" font-size="18" font-weight="800">WebSocket</text>
|
||||||
|
<text x="690" y="365" fill="#a8adbd" font-size="15">Persistent connection</text>
|
||||||
|
<text x="690" y="390" fill="#a8adbd" font-size="15">Full-duplex</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="830" cy="282" r="24" fill="#10b981"/>
|
||||||
|
<text x="830" y="289" fill="#fff" font-size="16" font-weight="800">5</text>
|
||||||
|
<text x="830" y="338" fill="#f8fafc" font-size="18" font-weight="800">Real-time Web</text>
|
||||||
|
<text x="830" y="365" fill="#a8adbd" font-size="15">Chat - Game</text>
|
||||||
|
<text x="830" y="390" fill="#a8adbd" font-size="15">Dashboard - IoT</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
43
docs/slides/assets/websocket-osi.svg
Normal file
43
docs/slides/assets/websocket-osi.svg
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket in OSI model</title>
|
||||||
|
<desc id="desc">A compact OSI stack with English technical names and Persian supporting labels.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".12"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<text x="72" y="76" fill="#f8fafc" font-size="34" font-weight="800">WebSocket in the OSI Model</text>
|
||||||
|
<text x="72" y="112" fill="#a8adbd" font-size="17">Application Layer over TCP/IP</text>
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="106" y="142" width="748" height="350" rx="26" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<g font-size="17">
|
||||||
|
<rect x="138" y="170" width="684" height="52" rx="14" fill="#2563eb"/>
|
||||||
|
<text x="162" y="203" fill="#fff" font-weight="800">Layer 7 - Application</text>
|
||||||
|
<text x="516" y="203" fill="#fff">WebSocket - JSON - Chat</text>
|
||||||
|
<rect x="138" y="232" width="684" height="44" rx="13" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<text x="162" y="260" fill="#f8fafc" font-weight="800">Layer 6 - Presentation</text>
|
||||||
|
<text x="516" y="260" fill="#a8adbd">UTF-8 - Text</text>
|
||||||
|
<rect x="138" y="286" width="684" height="44" rx="13" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<text x="162" y="314" fill="#f8fafc" font-weight="800">Layer 5 - Session</text>
|
||||||
|
<text x="516" y="314" fill="#a8adbd">Long-lived conversation</text>
|
||||||
|
<rect x="138" y="340" width="684" height="52" rx="14" fill="#10b981"/>
|
||||||
|
<text x="162" y="373" fill="#fff" font-weight="800">Layer 4 - Transport</text>
|
||||||
|
<text x="516" y="373" fill="#fff">TCP reliable stream</text>
|
||||||
|
<rect x="138" y="402" width="684" height="44" rx="13" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<text x="162" y="430" fill="#f8fafc" font-weight="800">Layer 3 - Network</text>
|
||||||
|
<text x="516" y="430" fill="#a8adbd">IP routing</text>
|
||||||
|
<rect x="138" y="456" width="684" height="44" rx="13" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<text x="162" y="484" fill="#f8fafc" font-weight="800">Layers 2-1 - Link / Physical</text>
|
||||||
|
<text x="516" y="484" fill="#a8adbd">Ethernet - Wi-Fi</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
44
docs/slides/assets/websocket-pros-cons.svg
Normal file
44
docs/slides/assets/websocket-pros-cons.svg
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">WebSocket pros and cons</title>
|
||||||
|
<desc id="desc">A compact pros and cons diagram for WebSocket.</desc>
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
}
|
||||||
|
text { font-family: "Vazirmatn", Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="18" flood-color="#0f172a" flood-opacity=".12"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width="960" height="540" fill="#262a3a"/>
|
||||||
|
<path d="M38 412 L244 320 L330 484 L96 548 Z" fill="#43d9a3" opacity=".12"/>
|
||||||
|
<path d="M720 34 L936 112 L828 236 L640 136 Z" fill="#fb7185" opacity=".12"/>
|
||||||
|
<text x="72" y="76" fill="#f8fafc" font-size="34" font-weight="800">WebSocket Trade-offs</text>
|
||||||
|
<text x="72" y="112" fill="#a8adbd" font-size="17">Strengths and limitations at a glance</text>
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="92" y="154" width="350" height="286" rx="28" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
<rect x="518" y="154" width="350" height="286" rx="28" fill="#ffffff" opacity=".08" stroke="#ffffff"/>
|
||||||
|
</g>
|
||||||
|
<text x="130" y="206" fill="#047857" font-size="25" font-weight="800">Pros</text>
|
||||||
|
<text x="158" y="256" fill="#f8fafc" font-size="17">Low latency</text>
|
||||||
|
<text x="158" y="304" fill="#f8fafc" font-size="17">Full-duplex</text>
|
||||||
|
<text x="158" y="352" fill="#f8fafc" font-size="17">Less HTTP overhead</text>
|
||||||
|
<text x="158" y="400" fill="#f8fafc" font-size="17">Good for Chat / Dashboard</text>
|
||||||
|
<circle cx="136" cy="250" r="7" fill="#10b981"/>
|
||||||
|
<circle cx="136" cy="298" r="7" fill="#10b981"/>
|
||||||
|
<circle cx="136" cy="346" r="7" fill="#10b981"/>
|
||||||
|
<circle cx="136" cy="394" r="7" fill="#10b981"/>
|
||||||
|
<text x="556" y="206" fill="#b42318" font-size="25" font-weight="800">Cons</text>
|
||||||
|
<text x="584" y="256" fill="#f8fafc" font-size="17">Server state</text>
|
||||||
|
<text x="584" y="304" fill="#f8fafc" font-size="17">Open socket memory</text>
|
||||||
|
<text x="584" y="352" fill="#f8fafc" font-size="17">Reconnect handling</text>
|
||||||
|
<text x="584" y="400" fill="#f8fafc" font-size="17">Scaling needs design</text>
|
||||||
|
<circle cx="562" cy="250" r="7" fill="#ef4444"/>
|
||||||
|
<circle cx="562" cy="298" r="7" fill="#ef4444"/>
|
||||||
|
<circle cx="562" cy="346" r="7" fill="#ef4444"/>
|
||||||
|
<circle cx="562" cy="394" r="7" fill="#ef4444"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
243
docs/slides/index.html
Normal file
243
docs/slides/index.html
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fa" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>ارائه WebSocket و Socket Programming</title>
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="deck" id="deck">
|
||||||
|
<section class="slide active title-slide" data-title="شروع">
|
||||||
|
<div class="kicker">پروژه مهندسی اینترنت</div>
|
||||||
|
<h1>چتروم ساده با WebSocket</h1>
|
||||||
|
<p class="subtitle">نمایش Socket Programming و ارتباط Real-time در وب</p>
|
||||||
|
|
||||||
|
<div class="hero-grid">
|
||||||
|
<img class="hero-img glass-media" src="assets/home.png" alt="نمای صفحه چتروم" />
|
||||||
|
<div class="talk-track glass-panel">
|
||||||
|
<span>مسیر ارائه</span>
|
||||||
|
<strong>History → Polling → Handshake → OSI → Architecture → Demo</strong>
|
||||||
|
<div class="signal-board">
|
||||||
|
<div class="signal"><i>1</i><div><b>Handshake</b><span>HTTP Upgrade</span></div></div>
|
||||||
|
<div class="signal"><i>2</i><div><b>Channel</b><span>Persistent TCP</span></div></div>
|
||||||
|
<div class="signal"><i>3</i><div><b>Frames</b><span>Full-duplex</span></div></div>
|
||||||
|
<div class="signal"><i>4</i><div><b>Broadcast</b><span>Room delivery</span></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">معرفی سریع پروژه و مسیر ارائه.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="History">
|
||||||
|
<div class="kicker">History</div>
|
||||||
|
<h2>چرا WebSocket لازم شد؟</h2>
|
||||||
|
<p class="subtitle">مسیر تکامل از HTTP کلاسیک تا اتصال پایدار دوطرفه</p>
|
||||||
|
|
||||||
|
<div class="timeline-panel">
|
||||||
|
<div class="image-focus glass-panel">
|
||||||
|
<img src="assets/websocket-history.svg" alt="تاریخچه WebSocket" />
|
||||||
|
</div>
|
||||||
|
<div class="timeline-cards point-column">
|
||||||
|
<article><small>HTTP</small><div><b>Request / Response</b><ul><li>ساده</li><li>بدون Server Push</li></ul></div></article>
|
||||||
|
<article><small>AJAX</small><div><b>Polling</b><ul><li>درخواست تکراری</li><li>هزینه شبکه بیشتر</li></ul></div></article>
|
||||||
|
<article><small>LONG</small><div><b>Long Polling</b><ul><li>اتصال منتظر</li><li>پیچیدگی بیشتر</li></ul></div></article>
|
||||||
|
<article><small>WS</small><div><b>WebSocket</b><ul><li>اتصال پایدار</li><li>پیام دوطرفه</li></ul></div></article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">توضیح کوتاه درباره نیاز به دریافت لحظهای داده.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Cost">
|
||||||
|
<div class="kicker">Resource Cost</div>
|
||||||
|
<h2>هزینه هر روش برای سرور</h2>
|
||||||
|
<p class="subtitle">هر روش، فشار متفاوتی روی Network، CPU، memory و socket دارد</p>
|
||||||
|
|
||||||
|
<div class="resource-grid point-cards">
|
||||||
|
<article><b>Classic HTTP</b><ul><li>درخواست جداگانه</li><li>پردازش header</li><li>پایان چرخه</li></ul></article>
|
||||||
|
<article><b>Polling</b><ul><li>درخواست زیاد</li><li>پاسخهای خالی</li><li>سربار Network</li></ul></article>
|
||||||
|
<article><b>Long Polling</b><ul><li>اتصال باز</li><li>نگهداری state</li><li>مصرف buffer</li></ul></article>
|
||||||
|
<article><b>WebSocket</b><ul><li>یک اتصال پایدار</li><li>فریم سبک</li><li>state فعال</li></ul></article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="quote glass-panel">Polling → درخواست تکراری · Long Polling → انتظار طولانی · WebSocket → کانال پایدار</div>
|
||||||
|
<aside class="notes">مقایسه فشرده هزینهها.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Handshake">
|
||||||
|
<div class="kicker">HTTP Upgrade Handshake</div>
|
||||||
|
<h2>WebSocket چطور شروع میشود؟</h2>
|
||||||
|
<p class="subtitle">ابتدا HTTP، سپس پاسخ 101 و بعد WebSocket Frames</p>
|
||||||
|
|
||||||
|
<div class="upgrade-map">
|
||||||
|
<div class="endpoint glass-panel"><span>Client</span><b>Browser</b><ul><li>ساخت WebSocket</li><li>درخواست Upgrade</li></ul></div>
|
||||||
|
<div class="upgrade-steps">
|
||||||
|
<div>1. GET + Upgrade</div>
|
||||||
|
<div>2. 101 Switching Protocols</div>
|
||||||
|
<div>3. WebSocket Frames</div>
|
||||||
|
</div>
|
||||||
|
<div class="endpoint glass-panel"><span>Server</span><b>FastAPI</b><ul><li>پذیرش Upgrade</li><li>نگهداری socket</li></ul></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="split">
|
||||||
|
<div class="diagram-card glass-panel">
|
||||||
|
<img src="assets/websocket-handshake.svg" alt="HTTP Upgrade Handshake" />
|
||||||
|
</div>
|
||||||
|
<div class="code-stack ltr">
|
||||||
|
<pre><code>GET /ws/general/ali HTTP/1.1
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: Upgrade
|
||||||
|
Sec-WebSocket-Key: ...</code></pre>
|
||||||
|
<pre><code>HTTP/1.1 101 Switching Protocols
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: Upgrade</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">نمایش handshake و تبدیل HTTP به WebSocket.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="HTTP Versions">
|
||||||
|
<div class="kicker">HTTP Versions</div>
|
||||||
|
<h2>WebSocket در نسخههای HTTP</h2>
|
||||||
|
<p class="subtitle">مدل آموزشی پروژه روی HTTP/1.1 Upgrade تمرکز دارد</p>
|
||||||
|
|
||||||
|
<div class="version-row point-cards">
|
||||||
|
<article><span>HTTP/1.1</span><b>Upgrade + 101</b><ul><li>مدل کلاسیک</li><li>یک TCP connection</li><li>ساده برای آموزش</li></ul></article>
|
||||||
|
<article><span>HTTP/2</span><b>Extended CONNECT</b><ul><li>روی یک stream</li><li>بدون Upgrade کلاسیک</li><li>پیچیدهتر</li></ul></article>
|
||||||
|
<article><span>HTTP/3</span><b>QUIC + CONNECT</b><ul><li>بدون TCP</li><li>روی QUIC</li><li>مدرنتر</li></ul></article>
|
||||||
|
</div>
|
||||||
|
<div class="focus-line glass-panel"><b>HTTP/1.1:</b> Upgrade → 101 → WebSocket Frames</div>
|
||||||
|
<aside class="notes">تفاوت نسخهها در یک نگاه.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="OSI">
|
||||||
|
<div class="kicker">OSI Model</div>
|
||||||
|
<h2>جایگاه WebSocket در شبکه</h2>
|
||||||
|
<p class="subtitle">WebSocket در لایه Application استفاده میشود؛ TCP/IP حمل داده را انجام میدهد</p>
|
||||||
|
|
||||||
|
<div class="osi-board">
|
||||||
|
<div class="osi-stack glass-panel">
|
||||||
|
<div class="osi-layer active"><b>Layer 7</b><span>WebSocket + JSON</span></div>
|
||||||
|
<div class="osi-layer"><b>Layer 6</b><span>UTF-8 text</span></div>
|
||||||
|
<div class="osi-layer"><b>Layer 5</b><span>Long-lived session</span></div>
|
||||||
|
<div class="osi-layer transport"><b>Layer 4</b><span>TCP reliable stream</span></div>
|
||||||
|
<div class="osi-layer"><b>Layer 3</b><span>IP routing</span></div>
|
||||||
|
<div class="osi-layer"><b>Layer 2/1</b><span>Ethernet / Wi-Fi</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="layer-note glass-panel point-column">
|
||||||
|
<div><i class="metric-icon">A</i><b>Application</b><ul><li>اتاق</li><li>کاربر</li><li>پیام</li></ul></div>
|
||||||
|
<div><i class="metric-icon">T</i><b>Transport</b><ul><li>TCP</li><li>تحویل قابل اعتماد</li></ul></div>
|
||||||
|
<div><i class="metric-icon">N</i><b>Network</b><ul><li>IP</li><li>مسیریابی</li></ul></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">تفکیک لایه کاربرد از لایههای انتقال و شبکه.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Architecture">
|
||||||
|
<div class="kicker">Project Architecture</div>
|
||||||
|
<h2>معماری پروژه چتروم</h2>
|
||||||
|
<p class="subtitle">هر تب مرورگر یک WebSocket connection دارد</p>
|
||||||
|
|
||||||
|
<div class="topology">
|
||||||
|
<div class="node-column">
|
||||||
|
<div class="node glass-panel"><b>Client A</b><span>Browser tab</span></div>
|
||||||
|
<div class="node glass-panel"><b>Client B</b><span>Browser tab</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="server-node glass-panel">
|
||||||
|
<b>FastAPI WebSocket Endpoint</b>
|
||||||
|
<p>/ws/{room}/{user}</p>
|
||||||
|
<div class="room-badge">ConnectionManager</div>
|
||||||
|
<div class="room-badge">Broadcast داخل همان room</div>
|
||||||
|
</div>
|
||||||
|
<div class="node-column">
|
||||||
|
<div class="node glass-panel"><b>Room: general</b><span>A · B · C</span></div>
|
||||||
|
<div class="node glass-panel"><b>Room: class</b><span>D · E</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mini-points">
|
||||||
|
<span>Browser UI</span>
|
||||||
|
<span>FastAPI endpoint</span>
|
||||||
|
<span>Room Manager</span>
|
||||||
|
<span>Broadcast</span>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">نمای معماری و نقش ConnectionManager.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Flow">
|
||||||
|
<div class="kicker">Message Flow</div>
|
||||||
|
<h2>جریان پیام در پروژه</h2>
|
||||||
|
<p class="subtitle">از ورود کاربر تا نمایش پیام بدون refresh</p>
|
||||||
|
|
||||||
|
<div class="pipeline">
|
||||||
|
<div class="pipe-step glass-panel"><i class="step-dot">1</i><b>Join</b><span>name + room</span></div>
|
||||||
|
<div class="pipe-step glass-panel"><i class="step-dot">2</i><b>Upgrade</b><span>HTTP → WebSocket</span></div>
|
||||||
|
<div class="pipe-step glass-panel"><i class="step-dot">3</i><b>Send</b><span>JSON content</span></div>
|
||||||
|
<div class="pipe-step glass-panel"><i class="step-dot">4</i><b>Broadcast</b><span>room sockets</span></div>
|
||||||
|
<div class="pipe-step glass-panel"><i class="step-dot">5</i><b>Render</b><span>بدون refresh</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flow">
|
||||||
|
<div>نام و اتاق</div>
|
||||||
|
<div>WebSocket در JS</div>
|
||||||
|
<div>HTTP Upgrade</div>
|
||||||
|
<div>قبول در FastAPI</div>
|
||||||
|
<div>Room Manager</div>
|
||||||
|
<div>JSON message</div>
|
||||||
|
<div>Broadcast</div>
|
||||||
|
<div>نمایش پیام</div>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">جریان اجرایی پیام در پروژه.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Pros Cons">
|
||||||
|
<div class="kicker">Trade-offs</div>
|
||||||
|
<h2>مزایا و محدودیتها</h2>
|
||||||
|
<p class="subtitle">WebSocket برای Real-time عالی است، اما state سرور را بیشتر میکند</p>
|
||||||
|
|
||||||
|
<div class="metrics">
|
||||||
|
<article class="metric-card glass-panel"><i class="metric-icon">↔</i><b>Full-duplex</b><ul><li>ارسال دوطرفه</li><li>بدون انتظار request بعدی</li></ul></article>
|
||||||
|
<article class="metric-card glass-panel"><i class="metric-icon">↓</i><b>Low latency</b><ul><li>تاخیر کم</li><li>فریم سبکتر</li></ul></article>
|
||||||
|
<article class="metric-card glass-panel"><i class="metric-icon">!</i><b>Server state</b><ul><li>socket باز</li><li>مدیریت disconnect</li></ul></article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mini-points">
|
||||||
|
<span>Chat</span>
|
||||||
|
<span>Notifications</span>
|
||||||
|
<span>Live dashboard</span>
|
||||||
|
<span>Online game</span>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">مزایا و هزینههای عملیاتی.</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="slide" data-title="Demo">
|
||||||
|
<div class="kicker">Demo</div>
|
||||||
|
<h2>برنامه اجرای دمو</h2>
|
||||||
|
<p class="subtitle">دو تب، یک room، پیام Real-time</p>
|
||||||
|
|
||||||
|
<div class="demo-layout">
|
||||||
|
<img class="glass-media" src="assets/home.png" alt="نمای دمو" />
|
||||||
|
<ol class="glass-panel">
|
||||||
|
<li>اجرای <code>uvicorn app.main:app --reload</code></li>
|
||||||
|
<li>باز کردن دو tab</li>
|
||||||
|
<li>ورود دو user به یک room</li>
|
||||||
|
<li>ارسال message</li>
|
||||||
|
<li>نمایش بدون refresh</li>
|
||||||
|
<li>تغییر online users</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<aside class="notes">جمعبندی و اجرای دمو.</aside>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="controls" aria-hidden="true">
|
||||||
|
<button id="prev" title="قبلی">‹</button>
|
||||||
|
<span id="counter">1 / 10</span>
|
||||||
|
<button id="next" title="بعدی">›</button>
|
||||||
|
</div>
|
||||||
|
<div class="hint">← → حرکت · N یادداشت · F تمامصفحه</div>
|
||||||
|
<div class="progress"><span id="progressBar"></span></div>
|
||||||
|
<div class="speaker-notes" id="speakerNotes"></div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
docs/slides/script.js
Normal file
69
docs/slides/script.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const slides = [...document.querySelectorAll('.slide')];
|
||||||
|
const counter = document.getElementById('counter');
|
||||||
|
const progressBar = document.getElementById('progressBar');
|
||||||
|
const notesBox = document.getElementById('speakerNotes');
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
function fitDeck() {
|
||||||
|
const vw = window.innerWidth;
|
||||||
|
const vh = window.innerHeight;
|
||||||
|
const root = document.documentElement;
|
||||||
|
|
||||||
|
// Mobile uses normal document flow, so text can shrink and content can scroll.
|
||||||
|
if (vw <= 900) {
|
||||||
|
const mobileScale = Math.max(0.64, Math.min(0.9, vw / 900));
|
||||||
|
root.style.setProperty('--slide-scale', mobileScale.toFixed(3));
|
||||||
|
root.style.removeProperty('--slide-w');
|
||||||
|
root.style.removeProperty('--slide-h');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop/tablet: keep a 16:9 slide and fill as much height as the viewport allows.
|
||||||
|
const pad = Math.max(24, Math.min(72, Math.min(vw, vh) * 0.065));
|
||||||
|
const availableW = vw - pad;
|
||||||
|
const availableH = vh - pad;
|
||||||
|
let slideW = Math.min(availableW, availableH * (16 / 9));
|
||||||
|
let slideH = slideW * (9 / 16);
|
||||||
|
|
||||||
|
// If the width-bound calculation is too tall, bind by height instead.
|
||||||
|
if (slideH > availableH) {
|
||||||
|
slideH = availableH;
|
||||||
|
slideW = slideH * (16 / 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = Math.max(0.78, Math.min(1.55, Math.min(slideW / 1200, slideH / 675)));
|
||||||
|
root.style.setProperty('--slide-w', `${Math.floor(slideW)}px`);
|
||||||
|
root.style.setProperty('--slide-h', `${Math.floor(slideH)}px`);
|
||||||
|
root.style.setProperty('--slide-scale', scale.toFixed(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
slides.forEach((slide, i) => slide.classList.toggle('active', i === index));
|
||||||
|
counter.textContent = `${index + 1} / ${slides.length}`;
|
||||||
|
progressBar.style.width = `${((index + 1) / slides.length) * 100}%`;
|
||||||
|
const note = slides[index].querySelector('.notes')?.textContent.trim() || '';
|
||||||
|
notesBox.textContent = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
function go(delta) {
|
||||||
|
index = Math.min(slides.length - 1, Math.max(0, index + delta));
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('prev').addEventListener('click', () => go(-1));
|
||||||
|
document.getElementById('next').addEventListener('click', () => go(1));
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (['ArrowRight', 'PageUp'].includes(e.key)) go(-1);
|
||||||
|
if (['ArrowLeft', 'PageDown', ' '].includes(e.key)) go(1);
|
||||||
|
if (e.key.toLowerCase() === 'n') notesBox.classList.toggle('show');
|
||||||
|
if (e.key.toLowerCase() === 'f') {
|
||||||
|
if (!document.fullscreenElement) document.documentElement.requestFullscreen?.();
|
||||||
|
else document.exitFullscreen?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', fitDeck);
|
||||||
|
window.addEventListener('orientationchange', fitDeck);
|
||||||
|
fitDeck();
|
||||||
|
update();
|
||||||
738
docs/slides/styles.css
Normal file
738
docs/slides/styles.css
Normal file
@@ -0,0 +1,738 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Vazirmatn";
|
||||||
|
src: url("../../app/static/Vazirmatn[wght].woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #171a22;
|
||||||
|
--paper: rgba(43, 43, 59, .86);
|
||||||
|
--ink: #f8fafc;
|
||||||
|
--muted: #a8adbd;
|
||||||
|
--blue: #6d8cff;
|
||||||
|
--blue-2: rgba(109, 140, 255, .16);
|
||||||
|
--green: #43d9a3;
|
||||||
|
--green-2: rgba(67, 217, 163, .16);
|
||||||
|
--amber: #f4b740;
|
||||||
|
--amber-2: rgba(244, 183, 64, .18);
|
||||||
|
--red: #fb7185;
|
||||||
|
--red-2: rgba(251, 113, 133, .16);
|
||||||
|
--line: rgba(255, 255, 255, .13);
|
||||||
|
--glass: rgba(255, 255, 255, .075);
|
||||||
|
--glass-strong: rgba(255, 255, 255, .12);
|
||||||
|
--soft: rgba(255, 255, 255, .06);
|
||||||
|
--shadow: 0 28px 90px rgba(0, 0, 0, .38);
|
||||||
|
--slide-w: min(1200px, calc(100vw - 72px));
|
||||||
|
--slide-h: min(675px, calc(100svh - 72px));
|
||||||
|
--slide-scale: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html, body { min-height: 100%; margin: 0; }
|
||||||
|
body {
|
||||||
|
font-family: "Vazirmatn", "Segoe UI", Tahoma, Arial, sans-serif;
|
||||||
|
background:
|
||||||
|
linear-gradient(120deg, rgba(244, 183, 64, .12), transparent 28%),
|
||||||
|
linear-gradient(300deg, rgba(109, 140, 255, .12), transparent 34%),
|
||||||
|
var(--bg);
|
||||||
|
color: var(--ink);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck {
|
||||||
|
min-height: 100svh;
|
||||||
|
width: 100vw;
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.slide {
|
||||||
|
display: none;
|
||||||
|
width: var(--slide-w);
|
||||||
|
height: var(--slide-h);
|
||||||
|
background: var(--paper);
|
||||||
|
border: 1px solid rgba(255, 255, 255, .10);
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 20px);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
backdrop-filter: blur(22px) saturate(130%);
|
||||||
|
padding: clamp(28px, calc(48px * var(--slide-scale)), 66px) clamp(32px, calc(58px * var(--slide-scale)), 80px);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.slide::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
linear-gradient(90deg, rgba(255, 255, 255, .035) 1px, transparent 1px),
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, .028) 1px, transparent 1px);
|
||||||
|
background-size: 42px 42px;
|
||||||
|
mask-image: linear-gradient(180deg, rgba(0,0,0,.7), transparent 55%);
|
||||||
|
}
|
||||||
|
.slide::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 420px;
|
||||||
|
height: 180px;
|
||||||
|
left: -110px;
|
||||||
|
bottom: 34px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background: linear-gradient(135deg, rgba(244, 183, 64, .18), rgba(109, 140, 255, .08));
|
||||||
|
transform: rotate(-24deg);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.slide > * { position: relative; z-index: 1; }
|
||||||
|
.slide.active { display: block; animation: fadeUp .28s ease-out; }
|
||||||
|
@keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
|
|
||||||
|
.kicker {
|
||||||
|
font-size: clamp(13px, calc(17px * var(--slide-scale)), 24px);
|
||||||
|
font-weight: 900;
|
||||||
|
color: var(--amber);
|
||||||
|
margin-bottom: clamp(5px, calc(8px * var(--slide-scale)), 14px);
|
||||||
|
letter-spacing: -.2px;
|
||||||
|
}
|
||||||
|
h1, h2 { margin: 0; line-height: 1.08; letter-spacing: -1px; }
|
||||||
|
h1 { font-size: clamp(34px, calc(58px * var(--slide-scale)), 82px); }
|
||||||
|
h2 { font-size: clamp(27px, calc(46px * var(--slide-scale)), 66px); }
|
||||||
|
.subtitle {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: clamp(16px, calc(22px * var(--slide-scale)), 32px);
|
||||||
|
margin: clamp(10px, calc(16px * var(--slide-scale)), 24px) 0 clamp(16px, calc(26px * var(--slide-scale)), 36px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.18fr .82fr;
|
||||||
|
gap: clamp(16px, calc(28px * var(--slide-scale)), 42px);
|
||||||
|
align-items: stretch;
|
||||||
|
margin-top: clamp(14px, calc(26px * var(--slide-scale)), 38px);
|
||||||
|
}
|
||||||
|
.hero-img, .demo-layout img {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(255, 255, 255, .12);
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
box-shadow: 0 22px 54px rgba(0, 0, 0, .28);
|
||||||
|
background: #202332;
|
||||||
|
}
|
||||||
|
.glass-panel,
|
||||||
|
.glass-media {
|
||||||
|
background: var(--glass);
|
||||||
|
border: 1px solid rgba(255, 255, 255, .12);
|
||||||
|
box-shadow: 0 22px 58px rgba(0, 0, 0, .24);
|
||||||
|
backdrop-filter: blur(18px) saturate(135%);
|
||||||
|
}
|
||||||
|
.talk-track {
|
||||||
|
background: var(--glass);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
padding: clamp(20px, calc(30px * var(--slide-scale)), 44px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: clamp(8px, calc(14px * var(--slide-scale)), 20px);
|
||||||
|
}
|
||||||
|
.talk-track span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: clamp(15px, calc(22px * var(--slide-scale)), 32px);
|
||||||
|
}
|
||||||
|
.talk-track strong {
|
||||||
|
font-size: clamp(20px, calc(28px * var(--slide-scale)), 42px);
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signal-board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
.signal {
|
||||||
|
min-height: 74px;
|
||||||
|
border: 1px solid rgba(255,255,255,.11);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255,255,255,.055);
|
||||||
|
padding: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
.signal i,
|
||||||
|
.step-dot,
|
||||||
|
.metric-icon {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
background: var(--amber);
|
||||||
|
color: #1f2937;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.signal b {
|
||||||
|
display: block;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.signal span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-focus {
|
||||||
|
margin-top: clamp(16px, calc(28px * var(--slide-scale)), 44px);
|
||||||
|
background: var(--glass);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
padding: clamp(12px, calc(18px * var(--slide-scale)), 26px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: clamp(260px, calc(365px * var(--slide-scale)), 560px);
|
||||||
|
}
|
||||||
|
.image-focus.compact { height: clamp(290px, calc(390px * var(--slide-scale)), 590px); }
|
||||||
|
.image-focus img { max-width: 100%; max-height: 100%; object-fit: contain; }
|
||||||
|
|
||||||
|
.mini-points {
|
||||||
|
position: absolute;
|
||||||
|
right: clamp(32px, calc(58px * var(--slide-scale)), 86px);
|
||||||
|
left: clamp(32px, calc(58px * var(--slide-scale)), 86px);
|
||||||
|
bottom: clamp(24px, calc(36px * var(--slide-scale)), 56px);
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: clamp(8px, calc(12px * var(--slide-scale)), 18px);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.mini-points span {
|
||||||
|
background: rgba(255,255,255,.08);
|
||||||
|
border: 1px solid rgba(255,255,255,.13);
|
||||||
|
color: #f8fafc;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: clamp(7px, calc(10px * var(--slide-scale)), 14px) clamp(11px, calc(16px * var(--slide-scale)), 22px);
|
||||||
|
font-size: clamp(12px, calc(16px * var(--slide-scale)), 22px);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-panel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.1fr .9fr;
|
||||||
|
gap: clamp(16px, calc(22px * var(--slide-scale)), 30px);
|
||||||
|
margin-top: clamp(16px, calc(28px * var(--slide-scale)), 42px);
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.timeline-panel .image-focus {
|
||||||
|
margin: 0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.timeline-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.timeline-cards article {
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.timeline-cards small {
|
||||||
|
min-width: 58px;
|
||||||
|
color: var(--amber);
|
||||||
|
font-weight: 900;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.timeline-cards b {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: clamp(12px, calc(15px * var(--slide-scale)), 21px);
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
li::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: .46em;
|
||||||
|
height: .46em;
|
||||||
|
margin-left: .55em;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--amber);
|
||||||
|
vertical-align: .08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: clamp(12px, calc(18px * var(--slide-scale)), 26px);
|
||||||
|
margin-top: clamp(24px, calc(44px * var(--slide-scale)), 62px);
|
||||||
|
}
|
||||||
|
.resource-grid article,
|
||||||
|
.version-row article {
|
||||||
|
min-height: clamp(170px, calc(250px * var(--slide-scale)), 350px);
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
padding: clamp(16px, calc(24px * var(--slide-scale)), 34px);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.resource-grid article::before,
|
||||||
|
.version-row article::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 42px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--amber);
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.resource-grid b,
|
||||||
|
.version-row b {
|
||||||
|
display: block;
|
||||||
|
font-size: clamp(18px, calc(25px * var(--slide-scale)), 36px);
|
||||||
|
margin-bottom: clamp(8px, calc(14px * var(--slide-scale)), 22px);
|
||||||
|
}
|
||||||
|
.quote {
|
||||||
|
position: absolute;
|
||||||
|
right: clamp(32px, calc(58px * var(--slide-scale)), 86px);
|
||||||
|
left: clamp(32px, calc(58px * var(--slide-scale)), 86px);
|
||||||
|
bottom: clamp(28px, calc(42px * var(--slide-scale)), 64px);
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #f8fafc;
|
||||||
|
font-size: clamp(16px, calc(22px * var(--slide-scale)), 32px);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.06fr .94fr;
|
||||||
|
gap: clamp(16px, calc(24px * var(--slide-scale)), 36px);
|
||||||
|
margin-top: clamp(20px, calc(34px * var(--slide-scale)), 48px);
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.diagram-card {
|
||||||
|
background: var(--glass);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
padding: clamp(12px, calc(18px * var(--slide-scale)), 26px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.diagram-card img { width: 100%; max-height: 100%; object-fit: contain; }
|
||||||
|
.code-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: clamp(12px, calc(18px * var(--slide-scale)), 26px);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
background: rgba(9, 12, 22, .78);
|
||||||
|
color: #e5edff;
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
padding: clamp(14px, calc(22px * var(--slide-scale)), 32px);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-size: clamp(11px, calc(16px * var(--slide-scale)), 22px);
|
||||||
|
line-height: 1.65;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255,255,255,.08);
|
||||||
|
}
|
||||||
|
.ltr { direction: ltr; text-align: left; }
|
||||||
|
|
||||||
|
.version-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: clamp(14px, calc(22px * var(--slide-scale)), 32px);
|
||||||
|
margin-top: clamp(28px, calc(52px * var(--slide-scale)), 78px);
|
||||||
|
}
|
||||||
|
.version-row article { min-height: clamp(210px, calc(300px * var(--slide-scale)), 430px); }
|
||||||
|
.version-row span {
|
||||||
|
display: inline-block;
|
||||||
|
background: var(--amber-2);
|
||||||
|
color: #ffe8aa;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: clamp(5px, calc(7px * var(--slide-scale)), 10px) clamp(10px, calc(14px * var(--slide-scale)), 20px);
|
||||||
|
font-size: clamp(12px, calc(16px * var(--slide-scale)), 22px);
|
||||||
|
font-weight: 900;
|
||||||
|
margin-bottom: clamp(12px, calc(20px * var(--slide-scale)), 30px);
|
||||||
|
}
|
||||||
|
.focus-line {
|
||||||
|
margin-top: clamp(18px, calc(30px * var(--slide-scale)), 44px);
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
color: #f8fafc;
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
padding: clamp(12px, calc(18px * var(--slide-scale)), 26px) clamp(16px, calc(22px * var(--slide-scale)), 34px);
|
||||||
|
font-size: clamp(15px, calc(22px * var(--slide-scale)), 32px);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: clamp(12px, calc(18px * var(--slide-scale)), 26px);
|
||||||
|
margin-top: clamp(28px, calc(48px * var(--slide-scale)), 72px);
|
||||||
|
}
|
||||||
|
.flow div {
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: clamp(10px, calc(14px * var(--slide-scale)), 18px);
|
||||||
|
padding: clamp(14px, calc(22px * var(--slide-scale)), 32px) clamp(10px, calc(14px * var(--slide-scale)), 22px);
|
||||||
|
min-height: clamp(80px, calc(105px * var(--slide-scale)), 155px);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: clamp(13px, calc(18px * var(--slide-scale)), 25px);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.flow div:not(:nth-child(4n))::after {
|
||||||
|
content: "←";
|
||||||
|
position: absolute;
|
||||||
|
left: clamp(-18px, calc(-16px * var(--slide-scale)), -10px);
|
||||||
|
top: 38%;
|
||||||
|
color: var(--amber);
|
||||||
|
font-size: clamp(18px, calc(24px * var(--slide-scale)), 34px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-map {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
.endpoint {
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 138px;
|
||||||
|
}
|
||||||
|
.endpoint span {
|
||||||
|
display: block;
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.endpoint b {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
.upgrade-steps {
|
||||||
|
width: min(410px, 34vw);
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.upgrade-steps div {
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #f7c75f;
|
||||||
|
}
|
||||||
|
.upgrade-steps div:nth-child(2) { color: #43d9a3; }
|
||||||
|
.upgrade-steps div:nth-child(3) { color: #aebdff; }
|
||||||
|
|
||||||
|
.osi-board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.05fr .95fr;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.osi-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 9px;
|
||||||
|
}
|
||||||
|
.osi-layer {
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 150px 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: center;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
.osi-layer.active {
|
||||||
|
background: rgba(109, 140, 255, .34);
|
||||||
|
border-color: rgba(109, 140, 255, .50);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.osi-layer.transport {
|
||||||
|
background: rgba(67, 217, 163, .26);
|
||||||
|
border-color: rgba(67, 217, 163, .44);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.osi-layer b { font-size: 16px; }
|
||||||
|
.osi-layer span { color: inherit; opacity: .85; font-size: 14px; }
|
||||||
|
.layer-note {
|
||||||
|
background: var(--glass);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 22px;
|
||||||
|
display: grid;
|
||||||
|
align-content: center;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
.layer-note div {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.layer-note i { background: var(--green); }
|
||||||
|
|
||||||
|
.topology {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1.1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
.node-column {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
.node {
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
min-height: 86px;
|
||||||
|
}
|
||||||
|
.node b { display: block; font-size: 18px; }
|
||||||
|
.node span { color: var(--muted); font-size: 14px; }
|
||||||
|
.server-node {
|
||||||
|
background: linear-gradient(145deg, rgba(244, 183, 64, .34), rgba(109, 140, 255, .20));
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 28px 18px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 18px 44px rgba(37, 99, 235, .22);
|
||||||
|
}
|
||||||
|
.server-node span { color: #dbeafe; }
|
||||||
|
.room-badge {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
background: rgba(255,255,255,.12);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 28px;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
.pipe-step {
|
||||||
|
min-height: 132px;
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
display: grid;
|
||||||
|
align-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.pipe-step:not(:last-child)::after {
|
||||||
|
content: "→";
|
||||||
|
position: absolute;
|
||||||
|
right: -17px;
|
||||||
|
top: 44%;
|
||||||
|
color: var(--amber);
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
.pipe-step b { font-size: 15px; }
|
||||||
|
.pipe-step span { color: var(--muted); font-size: 13px; }
|
||||||
|
|
||||||
|
.metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
.metric-card {
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 18px;
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 130px;
|
||||||
|
}
|
||||||
|
.metric-card b { font-size: 20px; }
|
||||||
|
.metric-card:nth-child(2) .metric-icon { background: var(--green); }
|
||||||
|
.metric-card:nth-child(3) .metric-icon { background: var(--amber); }
|
||||||
|
|
||||||
|
.demo-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr .9fr;
|
||||||
|
gap: clamp(20px, calc(36px * var(--slide-scale)), 52px);
|
||||||
|
margin-top: clamp(22px, calc(38px * var(--slide-scale)), 56px);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
ol {
|
||||||
|
margin: 0;
|
||||||
|
padding-right: clamp(20px, calc(28px * var(--slide-scale)), 42px);
|
||||||
|
font-size: clamp(15px, calc(21px * var(--slide-scale)), 30px);
|
||||||
|
line-height: 2;
|
||||||
|
color: #23324a;
|
||||||
|
background: rgba(255,255,255,.075);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding-top: 22px;
|
||||||
|
padding-bottom: 22px;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
direction: ltr;
|
||||||
|
unicode-bidi: embed;
|
||||||
|
background: rgba(255,255,255,.10);
|
||||||
|
padding: 3px 7px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #f7c75f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
position: fixed;
|
||||||
|
left: 28px;
|
||||||
|
bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 5;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
.controls button {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255,255,255,.10);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 10px 28px rgba(0, 0, 0, .26);
|
||||||
|
font-size: 28px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.controls span { font-weight: 800; color: var(--muted); min-width: 58px; text-align: center; }
|
||||||
|
.hint {
|
||||||
|
position: fixed;
|
||||||
|
right: 28px;
|
||||||
|
bottom: 31px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 5px;
|
||||||
|
background: rgba(37, 99, 235, .08);
|
||||||
|
}
|
||||||
|
.progress span { display: block; height: 100%; width: 10%; background: var(--amber); transition: width .2s ease; }
|
||||||
|
.speaker-notes {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
right: 50%;
|
||||||
|
transform: translateX(50%);
|
||||||
|
bottom: 84px;
|
||||||
|
width: min(900px, calc(100vw - 64px));
|
||||||
|
background: rgba(15, 23, 42, .92);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 22px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: clamp(14px, 1.45vw, 19px);
|
||||||
|
z-index: 6;
|
||||||
|
box-shadow: 0 22px 80px rgba(15,23,42,.35);
|
||||||
|
}
|
||||||
|
.speaker-notes.show { display: block; }
|
||||||
|
.notes { display: none; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body { overflow: visible; background: #fff; }
|
||||||
|
.deck { height: auto; display: block; }
|
||||||
|
.slide { display: block !important; box-shadow: none; margin: 0; page-break-after: always; width: 100vw; height: 56.25vw; border: 0; border-radius: 0; }
|
||||||
|
.controls, .hint, .progress, .speaker-notes { display: none !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
body { overflow: auto; }
|
||||||
|
.deck { width: 100%; min-height: auto; display: block; padding: 12px; }
|
||||||
|
.slide {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
min-height: calc(100svh - 24px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: clamp(18px, 5vw, 28px);
|
||||||
|
border-radius: 22px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
h1 { font-size: clamp(30px, 8.4vw, 42px); }
|
||||||
|
h2 { font-size: clamp(24px, 7vw, 34px); }
|
||||||
|
.subtitle { font-size: clamp(14px, 4vw, 18px); }
|
||||||
|
.kicker { font-size: clamp(12px, 3.7vw, 16px); }
|
||||||
|
.hero-grid, .split, .demo-layout, .version-row, .resource-grid { grid-template-columns: 1fr; }
|
||||||
|
.hero-grid, .split, .demo-layout { gap: 18px; }
|
||||||
|
.resource-grid, .version-row { gap: 12px; margin-top: 22px; }
|
||||||
|
.resource-grid article, .version-row article { min-height: auto; padding: 16px; }
|
||||||
|
.resource-grid b, .version-row b { font-size: 18px; }
|
||||||
|
.resource-grid p, .version-row p { font-size: 13px; }
|
||||||
|
.image-focus, .image-focus.compact { height: auto; min-height: 0; padding: 10px; }
|
||||||
|
.image-focus img { width: 100%; height: auto; }
|
||||||
|
.flow { grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 22px; }
|
||||||
|
.flow div { min-height: 72px; font-size: 13px; padding: 12px 8px; }
|
||||||
|
.flow div::after { display: none; }
|
||||||
|
.mini-points, .quote { position: static; margin-top: 18px; }
|
||||||
|
.mini-points { gap: 8px; }
|
||||||
|
.mini-points span { font-size: 12px; padding: 7px 10px; }
|
||||||
|
.quote { font-size: 15px; }
|
||||||
|
pre { font-size: clamp(10px, 2.8vw, 13px); padding: 14px; }
|
||||||
|
ol { font-size: 14px; line-height: 1.9; }
|
||||||
|
.controls { left: 14px; bottom: 14px; }
|
||||||
|
.controls button { width: 36px; height: 36px; font-size: 24px; }
|
||||||
|
.hint { display: none; }
|
||||||
|
.speaker-notes { bottom: 64px; width: calc(100vw - 28px); font-size: 14px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.slide { padding: 18px; }
|
||||||
|
.flow { grid-template-columns: 1fr; }
|
||||||
|
.talk-track strong { font-size: 17px; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user