diff --git a/docs/slides/README.md b/docs/slides/README.md new file mode 100644 index 0000000..d2a2cc7 --- /dev/null +++ b/docs/slides/README.md @@ -0,0 +1,9 @@ +# ارائه WebSocket فارسی + +- فایل `index.html` را در مرورگر باز کنید. +- با کلیدهای چپ/راست بین اسلایدها حرکت کنید. +- کلید `N` یادداشت سخنران را نشان می‌دهد. +- کلید `F` حالت تمام‌صفحه را فعال می‌کند. +- فونت اصلی `Vazirmatn` است؛ فایل ارائه از Google Fonts / فونت نصب‌شده روی سیستم استفاده می‌کند. +- در نمایشگرهای بزرگ، اسلایدها به صورت ۱۶:۹ تا جای ممکن ارتفاع صفحه را پر می‌کنند. +- در نمایشگرهای کوچک، اندازه فونت و چینش اسلایدها کوچک‌تر و عمودی‌تر می‌شود. diff --git a/docs/slides/assets/chatroom-architecture.svg b/docs/slides/assets/chatroom-architecture.svg new file mode 100644 index 0000000..2f38f02 --- /dev/null +++ b/docs/slides/assets/chatroom-architecture.svg @@ -0,0 +1,49 @@ + + Chatroom architecture + A compact architecture diagram for the chatroom project. + + + + + + + + + + + + Chatroom Architecture + Browser Clients - FastAPI - Room Manager + + + + + + + + Client A + Browser tab + Client B + Browser tab + FastAPI + WebSocket Endpoint + ConnectionManager + /ws/{room}/{user} + Room: general + A - B - C + Room: class + D - E + + + + + WebSocket Frames + Broadcast by room + diff --git a/docs/slides/assets/home.png b/docs/slides/assets/home.png new file mode 100644 index 0000000..9783380 Binary files /dev/null and b/docs/slides/assets/home.png differ diff --git a/docs/slides/assets/websocket-handshake.svg b/docs/slides/assets/websocket-handshake.svg new file mode 100644 index 0000000..a69eb6e --- /dev/null +++ b/docs/slides/assets/websocket-handshake.svg @@ -0,0 +1,40 @@ + + WebSocket handshake + A compact HTTP Upgrade handshake diagram. + + + + + + + + + + + + + HTTP Upgrade Handshake + Starts with HTTP, continues with WebSocket Frames + + + + + Browser + JavaScript WebSocket + FastAPI Server + /ws/{room}/{user} + + 1. GET + Upgrade + + 2. 101 Switching Protocols + + + 3. Full-duplex WebSocket Frames + diff --git a/docs/slides/assets/websocket-history.svg b/docs/slides/assets/websocket-history.svg new file mode 100644 index 0000000..2951da2 --- /dev/null +++ b/docs/slides/assets/websocket-history.svg @@ -0,0 +1,63 @@ + + WebSocket history + A compact Persian timeline with English technical names. + + + + + + + + + + WebSocket History + From Classic HTTP to Real-time communication + + + + + + + + 1 + Classic HTTP + Request / Response + No Server Push + + + + 2 + Polling + Repeated request + More overhead + + + + 3 + Long Polling + Open request + Long wait + + + + 4 + WebSocket + Persistent connection + Full-duplex + + + + 5 + Real-time Web + Chat - Game + Dashboard - IoT + + + diff --git a/docs/slides/assets/websocket-osi.svg b/docs/slides/assets/websocket-osi.svg new file mode 100644 index 0000000..60ea5be --- /dev/null +++ b/docs/slides/assets/websocket-osi.svg @@ -0,0 +1,43 @@ + + WebSocket in OSI model + A compact OSI stack with English technical names and Persian supporting labels. + + + + + + + + WebSocket in the OSI Model + Application Layer over TCP/IP + + + + + + Layer 7 - Application + WebSocket - JSON - Chat + + Layer 6 - Presentation + UTF-8 - Text + + Layer 5 - Session + Long-lived conversation + + Layer 4 - Transport + TCP reliable stream + + Layer 3 - Network + IP routing + + Layers 2-1 - Link / Physical + Ethernet - Wi-Fi + + diff --git a/docs/slides/assets/websocket-pros-cons.svg b/docs/slides/assets/websocket-pros-cons.svg new file mode 100644 index 0000000..80de71b --- /dev/null +++ b/docs/slides/assets/websocket-pros-cons.svg @@ -0,0 +1,44 @@ + + WebSocket pros and cons + A compact pros and cons diagram for WebSocket. + + + + + + + + + + WebSocket Trade-offs + Strengths and limitations at a glance + + + + + Pros + Low latency + Full-duplex + Less HTTP overhead + Good for Chat / Dashboard + + + + + Cons + Server state + Open socket memory + Reconnect handling + Scaling needs design + + + + + diff --git a/docs/slides/index.html b/docs/slides/index.html new file mode 100644 index 0000000..69062c1 --- /dev/null +++ b/docs/slides/index.html @@ -0,0 +1,243 @@ + + + + + + ارائه WebSocket و Socket Programming + + + +
+
+
پروژه مهندسی اینترنت
+

چت‌روم ساده با WebSocket

+

نمایش Socket Programming و ارتباط Real-time در وب

+ +
+ نمای صفحه چت‌روم +
+ مسیر ارائه + History → Polling → Handshake → OSI → Architecture → Demo +
+
1
HandshakeHTTP Upgrade
+
2
ChannelPersistent TCP
+
3
FramesFull-duplex
+
4
BroadcastRoom delivery
+
+
+
+ +
+ +
+
History
+

چرا WebSocket لازم شد؟

+

