liger_showcase / index.html
dltmdgus's picture
Update index.html
258dba8 verified
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Liger - Green-Drive Optimizer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Noto+Sans+KR:wght@300;400;500;700&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="style.css">
<meta name="description" content="Liger: 친환경 주행 경로 최적화 및 충전소 추천 솔루션">
</head>
<body>
<div class="background-glob"></div>
<nav class="navbar">
<div class="logo">LIGER</div>
<div class="nav-links">
<a href="#demo">Demo</a>
<a href="#presentation">Presentation</a>
<a href="#workflow">Workflow</a>
</div>
</nav>
<header class="hero">
<div class="hero-content">
<span class="badge">Green-Drive Optimizer</span>
<h1>스마트한 전기차 라이프의 시작,<br> <span class="gradient-text">LIGER</span></h1>
<p>최적의 경로와 충전소를 찾아주는 당신만의 AI 드라이빙 파트너</p>
<div class="cta-group">
<a href="#demo" class="btn primary">데모 영상 보기</a>
<a href="#workflow" class="btn secondary">워크플로우 확인하기</a>
</div>
</div>
<div class="hero-image">
<img src="hero_image.png" alt="Liger Dashboard Preview" class="glass-card">
</div>
</header>
<main>
<section id="demo" class="section">
<div class="section-header">
<h2>Demo Video</h2>
<p>Liger가 어떻게 작동하는지 확인해보세요</p>
</div>
<div class="video-container glass-card">
<video controls poster="hero_image.png">
<source src="liger_demo.mp4" type="video/mp4">
브라우저가 비디오 태그를 지원하지 않습니다.
</video>
</div>
</section>
<section id="presentation" class="section">
<div class="section-header">
<h2>Presentation</h2>
<p>프로젝트 상세 소개 및 기술 스택</p>
</div>
<div class="ppt-container glass-card">
<div class="ppt-wrapper glass-card" style="padding: 0; overflow: hidden; height: auto;">
<div class="pdf-canvas-container">
<canvas id="pdf-render"></canvas>
</div>
<div class="slide-controls">
<button id="prev-page" class="btn secondary small">Prev</button>
<span id="page-count-container">
Page <span id="page-num" style="color: white; font-weight: bold;">--</span> of <span
id="page-count">--</span>
</span>
<button id="next-page" class="btn secondary small">Next</button>
<a href="liger_presentation.pdf" download class="btn small"
style="margin-left: 1rem; padding: 0.4rem 0.8rem; font-size: 0.8rem; background: rgba(255,255,255,0.05);">
📥 PDF
</a>
</div>
</div>
<!-- PDF.js Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>
const url = 'liger_presentation.pdf';
let pdfDoc = null,
pageNum = 1,
pageRendering = false,
pageNumPending = null,
scale = 1.5,
canvas = document.getElementById('pdf-render'),
ctx = canvas.getContext('2d');
// Set worker source
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
/**
* Get page info from document, resize canvas accordingly, and render page.
* @param num Page number.
*/
function renderPage(num) {
pageRendering = true;
// Fetch page
pdfDoc.getPage(num).then(function (page) {
// Adjust scale to fit the container width if needed
const containerWidth = document.querySelector('.pdf-canvas-container').clientWidth;
const viewport = page.getViewport({ scale: 1 });
// Calculate scale to fit width (minus some padding)
let desiredScale = (containerWidth - 40) / viewport.width;
// Limit max scale to keep quality reasonable
if (desiredScale > 1.5) desiredScale = 1.5;
const scaledViewport = page.getViewport({ scale: desiredScale });
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
// Render PDF page into canvas context
const renderContext = {
canvasContext: ctx,
viewport: scaledViewport
};
const renderTask = page.render(renderContext);
// Wait for render to finish
renderTask.promise.then(function () {
pageRendering = false;
if (pageNumPending !== null) {
renderPage(pageNumPending);
pageNumPending = null;
}
});
});
// Update page counters
document.getElementById('page-num').textContent = num;
// Update buttons state
document.getElementById('prev-page').disabled = num <= 1;
document.getElementById('next-page').disabled = num >= pdfDoc.numPages;
}
/**
* If another page rendering in progress, waits until the rendering is
* finised. Otherwise, executes rendering immediately.
*/
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
}
/**
* Displays previous page.
*/
function onPrevPage() {
if (pageNum <= 1) {
return;
}
pageNum--;
queueRenderPage(pageNum);
}
document.getElementById('prev-page').addEventListener('click', onPrevPage);
/**
* Displays next page.
*/
function onNextPage() {
if (pageNum >= pdfDoc.numPages) {
return;
}
pageNum++;
queueRenderPage(pageNum);
}
document.getElementById('next-page').addEventListener('click', onNextPage);
/**
* Asynchronously downloads PDF.
*/
pdfjsLib.getDocument(url).promise.then(function (pdfDoc_) {
pdfDoc = pdfDoc_;
document.getElementById('page-count').textContent = pdfDoc.numPages;
// Initial/first page rendering
renderPage(pageNum);
}).catch(function (error) {
console.error('Error loading PDF:', error);
// Fallback UI or message could go here
ctx.font = '20px Arial';
ctx.fillStyle = 'white';
ctx.fillText('Error loading PDF.', 50, 50);
});
// Handle window resize
window.addEventListener('resize', () => {
// Re-render current page to adjust scale
if (pdfDoc) {
queueRenderPage(pageNum);
}
});
</script>
</div>
</section>
<section id="workflow" class="section">
<div class="section-header">
<h2>Core Logic & Workflow</h2>
<p>Google Calendar 연동부터 AI 최적화 제안까지, One-Stop 자동화 프로세스</p>
</div>
<div class="workflow-container glass-card">
<div class="workflow-img-wrapper">
<img src="workflow_diagram.png" alt="n8n Workflow Diagram" class="full-width-img">
</div>
<div class="workflow-details">
<div class="logic-step">
<h3>1. Trigger & analysis</h3>
<p>구글 캘린더에 새로운 일정이 등록되면 자동으로 감지하여 출발지와 목적지, 그리고 이동 시간을 계산합니다.</p>
</div>
<div class="logic-step">
<h3>2. Dual-Optimization Engine</h3>
<p>이동 경로를 바탕으로 두 가지 시나리오를 병렬로 생성합니다.</p>
<ul>
<li><strong>탄소 배출 최적화:</strong> 친환경 경로 및 운전 습관 제안</li>
<li><strong>경제적 충전 시나리오:</strong> 최저가 충전소 및 배터리 효율 분석</li>
</ul>
</div>
<div class="logic-step">
<h3>3. AI Evaluation (Upstage Solar)</h3>
<p>Upstage Solar LLM이 환경성과 경제성을 종합적으로 평가하여, 사용자에게 가장 밸런스 잡힌 최적의 스케줄을 확정합니다.</p>
</div>
<div class="logic-step">
<h3>4. Action & Report</h3>
<p>최종 확정된 일정은 캘린더에 다시 등록되며, 상세 분석 리포트가 이메일로 즉시 전송됩니다.</p>
</div>
</div>
</div>
</section>
<section id="team" class="section">
<div class="section-header">
<h2>Our Team</h2>
<p>Liger 프로젝트를 만들어가는 사람들</p>
</div>
<div class="team-container glass-card">
<!-- Image removed as requested -->
<div class="team-grid">
<div class="team-member">
<h4>강윤재</h4>
<span class="role">Data Research & Scenario</span>
<p>전기차 전비/배터리 자료 조사<br>경제적 충전 시나리오 로직 설계</p>
</div>
<div class="team-member">
<h4>엄서훈</h4>
<span class="role">Workflow Architect</span>
<p>n8n 메인 워크플로우 개발<br>최종 리포트 템플릿 구현</p>
</div>
<div class="team-member">
<h4>이승현</h4>
<span class="role">Project Lead</span>
<p>프로젝트 총괄 및 일정 조율<br>서비스 기획 및 소개서 작성</p>
</div>
<div class="team-member">
<h4>이하윤</h4>
<span class="role">Presentation & Data</span>
<p>발표 자료 제작 및 최종 발표<br>탄소 배출 데이터 리서치</p>
</div>
<div class="team-member">
<h4>차성경</h4>
<span class="role">Media & Validation</span>
<p>아이디어 산출 및 검증<br>홍보 영상 제작 및 자료 조사</p>
</div>
</div>
</div>
</section>
</main>
<footer>
<p>&copy; 2024 Liger Team. All rights reserved.</p>
<div class="socials">
<a href="https://huggingface.co/dltmdgus" target="_blank">Hugging Face</a>
</div>
</footer>
<script>
// Smooth scroll for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
// Simple intersection observer for fade-in animations
const observerOptions = {
threshold: 0.1
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, observerOptions);
document.querySelectorAll('.section, .hero').forEach(el => {
el.classList.add('fade-in-section');
observer.observe(el);
});
</script>
</body>
</html>