anycoder-9becffd1 / index.html
fwefwefw's picture
Upload folder using huggingface_hub
76bf311 verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>C# to Rust UDP Converter</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--secondary: #f97316;
--bg-dark: #0f172a;
--bg-card: #1e293b;
--bg-code: #0d1117;
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--border: #334155;
--success: #10b981;
--error: #ef4444;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, var(--bg-dark) 0%, #1a1f3a 100%);
color: var(--text-primary);
min-height: 100vh;
line-height: 1.6;
}
header {
background: rgba(30, 41, 59, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
padding: 1.5rem 2rem;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.header-content {
max-width: 1400px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.logo {
display: flex;
align-items: center;
gap: 1rem;
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
}
.logo i {
color: var(--primary);
font-size: 2rem;
}
.nav-links {
display: flex;
gap: 2rem;
align-items: center;
}
.nav-links a {
color: var(--text-secondary);
text-decoration: none;
transition: color 0.3s;
font-weight: 500;
}
.nav-links a:hover {
color: var(--primary);
}
.brand-link {
color: var(--primary) !important;
font-weight: 600;
}
main {
max-width: 1400px;
margin: 2rem auto;
padding: 0 2rem;
}
.converter-section {
background: var(--bg-card);
border-radius: 1rem;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
border: 1px solid var(--border);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.section-title {
font-size: 1.75rem;
font-weight: 700;
display: flex;
align-items: center;
gap: 0.75rem;
}
.section-title i {
color: var(--primary);
}
.code-container {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
@media (min-width: 1024px) {
.code-container {
grid-template-columns: 1fr 1fr;
}
}
.code-block {
position: relative;
background: var(--bg-code);
border-radius: 0.75rem;
overflow: hidden;
border: 1px solid var(--border);
}
.code-header {
background: rgba(30, 41, 59, 0.8);
padding: 1rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
}
.language-tag {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
color: var(--text-primary);
}
.language-tag i {
font-size: 1.25rem;
}
.csharp-tag i {
color: #9b4f96;
}
.rust-tag i {
color: #ce422b;
}
.code-actions {
display: flex;
gap: 0.5rem;
}
.btn-icon {
background: rgba(99, 102, 241, 0.1);
border: 1px solid var(--primary);
color: var(--primary);
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.3s;
font-size: 0.875rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn-icon:hover {
background: var(--primary);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.3);
}
.code-content {
max-height: 500px;
overflow-y: auto;
padding: 1.5rem;
}
.code-content::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.code-content::-webkit-scrollbar-track {
background: var(--bg-dark);
}
.code-content::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 4px;
}
pre {
margin: 0;
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 0.875rem;
line-height: 1.5;
}
code {
font-family: inherit;
}
.explanation-section {
background: linear-gradient(135deg, var(--bg-card) 0%, rgba(99, 102, 241, 0.05) 100%);
border-radius: 1rem;
padding: 2rem;
border: 1px solid var(--border);
}
.explanation-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
margin-top: 1.5rem;
}
@media (min-width: 768px) {
.explanation-grid {
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
}
.explanation-card {
background: rgba(15, 23, 42, 0.5);
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid var(--border);
transition: transform 0.3s, box-shadow 0.3s;
}
.explanation-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
}
.card-icon {
width: 48px;
height: 48px;
background: rgba(99, 102, 241, 0.1);
border-radius: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
}
.card-icon i {
font-size: 1.5rem;
color: var(--primary);
}
.card-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 0.75rem;
color: var(--text-primary);
}
.card-text {
color: var(--text-secondary);
font-size: 0.875rem;
line-height: 1.6;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.feature-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: rgba(99, 102, 241, 0.05);
border-radius: 0.5rem;
border: 1px solid rgba(99, 102, 241, 0.2);
}
.feature-item i {
color: var(--success);
font-size: 1.25rem;
}
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: var(--success);
color: white;
padding: 1rem 1.5rem;
border-radius: 0.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
transform: translateX(400px);
transition: transform 0.3s;
z-index: 1000;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
}
.toast.show {
transform: translateX(0);
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
}
.tab {
background: transparent;
border: none;
color: var(--text-secondary);
padding: 0.75rem 1.5rem;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
position: relative;
}
.tab:hover {
color: var(--text-primary);
}
.tab.active {
color: var(--primary);
}
.tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background: var(--primary);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.dependencies {
background: rgba(249, 115, 22, 0.1);
border: 1px solid rgba(249, 115, 22, 0.3);
border-radius: 0.5rem;
padding: 1rem;
margin-top: 1rem;
}
.dependencies-title {
color: var(--secondary);
font-weight: 600;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.dependencies-list {
font-family: 'Fira Code', monospace;
font-size: 0.875rem;
color: var(--text-secondary);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
</style>
</head>
<body>
<header>
<div class="header-content">
<div class="logo">
<i class="fas fa-code"></i>
<span>Code Converter</span>
</div>
<nav class="nav-links">
<a href="#converter">Конвертер</a>
<a href="#explanation">Объяснения</a>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">
<i class="fas fa-rocket"></i> Built with anycoder
</a>
</nav>
</div>
</header>
<main>
<section id="converter" class="converter-section fade-in">
<div class="section-header">
<h2 class="section-title">
<i class="fas fa-exchange-alt"></i>
Конвертация C# в Rust
</h2>
<button class="btn-icon" onclick="downloadRustCode()">
<i class="fas fa-download"></i> Скачать Rust код
</button>
</div>
<div class="tabs">
<button class="tab active" onclick="switchTab('server')">
<i class="fas fa-server"></i> UDP Сервер
</button>
<button class="tab" onclick="switchTab('client')">
<i class="fas fa-desktop"></i> UDP Клиент
</button>
<button class="tab" onclick="switchTab('complete')">
<i class="fas fa-file-code"></i> Полный код
</button>
</div>
<div id="server-tab" class="tab-content active">
<div class="code-container">
<div class="code-block">
<div class="code-header">
<span class="language-tag csharp-tag">
<i class="fab fa-microsoft"></i> C#
</span>
<div class="code-actions">
<button class="btn-icon" onclick="copyCode('csharp-server')">
<i class="fas fa-copy"></i> Копировать
</button>
</div>
</div>
<div class="code-content">
<pre><code class="language-csharp" id="csharp-server">int port_local = 11000;
UdpClient server_local = new UdpClient(port_local);
Console.WriteLine($"UDP сервер запущен на порту {port_local}...");
IPEndPoint remoteEP_local = new IPEndPoint(IPAddress.Any, 0);
CancellationTokenSource token_local = new CancellationTokenSource();
Task serverTask_local = Task.Run(() =>
{
while (token_local.IsCancellationRequested == false)
{
try
{
byte[] data = server_local.Receive(ref remoteEP_local);
server_local.Send(data, data.Length, remoteEP_local);
string message = Encoding.UTF8.GetString(data);
Console.WriteLine($"Получено от {remoteEP_local}: {message}");
}
catch (Exception ex)
{
Log.ошибка(ex, "UDP serverTask");
}
}
});</code></pre>
</div>
</div>
<div class="code-block">
<div class="code-header">
<span class="language-tag rust-tag">
<i class="fab fa-rust"></i> Rust
</span>
<div class="code-actions">
<button class="btn-icon" onclick="copyCode('rust-server')">
<i class="fas fa-copy"></i> Копировать
</button>
</div>
</div>
<div class="code-content">
<pre><code class="language-rust" id="rust-server">use std::net::{UdpSocket, Ipv4Addr, SocketAddr};
use std::thread;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
const PORT: u16 = 11000;
pub fn start_udp_server() -> Arc&lt;AtomicBool&gt; {
let socket = UdpSocket::bind(("0.0.0.0", PORT))
.expect("Не удалось привязать сокет");
println!("UDP сервер запущен на порту {}", PORT);
let running = Arc::new(AtomicBool::new(true));
let running_clone = running.clone();
thread::spawn(move || {
let mut buf = [0; 1024];
while running_clone.load(Ordering::Relaxed) {
match socket.recv_from(&mut buf) {
Ok((size, src)) => {
let data = &buf[..size];
let message = String::from_utf8_lossy(data);
println!("Получено от {}: {}", src, message);
// Отправляем эхо-ответ
if let Err(e) = socket.send_to(data, src) {
eprintln!("Ошибка отправки: {}", e);
}
}
Err(e) => {
eprintln!("Ошибка приёма: {}", e);
}
}
}
});
running
}</code></pre>
</div>
</div>
</div>
</div>
<div id="client-tab" class="tab-content">
<div class="code-container">
<div class="code-block">
<div class="code-header">
<span class="language-tag csharp-tag">
<i class="fab fa-microsoft"></i> C#
</span>
<div class="code-actions">
<button class="btn-icon" onclick="copyCode('csharp-client')">
<i class="fas fa-copy"></i> Копировать
</button>
</div>
</div>
<div class="code-content">
<pre><code class="language-csharp" id="csharp-client">UdpClient udp_client_local = new UdpClient();
Task clientTask_local = Task.Run(async () =>
{
try
{
IPEndPoint serverEP = new IPEndPoint(IPAddress.Any, 0);
int messageCounter = 1;
while (token_local.IsCancellationRequested == false)
{
string message = $"Сообщение {messageCounter}";
byte[] data = Encoding.UTF8.GetBytes(message);
long start_time = Stopwatch.GetTimestamp();
udp_client_local.Send(data, data.Length, "127.0.0.1", port_local);
byte[] receivedData = udp_client_local.Receive(ref serverEP);
TimeSpan rtt = Stopwatch.GetElapsedTime(start_time);
string response = Encoding.UTF8.GetString(receivedData);
Console.WriteLine($"Ответ от сервера: {response} | RTT: {rtt.TotalMilliseconds.ToString("F3")} ms | PING: {(rtt.TotalMilliseconds / 2).ToString("F3")} ms");
messageCounter++;
await Task.Delay(1000);
}
}
catch (Exception ex)
{
Log.ошибка(ex, "clientTask");
}
});</code></pre>
</div>
</div>
<div class="code-block">
<div class="code-header">
<span class="language-tag rust-tag">
<i class="fab fa-rust"></i> Rust
</span>
<div class="code-actions">
<button class="btn-icon" onclick="copyCode('rust-client')">
<i class="fas fa-copy"></i> Копировать
</button>
</div>
</div>
<div class="code-content">
<pre><code class="language-rust" id="rust-client">use std::net::UdpSocket;
use std::time::{Duration, Instant};
use std::thread;
pub async fn start_udp_client(running: Arc&lt;AtomicBool&gt;) {
let socket = UdpSocket::bind("0.0.0.0:0")
.expect("Не удалось создать клиентский сокет");
let server_addr: SocketAddr = ("127.0.0.1", PORT).into();
let mut message_counter = 1;
while running.load(Ordering::Relaxed) {
let message = format!("Сообщение {}", message_counter);
let data = message.as_bytes();
let start_time = Instant::now();
// Отправляем сообщение
if let Err(e) = socket.send_to(data, server_addr) {
eprintln!("Ошибка отправки: {}", e);
continue;
}
// Получаем ответ
let mut buf = [0; 1024];
match socket.recv_from(&mut buf) {
Ok((size, _)) => {
let rtt = start_time.elapsed();
let response = String::from_utf8_lossy(&buf[..size]);
println!("Ответ от сервера: {} | RTT: {:.3} ms | PING: {:.3} ms",
response,
rtt.as_millis(),
rtt.as_millis() as f64 / 2.0);
}
Err(e) => {
eprintln!("Ошибка приёма: {}", e);
}
}
message_counter += 1;
thread::sleep(Duration::from_millis(1000));
}
}</code></pre>
</div>
</div>
</div>
</div>
<div id="complete-tab" class="tab-content">
<div class="code-block">
<div class="code-header">
<span class="language-tag rust-tag">
<i class="fab fa-rust"></i> Полный код на Rust
</span>
<div class="code-actions">
<button class="btn-icon" onclick="copyCode('rust-complete')">
<i class="fas fa-copy"></i> Копировать
</button>
</div>
</div>
<div class="code-content">
<pre><code class="language-rust" id="rust-complete">use std::net::{UdpSocket, SocketAddr};
use std::sync::{Arc, mpsc};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant};
const PORT: u16 = 11000;
fn main() {
println!("Запуск UDP сервера и клиента...");
let running = Arc::new(AtomicBool::new(true));
// Запускаем сервер
let server_running = start_udp_server(running.clone());
// Запускаем клиент в отдельном потоке
let client_running = running.clone();
let client_handle = thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(start_udp_client(client_running));
});
println!("Нажмите Enter для остановки...");
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
// Останавливаем сервер и клиент
running.store(false, Ordering::Relaxed);
// Ждём завершения потоков
thread::sleep(Duration::from_secs(3));
println!("Программа завершена.");
}
pub fn start_udp_server(running: Arc&lt;AtomicBool&gt;) -> Arc&lt;AtomicBool&gt; {
let socket = UdpSocket::bind(("0.0.0.0", PORT))
.expect("Не удалось привязать сокет");
println!("UDP сервер запущен на порту {}", PORT);
let server_running = running.clone();
thread::spawn(move || {
let mut buf = [0; 1024];
while server_running.load(Ordering::Relaxed) {
match socket.recv_from(&mut buf) {
Ok((size, src)) => {
let data = &buf[..size];
let message = String::from_utf8_lossy(data);
println!("Получено от {}: {}", src, message);
if let Err(e) = socket.send_to(data, src) {
eprintln!("Ошибка отправки: {}", e);
}
}
Err(e) => {
eprintln!("Ошибка приёма: {}", e);
}
}
}
});
running
}
pub async fn start_udp_client(running: Arc&lt;AtomicBool&gt;) {
let socket = UdpSocket::bind("0.0.0.0:0")
.expect("Не удалось создать клиентский сокет");
let server_addr: SocketAddr = ("127.0.0.1", PORT).into();
let mut message_counter = 1;
while running.load(Ordering::Relaxed) {
let message = format!("Сообщение {}", message_counter);
let data = message.as_bytes();
let start_time = Instant::now();
if let Err(e) = socket.send_to(data, server_addr) {
eprintln!("Ошибка отправки: {}", e);
continue;
}
let mut buf = [0; 1024];
match socket.recv_from(&mut buf) {
Ok((size, _)) => {
let rtt = start_time.elapsed();
let response = String::from_utf8_lossy(&buf[..size]);
println!("Ответ от сервера: {} | RTT: {:.3} ms | PING: {:.3} ms",
response,
rtt.as_millis(),
rtt.as_millis() as f64 / 2.0);
}
Err(e) => {
eprintln!("Ошибка приёма: {}", e);
}
}
message_counter += 1;
thread::sleep(Duration::from_millis(1000));
}
}</code></pre>
</div>
<div class="dependencies">
<div class="dependencies-title">
<i class="fas fa-cube"></i> Зависимости для Cargo.toml
</div>
<div class="dependencies-list">
[dependencies]
tokio = { version = "1.0", features = ["full"] }
</div>
</div>
</div>
</div>
</section>
<section id="explanation" class="explanation-section fade-in">
<div class="section-header">
<h2 class="section-title">
<i class="fas fa-graduation-cap"></i>
Ключевые отличия и особенности
</h2>
</div>
<div class="explanation-grid">
<div class="explanation-card">
<div class="card-icon">
<i class="fas fa-memory"></i>
</div>
<h3 class="card-title">Управление памятью</h3>
<p class="card-text">
Rust обеспечивает безопасность памяти без сборщика мусора. Вместо C# GC, Rust использует владение и заимствование для предотвращения ошибок.
</p>
</div>
<div class="explanation-card">
<div class="card-icon">
<i class="fas fa-sync-alt"></i>
</div>
<h3 class="card-title">Асинхронность</h3>
<p class="card-text">
Вместо Task.Run из C#, Rust использует async/await с рантаймом tokio. Асинхронный код в Rust компилируется в state machine.
</p>
</div>
<div class="explanation-card">
<div class="card-icon">
<i class="fas fa-shield-alt"></i>
</div>
<h3 class="card-title">Обработка ошибок</h3>
<p class="card-text">
Rust использует Result&lt;T, E&gt; вместо исключений C#. Это заставляет обрабатывать все возможные ошибки компилятором.
</p>
</div>
<div class="explanation-card">
<div class="card-icon">
<i class="fas fa-tachometer-alt"></i>
</div>
<h3 class="card-title">Производительность</h3>
<p class="card-text">
Код Rust компилируется в машинный код без накладных расходов рантайма, обеспечивая производительность на уровне C/C++.
</p>
</div>
</div>
<h3 style="margin-top: 2rem; margin-bottom: 1rem; font-size: 1.25rem; color: var(--text-primary);">
<i class="fas fa-list-check" style="color: var(--primary); margin-right: 0.5rem;"></i>
Преимущества конвертации в Rust
</h3>
<div class="features-grid">
<div class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Нулевая стоимость абстракций</span>
</div>
<div class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Безопасные потоки без data races</span>
</div>
<div class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Детерминированное управление ресурсами</span>
</div>
<div class="feature-item">
<i class="fas fa-check-circle"></i>
<span>WebAssembly поддержка</span>
</div>
<div class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Отличная экосистема (Cargo)</span>
</div>
<div class="feature-item">
<i class="fas fa-check-circle"></i>
<span>Безопасность без потери производительности</span>
</div>
</div>
</section>
</main>
<div id="toast" class="toast">
<i class="fas fa-check-circle"></i>
<span id="toast-message">Скопировано в буфер обмена!</span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-csharp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js"></script>
<script>
function switchTab(tabName) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Remove active class from all tab buttons
document.querySelectorAll('.tab').forEach(btn => {
btn.classList.remove('active');
});
// Show selected tab
document.getElementById(tabName + '-tab').classList.add('active');
// Add active class to clicked button
event.target.classList.add('active');
// Re-highlight code
Prism.highlightAll();
}
function copyCode(codeId) {
const codeElement = document.getElementById(codeId);
const text = codeElement.textContent;
navigator.clipboard.writeText(text).then(() => {
showToast('Код скопирован в буфер обмена!');
}).catch(err => {
console.error('Ошибка копирования:', err);
showToast('Ошибка копирования кода', 'error');
});
}
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toast-message');
toastMessage.textContent = message;
if (type === 'error') {
toast.style.background = 'var(--error)';
} else {
toast.style.background = 'var(--success)';
}
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
function downloadRustCode() {
const rustCode = document.getElementById('rust-complete').textContent;
const cargoToml = `[dependencies]\ntokio = { version = "1.0", features = ["full"] }`;
const content = `// main.rs\n${rustCode}\n\n// Cargo.toml\n${cargoToml}`;
const blob = new Blob([content], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'udp_server_client_rust.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
showToast('Rust код загружен!');
}
// Add smooth scrolling
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Add fade-in animation on scroll
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, observerOptions);
document.querySelectorAll('.fade-in').forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
el.style.transition = 'opacity 0.5s ease-out, transform 0.5s ease-out';
observer.observe(el);
});
// Initialize syntax highlighting
Prism.highlightAll();
</script>
</body>
</html>