مسیر تکامل از HTTP کلاسیک تا اتصال پایدار دوطرفه

+ +
+
+ تاریخچه WebSocket +
+
+
HTTP
Request / Response
  • ساده
  • بدون Server Push
+
AJAX
Polling
  • درخواست تکراری
  • هزینه شبکه بیشتر
+
LONG
Long Polling
  • اتصال منتظر
  • پیچیدگی بیشتر
+
WS
WebSocket
  • اتصال پایدار
  • پیام دوطرفه
+
+
+ +
+ +
+
Resource Cost
+

هزینه هر روش برای سرور

+

هر روش، فشار متفاوتی روی Network، CPU، memory و socket دارد

+ +
+
Classic HTTP
  • درخواست جداگانه
  • پردازش header
  • پایان چرخه
+
Polling
  • درخواست زیاد
  • پاسخ‌های خالی
  • سربار Network
+
Long Polling
  • اتصال باز
  • نگهداری state
  • مصرف buffer
+
WebSocket
  • یک اتصال پایدار
  • فریم سبک
  • state فعال
+
+ +
Polling → درخواست تکراری · Long Polling → انتظار طولانی · WebSocket → کانال پایدار
+ +
+ +
+
HTTP Upgrade Handshake
+

WebSocket چطور شروع می‌شود؟

+

ابتدا HTTP، سپس پاسخ 101 و بعد WebSocket Frames

+ +
+
ClientBrowser
  • ساخت WebSocket
  • درخواست Upgrade
+
+
1. GET + Upgrade
+
2. 101 Switching Protocols
+
3. WebSocket Frames
+
+
ServerFastAPI
  • پذیرش Upgrade
  • نگهداری socket
+
+ +
+
+ HTTP Upgrade Handshake +
+
+
GET /ws/general/ali HTTP/1.1
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: ...
+
HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
+Connection: Upgrade
+
+
+ +
+ +
+
HTTP Versions
+

WebSocket در نسخه‌های HTTP

+

مدل آموزشی پروژه روی HTTP/1.1 Upgrade تمرکز دارد

+ +
+
HTTP/1.1Upgrade + 101
  • مدل کلاسیک
  • یک TCP connection
  • ساده برای آموزش
+
HTTP/2Extended CONNECT
  • روی یک stream
  • بدون Upgrade کلاسیک
  • پیچیده‌تر
+
HTTP/3QUIC + CONNECT
  • بدون TCP
  • روی QUIC
  • مدرن‌تر
+
+
HTTP/1.1: Upgrade → 101 → WebSocket Frames
+ +
+ +
+
OSI Model
+

جایگاه WebSocket در شبکه

+

WebSocket در لایه Application استفاده می‌شود؛ TCP/IP حمل داده را انجام می‌دهد

+ +
+
+
Layer 7WebSocket + JSON
+
Layer 6UTF-8 text
+
Layer 5Long-lived session
+
Layer 4TCP reliable stream
+
Layer 3IP routing
+
Layer 2/1Ethernet / Wi-Fi
+
+
+
AApplication
  • اتاق
  • کاربر
  • پیام
+
TTransport
  • TCP
  • تحویل قابل اعتماد
+
NNetwork
  • IP
  • مسیریابی
+
+
+ +
+ +
+
Project Architecture
+

معماری پروژه چت‌روم

+

هر تب مرورگر یک WebSocket connection دارد

+ +
+
+
Client ABrowser tab
+
Client BBrowser tab
+
+
+ FastAPI WebSocket Endpoint +

/ws/{room}/{user}

+
ConnectionManager
+
Broadcast داخل همان room
+
+
+
Room: generalA · B · C
+
Room: classD · E
+
+
+ +
+ Browser UI + FastAPI endpoint + Room Manager + Broadcast +
+ +
+ +
+
Message Flow
+

جریان پیام در پروژه

+

از ورود کاربر تا نمایش پیام بدون refresh

+ +
+
1Joinname + room
+
2UpgradeHTTP → WebSocket
+
3SendJSON content
+
4Broadcastroom sockets
+
5Renderبدون refresh
+
+ +
+
نام و اتاق
+
WebSocket در JS
+
HTTP Upgrade
+
قبول در FastAPI
+
Room Manager
+
JSON message
+
Broadcast
+
نمایش پیام
+
+ +
+ +
+
Trade-offs
+

مزایا و محدودیت‌ها

+

WebSocket برای Real-time عالی است، اما state سرور را بیشتر می‌کند

+ +
+
Full-duplex
  • ارسال دوطرفه
  • بدون انتظار request بعدی
+
Low latency
  • تاخیر کم
  • فریم سبک‌تر
+
!Server state
  • socket باز
  • مدیریت disconnect
+
+ +
+ Chat + Notifications + Live dashboard + Online game +
+ +
+ +
+
Demo
+

برنامه اجرای دمو

+

دو تب، یک room، پیام Real-time

+ +
+ نمای دمو +
    +
  1. اجرای uvicorn app.main:app --reload
  2. +
  3. باز کردن دو tab
  4. +
  5. ورود دو user به یک room
  6. +
  7. ارسال message
  8. +
  9. نمایش بدون refresh
  10. +
  11. تغییر online users
  12. +
+
+ +
+
+ + +
← → حرکت · N یادداشت · F تمام‌صفحه
+
+
+ + + diff --git a/docs/slides/script.js b/docs/slides/script.js new file mode 100644 index 0000000..21a8089 --- /dev/null +++ b/docs/slides/script.js @@ -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(); diff --git a/docs/slides/styles.css b/docs/slides/styles.css new file mode 100644 index 0000000..56c853d --- /dev/null +++ b/docs/slides/styles.css @@ -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; } +}