Deploy: TekDek Employees Portal (Daedalus, Talos, Icarus) - Live at web.tekdek.dev
This commit is contained in:
244
tekdek-employees-api/public/employees.html
Normal file
244
tekdek-employees-api/public/employees.html
Normal file
@@ -0,0 +1,244 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>The Architects of TekDek</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=Cinzel:wght@400;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--bg:#0d0d0f;--bg-card:#16161a;--bg-card-hover:#1a1a1f;
|
||||
--text:#e0ddd5;--text-muted:#8a8780;--text-dim:#5a5955;
|
||||
--font-serif:'Cinzel',serif;--font-sans:'Inter',sans-serif;--font-mono:'JetBrains Mono',monospace;
|
||||
}
|
||||
html{scroll-behavior:smooth}
|
||||
body{background:var(--bg);color:var(--text);font-family:var(--font-sans);line-height:1.7;overflow-x:hidden}
|
||||
|
||||
/* Hero */
|
||||
.hero{text-align:center;padding:6rem 1.5rem 4rem;max-width:800px;margin:0 auto}
|
||||
.hero h1{font-family:var(--font-serif);font-size:clamp(2rem,5vw,3.2rem);color:#f0ece2;letter-spacing:.04em;margin-bottom:1rem}
|
||||
.hero .subtitle{font-size:1.05rem;color:var(--text-muted);max-width:600px;margin:0 auto;line-height:1.8}
|
||||
.hero .divider{width:80px;height:2px;background:linear-gradient(90deg,transparent,#C4A24E,transparent);margin:2rem auto}
|
||||
|
||||
/* Employee list */
|
||||
.employees{max-width:900px;margin:0 auto;padding:0 1.5rem 4rem}
|
||||
|
||||
/* Card */
|
||||
.employee-card{
|
||||
background:var(--bg-card);border-radius:12px;padding:2.5rem;margin-bottom:3rem;
|
||||
border-left:4px solid var(--accent,#C4A24E);
|
||||
opacity:0;transform:translateY(30px);transition:opacity .6s ease,transform .6s ease;
|
||||
position:relative;
|
||||
}
|
||||
.employee-card.visible{opacity:1;transform:translateY(0)}
|
||||
.employee-card.stagger-left{margin-right:auto;margin-left:0}
|
||||
.employee-card.stagger-right{margin-left:auto;margin-right:0}
|
||||
|
||||
@media(min-width:769px){
|
||||
.employee-card.stagger-left{max-width:820px;margin-right:80px}
|
||||
.employee-card.stagger-right{max-width:820px;margin-left:80px}
|
||||
}
|
||||
|
||||
.card-header{display:flex;gap:1.5rem;align-items:center;margin-bottom:1.5rem;flex-wrap:wrap}
|
||||
|
||||
.avatar{
|
||||
width:120px;height:120px;border-radius:50%;flex-shrink:0;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-size:2.5rem;color:#0d0d0f;font-weight:700;
|
||||
background:var(--accent,#C4A24E);
|
||||
box-shadow:0 0 30px color-mix(in srgb,var(--accent) 30%,transparent);
|
||||
}
|
||||
.avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover}
|
||||
|
||||
.card-meta{flex:1;min-width:200px}
|
||||
.card-meta h2{font-family:var(--font-serif);font-size:1.6rem;color:#f0ece2;margin-bottom:.2rem}
|
||||
.card-meta .title{font-family:var(--font-mono);font-size:.85rem;color:var(--accent,#C4A24E);margin-bottom:.3rem}
|
||||
.card-meta .tagline{font-family:var(--font-sans);font-size:.95rem;color:var(--text-muted);font-style:italic}
|
||||
|
||||
.bio{margin-bottom:1.25rem;color:var(--text);font-size:.95rem}
|
||||
|
||||
/* Mythology expand */
|
||||
.mythology-toggle{
|
||||
background:none;border:none;color:var(--accent,#C4A24E);cursor:pointer;
|
||||
font-family:var(--font-sans);font-size:.9rem;display:inline-flex;align-items:center;gap:.4rem;
|
||||
padding:.3rem 0;margin-bottom:1rem;transition:opacity .2s;
|
||||
}
|
||||
.mythology-toggle:hover{opacity:.8}
|
||||
.mythology-toggle .arrow{display:inline-block;transition:transform .3s ease;font-size:.7rem}
|
||||
.mythology-toggle.open .arrow{transform:rotate(90deg)}
|
||||
.mythology-content{
|
||||
max-height:0;overflow:hidden;transition:max-height .5s ease,opacity .4s ease;opacity:0;
|
||||
color:var(--text-muted);font-size:.9rem;line-height:1.8;padding-left:1rem;
|
||||
border-left:2px solid color-mix(in srgb,var(--accent) 30%,transparent);margin-bottom:1rem;
|
||||
}
|
||||
.mythology-content.open{opacity:1;margin-bottom:1rem}
|
||||
|
||||
/* Skills */
|
||||
.skills{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1.25rem}
|
||||
.skill-pill{
|
||||
font-size:.75rem;padding:.3rem .7rem;border-radius:20px;font-weight:500;
|
||||
background:color-mix(in srgb,var(--accent) 15%,transparent);
|
||||
color:var(--accent,#C4A24E);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);
|
||||
}
|
||||
|
||||
/* Quote */
|
||||
.quote{font-style:italic;color:var(--text-muted);font-size:.9rem;padding-top:.75rem;border-top:1px solid #222}
|
||||
.quote::before{content:'"'}
|
||||
.quote::after{content:'"'}
|
||||
|
||||
/* Closing */
|
||||
.closing{text-align:center;padding:2rem 1.5rem 6rem;max-width:700px;margin:0 auto}
|
||||
.closing .divider{width:80px;height:2px;background:linear-gradient(90deg,transparent,#C4A24E,transparent);margin:0 auto 2rem}
|
||||
.closing p{color:var(--text-muted);font-size:1rem;line-height:1.8}
|
||||
|
||||
/* Loading */
|
||||
.loading{text-align:center;padding:4rem;color:var(--text-dim)}
|
||||
.loading .spinner{display:inline-block;width:24px;height:24px;border:2px solid var(--text-dim);border-top-color:transparent;border-radius:50%;animation:spin .8s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
|
||||
/* Error */
|
||||
.error{text-align:center;padding:4rem;color:#e07a2f}
|
||||
|
||||
/* Responsive */
|
||||
@media(max-width:768px){
|
||||
.hero{padding:4rem 1rem 2.5rem}
|
||||
.employees{padding:0 1rem 3rem}
|
||||
.employee-card{padding:1.5rem}
|
||||
.employee-card.stagger-left,.employee-card.stagger-right{margin-left:0;margin-right:0;max-width:100%}
|
||||
.card-header{flex-direction:column;text-align:center}
|
||||
.avatar{width:100px;height:100px;font-size:2rem}
|
||||
.card-meta{text-align:center}
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
.mythology-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
|
||||
|
||||
/* Reduced motion */
|
||||
@media(prefers-reduced-motion:reduce){
|
||||
.employee-card{opacity:1;transform:none;transition:none}
|
||||
.mythology-content{transition:none}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<main>
|
||||
<section class="hero" role="banner">
|
||||
<h1>The Architects of TekDek</h1>
|
||||
<div class="divider" aria-hidden="true"></div>
|
||||
<p class="subtitle">They didn't just build software. They forged something that remembers where it came from — mythology woven into every line of code, every pixel, every system. These are the minds behind TekDek.</p>
|
||||
</section>
|
||||
|
||||
<section class="employees" id="employees" aria-label="TekDek team members">
|
||||
<div class="loading" id="loading" role="status">
|
||||
<div class="spinner" aria-hidden="true"></div>
|
||||
<p>Summoning the architects…</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="closing" id="closing" style="display:none">
|
||||
<div class="divider" aria-hidden="true"></div>
|
||||
<p>Separately, they are forces. Together, they are TekDek — where myth meets machine, and every build is a step closer to legend.</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const SYMBOLS = {labyrinth:'◎',gear:'⚙',wings:'𓅃',compass:'⊕',forge:'⚒',eye:'◉'};
|
||||
const container = document.getElementById('employees');
|
||||
const loading = document.getElementById('loading');
|
||||
const closing = document.getElementById('closing');
|
||||
|
||||
async function init(){
|
||||
try{
|
||||
const res = await fetch('/api/employees');
|
||||
if(!res.ok) throw new Error(`API returned ${res.status}`);
|
||||
const data = await res.json();
|
||||
const employees = data.data || data;
|
||||
loading.remove();
|
||||
render(employees);
|
||||
closing.style.display='';
|
||||
observeCards();
|
||||
}catch(e){
|
||||
loading.innerHTML=`<div class="error"><p>Could not summon the architects.</p><p style="font-size:.85rem;margin-top:.5rem">${e.message}</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function render(employees){
|
||||
employees.forEach((emp,i)=>{
|
||||
const stagger = i%2===0?'stagger-left':'stagger-right';
|
||||
const card = document.createElement('article');
|
||||
card.className=`employee-card ${stagger}`;
|
||||
card.style.setProperty('--accent',emp.accent_color||'#C4A24E');
|
||||
card.setAttribute('aria-label',`${emp.name}, ${emp.title}`);
|
||||
|
||||
const symbol = SYMBOLS[emp.symbol]||emp.symbol||'◆';
|
||||
const avatarInner = emp.avatar_url
|
||||
? `<img src="${esc(emp.avatar_url)}" alt="${esc(emp.name)} avatar" loading="lazy">`
|
||||
: symbol;
|
||||
|
||||
const skillsHtml = (emp.skills||[]).map(s=>`<span class="skill-pill">${esc(s)}</span>`).join('');
|
||||
const mythId = `myth-${emp.slug||i}`;
|
||||
|
||||
card.innerHTML=`
|
||||
<div class="card-header">
|
||||
<div class="avatar" aria-hidden="true">${avatarInner}</div>
|
||||
<div class="card-meta">
|
||||
<h2>${esc(emp.name)}</h2>
|
||||
<div class="title">${esc(emp.title)}</div>
|
||||
${emp.tagline?`<div class="tagline">${esc(emp.tagline)}</div>`:''}
|
||||
</div>
|
||||
</div>
|
||||
${emp.bio?`<p class="bio">${esc(emp.bio)}</p>`:''}
|
||||
${emp.mythology?`
|
||||
<button class="mythology-toggle" aria-expanded="false" aria-controls="${mythId}">
|
||||
<span class="arrow">▶</span> Read the mythology
|
||||
</button>
|
||||
<div class="mythology-content" id="${mythId}" role="region" aria-label="Mythology of ${esc(emp.name)}">
|
||||
<p>${esc(emp.mythology)}</p>
|
||||
</div>
|
||||
`:''}
|
||||
${skillsHtml?`<div class="skills" aria-label="Skills">${skillsHtml}</div>`:''}
|
||||
${emp.quote?`<p class="quote">${esc(emp.quote)}</p>`:''}
|
||||
`;
|
||||
container.appendChild(card);
|
||||
|
||||
// Mythology toggle
|
||||
const btn = card.querySelector('.mythology-toggle');
|
||||
if(btn){
|
||||
const content = card.querySelector('.mythology-content');
|
||||
btn.addEventListener('click',()=>{
|
||||
const open = btn.classList.toggle('open');
|
||||
content.classList.toggle('open');
|
||||
btn.setAttribute('aria-expanded',open);
|
||||
if(open){content.style.maxHeight=content.scrollHeight+'px'}
|
||||
else{content.style.maxHeight='0'}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function observeCards(){
|
||||
if(!('IntersectionObserver' in window)){
|
||||
document.querySelectorAll('.employee-card').forEach(c=>c.classList.add('visible'));
|
||||
return;
|
||||
}
|
||||
const obs = new IntersectionObserver((entries)=>{
|
||||
entries.forEach(e=>{if(e.isIntersecting){e.target.classList.add('visible');obs.unobserve(e.target)}});
|
||||
},{threshold:0.15,rootMargin:'0px 0px -50px 0px'});
|
||||
document.querySelectorAll('.employee-card').forEach(c=>obs.observe(c));
|
||||
}
|
||||
|
||||
function esc(s){
|
||||
if(!s)return'';
|
||||
const d=document.createElement('div');d.textContent=s;return d.innerHTML;
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user