win 9x style frontend

This commit is contained in:
2026-05-06 22:24:22 +02:00
parent 0aede08d80
commit d46b54bd6a
11 changed files with 1766 additions and 1072 deletions
+78 -31
View File
@@ -1,39 +1,40 @@
<template>
<header class="bg-gray-800 text-white p-4 flex justify-between items-center relative">
<RouterLink to="/" class="text-xl font-bold">Teletype Games</RouterLink>
<div class="win-app">
<header class="win-menubar">
<span class="win-menu-logo">📟 Teletype Games</span>
<!-- Desktop Navigation -->
<nav class="hidden md:flex space-x-4">
<RouterLink to="/" class="p-2 hover:text-purple-300 transition-colors duration-200">Home</RouterLink>
<RouterLink to="/catalog" class="p-2 hover:text-purple-300 transition-colors duration-200">Catalog</RouterLink>
<RouterLink to="/blog" class="p-2 hover:text-purple-300 transition-colors duration-200">Blog</RouterLink>
<RouterLink to="/howtos" class="p-2 hover:text-purple-300 transition-colors duration-200">How-tos</RouterLink>
<RouterLink to="/code" class="p-2 hover:text-purple-300 transition-colors duration-200">Code</RouterLink>
<RouterLink to="/team" class="p-2 hover:text-purple-300 transition-colors duration-200">Team</RouterLink>
<RouterLink to="/contact" class="p-2 hover:text-purple-300 transition-colors duration-200">Contact us</RouterLink>
<nav class="win-menu-items hidden md:flex">
<RouterLink to="/" class="win-menu-item">Home</RouterLink>
<RouterLink to="/catalog" class="win-menu-item">Catalog</RouterLink>
<RouterLink to="/blog" class="win-menu-item">Blog</RouterLink>
<RouterLink to="/howtos" class="win-menu-item">How-tos</RouterLink>
<RouterLink to="/code" class="win-menu-item">Code</RouterLink>
<RouterLink to="/team" class="win-menu-item">Team</RouterLink>
<RouterLink to="/contact" class="win-menu-item">Contact</RouterLink>
</nav>
<button @click="toggleMenu" class="md:hidden win-btn mobile-menu-toggle"> Menu</button>
</header>
<nav v-show="menuOpen" class="md:hidden win-mobile-menu">
<RouterLink to="/" class="win-mobile-item">Home</RouterLink>
<RouterLink to="/catalog" class="win-mobile-item">Catalog</RouterLink>
<RouterLink to="/blog" class="win-mobile-item">Blog</RouterLink>
<RouterLink to="/howtos" class="win-mobile-item">How-tos</RouterLink>
<RouterLink to="/code" class="win-mobile-item">Code</RouterLink>
<RouterLink to="/team" class="win-mobile-item">Team</RouterLink>
<RouterLink to="/contact" class="win-mobile-item">Contact</RouterLink>
</nav>
<!-- Hamburger Button (Mobile only) -->
<button @click="toggleMenu" class="md:hidden p-2 focus:outline-none focus:ring-2 focus:ring-purple-500 rounded-md">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
<main class="win-main">
<slot />
</main>
<!-- Mobile Navigation -->
<nav v-show="menuOpen" class="md:hidden absolute top-full left-0 w-full bg-gray-800 flex flex-col items-center py-4 space-y-2 z-50">
<RouterLink to="/" class="block p-2 w-full text-center hover:bg-gray-700 transition-colors duration-200">Home</RouterLink>
<RouterLink to="/catalog" class="block p-2 w-full text-center hover:bg-gray-700 transition-colors duration-200">Catalog</RouterLink>
<RouterLink to="/blog" class="block p-2 w-full text-center hover:bg-gray-700 transition-colors duration-200">Blog</RouterLink>
<RouterLink to="/howtos" class="block p-2 w-full text-center hover:bg-gray-700 transition-colors duration-200">How-tos</RouterLink>
<RouterLink to="/code" class="block p-2 w-full text-center hover:bg-gray-700 transition-colors duration-200">Code</RouterLink>
<RouterLink to="/team" class="block p-2 w-full text-center hover:bg-gray-700 transition-colors duration-200">Team</RouterLink>
<RouterLink to="/contact" class="block p-2 w-full text-center hover:bg-gray-700 transition-colors duration-200">Contact us</RouterLink>
</nav>
</header>
<main>
<slot />
</main>
<footer class="win-statusbar">
<div class="win-status-panel">Teletype Games &copy; 2024 Independent Game Development Community</div>
<div class="win-status-panel" style="flex: 0; white-space: nowrap;">Open Source</div>
</footer>
</div>
</template>
<script setup lang="ts">
@@ -51,3 +52,49 @@ watch(() => route.path, () => {
menuOpen.value = false
})
</script>
<style scoped>
.win-app {
min-height: 100vh;
display: flex;
flex-direction: column;
font-family: Tahoma, "MS Sans Serif", Arial, sans-serif;
background: #D4D0C8;
}
.win-main {
flex: 1;
}
.mobile-menu-toggle {
margin-left: auto;
margin-right: 4px;
min-width: auto;
padding: 2px 8px;
font-size: 11px;
}
.win-mobile-menu {
background: #D4D0C8;
border-bottom: 1px solid #808080;
box-shadow: 0 1px 0 #fff;
display: flex;
flex-direction: column;
}
.win-mobile-item {
padding: 5px 12px;
color: #000;
text-decoration: none;
display: block;
border-bottom: 1px solid #C0BDB8;
font-size: 12px;
font-family: Tahoma, Arial, sans-serif;
}
.win-mobile-item:hover,
.win-mobile-item.router-link-exact-active {
background: #000080;
color: #fff;
}
</style>
+90 -115
View File
@@ -1,75 +1,58 @@
<template>
<div class="blog-container">
<header class="blog-header">
<div class="blog-header-decor">
<div class="hero-decor-blob -top-24 -left-24 h-96 w-96 bg-purple-600"></div>
<div class="hero-decor-blob -bottom-24 -right-24 h-96 w-96 bg-blue-600"></div>
</div>
<div class="hero-container">
<div class="hero-badge">Community Stories</div>
<h1 class="hero-title">
Developer <span class="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-400">Blog</span>
</h1>
<p class="hero-subtitle mb-10">
News, updates, and behind-the-scenes stories from the Teletype Games community.
</p>
</div>
</header>
<main class="blog-main">
<div v-if="error" class="error-banner" role="alert">
<span class="text-2xl"></span>
<div>
<h3 class="error-banner-title">Failed to connect to Wiki</h3>
<p class="error-banner-desc">{{ error }}</p>
<div class="main-container">
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📰 Developer Blog</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div v-else-if="!loading && blogPages.length === 0" class="empty-state">
<div class="empty-state-icon">📭</div>
<h2 class="empty-state-title">No blog posts found</h2>
<p class="empty-state-desc">We haven't published any blog posts yet. Check back soon!</p>
<div class="win-body" style="padding: 4px 8px 6px;">
<p style="margin: 0 0 2px; font-size: 12px; color: #404040;">
News, updates, and behind-the-scenes stories from the Teletype Games community.
</p>
</div>
<div v-else class="blog-posts-list">
<article v-for="page in blogPages" :key="page.id" class="blog-post-card">
<div class="blog-post-content">
<div class="blog-post-meta">
<div class="flex items-center gap-4">
<div class="blog-post-timestamp">
<span v-if="isNew(page.createdAt)" class="new-post-badge">
<span class="new-post-dot"></span>
New Post
</span>
<span>{{ formatDateTime(page.createdAt) }}</span>
</div>
</div>
</div>
<div class="win-body" style="padding: 0;">
<h2 class="blog-post-title">
<RouterLink :to="getPermalink(page.path)" class="hover:text-indigo-600 transition-colors">
<div v-if="error" class="win-notice-error" style="margin: 8px;">
<span></span>
<div>
<strong>Failed to connect to Wiki</strong><br>
<span style="font-size: 11px;">{{ error }}</span>
</div>
</div>
<div v-else-if="!loading && blogPages.length === 0" class="empty-state">
<div style="font-size: 32px; margin-bottom: 8px;">📭</div>
<div style="font-weight: bold; margin-bottom: 4px;">No blog posts found</div>
<div style="font-size: 11px; color: #404040;">We haven't published any blog posts yet. Check back soon!</div>
</div>
<div v-else>
<article v-for="page in blogPages" :key="page.id" class="blog-post-row">
<div class="blog-post-inner">
<div class="blog-post-meta-row">
<span v-if="isNew(page.createdAt)" class="new-badge">★ New</span>
<span class="blog-post-date">{{ formatDateTime(page.createdAt) }}</span>
</div>
<RouterLink :to="getPermalink(page.path)" class="blog-post-title-link">
{{ page.title }}
</RouterLink>
</h2>
<div class="blog-post-preview">
<p v-if="page.description" class="blog-post-description">{{ page.description }}</p>
<p v-if="page.content" class="blog-post-body">{{ getCleanPreview(page.content) }}</p>
<div v-if="page.description" class="blog-post-desc">{{ page.description }}</div>
<div v-else-if="page.content" class="blog-post-desc">{{ getCleanPreview(page.content) }}</div>
<div class="blog-post-footer">
<RouterLink :to="getPermalink(page.path)" class="win-btn win-btn-sm">Read more →</RouterLink>
</div>
</div>
</article>
</div>
<div class="mt-6">
<RouterLink :to="getPermalink(page.path)" class="read-more-btn">
Read more
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</RouterLink>
</div>
</div>
</article>
</div>
</main>
</div>
</div>
</template>
@@ -95,7 +78,7 @@ function getPermalink(path: string): string {
function getCleanPreview(content: string): string {
if (!content) return ''
return content.replace(/[#*`_\[\]()]/g, '').trim().slice(0, 300) + '...'
return content.replace(/[#*`_\[\]()]/g, '').trim().slice(0, 250) + '...'
}
onMounted(async () => {
@@ -110,69 +93,61 @@ onMounted(async () => {
</script>
<style scoped>
.blog-container {
@apply bg-gray-50 min-h-screen pb-20;
}
.blog-header {
@apply relative overflow-hidden py-24 text-white text-center bg-indigo-900;
}
.blog-header-decor {
@apply absolute inset-0 opacity-30;
}
.blog-main {
@apply max-w-5xl mx-auto px-4 md:px-8 -mt-12 relative z-10;
}
.banner-base {
@apply max-w-7xl mx-auto border-l-4 p-6 rounded-r-xl shadow-lg mb-12 flex items-start gap-4;
}
.error-banner { @apply banner-base bg-red-50 border-red-500; }
.error-banner-title { @apply text-red-800 font-bold text-lg; }
.error-banner-desc { @apply text-red-700 mt-1; }
.empty-state {
@apply max-w-7xl mx-auto bg-white rounded-2xl shadow-xl p-12 text-center border border-gray-100;
text-align: center;
padding: 24px;
color: #404040;
font-size: 12px;
}
.empty-state-icon { @apply text-6xl mb-4; }
.empty-state-title { @apply text-2xl font-bold text-gray-900 mb-2; }
.empty-state-desc { @apply text-gray-500 max-w-md mx-auto; }
.blog-posts-list {
@apply flex flex-col gap-12;
.blog-post-row {
border-bottom: 1px solid #D0D0D0;
}
.blog-post-card {
@apply bg-white rounded-2xl shadow-xl border border-gray-100 overflow-hidden;
.blog-post-row:last-child { border-bottom: none; }
.blog-post-inner {
padding: 8px 10px;
}
.blog-post-content {
@apply p-6 md:p-10;
.blog-post-row:hover .blog-post-inner {
background: #EEF0F8;
}
.blog-post-meta {
@apply flex items-center justify-between mb-4;
.blog-post-meta-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 3px;
}
.blog-post-timestamp {
@apply flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-400;
.new-badge {
font-size: 10px;
font-weight: bold;
background: #C8F0C8;
color: #007000;
border: 1px solid #009000;
padding: 0 4px;
}
.new-post-badge {
@apply flex items-center gap-1 text-green-600 bg-green-50 px-2 py-0.5 rounded-full border border-green-100;
.blog-post-date {
font-size: 11px;
color: #808080;
font-family: "Courier New", Courier, monospace;
}
.new-post-dot {
@apply w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse;
.blog-post-title-link {
display: block;
font-size: 14px;
font-weight: bold;
color: #000080;
text-decoration: underline;
margin-bottom: 4px;
line-height: 1.3;
}
.blog-post-title {
@apply text-2xl md:text-3xl font-black text-gray-900 mb-4 leading-tight;
.blog-post-title-link:hover { color: #0000FF; }
.blog-post-desc {
font-size: 12px;
color: #404040;
margin-bottom: 8px;
line-height: 1.5;
max-height: 4em;
overflow: hidden;
}
.blog-post-preview {
@apply relative overflow-hidden;
max-height: 8rem;
}
.blog-post-preview::after {
content: "";
@apply absolute bottom-0 left-0 w-full h-16 bg-gradient-to-b from-transparent to-white pointer-events-none;
}
.blog-post-description {
@apply text-lg text-gray-800 font-bold mb-2 leading-relaxed;
}
.blog-post-body {
@apply text-base text-gray-500 font-medium leading-relaxed;
}
.read-more-btn {
@apply inline-flex items-center text-indigo-600 font-bold hover:text-indigo-800 transition-colors;
.blog-post-footer {
display: flex;
justify-content: flex-end;
}
</style>
+63 -66
View File
@@ -1,41 +1,48 @@
<template>
<div class="blog-container">
<header class="blog-header">
<div class="blog-header-decor">
<div class="hero-decor-blob -top-24 -left-24 h-96 w-96 bg-purple-600"></div>
<div class="hero-decor-blob -bottom-24 -right-24 h-96 w-96 bg-blue-600"></div>
</div>
<div class="post-hero-container">
<RouterLink to="/blog" class="back-link"> Back to Blog</RouterLink>
<template v-if="pageContent">
<h1 class="hero-title mt-4">{{ pageContent.title }}</h1>
<p class="hero-subtitle mb-4">{{ pageContent.description }}</p>
<div class="blog-post-timestamp text-indigo-200">{{ formatDateTime(pageContent.createdAt) }}</div>
</template>
</div>
</header>
<main class="blog-main">
<div v-if="error" class="error-banner" role="alert">
<span class="text-2xl"></span>
<div>
<h3 class="error-banner-title">Error</h3>
<p class="error-banner-desc">{{ error }}</p>
<div class="main-container">
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📰 {{ pageContent ? pageContent.title : 'Blog Post' }}</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<article v-else-if="pageContent" class="blog-post-card">
<div class="blog-post-content">
<div v-if="pageContent.render" class="wiki-content max-w-none text-gray-700 leading-relaxed" v-html="pageContent.render" />
<div v-else class="blog-post-fallback">No content available for this post.</div>
</div>
</article>
<div v-else-if="loading" class="blog-post-card">
<div class="blog-post-content text-center text-gray-400 py-12">Loading...</div>
<div class="win-toolbar">
<RouterLink to="/blog" class="win-btn win-btn-sm"> Back to Blog</RouterLink>
<span v-if="pageContent" class="post-date">
{{ formatDateTime(pageContent.createdAt) }}
</span>
</div>
</main>
<div class="win-body" style="padding: 0;">
<div v-if="error" class="win-notice-error" style="margin: 8px;">
<span></span>
<div><strong>Error</strong><br><span style="font-size: 11px;">{{ error }}</span></div>
</div>
<article v-else-if="pageContent" class="post-content">
<div v-if="pageContent.description" class="post-description">
{{ pageContent.description }}
</div>
<hr class="win-separator" style="margin: 8px 0;">
<div
v-if="pageContent.render"
class="wiki-content"
v-html="pageContent.render"
/>
<div v-else class="post-empty">No content available for this post.</div>
</article>
<div v-else-if="loading" class="post-loading">
Loading...
</div>
</div>
</div>
</div>
</template>
@@ -69,42 +76,32 @@ onMounted(async () => {
</script>
<style scoped>
.blog-container {
@apply bg-gray-50 min-h-screen pb-20;
.post-date {
font-size: 11px;
color: #404040;
font-family: "Courier New", Courier, monospace;
margin-left: 8px;
}
.blog-header {
@apply relative overflow-hidden py-24 text-white text-center bg-indigo-900;
.post-content {
padding: 10px 12px;
}
.blog-header-decor {
@apply absolute inset-0 opacity-30;
.post-description {
font-size: 13px;
font-weight: bold;
color: #000080;
margin-bottom: 8px;
line-height: 1.4;
}
.post-hero-container {
@apply relative z-10 max-w-4xl mx-auto px-4;
.post-empty {
padding: 16px;
text-align: center;
color: #808080;
font-size: 12px;
}
.back-link {
@apply inline-block text-indigo-300 hover:text-white transition-colors font-medium;
.post-loading {
padding: 16px;
text-align: center;
color: #404040;
font-size: 12px;
}
.hero-title {
@apply text-4xl md:text-5xl font-black mb-4 leading-tight;
}
.hero-subtitle {
@apply text-xl text-indigo-100 font-medium;
}
.blog-post-timestamp {
@apply text-sm font-semibold uppercase tracking-wider;
}
.blog-main {
@apply max-w-4xl mx-auto px-4 md:px-8 -mt-12 relative z-10;
}
.blog-post-card {
@apply bg-white rounded-2xl shadow-xl border border-gray-100 overflow-hidden;
}
.blog-post-content {
@apply p-6 md:p-12;
}
.error-banner {
@apply max-w-7xl mx-auto bg-red-50 border-l-4 border-red-500 p-6 rounded-r-xl shadow-lg mb-12 flex items-start gap-4;
}
.error-banner-title { @apply text-red-800 font-bold text-lg; }
.error-banner-desc { @apply text-red-700 mt-1; }
</style>
@@ -1,63 +1,63 @@
<template>
<header class="hero-section-gradient from-purple-600 to-indigo-600 py-16">
<div class="hero-container">
<h1 class="hero-title">Our Games</h1>
<p class="hero-subtitle text-purple-50">Discover games made for TIC-80 and other platforms, with versions playable directly in your browser!</p>
</div>
</header>
<div class="main-container">
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">🎮 Games Catalog</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<main class="main-container py-12 px-4 mt-0">
<div class="flex flex-wrap gap-4 mb-8 justify-center filter-buttons pb-4">
<button
v-for="f in filters"
:key="f"
:class="['filter-btn', { active: activeFilter === f }]"
@click="activeFilter = f"
>
{{ f.charAt(0).toUpperCase() + f.slice(1) }}
</button>
</div>
<div class="win-toolbar">
<span style="font-size: 11px; margin-right: 4px;">Filter:</span>
<button
v-for="f in filters"
:key="f"
:class="['win-btn-sm', { 'is-active': activeFilter === f }]"
@click="activeFilter = f"
>{{ f.charAt(0).toUpperCase() + f.slice(1) }}</button>
</div>
<div class="grid grid-cols-1 gap-8">
<template v-for="{ software, releases } in softwares" :key="software.name">
<div v-show="activeFilter === 'all' || software.status === activeFilter" class="software-card group">
<div class="software-card-content">
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div v-if="software.imageUrl" class="flex-shrink-0">
<div class="win-body" style="padding: 4px;">
<template v-for="{ software, releases } in softwares" :key="software.name">
<div v-show="activeFilter === 'all' || software.status === activeFilter" class="software-row">
<div class="software-row-inner">
<div v-if="software.imageUrl" class="software-thumb-wrap">
<img :src="software.imageUrl" :alt="software.title" class="software-thumb" />
</div>
<div class="flex-grow">
<div class="flex items-center gap-3 mb-2">
<RouterLink :to="`/catalog/${software.name}`" class="hover:text-purple-600 transition-colors">
<h2 class="software-title mb-0">{{ software.title }}</h2>
<div class="software-info">
<div class="software-header-row">
<RouterLink :to="`/catalog/${software.name}`" class="software-title-link">
{{ software.title }}
</RouterLink>
<span :class="`status-badge status-${software.status}`">{{ software.status }}</span>
</div>
<p class="software-desc max-w-2xl">{{ software.desc }}</p>
<div class="software-desc">{{ software.desc }}</div>
<div class="software-meta">
<span>Author: <span class="text-gray-900 font-medium">{{ software.author }}</span></span>
<span class="mx-2 text-gray-300"></span>
<span>Platform: <span class="text-gray-900 font-medium">{{ software.platform }}</span></span>
Author: <strong>{{ software.author }}</strong>
&nbsp;·&nbsp;
Platform: <strong>{{ software.platform }}</strong>
</div>
</div>
<div class="flex flex-wrap gap-2 items-center">
<div class="software-actions">
<a
v-if="getLatestStable(releases)?.htmlFolderPath"
:href="getLatestStable(releases).htmlFolderPath"
target="_blank"
class="btn-play-sm"
class="win-btn-sm"
> Play</a>
<RouterLink :to="`/catalog/${software.name}`" class="btn-info-sm">
More Info
<RouterLink :to="`/catalog/${software.name}`" class="win-btn-sm">
Info
</RouterLink>
</div>
</div>
</div>
</div>
</template>
</template>
</div>
</div>
</main>
</div>
</template>
<script setup lang="ts">
@@ -86,40 +86,60 @@ onMounted(async () => {
</script>
<style scoped>
.filter-btn {
@apply px-4 py-2 rounded-full border-2 border-purple-600 text-purple-600 font-bold transition-all hover:bg-purple-600 hover:text-white;
.software-row {
border-bottom: 1px solid #D0D0D0;
}
.filter-btn.active {
@apply bg-purple-600 text-white;
.software-row:last-child {
border-bottom: none;
}
.status-badge {
@apply px-2 py-0.5 rounded text-xs font-bold uppercase;
.software-row:hover {
background: #EEF0F8;
}
.status-released { @apply bg-green-100 text-green-800 border border-green-200; }
.status-demo { @apply bg-blue-100 text-blue-800 border border-blue-200; }
.status-development { @apply bg-yellow-100 text-yellow-800 border border-yellow-200; }
.status-archived { @apply bg-gray-100 text-gray-800 border border-gray-200; }
.software-card {
@apply bg-white shadow-lg relative rounded-xl overflow-hidden hover:shadow-2xl transition-shadow duration-300;
.software-row-inner {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 6px 8px;
flex-wrap: wrap;
}
.software-card-content {
@apply p-6;
.software-thumb-wrap { flex-shrink: 0; }
.software-thumb {
width: 56px;
height: 56px;
object-fit: cover;
border: 2px solid;
border-color: #808080 #fff #fff #808080;
}
.software-title {
@apply text-2xl font-bold mb-0;
.software-info { flex: 1; min-width: 180px; }
.software-header-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 3px;
flex-wrap: wrap;
}
.software-title-link {
font-size: 13px;
font-weight: bold;
color: #000080;
text-decoration: underline;
}
.software-title-link:hover { color: #0000FF; }
.software-desc {
@apply text-gray-600 mb-3;
font-size: 12px;
color: #404040;
margin-bottom: 3px;
line-height: 1.4;
}
.software-meta {
@apply text-sm text-gray-500;
font-size: 11px;
color: #808080;
}
.btn-base-sm {
@apply inline-flex items-center justify-center font-bold py-2.5 px-5 rounded-xl text-sm transition-all active:scale-95;
}
.btn-play-sm { @apply btn-base-sm bg-purple-600 hover:bg-purple-700 text-white shadow-lg shadow-purple-600/20; }
.btn-info-sm { @apply btn-base-sm bg-gray-100 hover:bg-gray-200 text-gray-700; }
.software-thumb {
@apply w-24 h-24 object-cover rounded-xl border border-gray-100 shadow-sm;
.software-actions {
display: flex;
flex-direction: column;
gap: 3px;
align-items: flex-start;
flex-shrink: 0;
}
</style>
+219 -150
View File
@@ -1,168 +1,170 @@
<template>
<div class="bg-gray-50 min-h-screen pb-20">
<header class="hero-section-gradient from-purple-700 to-indigo-800 py-12 md:py-20">
<div class="hero-container">
<nav class="mb-8">
<RouterLink to="/catalog" class="text-purple-200 hover:text-white transition-colors flex items-center gap-2 font-medium">
Back to Catalog
</RouterLink>
</nav>
<div class="main-container">
<div v-if="software" class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div class="flex-grow">
<div class="flex items-center gap-3 mb-4">
<span :class="`status-badge status-${software.status} px-3 py-1`">{{ software.status }}</span>
<span class="text-purple-300 font-mono text-sm tracking-widest uppercase">{{ software.platform }}</span>
<div v-if="software" class="show-layout">
<!-- Info Sidebar -->
<div class="show-sidebar">
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text"> Project Info</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
<h1 class="text-4xl md:text-6xl font-black text-white mb-4 leading-tight">{{ software.title }}</h1>
<p class="text-xl text-purple-100 max-w-3xl leading-relaxed">{{ software.desc }}</p>
</div>
<div class="win-body">
<div class="back-link-row">
<RouterLink to="/catalog" class="win-btn win-btn-sm"> Back to Catalog</RouterLink>
</div>
<div v-if="software.imageUrl" class="sidebar-img-wrap">
<img :src="software.imageUrl" :alt="software.title" class="sidebar-img" />
</div>
<table class="info-table">
<tr>
<td class="info-label">Title</td>
<td class="info-value"><strong>{{ software.title }}</strong></td>
</tr>
<tr>
<td class="info-label">Status</td>
<td class="info-value">
<span :class="`status-badge status-${software.status}`">{{ software.status }}</span>
</td>
</tr>
<tr>
<td class="info-label">Author</td>
<td class="info-value">{{ software.author }}</td>
</tr>
<tr>
<td class="info-label">Platform</td>
<td class="info-value">{{ software.platform }}</td>
</tr>
<tr v-if="software.license">
<td class="info-label">License</td>
<td class="info-value">{{ software.license }}</td>
</tr>
<tr v-for="link in software.externalLinks" :key="link.url">
<td class="info-label">{{ link.label }}</td>
<td class="info-value">
<a :href="link.url" target="_blank" class="win-link">{{ link.url }} </a>
</td>
</tr>
</table>
</div>
</div>
</div>
</header>
<main v-if="software" class="main-container -mt-10">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Content -->
<div class="show-main">
<!-- Sidebar Info -->
<div class="lg:col-span-1 space-y-6">
<div class="bg-white rounded-3xl shadow-xl p-8 border border-gray-100">
<div v-if="software.imageUrl" class="mb-6 flex justify-center">
<img :src="software.imageUrl" :alt="software.title" class="max-h-48 w-auto object-contain border-4 border-gray-100 shadow-sm" />
<!-- About Window -->
<div v-if="software.story" class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📄 About {{ software.title }}</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-body">
<div class="win-inset" style="padding: 8px;">
<p style="margin: 0; font-size: 12px; line-height: 1.6; white-space: pre-line;">{{ software.story }}</p>
</div>
<h2 class="text-xl font-bold text-gray-900 mb-6 flex items-center gap-2">
<span class="text-purple-600"></span> Project Info
</h2>
<dl class="space-y-4">
<div>
<dt class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-1">Author</dt>
<dd class="text-gray-900 font-medium">{{ software.author }}</dd>
</div>
<div>
<dt class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-1">Platform</dt>
<dd class="text-gray-900 font-medium">{{ software.platform }}</dd>
</div>
<div v-if="software.license">
<dt class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-1">License</dt>
<dd class="text-gray-900 font-medium">{{ software.license }}</dd>
</div>
<div v-for="link in software.externalLinks" :key="link.url">
<dt class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-1">{{ link.label }}</dt>
<dd>
<a :href="link.url" target="_blank" class="text-purple-600 hover:text-purple-800 font-bold break-all">
{{ link.url }}
</a>
</dd>
</div>
</dl>
</div>
</div>
<!-- Main Content Area -->
<div class="lg:col-span-2 space-y-8">
<div v-if="software.story" class="bg-white rounded-3xl shadow-xl p-8 border border-gray-100">
<h2 class="text-xl font-bold text-gray-900 mb-4">About</h2>
<p class="text-gray-700 leading-relaxed whitespace-pre-line">{{ software.story }}</p>
</div>
<div v-if="latestStable" class="bg-gradient-to-br from-indigo-600 to-purple-700 rounded-3xl shadow-xl p-8 text-white">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-6">
<div>
<div class="flex items-center gap-2 mb-2">
<span class="px-2 py-0.5 bg-white/20 text-white text-[10px] font-black uppercase rounded tracking-widest">Latest Stable</span>
</div>
<h2 class="text-4xl font-black mb-1">{{ latestStable.version }}</h2>
<p class="text-indigo-100 text-sm">Released: {{ formatDateTime(latestStable.UpdatedAt) }}</p>
</div>
<div class="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-2 xl:grid-cols-4 gap-2 w-full md:w-auto">
<a v-if="latestStable.htmlFolderPath" :href="latestStable.htmlFolderPath" target="_blank" class="flex items-center justify-center py-3 px-4 bg-white text-indigo-900 font-bold rounded-xl transition-all hover:bg-indigo-50 shadow-lg shadow-black/10 text-sm whitespace-nowrap"> Play Now</a>
<a v-if="latestStable.cartridgePath" :href="latestStable.cartridgePath" target="_blank" class="flex items-center justify-center py-3 px-4 bg-indigo-500/30 border border-indigo-400/30 text-white font-bold rounded-xl transition-all hover:bg-indigo-500/40 text-sm whitespace-nowrap">💾 Download</a>
<a v-if="latestStable.sourcePath" :href="latestStable.sourcePath" target="_blank" class="flex items-center justify-center py-3 px-4 bg-indigo-500/30 border border-indigo-400/30 text-white font-bold rounded-xl transition-all hover:bg-indigo-500/40 text-sm whitespace-nowrap">📄 Source</a>
<a v-if="latestStable.docsFolderPath" :href="latestStable.docsFolderPath" target="_blank" class="flex items-center justify-center py-3 px-4 bg-indigo-500/30 border border-indigo-400/30 text-white font-bold rounded-xl transition-all hover:bg-indigo-500/40 text-sm whitespace-nowrap">📖 Docs</a>
</div>
<!-- Latest Stable Window -->
<div v-if="latestStable" class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">💾 Latest Stable Release {{ latestStable.version }}</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="bg-white rounded-3xl shadow-xl overflow-hidden border border-gray-100">
<div class="p-8 border-b border-gray-100">
<h2 class="text-2xl font-bold text-gray-900">All Releases</h2>
<div class="win-body">
<div style="font-size: 11px; color: #404040; margin-bottom: 8px;">
Released: <span style="font-family: 'Courier New', monospace;">{{ formatDateTime(latestStable.UpdatedAt) }}</span>
</div>
<div class="p-0">
<!-- Desktop Table -->
<div class="hidden md:block">
<table class="w-full text-left">
<thead class="bg-gray-50 text-gray-500 text-xs font-bold uppercase tracking-widest">
<tr>
<th class="px-8 py-4">Version</th>
<th class="px-8 py-4">Release Date</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tr v-for="release in stableReleases" :key="release.version" class="hover:bg-gray-50/50 transition-colors">
<td class="px-8 py-4">
<div class="font-bold text-gray-900 text-lg mb-2">{{ release.version }}</div>
<div class="flex flex-wrap gap-4">
<a v-if="release.htmlFolderPath" :href="release.htmlFolderPath" target="_blank" class="text-purple-600 hover:text-purple-900 font-bold text-sm flex items-center gap-1"> Play</a>
<a v-if="release.cartridgePath" :href="release.cartridgePath" target="_blank" class="text-blue-600 hover:text-blue-900 font-bold text-sm flex items-center gap-1">💾 Download</a>
<a v-if="release.sourcePath" :href="release.sourcePath" target="_blank" class="text-green-600 hover:text-green-900 font-bold text-sm flex items-center gap-1">📄 Source</a>
<a v-if="release.docsFolderPath" :href="release.docsFolderPath" target="_blank" class="text-yellow-600 hover:text-yellow-900 font-bold text-sm flex items-center gap-1">📖 Docs</a>
</div>
</td>
<td class="px-8 py-4 text-gray-500 align-top pt-5">{{ formatDateTime(release.UpdatedAt) }}</td>
</tr>
<div class="release-actions">
<a v-if="latestStable.htmlFolderPath" :href="latestStable.htmlFolderPath" target="_blank" class="win-btn win-btn-default-action"> Play Now</a>
<a v-if="latestStable.cartridgePath" :href="latestStable.cartridgePath" target="_blank" class="win-btn">💾 Download</a>
<a v-if="latestStable.sourcePath" :href="latestStable.sourcePath" target="_blank" class="win-btn">📄 Source</a>
<a v-if="latestStable.docsFolderPath" :href="latestStable.docsFolderPath" target="_blank" class="win-btn">📖 Docs</a>
</div>
</div>
</div>
<tr v-if="devReleases.length > 0" class="bg-yellow-50/30">
<td colspan="2" class="px-8 py-3 text-[10px] font-black text-yellow-700 uppercase tracking-tighter">Development Versions</td>
</tr>
<tr v-for="release in devReleases" :key="release.version" class="bg-yellow-50/10 hover:bg-yellow-50/30 transition-colors">
<td class="px-8 py-4">
<div class="font-bold text-yellow-800 text-lg mb-2">{{ release.version }}</div>
<div class="flex flex-wrap gap-4">
<a v-if="release.htmlFolderPath" :href="release.htmlFolderPath" target="_blank" class="text-purple-600 hover:text-purple-900 font-bold text-sm flex items-center gap-1"> Play</a>
<a v-if="release.cartridgePath" :href="release.cartridgePath" target="_blank" class="text-blue-600 hover:text-blue-900 font-bold text-sm flex items-center gap-1">💾 Download</a>
</div>
</td>
<td class="px-8 py-4 text-gray-500 align-top pt-5">{{ formatDateTime(release.UpdatedAt) }}</td>
</tr>
</tbody>
</table>
</div>
<!-- Mobile View -->
<div class="md:hidden divide-y divide-gray-100">
<div
v-for="release in allReleases"
:key="release.version"
:class="['p-6', release.version.startsWith('dev-') ? 'bg-yellow-50/20' : '']"
>
<div class="flex justify-between items-start mb-4">
<div>
<div class="font-bold text-gray-900 text-lg">{{ release.version }}</div>
<div class="text-gray-500 text-xs mt-1">{{ formatDateTime(release.UpdatedAt) }}</div>
<!-- All Releases Window -->
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📋 All Releases</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-body" style="padding: 0;">
<table class="win-table" style="width: 100%;">
<thead>
<tr>
<th>Version</th>
<th>Release Date</th>
<th>Downloads</th>
</tr>
</thead>
<tbody>
<tr v-for="release in stableReleases" :key="release.version">
<td><strong>{{ release.version }}</strong></td>
<td style="font-family: 'Courier New', monospace; font-size: 11px; white-space: nowrap;">{{ formatDateTime(release.UpdatedAt) }}</td>
<td>
<div class="release-link-row">
<a v-if="release.htmlFolderPath" :href="release.htmlFolderPath" target="_blank" class="win-link-small"> Play</a>
<a v-if="release.cartridgePath" :href="release.cartridgePath" target="_blank" class="win-link-small">💾 Download</a>
<a v-if="release.sourcePath" :href="release.sourcePath" target="_blank" class="win-link-small">📄 Source</a>
<a v-if="release.docsFolderPath" :href="release.docsFolderPath" target="_blank" class="win-link-small">📖 Docs</a>
</div>
<span v-if="release.version.startsWith('dev-')" class="px-2 py-0.5 bg-yellow-100 text-yellow-800 text-[10px] font-black uppercase rounded">Dev</span>
</div>
<div class="grid grid-cols-2 gap-2">
<a v-if="release.htmlFolderPath" :href="release.htmlFolderPath" target="_blank" class="flex items-center justify-center py-2 bg-purple-100 text-purple-700 font-bold rounded-lg text-sm">Play</a>
<a v-if="release.cartridgePath" :href="release.cartridgePath" target="_blank" class="flex items-center justify-center py-2 bg-blue-100 text-blue-700 font-bold rounded-lg text-sm">Download</a>
<a v-if="release.sourcePath" :href="release.sourcePath" target="_blank" class="flex items-center justify-center py-2 bg-green-100 text-green-700 font-bold rounded-lg text-sm">Source</a>
<a v-if="release.docsFolderPath" :href="release.docsFolderPath" target="_blank" class="flex items-center justify-center py-2 bg-yellow-100 text-yellow-700 font-bold rounded-lg text-sm">Docs</a>
</div>
</div>
</div>
</div>
</td>
</tr>
<tr v-if="devReleases.length > 0">
<td colspan="3" class="dev-section-header"> Development Versions </td>
</tr>
<tr v-for="release in devReleases" :key="release.version" class="dev-row">
<td><span style="color: #808000; font-weight: bold;">{{ release.version }}</span></td>
<td style="font-family: 'Courier New', monospace; font-size: 11px; white-space: nowrap;">{{ formatDateTime(release.UpdatedAt) }}</td>
<td>
<div class="release-link-row">
<a v-if="release.htmlFolderPath" :href="release.htmlFolderPath" target="_blank" class="win-link-small"> Play</a>
<a v-if="release.cartridgePath" :href="release.cartridgePath" target="_blank" class="win-link-small">💾 Download</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
<main v-else class="main-container -mt-10">
<div class="bg-white rounded-3xl shadow-xl p-12 text-center text-gray-400">
</div>
</div>
<div v-else class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">Loading...</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-body" style="text-align: center; padding: 24px;">
{{ error || 'Loading...' }}
</div>
</main>
</div>
</div>
</template>
@@ -190,7 +192,6 @@ const devReleases = computed(() =>
.sort((a, b) => new Date(b.UpdatedAt).getTime() - new Date(a.UpdatedAt).getTime())
)
const allReleases = computed(() => [...stableReleases.value, ...devReleases.value])
const latestStable = computed(() => stableReleases.value[0] ?? null)
onMounted(async () => {
@@ -210,11 +211,79 @@ onMounted(async () => {
</script>
<style scoped>
.status-badge {
@apply rounded text-[10px] font-black uppercase tracking-wider;
.show-layout {
display: grid;
grid-template-columns: 200px 1fr;
gap: 8px;
align-items: flex-start;
}
@media (max-width: 640px) {
.show-layout { grid-template-columns: 1fr; }
}
.show-sidebar, .show-main { }
.back-link-row { margin-bottom: 8px; }
.sidebar-img-wrap {
margin-bottom: 8px;
text-align: center;
}
.sidebar-img {
max-width: 100%;
max-height: 100px;
object-fit: contain;
border: 2px solid;
border-color: #808080 #fff #fff #808080;
}
.info-table {
width: 100%;
font-size: 11px;
border-collapse: collapse;
}
.info-table tr { border-bottom: 1px solid #D0D0D0; }
.info-label {
color: #404040;
padding: 2px 4px 2px 0;
white-space: nowrap;
vertical-align: top;
width: 50px;
}
.info-value {
padding: 2px 0 2px 4px;
word-break: break-all;
}
.win-link {
color: #0000FF;
text-decoration: underline;
font-size: 11px;
word-break: break-all;
}
.release-actions {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.release-link-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.win-link-small {
color: #0000FF;
text-decoration: underline;
font-size: 11px;
}
.win-link-small:visited { color: #800080; }
.dev-section-header {
background: #FFFFC8;
font-size: 11px;
color: #808000;
padding: 2px 8px;
text-align: center;
font-style: italic;
}
.dev-row td {
background: #FFFFE8;
}
.status-released { @apply bg-green-500 text-white shadow-lg shadow-green-500/20; }
.status-demo { @apply bg-blue-500 text-white shadow-lg shadow-blue-500/20; }
.status-development { @apply bg-yellow-500 text-white shadow-lg shadow-yellow-500/20; }
.status-archived { @apply bg-gray-500 text-white shadow-lg shadow-gray-500/20; }
</style>
+100 -95
View File
@@ -1,61 +1,99 @@
<template>
<header class="hero-section-gradient from-teal-600 to-green-600 py-20">
<div class="hero-container max-w-4xl">
<h1 class="hero-title">Codebase</h1>
<p class="hero-subtitle text-teal-50 mb-10">
Explore our collection of open-source projects, study our source code, and contribute to our independent game development tools.
</p>
<a href="https://git.teletype.hu" target="_blank" class="btn-hero-teal">
Open Gitea
</a>
</div>
</header>
<div class="main-container">
<main class="main-container">
<div v-if="error" class="error-alert" role="alert">
<strong class="font-bold">Error!</strong>
<span class="block sm:inline"> {{ error }}</span>
<div v-if="error" class="win-notice-error">
<span></span>
<div><strong>Error</strong><br><span style="font-size: 11px;">{{ error }}</span></div>
</div>
<div v-if="!error && publicRepos.length > 0" class="card-grid mb-16">
<div v-for="repo in publicRepos" :key="repo.name" class="card-base">
<h3 class="repo-card-title">
<a :href="repo.html_url" target="_blank" class="repo-link">
{{ repo.owner.login }}/{{ repo.name }}
</a>
</h3>
<p v-if="repo.description" class="repo-card-desc">{{ repo.description }}</p>
<p v-if="repo.language" class="repo-card-meta">Language: {{ repo.language }}</p>
<p class="repo-card-meta">Updated: {{ formatDateTime(repo.updated_at) }}</p>
</div>
</div>
<h2 class="commits-section-title">Recent Commits</h2>
<p v-if="!error && recentCommits.length === 0" class="empty-commits-msg">
No recent commits found or accessible from public repositories.
</p>
<div class="commits-list">
<div v-for="commit in recentCommits" :key="commit.sha" class="commit-item">
<div class="commit-icon">🚀</div>
<div class="commit-content">
<p class="commit-message">
<a :href="commit.repo.html_url" target="_blank" class="commit-repo-link">
<span class="font-bold">{{ commit.repo.owner }}</span> / <span class="font-bold">{{ commit.repo.name }}</span>
</a>
: <span class="text-gray-700">{{ commit.message.split('\n')[0] }}</span>
</p>
<p class="commit-meta">
Authored by <span class="font-bold">{{ commit.author.name }}</span> on {{ formatDateTime(commit.date) }}
<a :href="commit.url" target="_blank" class="commit-sha-link">
<span class="font-mono text-xs">{{ commit.sha.substring(0, 7) }}</span>
</a>
</p>
<!-- Repositories Window -->
<div v-if="!error && publicRepos.length > 0" class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">💻 Public Repositories</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-toolbar">
<a href="https://git.teletype.hu" target="_blank" class="win-btn win-btn-sm">Open Gitea </a>
<span style="font-size: 11px; color: #404040; margin-left: 4px;">{{ publicRepos.length }} repositories</span>
</div>
<div class="win-body" style="padding: 0;">
<table class="win-table">
<thead>
<tr>
<th>Repository</th>
<th>Description</th>
<th>Language</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
<tr v-for="repo in publicRepos" :key="repo.name">
<td>
<a :href="repo.html_url" target="_blank" class="repo-link">
{{ repo.owner.login }}/{{ repo.name }}
</a>
</td>
<td style="font-size: 11px; color: #404040; max-width: 300px;">{{ repo.description || '—' }}</td>
<td style="font-size: 11px; font-family: 'Courier New', monospace; white-space: nowrap;">{{ repo.language || '—' }}</td>
<td style="font-size: 11px; font-family: 'Courier New', monospace; white-space: nowrap;">{{ formatDateTime(repo.updated_at) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
<!-- Recent Commits Window -->
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">🚀 Recent Commits</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div v-if="recentCommits.length === 0" class="win-body" style="text-align: center; color: #404040; font-size: 12px;">
No recent commits found.
</div>
<div v-else class="win-body" style="padding: 0;">
<table class="win-table">
<thead>
<tr>
<th>Repository</th>
<th>Commit Message</th>
<th>Author</th>
<th>Date</th>
<th>SHA</th>
</tr>
</thead>
<tbody>
<tr v-for="commit in recentCommits" :key="commit.sha">
<td style="white-space: nowrap;">
<a :href="commit.repo.html_url" target="_blank" class="repo-link" style="font-size: 11px;">
{{ commit.repo.owner }}/{{ commit.repo.name }}
</a>
</td>
<td style="font-size: 11px; max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{ commit.message.split('\n')[0] }}
</td>
<td style="font-size: 11px; white-space: nowrap;">{{ commit.author.name }}</td>
<td style="font-size: 11px; font-family: 'Courier New', monospace; white-space: nowrap;">{{ formatDateTime(commit.date) }}</td>
<td>
<a :href="commit.url" target="_blank" class="sha-link">{{ commit.sha.substring(0, 7) }}</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
@@ -71,11 +109,9 @@ const error = ref<string | null>(null)
onMounted(async () => {
try {
publicRepos.value = await gitApi.repos()
const commitPromises = publicRepos.value.map(repo =>
gitApi.commits(repo.owner.login, repo.name, repo.html_url)
)
const all = (await Promise.all(commitPromises)).flat()
all.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
recentCommits.value = all.slice(0, 20)
@@ -86,49 +122,18 @@ onMounted(async () => {
</script>
<style scoped>
.error-alert {
@apply bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-8;
}
.repo-card-title {
@apply text-xl font-semibold mb-2;
}
.repo-link {
@apply text-blue-600 hover:text-blue-800;
color: #0000FF;
text-decoration: underline;
font-size: 12px;
font-family: "Courier New", Courier, monospace;
}
.repo-card-desc {
@apply text-gray-700 text-sm;
}
.repo-card-meta {
@apply text-gray-500 text-xs mt-1;
}
.commits-section-title {
@apply text-3xl font-bold mb-8 text-center text-gray-800;
}
.empty-commits-msg {
@apply text-center text-gray-600 text-lg;
}
.commits-list {
@apply space-y-4 mb-12;
}
.commit-item {
@apply bg-white shadow-lg rounded-lg p-4 flex items-center space-x-4 border border-gray-100;
}
.commit-icon {
@apply flex-shrink-0 text-3xl;
}
.commit-content {
@apply flex-grow;
}
.commit-message {
@apply text-gray-800 font-semibold;
}
.commit-repo-link {
@apply text-blue-600 hover:text-blue-800;
}
.commit-meta {
@apply text-gray-600 text-sm mt-1;
}
.commit-sha-link {
@apply ml-2 text-blue-500 hover:text-blue-700;
.repo-link:hover { color: #0000CC; }
.sha-link {
color: #0000FF;
text-decoration: underline;
font-family: "Courier New", Courier, monospace;
font-size: 11px;
}
.sha-link:hover { color: #0000CC; }
</style>
+153 -105
View File
@@ -1,79 +1,99 @@
<template>
<header class="hero-section-gradient from-blue-600 to-indigo-700 py-20 text-white">
<div class="hero-container text-center">
<h1 class="hero-title mb-6">Contact Us</h1>
<p class="hero-subtitle mb-10 text-blue-100">
Have questions, feedback, or just want to say hi? We'd love to hear from you!
</p>
<a :href="DISCORD_INVITE_LINK" target="_blank" class="discord-hero-btn">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037 19.736 19.736 0 0 0-4.885 1.515.069.069 0 0 0-.032.027C.533 9.048-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.862-1.297 1.197-1.99.051-.105.001-.23-.106-.272a13.223 13.223 0 0 1-1.878-.894.077.077 0 0 1-.007-.128c.126-.094.252-.192.372-.29a.074.074 0 0 1 .077-.01c3.927 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .077.01c.12.098.246.196.373.29a.077.077 0 0 1-.006.128 12.299 12.299 0 0 1-1.878.894.077.077 0 0 0-.106.272c.335.693.735 1.36 1.197 1.99a.078.078 0 0 0 .084.028 19.83 19.83 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.182 0-2.156-1.085-2.156-2.419 0-1.333.956-2.419 2.156-2.419 1.21 0 2.176 1.096 2.156 2.419 0 1.334-.956 2.419-2.156 2.419zm7.974 0c-1.182 0-2.156-1.085-2.156-2.419 0-1.333.955-2.419 2.156-2.419 1.21 0 2.176 1.096 2.156 2.419 0 1.334-.946 2.419-2.156 2.419z"/>
</svg>
Join our Discord
</a>
</div>
</header>
<div class="main-container">
<main class="main-container py-16 px-4">
<div class="contact-grid">
<div class="contact-card">
<h2 class="contact-card-title">
<span class="text-3xl">📧</span> Email Us
</h2>
<p class="contact-card-desc">
For business inquiries, support, or detailed feedback, feel free to drop us an email.
</p>
<div @click="openEmail" class="email-link cursor-pointer">
contact@teletype.hu
<!-- Email Window -->
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📧 Contact Us</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="contact-card">
<h2 class="contact-card-title">
<span class="text-3xl">💬</span> Community
</h2>
<p class="contact-card-desc mb-8">
Join our primarily Hungarian-speaking community (English speakers are also welcome!).
<div class="win-body">
<p style="margin: 0 0 8px; font-size: 12px; color: #404040;">
Have questions, feedback, or just want to say hi?
</p>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 justify-center">
<div class="community-section border-[#5865F2]">
<h3 class="community-section-title text-[#5865F2]">Discord</h3>
<p class="community-section-desc">We have a vibrant Discord server for real-time chat and support.</p>
<a :href="DISCORD_INVITE_LINK" target="_blank" class="community-link-discord">
Join Discord Server <span>→</span>
</a>
</div>
<div class="community-section border-red-600">
<h3 class="community-section-title text-red-600">YouTube</h3>
<p class="community-section-desc">Watch our latest vlogs and development videos on our channel.</p>
<a href="https://www.youtube.com/@teletypegames" target="_blank" class="community-link-youtube">
Visit Channel <span>→</span>
</a>
</div>
<div class="community-section border-green-500">
<h3 class="community-section-title text-green-700">IRC</h3>
<p class="text-sm text-gray-600 mb-3">If Discord is too modern, find us on IRC!</p>
<div class="irc-box">
<span class="irc-network">Network: Libera.Chat</span>
<code class="irc-channel">#teletypegames</code>
</div>
</div>
<div class="community-section border-amber-500">
<h3 class="community-section-title text-amber-700">BBS</h3>
<p class="community-section-desc">Join us for some retro vibes on our BBS!</p>
<div class="bbs-box">
<span class="bbs-host">Host: games.teletype.hu</span>
<code class="bbs-port">Port: 2323</code>
</div>
</div>
<table class="contact-table">
<tr>
<td class="contact-label">Email:</td>
<td>
<span @click="openEmail" class="email-link">contact@teletype.hu</span>
</td>
</tr>
</table>
<div style="margin-top: 8px;">
<a :href="DISCORD_INVITE_LINK" target="_blank" class="win-btn">
<svg class="discord-icon" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037 19.736 19.736 0 0 0-4.885 1.515.069.069 0 0 0-.032.027C.533 9.048-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.862-1.297 1.197-1.99.051-.105.001-.23-.106-.272a13.223 13.223 0 0 1-1.878-.894.077.077 0 0 1-.007-.128c.126-.094.252-.192.372-.29a.074.074 0 0 1 .077-.01c3.927 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .077.01c.12.098.246.196.373.29a.077.077 0 0 1-.006.128 12.299 12.299 0 0 1-1.878.894.077.077 0 0 0-.106.272c.335.693.735 1.36 1.197 1.99a.078.078 0 0 0 .084.028 19.83 19.83 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/>
</svg>
Join our Discord Server
</a>
</div>
</div>
</div>
</main>
<!-- Community Window -->
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">💬 Community Channels</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-body">
<p style="margin: 0 0 10px; font-size: 12px; color: #404040;">
Join our primarily Hungarian-speaking community (English speakers also welcome!).
</p>
<div class="community-grid">
<div class="community-item win-raised-panel">
<div class="community-header" style="color: #5865F2;">
<span class="community-icon">💬</span> Discord
</div>
<p class="community-desc">Real-time chat and support on our Discord server.</p>
<a :href="DISCORD_INVITE_LINK" target="_blank" class="win-btn win-btn-sm">Join Discord </a>
</div>
<div class="community-item win-raised-panel">
<div class="community-header" style="color: #CC0000;">
<span class="community-icon">📺</span> YouTube
</div>
<p class="community-desc">Watch our latest vlogs and development videos.</p>
<a href="https://www.youtube.com/@teletypegames" target="_blank" class="win-btn win-btn-sm">Visit Channel </a>
</div>
<div class="community-item win-raised-panel">
<div class="community-header" style="color: #007000;">
<span class="community-icon">🖥</span> IRC
</div>
<p class="community-desc">If Discord is too modern, find us on IRC!</p>
<div class="code-box">
<div style="font-size: 10px; color: #404040; margin-bottom: 2px;">Network: Libera.Chat</div>
<code class="irc-code">#teletypegames</code>
</div>
</div>
<div class="community-item win-raised-panel">
<div class="community-header" style="color: #805000;">
<span class="community-icon">📟</span> BBS
</div>
<p class="community-desc">Retro vibes on our BBS!</p>
<div class="code-box">
<div style="font-size: 10px; color: #404040; margin-bottom: 2px;">Host: games.teletype.hu</div>
<code class="bbs-code">Port: 2323</code>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
@@ -85,55 +105,83 @@ function openEmail() {
</script>
<style scoped>
.discord-hero-btn {
@apply inline-flex items-center gap-3 bg-[#5865F2] hover:bg-[#4752C4] text-white font-black py-4 px-8 rounded-full text-lg transition-all transform hover:scale-105 shadow-xl;
.contact-table {
font-size: 12px;
margin-bottom: 4px;
}
.contact-grid {
@apply grid grid-cols-1 gap-12 max-w-5xl mx-auto;
}
.contact-card {
@apply bg-white p-8 rounded-2xl shadow-xl border border-gray-100;
}
.contact-card-title {
@apply text-2xl font-black text-gray-900 mb-6 flex items-center gap-3;
}
.contact-card-desc {
@apply text-gray-600 mb-6 leading-relaxed;
.contact-label {
color: #404040;
padding-right: 8px;
white-space: nowrap;
}
.email-link {
@apply text-indigo-600 font-bold text-xl hover:underline transition-all;
color: #0000FF;
text-decoration: underline;
cursor: pointer;
font-size: 13px;
}
.community-section {
@apply border-l-4 pl-4;
.email-link:hover { color: #0000CC; }
.discord-icon {
width: 14px;
height: 14px;
}
.community-section-title {
@apply text-lg font-bold mb-2;
.community-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
}
.community-section-desc {
@apply text-gray-600 text-sm mb-3;
@media (max-width: 768px) {
.community-grid { grid-template-columns: repeat(2, 1fr); }
}
.community-link-discord {
@apply text-[#5865F2] font-bold hover:underline inline-flex items-center gap-1;
@media (max-width: 420px) {
.community-grid { grid-template-columns: 1fr; }
}
.community-link-youtube {
@apply text-red-600 font-bold hover:underline inline-flex items-center gap-1;
.community-item {
padding: 8px;
display: flex;
flex-direction: column;
}
.irc-box {
@apply bg-green-100 p-4 rounded-xl border border-green-200 mt-3 inline-block;
.community-header {
font-size: 13px;
font-weight: bold;
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.irc-network {
@apply block text-xs font-semibold text-green-500 uppercase tracking-wider mb-1;
.community-icon { font-size: 14px; }
.community-desc {
font-size: 11px;
color: #404040;
margin: 0 0 8px;
flex-grow: 1;
line-height: 1.4;
}
.irc-channel {
@apply text-green-800 font-mono text-lg font-bold;
.code-box {
background: #fff;
border: 1px solid #808080;
padding: 4px 6px;
margin-top: auto;
box-shadow:
inset 1px 1px 0 0 #808080,
inset -1px -1px 0 0 #D4D0C8;
}
.bbs-box {
@apply bg-amber-50 p-4 rounded-xl border border-amber-100 mt-3 inline-block;
.irc-code {
font-family: "Courier New", Courier, monospace;
font-size: 12px;
font-weight: bold;
color: #007000;
display: block;
}
.bbs-host {
@apply block text-xs font-semibold text-amber-500 uppercase tracking-wider mb-1;
}
.bbs-port {
@apply text-amber-700 font-mono text-lg font-bold;
.bbs-code {
font-family: "Courier New", Courier, monospace;
font-size: 12px;
font-weight: bold;
color: #805000;
display: block;
}
</style>
+318 -255
View File
@@ -1,188 +1,179 @@
<template>
<div class="home-container">
<!-- Header Section -->
<header class="hero-section-slate">
<div class="home-header-decor">
<div class="hero-decor-blob -top-24 -left-24 h-96 w-96 bg-indigo-600"></div>
<div class="hero-decor-blob -bottom-24 -right-24 h-96 w-96 bg-purple-600"></div>
</div>
<div class="main-container">
<div class="hero-container">
<div class="hero-badge">Welcome to</div>
<h1 class="hero-title">
Teletype <span class="text-transparent bg-clip-text bg-gradient-to-r from-violet-400 to-fuchsia-400">Games</span>
</h1>
<p class="hero-subtitle">
Teletype Games is an independent game development community built on creative freedom, equality, and an open-source mindset. Our goal is to create experimental and full-fledged games with short development cycles.
<!-- Welcome Window -->
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📟 Welcome to Teletype Games</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-body">
<p style="margin: 0; font-size: 13px;">
Teletype Games is an independent game development community built on creative freedom, equality, and an open-source mindset.
Our goal is to create experimental and full-fledged games with short development cycles.
</p>
</div>
</header>
</div>
<main class="main-container max-w-6xl">
<!-- Countdown Section -->
<section v-if="nextEvent" class="mb-12">
<div class="countdown-card">
<h2 class="countdown-event-name">{{ nextEvent.name }}</h2>
<div class="countdown-timer">{{ countdownText }}</div>
<p class="countdown-date">Starts on: <span class="font-semibold">{{ nextEvent.dateText }}</span></p>
<!-- Two-column: Countdown + Featured -->
<div class="home-two-col">
<div v-if="followingEvents.length > 0" class="upcoming-events-section">
<h3 class="upcoming-events-title">Upcoming Events</h3>
<div class="space-y-4">
<div v-for="event in followingEvents" :key="event.name" class="upcoming-event-item">
<span class="font-bold text-gray-800">{{ event.name }}</span>
<span class="text-sm text-gray-500 font-mono">{{ event.dateText }}</span>
</div>
<!-- Countdown Window -->
<div v-if="nextEvent" class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text"> Next Event</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-body" style="text-align: center;">
<div class="countdown-name">{{ nextEvent.name }}</div>
<div class="win-inset countdown-display">
<span class="countdown-mono">{{ countdownText }}</span>
</div>
<div class="countdown-date">
Starts: <strong>{{ nextEvent.dateText }}</strong>
</div>
<div v-if="followingEvents.length > 0" class="upcoming-section">
<div class="win-separator"></div>
<div class="upcoming-label">Upcoming Events:</div>
<div v-for="event in followingEvents" :key="event.name" class="upcoming-row">
<span>{{ event.name }}</span>
<span class="upcoming-date-mono">{{ event.dateText }}</span>
</div>
</div>
</div>
</section>
</div>
<!-- Featured Software Section -->
<section v-if="highlightedSoftware" class="mb-16">
<div class="featured-card group">
<div v-if="highlightedSoftware.software.imageUrl" class="featured-image-panel">
<img :src="highlightedSoftware.software.imageUrl" :alt="highlightedSoftware.software.title" class="featured-image" />
<!-- Featured Software Window -->
<div v-if="highlightedSoftware" class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">🎮 Featured Game</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
<div class="featured-content">
<div class="flex items-center gap-2 mb-4">
<span class="featured-badge">Featured Game</span>
</div>
<div class="win-body featured-body">
<div v-if="highlightedSoftware.software.imageUrl" class="featured-img-wrap">
<img :src="highlightedSoftware.software.imageUrl" :alt="highlightedSoftware.software.title" class="featured-img" />
</div>
<div class="featured-info">
<div class="featured-badges">
<span :class="`status-badge status-${highlightedSoftware.software.status}`">{{ highlightedSoftware.software.status }}</span>
<span v-if="highlightedStableRelease" class="featured-version-badge">{{ highlightedStableRelease.version }}</span>
<span v-if="highlightedStableRelease" class="featured-version">{{ highlightedStableRelease.version }}</span>
</div>
<h2 class="featured-title">{{ highlightedSoftware.software.title }}</h2>
<p class="featured-desc">{{ highlightedSoftware.software.desc }}</p>
<div class="flex flex-wrap gap-4 mt-8">
<div class="featured-title">{{ highlightedSoftware.software.title }}</div>
<div class="featured-desc">{{ highlightedSoftware.software.desc }}</div>
<div class="featured-actions">
<a
v-if="highlightedStableRelease?.htmlFolderPath"
:href="highlightedStableRelease.htmlFolderPath"
target="_blank"
class="featured-btn-play"
class="win-btn"
> Play in Browser</a>
<RouterLink :to="`/catalog/${highlightedSoftware.software.name}`" class="featured-btn-secondary">
View Project Details
<RouterLink :to="`/catalog/${highlightedSoftware.software.name}`" class="win-btn">
More Info
</RouterLink>
</div>
</div>
</div>
</section>
</div>
<!-- Latest YouTube Video -->
<section class="mb-16">
<div class="yt-featured-wrapper group">
<div class="yt-glow"></div>
</div>
<div class="yt-header-area">
<div class="flex items-center gap-3">
<div class="yt-status-blob">
<div class="yt-status-dot"></div>
</div>
<span class="yt-label-text">Latest from YouTube</span>
</div>
<a href="https://www.youtube.com/@teletypegames" target="_blank" class="yt-channel-link">
Visit Channel
</a>
</div>
<div class="yt-content-grid">
<div class="yt-player-container">
<div class="player-wrap relative w-full aspect-video">
<div v-if="ytState === 'loading'" class="state-overlay absolute inset-0 flex items-center justify-center bg-slate-950">
<div class="w-10 h-10 border-4 border-white/5 border-t-indigo-500 rounded-full animate-spin"></div>
</div>
<div v-else-if="ytState === 'error'" class="state-overlay absolute inset-0 flex items-center justify-center bg-slate-950 flex-col gap-2">
<div class="text-xl"></div>
<div class="text-[11px] text-slate-500 text-center px-4 font-bold uppercase tracking-tighter">{{ ytError }}</div>
</div>
<div v-else-if="ytState === 'no-config'" class="state-overlay absolute inset-0 flex items-center justify-center bg-slate-950">
<div class="text-slate-500 text-xs">Missing API Configuration</div>
</div>
<iframe
v-else-if="ytVideoId"
:src="`https://www.youtube.com/embed/${ytVideoId}?autoplay=0&rel=0&modestbranding=1`"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
class="w-full h-full border-none block"
></iframe>
</div>
</div>
<div v-if="ytVideoId" class="yt-info-sidebar">
<div class="mb-auto">
<span class="yt-new-tag">New Release</span>
<h3 class="yt-video-title">{{ ytTitle }}</h3>
<div class="yt-stats-row">
<span v-if="ytPublishDate">📅 {{ ytPublishDate }}</span>
<span v-if="ytViewCount">👁 {{ ytViewCount }}</span>
</div>
</div>
<div class="mt-6">
<a :href="`https://www.youtube.com/watch?v=${ytVideoId}`" target="_blank" class="yt-play-button">
<span class="text-xl"></span> Watch on YouTube
</a>
</div>
</div>
</div>
</div>
</section>
<!-- Navigation Grid -->
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="nav-card group card-interactive">
<div class="nav-card-icon bg-purple-50 group-hover:bg-purple-100">
<span class="text-3xl">🎮</span>
</div>
<h2 class="nav-card-title">Catalog</h2>
<p class="nav-card-desc">Here you can find our games. Discover our creations for TIC-80 and other platforms, many of which you can try directly in your browser.</p>
<RouterLink to="/catalog" class="btn-primary bg-purple-600 hover:bg-purple-700 text-white hover:shadow-purple-200">
Browse Games
</RouterLink>
</div>
<div class="nav-card group card-interactive">
<div class="nav-card-icon bg-green-50 group-hover:bg-green-100">
<span class="text-3xl">📚</span>
</div>
<h2 class="nav-card-title">Wiki</h2>
<p class="nav-card-desc">We gather our public interest development knowledge here. Read through technical descriptions and development logs.</p>
<a href="https://wiki.teletype.hu" target="_blank" rel="noopener noreferrer" class="btn-primary bg-green-600 hover:bg-green-700 text-white hover:shadow-green-200">
Knowledge Base
</a>
</div>
<div class="nav-card group card-interactive">
<div class="nav-card-icon bg-slate-50 group-hover:bg-slate-100">
<span class="text-3xl">💻</span>
</div>
<h2 class="nav-card-title">Git Repos</h2>
<p class="nav-card-desc">All our projects are open source. Browse our code, study our solutions, or even participate in the development.</p>
<a href="https://git.teletype.hu" target="_blank" rel="noopener noreferrer" class="btn-primary bg-slate-800 hover:bg-slate-900 text-white hover:shadow-slate-200">
Gitea
</a>
</div>
<div class="nav-card group card-interactive">
<div class="nav-card-icon bg-red-50 group-hover:bg-red-100">
<span class="text-3xl">📺</span>
</div>
<h2 class="nav-card-title">YouTube</h2>
<p class="nav-card-desc">Watch our development vlogs, gameplay videos, and tutorials on our official YouTube channel.</p>
<a href="https://www.youtube.com/@teletypegames" target="_blank" rel="noopener noreferrer" class="btn-primary bg-red-600 hover:bg-red-700 text-white hover:shadow-red-200">
Watch on YouTube
</a>
</div>
</section>
<!-- AI Content Notice -->
<div class="mb-12 mt-12 bg-white rounded-2xl border border-indigo-100 p-6 shadow-sm flex items-center gap-4">
<div class="w-12 h-12 rounded-full bg-indigo-50 flex items-center justify-center text-2xl flex-shrink-0">🤖</div>
<div class="flex-grow text-slate-600 text-sm md:text-base italic">
<strong>Note:</strong> Our HowTo-s and blog posts are written by AI with human supervision.
<!-- YouTube Window -->
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📺 Latest from YouTube</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
</main>
<div class="win-body yt-layout">
<div class="yt-player win-inset">
<div v-if="ytState === 'loading'" class="yt-state">Loading...</div>
<div v-else-if="ytState === 'error'" class="yt-state yt-state-error"> {{ ytError }}</div>
<div v-else-if="ytState === 'no-config'" class="yt-state">YouTube API not configured.</div>
<iframe
v-else-if="ytVideoId"
:src="`https://www.youtube.com/embed/${ytVideoId}?autoplay=0&rel=0&modestbranding=1`"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
style="width: 100%; height: 100%; border: none; display: block;"
></iframe>
</div>
<div v-if="ytVideoId" class="yt-info">
<div class="yt-title">{{ ytTitle }}</div>
<div class="yt-meta">
<span v-if="ytPublishDate">{{ ytPublishDate }}</span>
<span v-if="ytPublishDate && ytViewCount"> · </span>
<span v-if="ytViewCount">{{ ytViewCount }}</span>
</div>
<div class="yt-actions">
<a :href="`https://www.youtube.com/watch?v=${ytVideoId}`" target="_blank" class="win-btn"> Watch on YouTube</a>
<a href="https://www.youtube.com/@teletypegames" target="_blank" class="win-btn">Visit Channel</a>
</div>
</div>
</div>
</div>
<!-- Navigation Window -->
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">🗺 Navigation</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div class="win-body">
<div class="nav-grid">
<div class="nav-item win-raised-panel">
<div class="nav-icon">🎮</div>
<div class="nav-title">Catalog</div>
<div class="nav-desc">Discover our games for TIC-80 and other platforms, many playable directly in browser.</div>
<RouterLink to="/catalog" class="win-btn nav-btn">Browse Games</RouterLink>
</div>
<div class="nav-item win-raised-panel">
<div class="nav-icon">📚</div>
<div class="nav-title">Wiki</div>
<div class="nav-desc">Technical descriptions and development logs from our knowledge base.</div>
<a href="https://wiki.teletype.hu" target="_blank" class="win-btn nav-btn">Knowledge Base</a>
</div>
<div class="nav-item win-raised-panel">
<div class="nav-icon">💻</div>
<div class="nav-title">Git Repos</div>
<div class="nav-desc">All our projects are open source. Browse code, study solutions, or contribute.</div>
<a href="https://git.teletype.hu" target="_blank" class="win-btn nav-btn">Gitea</a>
</div>
<div class="nav-item win-raised-panel">
<div class="nav-icon">📺</div>
<div class="nav-title">YouTube</div>
<div class="nav-desc">Development vlogs, gameplay videos, and tutorials on our official channel.</div>
<a href="https://www.youtube.com/@teletypegames" target="_blank" class="win-btn nav-btn">Watch on YouTube</a>
</div>
</div>
</div>
</div>
<!-- AI Notice -->
<div class="win-notice-info">
<span>🤖</span>
<span><strong>Note:</strong> Our HowTo-s and blog posts are written by AI with human supervision.</span>
</div>
</div>
</template>
@@ -293,132 +284,204 @@ onUnmounted(() => {
</script>
<style scoped>
.home-container {
@apply bg-gray-50 min-h-screen pb-20;
}
.home-header-decor {
@apply absolute inset-0 opacity-30;
.home-two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 0;
}
/* Featured Card */
.featured-card {
@apply relative overflow-hidden bg-gradient-to-br from-indigo-900 to-slate-900 rounded-3xl shadow-2xl flex flex-col md:flex-row min-h-[400px];
}
.featured-content {
@apply p-8 md:p-12 flex flex-col justify-center flex-1 z-10;
}
.featured-badge {
@apply px-3 py-1 bg-indigo-500/20 text-indigo-300 border border-indigo-400/30 rounded-full text-xs font-bold uppercase tracking-wider;
}
.featured-title {
@apply text-4xl md:text-5xl font-black text-white mb-6;
}
.featured-desc {
@apply text-indigo-100/80 text-lg leading-relaxed max-w-xl;
}
.featured-btn-play {
@apply px-8 py-4 bg-white text-indigo-900 font-bold rounded-xl transition-all hover:scale-105 hover:shadow-xl hover:shadow-indigo-500/20 active:scale-95;
}
.featured-btn-secondary {
@apply px-8 py-4 bg-indigo-500/10 text-white border border-indigo-400/30 font-bold rounded-xl transition-all hover:bg-indigo-500/20;
}
.featured-image-panel {
@apply flex-shrink-0 flex items-center justify-center p-6 md:p-8;
}
.featured-image {
@apply max-h-48 w-auto object-contain border-4 border-white shadow-xl opacity-90 group-hover:opacity-100 transition-opacity;
}
.featured-version-badge {
@apply px-2 py-0.5 rounded text-[10px] font-bold bg-white/10 text-white/70 border border-white/20;
@media (max-width: 640px) {
.home-two-col {
grid-template-columns: 1fr;
}
}
/* Status Badges */
.status-badge {
@apply px-2 py-0.5 rounded text-[10px] font-bold uppercase;
/* Countdown */
.countdown-name {
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #000080;
margin-bottom: 8px;
}
.status-released { @apply bg-green-500/20 text-green-300 border border-green-500/30; }
.status-demo { @apply bg-blue-500/20 text-blue-300 border border-blue-500/30; }
.status-development { @apply bg-yellow-500/20 text-yellow-300 border border-yellow-500/30; }
.status-archived { @apply bg-gray-500/20 text-gray-300 border border-gray-500/30; }
/* Countdown Card */
.countdown-card {
@apply bg-white rounded-2xl shadow-xl overflow-hidden border border-gray-100 p-8 md:p-12 text-center transition-all duration-300;
.countdown-display {
display: inline-block;
padding: 6px 12px;
margin: 4px auto 8px;
}
.countdown-event-name {
@apply text-2xl font-bold text-gray-400 uppercase tracking-widest mb-2;
}
.countdown-timer {
@apply text-5xl md:text-7xl font-mono font-black text-transparent bg-clip-text bg-gradient-to-r from-violet-600 to-fuchsia-600 my-6;
.countdown-mono {
font-family: "Courier New", Courier, monospace;
font-size: 20px;
font-weight: bold;
letter-spacing: 2px;
color: #000080;
}
.countdown-date {
@apply text-gray-500 text-lg;
font-size: 11px;
color: #404040;
margin-bottom: 4px;
}
.upcoming-events-section {
@apply mt-12 pt-12 border-t border-gray-100 text-left max-w-2xl mx-auto;
.upcoming-section {
text-align: left;
margin-top: 8px;
}
.upcoming-events-title {
@apply text-xl font-bold text-gray-900 mb-6;
.upcoming-label {
font-size: 11px;
font-weight: bold;
color: #000080;
margin-bottom: 4px;
}
.upcoming-event-item {
@apply flex items-center justify-between p-4 bg-gray-50 rounded-xl border border-gray-100;
.upcoming-row {
display: flex;
justify-content: space-between;
padding: 2px 4px;
font-size: 12px;
border-bottom: 1px solid #D0D0D0;
}
.upcoming-row:hover {
background: #000080;
color: #fff;
}
.upcoming-date-mono {
font-family: "Courier New", Courier, monospace;
font-size: 11px;
color: #404040;
}
.upcoming-row:hover .upcoming-date-mono {
color: #fff;
}
/* Nav Cards */
.nav-card {
@apply flex flex-col h-full;
/* Featured */
.featured-body {
display: flex;
gap: 10px;
align-items: flex-start;
flex-wrap: wrap;
}
.nav-card-icon {
@apply w-14 h-14 rounded-xl flex items-center justify-center mb-6 transition-colors;
.featured-img-wrap { flex-shrink: 0; }
.featured-img {
width: 80px;
height: 80px;
object-fit: cover;
border: 2px solid;
border-color: #808080 #fff #fff #808080;
}
.nav-card-title {
@apply text-2xl font-bold text-gray-900 mb-3;
.featured-info { flex: 1; min-width: 160px; }
.featured-badges {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.nav-card-desc {
@apply text-gray-600 mb-6 flex-grow leading-relaxed;
.featured-version {
font-family: "Courier New", Courier, monospace;
font-size: 11px;
color: #404040;
}
.featured-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 4px;
}
.featured-desc {
font-size: 11px;
color: #404040;
margin-bottom: 10px;
line-height: 1.5;
}
.featured-actions {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
/* YouTube Featured Wrapper */
.yt-featured-wrapper {
@apply relative overflow-hidden bg-gradient-to-br from-slate-900 via-slate-900 to-indigo-950 rounded-3xl shadow-2xl border border-slate-800 p-6 md:p-8;
/* YouTube */
.yt-layout {
display: flex;
gap: 8px;
align-items: flex-start;
flex-wrap: wrap;
}
.yt-glow {
@apply absolute -top-24 -right-24 w-96 h-96 bg-indigo-500/10 rounded-full blur-[100px] pointer-events-none;
.yt-player {
flex: 1.6;
min-width: 200px;
aspect-ratio: 16 / 9;
overflow: hidden;
}
.yt-header-area {
@apply flex items-center justify-between mb-6 relative z-10;
.yt-state {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 120px;
font-size: 12px;
color: #404040;
padding: 16px;
}
.yt-status-blob {
@apply w-3 h-3 bg-red-500/20 rounded-full flex items-center justify-center;
.yt-state-error { color: #800000; }
.yt-info {
flex: 1;
min-width: 140px;
}
.yt-status-dot {
@apply w-1.5 h-1.5 bg-red-500 rounded-full animate-pulse;
.yt-title {
font-size: 13px;
font-weight: bold;
margin-bottom: 4px;
color: #000080;
line-height: 1.4;
}
.yt-label-text {
@apply text-xs font-bold uppercase tracking-widest text-slate-400;
.yt-meta {
font-size: 11px;
color: #404040;
margin-bottom: 10px;
font-family: "Courier New", Courier, monospace;
}
.yt-channel-link {
@apply text-xs font-semibold text-indigo-400 hover:text-indigo-300 transition-colors;
.yt-actions {
display: flex;
flex-direction: column;
gap: 4px;
}
.yt-content-grid {
@apply flex flex-col lg:flex-row gap-8 relative z-10;
/* Navigation Grid */
.nav-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
}
.yt-player-container {
@apply flex-[1.6] overflow-hidden rounded-2xl border border-white/5 shadow-inner bg-black;
@media (max-width: 768px) {
.nav-grid { grid-template-columns: repeat(2, 1fr); }
}
.yt-info-sidebar {
@apply flex-1 flex flex-col justify-center;
@media (max-width: 420px) {
.nav-grid { grid-template-columns: 1fr; }
}
.yt-new-tag {
@apply inline-block px-2 py-1 bg-red-500/10 text-red-400 border border-red-500/20 rounded-md text-[10px] font-black uppercase tracking-tighter mb-4;
.nav-item {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 8px;
}
.yt-video-title {
@apply text-2xl font-black text-white mb-4 leading-tight group-hover:text-indigo-200 transition-colors;
.nav-icon {
font-size: 22px;
margin-bottom: 4px;
}
.yt-stats-row {
@apply flex gap-4 text-sm text-slate-400 font-medium;
.nav-title {
font-size: 13px;
font-weight: bold;
margin-bottom: 3px;
}
.yt-play-button {
@apply flex items-center justify-center gap-2 w-full py-4 bg-white text-slate-900 font-black rounded-xl transition-all hover:scale-[1.02] hover:bg-indigo-50 active:scale-95 shadow-xl;
.nav-desc {
font-size: 11px;
color: #404040;
margin-bottom: 8px;
flex-grow: 1;
line-height: 1.5;
}
.nav-btn {
margin-top: auto;
min-width: auto;
font-size: 11px;
padding: 2px 8px 3px;
}
</style>
+145 -106
View File
@@ -1,79 +1,66 @@
<template>
<div class="howtos-container">
<header class="hero-section-slate">
<div class="howtos-header-decor">
<div class="hero-decor-blob -top-24 -left-24 h-96 w-96 bg-indigo-600"></div>
<div class="hero-decor-blob -bottom-24 -right-24 h-96 w-96 bg-purple-600"></div>
</div>
<div class="hero-container">
<div class="hero-badge">Knowledge Base</div>
<h1 class="hero-title">
Tech <span class="text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-purple-400">HowTo Center</span>
</h1>
<p class="hero-subtitle mb-10">
The latest documentation, technical guides, and development logs from our knowledge base.
</p>
<a :href="WIKI_BASE" target="_blank" class="btn-hero-indigo">
Knowledge Base
</a>
</div>
</header>
<main class="main-container">
<div v-if="error" class="error-banner" role="alert">
<span class="text-2xl"></span>
<div>
<h3 class="error-banner-title">Failed to connect to Wiki</h3>
<p class="error-banner-desc">{{ error }}</p>
<div class="main-container">
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">📖 Tech HowTo Center</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
</div>
<div v-else-if="!loading && recentPages.length === 0" class="empty-state">
<div class="empty-state-icon">📭</div>
<h2 class="empty-state-title">No pages found</h2>
<p class="empty-state-desc">It seems like there aren't any public pages available on the wiki at the moment.</p>
<div class="win-toolbar">
<a :href="WIKI_BASE" target="_blank" class="win-btn win-btn-sm">Open Wiki </a>
<span style="font-size: 11px; color: #404040; margin-left: 4px;">
Latest documentation, technical guides, and development logs.
</span>
</div>
<div v-else class="card-grid">
<a
v-for="page in recentPages"
:key="page.id"
:href="`${WIKI_BASE}/${page.locale}/${page.path}`"
target="_blank"
class="group card-interactive flex flex-col"
>
<div class="howto-card-meta">
<div class="howto-card-timestamp">
<span v-if="isNew(page.createdAt)" class="new-badge">
<span class="new-dot"></span>
New
</span>
<span>{{ formatDateTime(page.updatedAt) }}</span>
<div class="win-body" style="padding: 0;">
<div v-if="error" class="win-notice-error" style="margin: 8px;">
<span></span>
<div>
<strong>Failed to connect to Wiki</strong><br>
<span style="font-size: 11px;">{{ error }}</span>
</div>
</div>
<div v-else-if="!loading && recentPages.length === 0" class="empty-state">
<div style="font-size: 32px; margin-bottom: 8px;">📭</div>
<div style="font-weight: bold; margin-bottom: 4px;">No pages found</div>
<div style="font-size: 11px; color: #404040;">No public pages available on the wiki at the moment.</div>
</div>
<div v-else class="howto-grid">
<a
v-for="page in recentPages"
:key="page.id"
:href="`${WIKI_BASE}/${page.locale}/${page.path}`"
target="_blank"
class="howto-card win-raised-panel"
>
<div class="howto-meta">
<span v-if="isNew(page.createdAt)" class="new-badge"> New</span>
<span class="howto-date">{{ formatDateTime(page.updatedAt) }}</span>
</div>
</div>
<div class="howto-title">{{ page.title }}</div>
<div v-if="page.description" class="howto-desc">{{ page.description }}</div>
<div v-else class="howto-no-desc">No description provided.</div>
<div class="howto-footer">
<span class="howto-path">{{ page.path }}</span>
<span class="howto-arrow">Read </span>
</div>
</a>
</div>
<h2 class="howto-card-title">{{ page.title }}</h2>
<div v-if="!error && recentPages.length > 0" class="howto-more">
<a :href="WIKI_BASE" target="_blank" class="win-btn">Browse All Wiki Pages </a>
</div>
<p v-if="page.description" class="howto-card-desc">{{ page.description }}</p>
<div v-else class="howto-card-no-desc">No description provided.</div>
<div class="howto-card-footer">
<span class="howto-card-path">{{ page.path }}</span>
<span class="howto-card-read-link">Read <span class="text-lg">→</span></span>
</div>
</a>
</div>
<div v-if="!error && recentPages.length > 0" class="howtos-footer">
<a :href="WIKI_BASE" target="_blank" class="browse-wiki-btn">
Browse Wiki Home
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</a>
</div>
</main>
</div>
</div>
</template>
@@ -103,58 +90,110 @@ onMounted(async () => {
</script>
<style scoped>
.howtos-container {
@apply bg-gray-50 min-h-screen pb-20;
}
.howtos-header-decor {
@apply absolute inset-0 opacity-30;
}
.banner-base {
@apply max-w-7xl mx-auto border-l-4 p-6 rounded-r-xl shadow-lg mb-12 flex items-start gap-4;
}
.error-banner { @apply banner-base bg-red-50 border-red-500; }
.error-banner-title { @apply text-red-800 font-bold text-lg; }
.error-banner-desc { @apply text-red-700 mt-1; }
.empty-state {
@apply max-w-7xl mx-auto bg-white rounded-2xl shadow-xl p-12 text-center border border-gray-100;
text-align: center;
padding: 24px;
color: #404040;
font-size: 12px;
}
.empty-state-icon { @apply text-6xl mb-4; }
.empty-state-title { @apply text-2xl font-bold text-gray-900 mb-2; }
.empty-state-desc { @apply text-gray-500 max-w-md mx-auto; }
.howto-card-meta {
@apply flex items-center justify-end mb-4;
.howto-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 6px;
padding: 6px;
}
.howto-card-timestamp {
@apply flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-400;
@media (max-width: 768px) {
.howto-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
.howto-grid { grid-template-columns: 1fr; }
}
.howto-card {
display: flex;
flex-direction: column;
padding: 7px;
text-decoration: none;
color: inherit;
cursor: pointer;
}
.howto-card:hover {
background: #EEF0F8;
}
.howto-meta {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 3px;
}
.new-badge {
@apply flex items-center gap-1 text-green-600 bg-green-50 px-2 py-0.5 rounded-full border border-green-100;
font-size: 10px;
font-weight: bold;
background: #C8F0C8;
color: #007000;
border: 1px solid #009000;
padding: 0 3px;
}
.new-dot {
@apply w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse;
.howto-date {
font-size: 10px;
color: #808080;
font-family: "Courier New", Courier, monospace;
}
.howto-card-title {
@apply text-xl font-bold text-gray-900 group-hover:text-indigo-600 transition-colors mb-2 line-clamp-2 leading-tight;
.howto-title {
font-size: 12px;
font-weight: bold;
color: #000080;
margin-bottom: 3px;
line-height: 1.3;
}
.howto-card-desc {
@apply text-gray-600 text-sm mb-4 line-clamp-3 leading-relaxed flex-grow;
.howto-card:hover .howto-title { color: #0000FF; }
.howto-desc {
font-size: 11px;
color: #404040;
margin-bottom: 4px;
flex-grow: 1;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.howto-card-no-desc {
@apply flex-grow mb-4 italic text-gray-400 text-sm;
.howto-no-desc {
font-size: 11px;
color: #808080;
font-style: italic;
flex-grow: 1;
margin-bottom: 4px;
}
.howto-card-footer {
@apply pt-4 border-t border-gray-100 mt-auto flex items-center justify-between;
.howto-footer {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #D0D0D0;
padding-top: 4px;
margin-top: auto;
}
.howto-card-path {
@apply text-xs font-mono text-gray-400 truncate max-w-[180px];
.howto-path {
font-size: 10px;
color: #808080;
font-family: "Courier New", Courier, monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 120px;
}
.howto-card-read-link {
@apply text-indigo-600 text-sm font-bold flex items-center gap-1 group-hover:translate-x-1 transition-transform;
.howto-arrow {
font-size: 11px;
color: #000080;
font-weight: bold;
flex-shrink: 0;
}
.howtos-footer {
@apply mt-16 text-center;
}
.browse-wiki-btn {
@apply inline-flex items-center gap-2 bg-slate-900 text-white px-8 py-4 rounded-xl hover:bg-slate-800 transition-all font-bold shadow-lg hover:shadow-indigo-200/50;
.howto-more {
padding: 8px;
border-top: 1px solid #D0D0D0;
text-align: center;
}
</style>
+70 -30
View File
@@ -1,26 +1,36 @@
<template>
<header class="hero-section-gradient from-purple-600 to-blue-700 py-20 text-white">
<div class="hero-container text-center">
<h1 class="hero-title mb-6">Our Team</h1>
<p class="hero-subtitle mb-10 text-purple-100">
Meet the brilliant minds behind Teletype Games.
</p>
</div>
</header>
<main class="main-container py-16 px-4">
<div class="team-grid">
<div v-for="member in members" :key="member.nick" class="team-card">
<div class="team-card-image-container">
<img :src="resolvedAvatarUrl(member)" :alt="member.nick" class="team-card-image" />
<div class="main-container">
<div class="win-window">
<div class="win-titlebar">
<span class="win-titlebar-text">👥 Our Team</span>
<div class="win-titlebar-btns">
<button class="win-titlebar-btn">_</button>
<button class="win-titlebar-btn"></button>
<button class="win-titlebar-btn">×</button>
</div>
<div class="team-card-content">
<h2 class="team-card-title">{{ member.nick }}: {{ member.real_nick }}</h2>
<p class="team-card-desc">{{ member.motto }}</p>
</div>
<div class="win-body">
<p style="margin: 0 0 8px; font-size: 12px; color: #404040;">
Meet the people behind Teletype Games.
</p>
<div class="team-grid">
<div v-for="member in members" :key="member.nick" class="team-card win-raised-panel">
<div class="team-img-wrap">
<img :src="resolvedAvatarUrl(member)" :alt="member.nick" class="team-img" />
</div>
<div class="team-info">
<div class="team-nick">{{ member.nick }}</div>
<div class="team-realname">{{ member.real_nick }}</div>
<div class="win-separator"></div>
<div class="team-motto">{{ member.motto }}</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup lang="ts">
@@ -46,24 +56,54 @@ onMounted(async () => {
<style scoped>
.team-grid {
@apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8 max-w-7xl mx-auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
}
@media (max-width: 768px) {
.team-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 420px) {
.team-grid { grid-template-columns: 1fr; }
}
.team-card {
@apply bg-white rounded-2xl shadow-xl overflow-hidden border border-gray-100 transition-all hover:scale-105;
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
text-align: center;
}
.team-card-image-container {
@apply aspect-square overflow-hidden bg-gray-200;
.team-img-wrap {
width: 80px;
height: 80px;
overflow: hidden;
border: 2px solid;
border-color: #808080 #fff #fff #808080;
margin-bottom: 8px;
flex-shrink: 0;
}
.team-card-image {
@apply w-full h-full object-cover;
.team-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.team-card-content {
@apply p-6;
.team-info { width: 100%; }
.team-nick {
font-size: 13px;
font-weight: bold;
color: #000080;
margin-bottom: 2px;
}
.team-card-title {
@apply text-xl font-black text-gray-900 mb-2;
.team-realname {
font-size: 11px;
color: #404040;
margin-bottom: 4px;
}
.team-card-desc {
@apply text-gray-600 leading-relaxed;
.team-motto {
font-size: 11px;
color: #404040;
font-style: italic;
line-height: 1.4;
}
</style>
+446 -55
View File
@@ -2,98 +2,489 @@
@tailwind components;
@tailwind utilities;
html,
body {
margin: 0;
width: 100%;
height: 100%;
/* ===== WIN98/2000 DESIGN SYSTEM ===== */
:root {
--w-bg: #D4D0C8;
--w-face: #D4D0C8;
--w-text: #000000;
--w-gray: #808080;
--w-dark: #404040;
--w-ink: #0a0a0a;
--w-white: #FFFFFF;
--w-titlebar-start: #000080;
--w-titlebar-end: #1084D0;
--w-select: #000080;
--w-select-text: #FFFFFF;
--w-window: #FFFFFF;
--w-font: Tahoma, "MS Sans Serif", Arial, sans-serif;
}
html, body {
margin: 0;
width: 100%;
min-height: 100%;
font-family: var(--w-font);
font-size: 13px;
background: var(--w-bg);
color: var(--w-text);
}
* { box-sizing: border-box; }
@layer components {
/* Hero Section */
.hero-section {
@apply relative overflow-hidden py-24 text-white text-center;
/* ---- BUTTONS ---- */
.win-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 3px 12px 4px;
min-height: 23px;
min-width: 75px;
background: var(--w-face);
color: var(--w-text);
font-family: var(--w-font);
font-size: 12px;
cursor: pointer;
border: 1px solid #0a0a0a;
border-radius: 0;
text-decoration: none;
white-space: nowrap;
box-shadow:
inset 1px 1px 0 0 #fff,
inset -1px -1px 0 0 #404040,
inset 2px 2px 0 0 #D4D0C8,
inset -2px -2px 0 0 #808080;
user-select: none;
}
.hero-section-gradient {
@apply hero-section bg-gradient-to-r;
.win-btn:active,
.win-btn.is-active {
box-shadow:
inset 1px 1px 0 0 #404040,
inset -1px -1px 0 0 #fff,
inset 2px 2px 0 0 #808080,
inset -2px -2px 0 0 #D4D0C8;
padding: 4px 11px 3px 13px;
}
.hero-section-slate {
@apply hero-section bg-slate-900;
.win-btn-sm {
@apply win-btn;
min-height: 20px;
min-width: 50px;
padding: 2px 8px 3px;
font-size: 11px;
}
.hero-container {
@apply relative max-w-5xl mx-auto px-4 z-10;
.win-btn-default-action {
@apply win-btn;
border: 2px solid #0a0a0a;
font-weight: bold;
}
.hero-badge {
@apply inline-block px-3 py-1 rounded-full bg-indigo-500/20 border border-indigo-400/30 text-indigo-300 text-sm font-medium mb-6 backdrop-blur-sm;
/* ---- WINDOWS ---- */
.win-window {
background: var(--w-face);
border: 1px solid #0a0a0a;
box-shadow:
inset 1px 1px 0 0 #fff,
inset -1px -1px 0 0 #808080;
margin-bottom: 8px;
}
.hero-title {
@apply text-5xl md:text-6xl font-extrabold tracking-tight mb-6;
.win-titlebar {
background: linear-gradient(to right, var(--w-titlebar-start), var(--w-titlebar-end));
color: #fff;
font-family: var(--w-font);
font-size: 12px;
font-weight: bold;
padding: 3px 2px 3px 6px;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
min-height: 20px;
line-height: 1.3;
}
.hero-subtitle {
@apply text-lg md:text-xl text-slate-300 max-w-2xl mx-auto leading-relaxed;
.win-titlebar-text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.4);
}
/* Buttons */
.btn-hero {
@apply inline-flex items-center gap-3 bg-white px-8 py-4 rounded-xl transition-all font-bold shadow-xl hover:scale-105 active:scale-95;
.win-titlebar-btns {
display: flex;
gap: 2px;
margin-left: 4px;
flex-shrink: 0;
}
.btn-hero-teal {
@apply btn-hero text-teal-700 hover:bg-teal-50;
.win-titlebar-btn {
width: 16px;
height: 14px;
background: var(--w-face);
color: var(--w-text);
font-size: 9px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
cursor: default;
border: 1px solid #0a0a0a;
border-radius: 0;
user-select: none;
box-shadow:
inset 1px 1px 0 0 #fff,
inset -1px -1px 0 0 #404040,
inset 2px 2px 0 0 #D4D0C8,
inset -2px -2px 0 0 #808080;
flex-shrink: 0;
line-height: 1;
}
.btn-hero-indigo {
@apply btn-hero text-indigo-900 hover:bg-indigo-50;
.win-body {
padding: 8px;
background: var(--w-face);
font-family: var(--w-font);
font-size: 12px;
}
.btn-primary {
@apply inline-flex items-center justify-center font-bold py-3 px-8 rounded-xl transition-all shadow-lg;
.win-content-area {
padding: 8px;
background: var(--w-window);
box-shadow:
inset 1px 1px 0 0 #808080,
inset -1px -1px 0 0 #fff;
}
/* Grid and Containers */
.win-inset {
background: var(--w-window);
box-shadow:
inset 1px 1px 0 0 #808080,
inset -1px -1px 0 0 #D4D0C8,
inset 2px 2px 0 0 #0a0a0a,
inset -2px -2px 0 0 #fff;
padding: 4px;
}
.win-raised-panel {
background: var(--w-face);
box-shadow:
inset 1px 1px 0 0 #fff,
inset -1px -1px 0 0 #808080;
padding: 6px;
}
/* ---- NAVIGATION ---- */
.win-menubar {
background: var(--w-face);
font-family: var(--w-font);
font-size: 12px;
border-bottom: 1px solid var(--w-gray);
box-shadow: 0 1px 0 #fff;
display: flex;
align-items: stretch;
min-height: 22px;
}
.win-menu-logo {
font-weight: bold;
font-size: 12px;
padding: 3px 10px;
color: var(--w-text);
border-right: 1px solid #808080;
box-shadow: inset -1px 0 0 #fff;
user-select: none;
white-space: nowrap;
display: flex;
align-items: center;
cursor: default;
}
.win-menu-items {
display: flex;
align-items: stretch;
}
.win-menu-item {
padding: 0 8px;
color: var(--w-text);
text-decoration: none;
display: inline-flex;
align-items: center;
white-space: nowrap;
cursor: default;
user-select: none;
font-size: 12px;
min-height: 100%;
}
.win-menu-item:hover,
.win-menu-item.router-link-exact-active {
background: var(--w-select);
color: var(--w-select-text);
}
/* ---- STATUS BAR ---- */
.win-statusbar {
background: var(--w-face);
border-top: 1px solid var(--w-gray);
box-shadow: inset 0 1px 0 #fff;
padding: 2px 4px;
font-size: 11px;
font-family: var(--w-font);
display: flex;
align-items: center;
gap: 2px;
}
.win-status-panel {
box-shadow:
inset 1px 1px 0 0 #808080,
inset -1px -1px 0 0 #fff;
padding: 1px 6px;
flex: 1;
font-size: 11px;
line-height: 1.4;
}
/* ---- LAYOUT ---- */
.main-container {
@apply max-w-7xl mx-auto px-4 -mt-12 relative z-10;
max-width: 960px;
margin: 0 auto;
padding: 8px;
position: relative;
z-index: auto;
margin-top: 0 !important;
}
/* ---- TOOLBAR ---- */
.win-toolbar {
background: var(--w-face);
padding: 3px 4px;
display: flex;
align-items: center;
gap: 4px;
border-bottom: 1px solid var(--w-gray);
box-shadow: 0 1px 0 #fff;
flex-wrap: wrap;
}
/* ---- TABLE ---- */
.win-table {
width: 100%;
border-collapse: collapse;
font-family: var(--w-font);
font-size: 12px;
}
.win-table th {
background: var(--w-face);
text-align: left;
padding: 3px 8px;
font-weight: bold;
border: 1px solid var(--w-gray);
white-space: nowrap;
cursor: default;
box-shadow:
inset 1px 1px 0 0 #fff,
inset -1px -1px 0 0 #808080;
}
.win-table td {
padding: 3px 8px;
border-bottom: 1px solid #D0D0D0;
vertical-align: top;
}
.win-table tr:hover td {
background: var(--w-select);
color: var(--w-select-text);
}
.win-table tr:hover td a {
color: #fff;
}
/* ---- INPUT ---- */
.win-input {
border: 1px solid #0a0a0a;
border-radius: 0;
background: #fff;
padding: 2px 4px;
font-family: var(--w-font);
font-size: 12px;
box-shadow:
inset 1px 1px 0 0 #808080,
inset -1px -1px 0 0 #D4D0C8,
inset 2px 2px 0 0 #0a0a0a,
inset -2px -2px 0 0 #fff;
}
.win-textarea {
@apply win-input;
resize: vertical;
padding: 4px;
min-height: 80px;
width: 100%;
}
/* ---- STATUS BADGES ---- */
.status-badge {
padding: 1px 5px;
font-size: 10px;
font-weight: bold;
text-transform: uppercase;
font-family: var(--w-font);
border: 1px solid;
border-radius: 0;
}
.status-released { background: #C8F0C8; color: #007000; border-color: #009000; }
.status-demo { background: #C8C8FF; color: #000080; border-color: #0000C0; }
.status-development { background: #FFFFC8; color: #808000; border-color: #B0A000; }
.status-archived { background: #E0E0E0; color: #404040; border-color: #808080; }
/* ---- SEPARATORS ---- */
.win-separator {
border: none;
border-top: 1px solid var(--w-gray);
border-bottom: 1px solid #fff;
margin: 4px 2px;
}
/* ---- NOTIFICATION BOXES ---- */
.win-notice-info {
background: #CCCCFF;
border-left: 3px solid #0000CC;
border: 1px solid #0000CC;
padding: 6px 8px;
font-size: 12px;
margin-bottom: 8px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.win-notice-warning {
background: #FFFFC8;
border: 1px solid #AAAA00;
padding: 6px 8px;
font-size: 12px;
margin-bottom: 8px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.win-notice-error {
background: #FFCCCC;
border: 1px solid #CC0000;
padding: 6px 8px;
font-size: 12px;
margin-bottom: 8px;
display: flex;
align-items: flex-start;
gap: 8px;
}
/* ---- CARD GRID ---- */
.card-grid {
@apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6;
@apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3;
gap: 6px;
}
/* Cards */
.card-base {
@apply bg-white rounded-2xl border border-gray-200 p-6 shadow-sm;
@apply win-window;
padding: 8px;
}
.card-interactive {
@apply bg-white rounded-2xl border border-gray-200 p-6 shadow-sm hover:shadow-xl transition-all duration-300 ease-out hover:-translate-y-1;
@apply win-window;
padding: 8px;
cursor: default;
}
/* Decorative Background Elements */
.hero-decor-blob {
@apply absolute rounded-full blur-3xl opacity-30;
/* ---- LEGACY HERO OVERRIDES ---- */
.hero-section,
.hero-section-gradient,
.hero-section-slate {
display: none;
}
.hero-badge,
.hero-decor-blob,
.hero-container,
.home-header-decor,
.howtos-header-decor,
.blog-header-decor {
display: none;
}
.hero-title {
font-size: 15px;
font-weight: bold;
font-family: var(--w-font);
color: var(--w-text);
margin: 0;
text-transform: none;
}
.hero-subtitle {
font-size: 12px;
font-family: var(--w-font);
color: var(--w-dark);
margin: 4px 0 0;
}
/* ---- LEGACY BUTTON OVERRIDES ---- */
.btn-hero,
.btn-hero-teal,
.btn-hero-indigo,
.btn-primary {
@apply win-btn;
border-radius: 0 !important;
transform: none !important;
}
}
/* Wiki content styles (used with v-html) */
.wiki-content h1 { @apply text-2xl font-bold mt-8 mb-4 text-gray-900; }
.wiki-content h2 { @apply text-xl font-bold mt-6 mb-3 text-gray-800; }
.wiki-content h3 { @apply text-lg font-bold mt-4 mb-2 text-gray-800; }
.wiki-content p { @apply mb-4 text-gray-700 leading-relaxed; }
.wiki-content ul { @apply list-disc ml-6 mb-4 text-gray-700; }
.wiki-content ol { @apply list-decimal ml-6 mb-4 text-gray-700; }
.wiki-content li { @apply mb-1; }
.wiki-content a { @apply text-indigo-600 hover:underline; }
.wiki-content img { @apply max-w-full h-auto rounded-lg my-6 shadow-md; }
.wiki-content pre { @apply bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto my-6 font-mono text-sm; }
.wiki-content code { @apply bg-gray-100 text-pink-600 px-1 rounded font-mono text-sm; }
.wiki-content blockquote { @apply border-l-4 border-gray-300 pl-4 italic my-6 text-gray-600; }
.wiki-content table { @apply w-full border-collapse my-6; }
.wiki-content th,
.wiki-content td { @apply border border-gray-200 p-3 text-left; }
.wiki-content th { @apply bg-gray-50 font-bold; }
/* ---- WIKI CONTENT ---- */
.wiki-content {
font-family: var(--w-font);
font-size: 13px;
color: var(--w-text);
line-height: 1.6;
}
.wiki-content h1 {
font-size: 17px; font-weight: bold; margin: 12px 0 6px; color: #000080;
border-bottom: 1px solid var(--w-gray); padding-bottom: 4px;
}
.wiki-content h2 { font-size: 15px; font-weight: bold; margin: 10px 0 5px; color: #000060; }
.wiki-content h3 { font-size: 13px; font-weight: bold; margin: 8px 0 4px; }
.wiki-content p { margin-bottom: 8px; }
.wiki-content ul { list-style: disc; margin: 4px 0 8px 20px; }
.wiki-content ol { list-style: decimal; margin: 4px 0 8px 20px; }
.wiki-content li { margin-bottom: 2px; }
.wiki-content a { color: #0000FF; text-decoration: underline; }
.wiki-content a:visited { color: #800080; }
.wiki-content img { max-width: 100%; height: auto; border: 1px solid var(--w-gray); margin: 6px 0; }
.wiki-content pre {
background: #000080; color: #C0C0C0; padding: 8px; overflow-x: auto; margin: 8px 0;
font-family: "Courier New", Courier, monospace; font-size: 11px;
}
.wiki-content code {
background: var(--w-window); color: #800000; padding: 1px 4px;
font-family: "Courier New", Courier, monospace; font-size: 11px; border: 1px solid var(--w-gray);
}
.wiki-content blockquote {
border-left: 3px solid var(--w-gray); margin: 6px 0; padding-left: 10px;
font-style: italic; color: #404040;
}
.wiki-content table { width: 100%; border-collapse: collapse; margin: 8px 0; }
.wiki-content th { background: #000080; color: #fff; padding: 3px 8px; text-align: left; font-weight: bold; font-size: 11px; }
.wiki-content td { padding: 3px 8px; border: 1px solid var(--w-gray); font-size: 12px; }
.wiki-content tr:nth-child(even) td { background: #F0F0F0; }