refactor code

This commit is contained in:
kankys committed 2026-05-15 23:22:58 +02:00
1 parent 9a38077576
commit b13422d4af
13 files changed
+11934

No files matched your search

+684
View File
@@ -0,0 +1,684 @@
/* ============================================================
ApolloNvim – animations.css
Web: apollo-nvim.lukan.cz
All keyframes, scroll animations, stagger delays,
micro-interactions and loading screen transitions.
============================================================ */
/* ────────────────────────────────────────────────────────────
1. CORE KEYFRAMES
──────────────────────────────────────────────────────────── */
/* 1.1 fadeIn ─ simple opacity */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 1.2 slideUp ─ rise + fade */
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 1.3 slideInLeft ─ enter from left */
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-60px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* 1.4 slideInRight ─ enter from right */
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(60px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* 1.5 float ─ gentle levitation for hero images / icons */
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
33% {
transform: translateY(-10px);
}
66% {
transform: translateY(-6px);
}
}
/* 1.6 pulse-glow ─ pulsing purple box-shadow */
@keyframes pulse-glow {
0%, 100% {
box-shadow:
0 0 0 0 rgba(189, 147, 249, 0),
0 0 20px 2px rgba(189, 147, 249, 0.15);
}
50% {
box-shadow:
0 0 0 4px rgba(189, 147, 249, 0.08),
0 0 40px 8px rgba(189, 147, 249, 0.35);
}
}
/* 1.7 typing ─ blinking cursor caret */
@keyframes typing {
0%, 100% { border-right-color: rgba(189, 147, 249, 1); }
50% { border-right-color: transparent; }
}
/* Typewriter text reveal (pair with .typewriter class) */
@keyframes typewriter {
from { width: 0; }
to { width: 100%; }
}
/* 1.8 spin-slow ─ slow full rotation (used by loading logo) */
@keyframes spin-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 1.9 glitch ─ subtle horizontal glitch for headings */
@keyframes glitch {
0% { clip-path: inset(0 0 100% 0); transform: translate(0); }
10% { clip-path: inset(10% 0 60% 0); transform: translate(-3px, 1px); }
20% { clip-path: inset(60% 0 10% 0); transform: translate(3px, -1px); }
30% { clip-path: inset(30% 0 40% 0); transform: translate(-2px, 0); }
40% { clip-path: inset(0 0 80% 0); transform: translate(2px, 1px); }
50% { clip-path: inset(80% 0 0 0); transform: translate(-1px, -2px); }
60% { clip-path: inset(20% 0 50% 0); transform: translate(1px, 0); }
70% { clip-path: inset(50% 0 20% 0); transform: translate(-2px, 1px); }
80% { clip-path: inset(5% 0 70% 0); transform: translate(2px, -1px); }
90% { clip-path: inset(70% 0 5% 0); transform: translate(0, 2px); }
100% { clip-path: inset(0 0 0 0); transform: translate(0); }
}
/* 1.10 rocket-launch ─ rocket rises with shrinking trail */
@keyframes rocket-launch {
0% {
transform: translateY(0) scale(1);
opacity: 1;
filter: drop-shadow(0 8px 16px rgba(189, 147, 249, 0.6));
}
30% {
transform: translateY(-20px) scale(1.05);
filter: drop-shadow(0 16px 32px rgba(255, 121, 198, 0.7));
}
70% {
transform: translateY(-80px) scale(0.9);
opacity: 0.85;
filter: drop-shadow(0 4px 8px rgba(139, 233, 253, 0.5));
}
100% {
transform: translateY(-200px) scale(0.4);
opacity: 0;
filter: drop-shadow(0 0 0 transparent);
}
}
/* Rocket trail (::after pseudo) */
@keyframes rocket-trail {
0% { opacity: 0.8; transform: scaleY(1) scaleX(1); }
50% { opacity: 0.5; transform: scaleY(1.4) scaleX(0.7); }
100% { opacity: 0; transform: scaleY(2) scaleX(0.3); }
}
/* 1.11 star-twinkle ─ random opacity blink for star dots */
@keyframes star-twinkle {
0%, 100% { opacity: 0.15; transform: scale(0.85); }
25% { opacity: 0.9; transform: scale(1.1); }
50% { opacity: 0.4; transform: scale(0.95); }
75% { opacity: 1; transform: scale(1.05); }
}
/* 1.12 gradient-shift ─ animated gradient background */
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* ────────────────────────────────────────────────────────────
2. ANIMATION UTILITY CLASSES
──────────────────────────────────────────────────────────── */
/* Basic named animations */
.anim-fade-in { animation: fadeIn 0.5s ease both; }
.anim-slide-up { animation: slideUp 0.55s ease both; }
.anim-slide-left { animation: slideInLeft 0.55s ease both; }
.anim-slide-right { animation: slideInRight 0.55s ease both; }
.anim-float { animation: float 4s ease-in-out infinite; }
.anim-pulse-glow { animation: pulse-glow 2.5s ease-in-out infinite; }
.anim-spin-slow { animation: spin-slow 20s linear infinite; }
.anim-star-twinkle { animation: star-twinkle 3s ease-in-out infinite; }
/* Gradient-shift background (set background-size: 200% 200% on element) */
.anim-gradient-bg {
background-size: 200% 200%;
animation: gradient-shift 6s ease infinite;
}
/* ────────────────────────────────────────────────────────────
3. TYPEWRITER EFFECT
──────────────────────────────────────────────────────────── */
.typewriter {
display: inline-block;
overflow: hidden;
white-space: nowrap;
border-right: 2px solid;
/* combine reveal + cursor blink */
animation:
typewriter 2s steps(30, end) both,
typing 0.7s step-end infinite;
}
/* Standalone cursor only (no text reveal) */
.cursor-blink {
display: inline-block;
width: 2px;
height: 1.1em;
background: #bd93f9;
vertical-align: text-bottom;
animation: typing 0.7s step-end infinite;
}
/* ────────────────────────────────────────────────────────────
4. GLITCH EFFECT
──────────────────────────────────────────────────────────── */
.glitch-text {
position: relative;
display: inline-block;
}
/* Glitch layers via pseudo-elements */
.glitch-text::before,
.glitch-text::after {
content: attr(data-text);
position: absolute;
inset: 0;
pointer-events: none;
}
.glitch-text::before {
color: #ff79c6;
animation: glitch 3.5s infinite linear alternate-reverse;
animation-delay: 0.1s;
opacity: 0.7;
}
.glitch-text::after {
color: #8be9fd;
animation: glitch 2.8s infinite linear alternate;
animation-delay: 0.25s;
opacity: 0.6;
}
/* On hover only */
.glitch-text--hover::before,
.glitch-text--hover::after {
animation-play-state: paused;
}
.glitch-text--hover:hover::before,
.glitch-text--hover:hover::after {
animation-play-state: running;
}
/* ────────────────────────────────────────────────────────────
5. ROCKET LAUNCH
──────────────────────────────────────────────────────────── */
.rocket {
display: inline-block;
position: relative;
transition: transform 0.3s ease;
}
.rocket--launch {
animation: rocket-launch 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
/* Trail element */
.rocket__trail {
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 6px;
height: 20px;
border-radius: 0 0 4px 4px;
background: linear-gradient(180deg, rgba(255, 121, 198, 0.8), transparent);
transform-origin: top center;
animation: rocket-trail 1.2s ease forwards;
opacity: 0;
}
.rocket--launch .rocket__trail {
opacity: 0.8;
}
/* ────────────────────────────────────────────────────────────
6. STAR FIELD
──────────────────────────────────────────────────────────── */
.star {
position: absolute;
width: 3px;
height: 3px;
background: #f8f8f2;
border-radius: 50%;
animation: star-twinkle var(--duration, 3s) ease-in-out infinite;
animation-delay: var(--delay, 0s);
}
.star--sm { width: 1.5px; height: 1.5px; }
.star--md { width: 2.5px; height: 2.5px; }
.star--lg { width: 4px; height: 4px; box-shadow: 0 0 4px 1px rgba(248, 248, 242, 0.4); }
/* ────────────────────────────────────────────────────────────
7. SCROLL-TRIGGERED ANIMATIONS
──────────────────────────────────────────────────────────── */
/* Base hidden state – applied by JS IntersectionObserver */
.animate-on-scroll {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
/* Revealed state – JS adds .visible class */
.animate-on-scroll.visible {
opacity: 1;
transform: translateY(0);
}
/* Directional variants */
.animate-on-scroll--left {
opacity: 0;
transform: translateX(-40px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.animate-on-scroll--left.visible {
opacity: 1;
transform: translateX(0);
}
.animate-on-scroll--right {
opacity: 0;
transform: translateX(40px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.animate-on-scroll--right.visible {
opacity: 1;
transform: translateX(0);
}
.animate-on-scroll--scale {
opacity: 0;
transform: scale(0.9);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.animate-on-scroll--scale.visible {
opacity: 1;
transform: scale(1);
}
/* ────────────────────────────────────────────────────────────
8. STAGGER DELAYS
──────────────────────────────────────────────────────────── */
.delay-1 { animation-delay: 100ms; transition-delay: 100ms; }
.delay-2 { animation-delay: 200ms; transition-delay: 200ms; }
.delay-3 { animation-delay: 300ms; transition-delay: 300ms; }
.delay-4 { animation-delay: 400ms; transition-delay: 400ms; }
.delay-5 { animation-delay: 500ms; transition-delay: 500ms; }
/* Extended stagger for large grids */
.delay-6 { animation-delay: 600ms; transition-delay: 600ms; }
.delay-7 { animation-delay: 700ms; transition-delay: 700ms; }
.delay-8 { animation-delay: 800ms; transition-delay: 800ms; }
/* ────────────────────────────────────────────────────────────
9. HOVER MICRO-ANIMATIONS
──────────────────────────────────────────────────────────── */
/* ── Buttons ── */
/* Primary button shine sweep */
.btn-primary {
position: relative;
overflow: hidden;
}
.btn-primary::after {
content: '';
position: absolute;
top: 0;
left: -75%;
width: 50%;
height: 100%;
background: linear-gradient(
120deg,
transparent 20%,
rgba(255, 255, 255, 0.22) 50%,
transparent 80%
);
transform: skewX(-20deg);
transition: left 0.55s ease;
pointer-events: none;
}
.btn-primary:hover::after {
left: 135%;
}
/* Secondary button border glow pulse */
.btn-secondary {
position: relative;
}
.btn-secondary::before {
content: '';
position: absolute;
inset: -1px;
border-radius: inherit;
background: linear-gradient(135deg, transparent, rgba(189, 147, 249, 0.3), transparent);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: -1;
}
.btn-secondary:hover::before {
opacity: 1;
}
/* ── Links ── */
.link-underline {
position: relative;
text-decoration: none;
display: inline-block;
}
.link-underline::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 1px;
background: linear-gradient(90deg, #bd93f9, #ff79c6);
transform: scaleX(0);
transform-origin: right;
transition: transform 0.35s ease;
}
.link-underline:hover::after {
transform: scaleX(1);
transform-origin: left;
}
/* ── Card tilt (subtle) ── */
.card-tilt {
transition: transform 0.3s ease, box-shadow 0.3s ease;
will-change: transform;
}
/* Managed by JS mousemove if desired – CSS fallback hover */
.card-tilt:hover {
transform: perspective(800px) rotateX(2deg) rotateY(-2deg) translateY(-4px);
}
/* ── Icon bounce ── */
.icon-bounce {
display: inline-block;
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.icon-bounce:hover {
transform: scale(1.25) translateY(-2px);
}
/* ── Icon spin on hover ── */
.icon-spin-hover {
transition: transform 0.4s ease;
display: inline-block;
}
.icon-spin-hover:hover {
transform: rotate(180deg);
}
/* ── Pill/badge pop ── */
.badge {
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 0.2s ease;
}
.badge:hover {
transform: scale(1.08) translateY(-1px);
box-shadow: 0 4px 12px rgba(189, 147, 249, 0.3);
}
/* ── Nav link underline (already in style.css, reinforced here) ── */
.navbar__link {
will-change: color;
}
/* ── Feature card shimmer border ── */
.feature-card {
background-clip: padding-box;
}
/* ────────────────────────────────────────────────────────────
10. ANIMATED GRADIENT HERO BACKGROUND
──────────────────────────────────────────────────────────── */
.hero-gradient-bg {
background: linear-gradient(
-45deg,
#0d0d1a,
#12122a,
#1a0a2e,
#0d1a2e
);
background-size: 400% 400%;
animation: gradient-shift 12s ease infinite;
}
/* ────────────────────────────────────────────────────────────
11. LOADING SCREEN – FADE OUT
──────────────────────────────────────────────────────────── */
@keyframes loading-fade-out {
0% { opacity: 1; transform: scale(1); }
80% { opacity: 1; transform: scale(1.02); }
100% { opacity: 0; transform: scale(0.98); }
}
/* Trigger by adding .is-hiding to .loading-screen via JS */
.loading-screen.is-hiding {
animation: loading-fade-out 0.55s ease forwards;
pointer-events: none;
}
/* Orbiting dots spinner (used inside loading screen) */
@keyframes orbit {
from { transform: rotate(0deg) translateX(28px) rotate(0deg); }
to { transform: rotate(360deg) translateX(28px) rotate(-360deg); }
}
.loader-orbit {
position: relative;
width: 60px;
height: 60px;
}
.loader-orbit__dot {
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--purple, #bd93f9);
margin: -4px 0 0 -4px;
animation: orbit 1.4s linear infinite;
}
.loader-orbit__dot:nth-child(2) {
background: var(--pink, #ff79c6);
animation-delay: -0.46s;
}
.loader-orbit__dot:nth-child(3) {
background: var(--cyan, #8be9fd);
animation-delay: -0.93s;
}
/* ────────────────────────────────────────────────────────────
12. REVEAL ANIMATIONS FOR PAGE SECTIONS
──────────────────────────────────────────────────────────── */
/* Used by JS: section fades in as a block */
.section-reveal {
opacity: 0;
transition: opacity 0.8s ease, transform 0.8s ease;
transform: translateY(24px);
}
.section-reveal.in-view {
opacity: 1;
transform: translateY(0);
}
/* Hero content staggered entrance */
.hero__content > * {
opacity: 0;
animation: slideUp 0.65s ease both;
}
.hero__content > *:nth-child(1) { animation-delay: 0.1s; }
.hero__content > *:nth-child(2) { animation-delay: 0.25s; }
.hero__content > *:nth-child(3) { animation-delay: 0.4s; }
.hero__content > *:nth-child(4) { animation-delay: 0.55s; }
.hero__content > *:nth-child(5) { animation-delay: 0.7s; }
/* ────────────────────────────────────────────────────────────
13. PROGRESS / SKILL BARS
──────────────────────────────────────────────────────────── */
@keyframes bar-fill {
from { width: 0%; }
}
.progress-bar {
height: 4px;
background: var(--border, rgba(189, 147, 249, 0.2));
border-radius: 4px;
overflow: hidden;
}
.progress-bar__fill {
height: 100%;
border-radius: 4px;
background: linear-gradient(90deg, var(--purple, #bd93f9), var(--pink, #ff79c6));
width: var(--progress, 0%);
animation: bar-fill 1.2s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: var(--delay, 0.2s);
}
/* ────────────────────────────────────────────────────────────
14. NOTIFICATION / TOAST ENTRANCE
──────────────────────────────────────────────────────────── */
@keyframes toast-in {
from {
opacity: 0;
transform: translateX(calc(100% + 24px));
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes toast-out {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(calc(100% + 24px));
}
}
.toast {
animation: toast-in 0.35s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.toast.closing {
animation: toast-out 0.3s ease forwards;
}
/* ────────────────────────────────────────────────────────────
15. REDUCED MOTION OVERRIDES
──────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
/* Disable all custom animations */
.anim-fade-in,
.anim-slide-up,
.anim-slide-left,
.anim-slide-right,
.anim-float,
.anim-pulse-glow,
.anim-spin-slow,
.anim-star-twinkle,
.anim-gradient-bg,
.typewriter,
.cursor-blink,
.glitch-text::before,
.glitch-text::after,
.hero__content > *,
.rocket--launch,
.rocket__trail,
.loader-orbit__dot,
.loading-screen__bar-fill,
.loading-screen__logo,
.section-reveal,
.progress-bar__fill,
.toast,
.toast.closing {
animation: none !important;
transition: none !important;
}
/* Scroll-triggered: instantly reveal instead */
.animate-on-scroll,
.animate-on-scroll--left,
.animate-on-scroll--right,
.animate-on-scroll--scale {
opacity: 1 !important;
transform: none !important;
transition: none !important;
}
/* Hero items also visible immediately */
.hero__content > * {
opacity: 1 !important;
}
}
+3026
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+822
View File
@@ -0,0 +1,822 @@
<!doctype html>
<html lang="cs">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>📰 Novinky | ApolloNvim</title>
<!-- Meta description & SEO -->
<meta
name="description"
content="Changelog a novinky ApolloNvim – sledujte vývoj distribuce, nové verze a plány do budoucna."
/>
<meta name="author" content="Lukáš Kaňka" />
<meta
name="keywords"
content="ApolloNvim, Neovim, changelog, novinky, aktualizace, verze, vývoj"
/>
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta
property="og:url"
content="https://apollo-nvim.lukan.cz/novinky.html"
/>
<meta property="og:title" content="📰 Novinky | ApolloNvim" />
<meta
property="og:description"
content="Changelog a novinky ApolloNvim – sledujte vývoj distribuce a nové verze."
/>
<meta property="og:image" content="../img/apollonvim.jpg" />
<meta property="og:locale" content="cs_CZ" />
<meta property="og:site_name" content="ApolloNvim" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="📰 Novinky | ApolloNvim" />
<meta
name="twitter:description"
content="Changelog a novinky ApolloNvim – sledujte vývoj distribuce."
/>
<meta name="twitter:image" content="../img/apollonvim.jpg" />
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<!-- Stylesheets -->
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/animations.css" />
<!-- Canonical -->
<link
rel="canonical"
href="https://apollo-nvim.lukan.cz/novinky.html"
/>
<style>
/* ── Timeline ── */
.timeline-wrap {
max-width: 760px;
margin: 0 auto;
}
.timeline {
position: relative;
padding-left: 2.5rem;
list-style: none;
margin: 0;
padding-top: 0.5rem;
}
.timeline::before {
content: "";
position: absolute;
left: 0.55rem;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(
to bottom,
var(--purple),
var(--cyan),
transparent
);
border-radius: 99px;
}
.timeline__item {
position: relative;
margin-bottom: 3rem;
}
.timeline__item::before {
content: "";
position: absolute;
left: -2.05rem;
top: 0.35rem;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--purple);
border: 3px solid var(--bg);
box-shadow: 0 0 0 2px var(--purple);
transition:
box-shadow var(--transition),
background var(--transition);
}
.timeline__item:hover::before {
background: var(--cyan);
box-shadow:
0 0 0 2px var(--cyan),
0 0 12px rgba(6, 182, 212, 0.4);
}
.timeline__item--current::before {
background: var(--green, #22c55e);
box-shadow:
0 0 0 2px var(--green, #22c55e),
0 0 14px rgba(34, 197, 94, 0.5);
animation: pulse-dot 2s ease-in-out infinite;
}
@keyframes pulse-dot {
0%,
100% {
box-shadow:
0 0 0 2px var(--green, #22c55e),
0 0 8px rgba(34, 197, 94, 0.4);
}
50% {
box-shadow:
0 0 0 2px var(--green, #22c55e),
0 0 20px rgba(34, 197, 94, 0.7);
}
}
.timeline__header {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.6rem;
margin-bottom: 0.85rem;
}
.timeline__version {
font-family: var(--font-mono);
font-size: 1.15rem;
font-weight: 700;
color: var(--text-primary);
}
.timeline__date {
font-size: 0.8rem;
color: var(--text-muted);
margin-left: auto;
}
.timeline__card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.5rem 1.75rem;
transition:
border-color var(--transition),
box-shadow var(--transition);
}
.timeline__item:hover .timeline__card {
border-color: var(--purple);
box-shadow: 0 4px 24px rgba(139, 92, 246, 0.1);
}
.timeline__card ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.55rem;
}
.timeline__card li {
display: flex;
align-items: flex-start;
gap: 0.6rem;
font-size: 0.9rem;
color: var(--text-secondary, var(--text-muted));
line-height: 1.5;
}
.timeline__card li::before {
content: "▸";
color: var(--purple);
flex-shrink: 0;
margin-top: 0.05rem;
}
/* ── Roadmap cards ── */
.roadmap-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1.25rem;
margin-top: 3rem;
}
.roadmap-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
transition:
transform var(--transition),
border-color var(--transition),
box-shadow var(--transition);
}
.roadmap-card:hover {
transform: translateY(-3px);
border-color: var(--cyan);
box-shadow: 0 6px 24px rgba(6, 182, 212, 0.12);
}
.roadmap-card__icon {
font-size: 1.75rem;
line-height: 1;
}
.roadmap-card__title {
font-size: 1rem;
font-weight: 700;
color: var(--text-primary);
margin: 0;
}
.roadmap-card__desc {
font-size: 0.85rem;
color: var(--text-muted);
line-height: 1.55;
margin: 0;
}
.roadmap-card__status {
margin-top: auto;
padding-top: 0.5rem;
}
/* ── CTA / Odber ── */
.news-cta {
text-align: center;
padding: 4rem 2rem;
background: linear-gradient(
135deg,
rgba(139, 92, 246, 0.08) 0%,
rgba(6, 182, 212, 0.05) 100%
);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
}
.news-cta__title {
font-size: clamp(1.25rem, 3vw, 1.75rem);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.75rem;
}
.news-cta__subtitle {
font-size: 1rem;
color: var(--text-muted);
margin-bottom: 2rem;
}
/* Animate on scroll */
.animate-on-scroll {
opacity: 0;
transform: translateY(24px);
transition:
opacity 0.55s ease,
transform 0.55s ease;
}
.animate-on-scroll.visible {
opacity: 1;
transform: translateY(0);
}
@media (max-width: 640px) {
.roadmap-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 420px) {
.roadmap-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- ===================== NAVBAR ===================== -->
<header class="navbar" id="navbar">
<nav class="navbar__inner" aria-label="Hlavní navigace">
<a
href="../index.html"
class="navbar__logo"
aria-label="ApolloNvim – Domů"
>🚀 <span>ApolloNvim</span></a
>
<ul class="navbar__links" id="nav-links" role="list">
<li><a href="../index.html">Domů</a></li>
<li><a href="./instalace.html">Instalace</a></li>
<li><a href="./pluginy.html">Pluginy</a></li>
<li><a href="./tutorialy.html">Tutoriály</a></li>
<li>
<a
href="./novinky.html"
class="active"
aria-current="page"
>Novinky</a
>
</li>
<li><a href="./zkratky.html">Zkratky</a></li>
</ul>
<button
id="hamburger"
class="navbar__hamburger"
aria-label="Otevřít menu"
aria-expanded="false"
aria-controls="mobile-menu"
>
<span></span><span></span><span></span>
</button>
</nav>
<div class="navbar__mobile" id="mobile-menu" hidden>
<ul role="list">
<li><a href="../index.html">Domů</a></li>
<li><a href="./instalace.html">Instalace</a></li>
<li><a href="./pluginy.html">Pluginy</a></li>
<li><a href="./tutorialy.html">Tutoriály</a></li>
<li><a href="./novinky.html" class="active">Novinky</a></li>
<li><a href="./zkratky.html">Zkratky</a></li>
</ul>
</div>
</header>
<main id="main-content">
<!-- ===================== HERO ===================== -->
<section
class="hero"
aria-labelledby="hero-title"
style="min-height: 42vh"
>
<canvas id="particles-canvas" aria-hidden="true"></canvas>
<div
class="hero__content"
style="padding-top: 6rem; padding-bottom: 4rem"
>
<div class="hero__badge">
<span>📰 Changelog & Aktualizace</span>
</div>
<h1 id="hero-title" class="hero__title">Novinky</h1>
<p class="hero__subtitle">Changelog a novinky ApolloNvim</p>
</div>
</section>
<!-- ===================== CHANGELOG TIMELINE ===================== -->
<section class="section" aria-labelledby="changelog-title">
<div class="container">
<header class="section-title section-title--center">
<h2 id="changelog-title">📋 Changelog</h2>
<p class="section__subtitle section__subtitle--center">
Historie verzí a provedených změn
</p>
<div class="section-title__line"></div>
</header>
<div class="timeline-wrap" style="margin-top: 3.5rem">
<ol class="timeline" aria-label="Changelog ApolloNvim">
<!-- v25.05 – Aktuální -->
<li
class="timeline__item timeline__item--current animate-on-scroll"
>
<div class="timeline__header">
<span class="timeline__version"
>v26.05</span
>
<span class="badge badge--green"
>Aktuální</span
>
<time
class="timeline__date"
datetime="2026-05"
>Květen 2026</time
>
</div>
<div class="timeline__card">
<ul>
<li>Nový redesign webu ApolloNvim</li>
<li>
Vícestrankový web s HTML/CSS/JS
strukturou
</li>
<li>
Temný vesmírný design inspirovaný
Dracula tématem
</li>
<li>
Stránky: Domů, Instalace, Pluginy,
Tutoriály, Novinky, Zkratky
</li>
<li>
Particle systém s animovanými
hvězdičkami
</li>
<li>
Kopírovatelné terminálové bloky,
responzivní design
</li>
</ul>
</div>
</li>
<!-- v25.04 -->
<li class="timeline__item animate-on-scroll">
<div class="timeline__header">
<span class="timeline__version"
>v25.04</span
>
<span class="badge badge--purple"
>Duben 2025</span
>
<time
class="timeline__date"
datetime="2025-04"
>Duben 2025</time
>
</div>
<div class="timeline__card">
<ul>
<li>
Verze 25.04 – aktuální stabilní
vydání
</li>
</ul>
</div>
</li>
<!-- v25.02 -->
<li class="timeline__item animate-on-scroll">
<div class="timeline__header">
<span class="timeline__version"
>v25.02</span
>
<span class="badge badge--purple"
>Únor 2025</span
>
<time
class="timeline__date"
datetime="2025-02"
>Únor 2025</time
>
</div>
<div class="timeline__card">
<ul>
<li>Nový web ApolloNvim</li>
<li>Lokalizace do angličtiny</li>
<li>
Vylepšená instalace pro jednodušší
onboarding
</li>
<li>Podpora pouze pro Linux a Mac</li>
</ul>
</div>
</li>
<!-- v25.01 -->
<li class="timeline__item animate-on-scroll">
<div class="timeline__header">
<span class="timeline__version"
>v25.01</span
>
<span class="badge badge--purple"
>Leden 2025</span
>
<time
class="timeline__date"
datetime="2025-01"
>Leden 2025</time
>
</div>
<div class="timeline__card">
<ul>
<li>
Nová instalace s přepracovaným
procesem nastavení
</li>
<li>
Nový website – moderní design a
lepší přehlednost
</li>
<li>
Nový obrázek ApolloNvim –
aktualizovaná vizuální identita
</li>
</ul>
</div>
</li>
<!-- v24.10 -->
<li class="timeline__item animate-on-scroll">
<div class="timeline__header">
<span class="timeline__version"
>v24.10</span
>
<span class="badge badge--purple"
>Říjen 2024</span
>
<time
class="timeline__date"
datetime="2024-10"
>Říjen 2024</time
>
</div>
<div class="timeline__card">
<ul>
<li>
Podpora PHP – LSP a snippety pro PHP
vývojáře
</li>
<li>
Automatická instalace pluginů po
přidání do <code>init.vim</code>
</li>
<li>
Rainbow Vim – plugin pro barevné
závorky pro lepší čitelnost kódu
</li>
<li>
Gitgutter – plugin pro správu a
zobrazení git změn v editoru
</li>
</ul>
</div>
</li>
</ol>
</div>
<!-- /.timeline-wrap -->
</div>
<!-- /.container -->
</section>
<!-- ===================== ROADMAP ===================== -->
<section
class="section section--alt"
aria-labelledby="roadmap-title"
>
<div class="container">
<header class="section-title section-title--center">
<h2 id="roadmap-title">🗺️ Plán vývoje</h2>
<p class="section__subtitle section__subtitle--center">
Co připravujeme pro budoucí verze ApolloNvim
</p>
<div class="section-title__line"></div>
</header>
<div class="roadmap-grid">
<div class="roadmap-card animate-on-scroll">
<div class="roadmap-card__icon" aria-hidden="true">
🎨
</div>
<h3 class="roadmap-card__title">
Nová uvítací obrazovka
</h3>
<p class="roadmap-card__desc">
Vlastní dashboard při startu – rychlé otevření
posledních souborů, zkratky a statistiky.
</p>
<div class="roadmap-card__status">
<span class="badge badge--yellow"
>Plánováno</span
>
</div>
</div>
<div class="roadmap-card animate-on-scroll">
<div class="roadmap-card__icon" aria-hidden="true">
🔌
</div>
<h3 class="roadmap-card__title">Nové pluginy</h3>
<p class="roadmap-card__desc">
Rozšiřování knihovny pluginů o nejlepší nástroje
z ekosystému Neovim komunity.
</p>
<div class="roadmap-card__status">
<span class="badge badge--yellow"
>Plánováno</span
>
</div>
</div>
<div class="roadmap-card animate-on-scroll">
<div class="roadmap-card__icon" aria-hidden="true">
📚
</div>
<h3 class="roadmap-card__title">
Návody k pluginům
</h3>
<p class="roadmap-card__desc">
Podrobné tutoriály pro každý zahrnutý plugin s
příklady a tipy pro efektivní použití.
</p>
<div class="roadmap-card__status">
<span class="badge badge--cyan">Probíhá</span>
</div>
</div>
<div class="roadmap-card animate-on-scroll">
<div class="roadmap-card__icon" aria-hidden="true">
⚡
</div>
<h3 class="roadmap-card__title">
Instalátor přes link z Git
</h3>
<p class="roadmap-card__desc">
Jednořádkový instalátor přímo z Codeberg – bez
klonování, bez komplikací.
</p>
<div class="roadmap-card__status">
<span class="badge badge--yellow"
>Plánováno</span
>
</div>
</div>
</div>
<!-- /.roadmap-grid -->
</div>
<!-- /.container -->
</section>
<!-- ===================== CTA / ODBER ===================== -->
<section class="section" aria-labelledby="news-cta-title">
<div class="container">
<div class="news-cta animate-on-scroll">
<p
style="font-size: 2.5rem; margin-bottom: 1rem"
aria-hidden="true"
>
✈️
</p>
<h2 id="news-cta-title" class="news-cta__title">
Sledujte novinky na Telegramu
</h2>
<p class="news-cta__subtitle">
Buďte první, kdo se dozví o nových verzích,
pluginech a tutoriálech ApolloNvim.
</p>
<a
href="https://t.me/apollonvim"
class="btn-primary"
target="_blank"
rel="noopener noreferrer"
aria-label="Připojit se na Telegram ApolloNvim (otevře se v novém okně)"
>
<span>✈️</span> Připojit se na Telegram
</a>
</div>
</div>
</section>
</main>
<!-- ===================== FOOTER ===================== -->
<footer class="footer" role="contentinfo">
<div class="container">
<div class="footer__grid">
<div class="footer__brand">
<a
href="../index.html"
class="footer__logo"
aria-label="ApolloNvim – Domů"
>
🚀 ApolloNvim
</a>
<p class="footer__tagline">
Rychlý, lehký a plně konfigurovatelný Neovim pro
Front-End vývojáře.
</p>
<p
class="footer__love"
aria-label="Vytvořeno s láskou a klávesnicí"
>
Made with ❤️ and ⌨️
</p>
</div>
<nav class="footer__nav" aria-label="Patička navigace">
<h4 class="footer__nav-title">Navigace</h4>
<ul class="footer__nav-list" role="list">
<li>
<a href="../index.html" class="footer__link"
>Domů</a
>
</li>
<li>
<a href="./instalace.html" class="footer__link"
>Instalace</a
>
</li>
<li>
<a href="./pluginy.html" class="footer__link"
>Pluginy</a
>
</li>
<li>
<a href="./tutorialy.html" class="footer__link"
>Tutoriály</a
>
</li>
<li>
<a href="./novinky.html" class="footer__link"
>Novinky</a
>
</li>
<li>
<a href="./zkratky.html" class="footer__link"
>Zkratky</a
>
</li>
</ul>
</nav>
<div class="footer__links">
<h4 class="footer__nav-title">Externe</h4>
<ul class="footer__nav-list" role="list">
<li>
<a
href="https://t.me/apollonvim"
class="footer__link"
target="_blank"
rel="noopener noreferrer"
aria-label="Telegram (otevře se v novém okně)"
>
✈️ Telegram
</a>
</li>
<li>
<a
href="https://codeberg.org/ApolloNvim/ApolloNvim2025"
class="footer__link"
target="_blank"
rel="noopener noreferrer"
aria-label="Codeberg repozitář (otevře se v novém okně)"
>
🏔️ Codeberg
</a>
</li>
<li>
<a
href="https://lukaskanka.cz/"
class="footer__link"
target="_blank"
rel="noopener noreferrer"
aria-label="Web autora (otevře se v novém okně)"
>
🧑‍💻 Web autora
</a>
</li>
</ul>
</div>
</div>
<div class="footer__bottom">
<p class="footer__copy">
Copyright &copy; 2024 &ndash; 2025 🚀 ApolloNvim / Lukáš
Kaňka
</p>
<p class="footer__version">
<span class="badge badge--version">v25.04</span>
</p>
</div>
</div>
</footer>
<!-- ===================== SCRIPTS ===================== -->
<script src="../js/particles.js"></script>
<script src="../js/main.js"></script>
<script>
// Animate on scroll observer
(function () {
const elements =
document.querySelectorAll(".animate-on-scroll");
if (!elements.length) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: "0px 0px -40px 0px" },
);
elements.forEach((el, i) => {
el.style.transitionDelay = i * 100 + "ms";
observer.observe(el);
});
})();
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

+442
View File
@@ -0,0 +1,442 @@
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>🚀 ApolloNvim | Neovim distribuce pro Front-End</title>
<!-- Meta description & SEO -->
<meta name="description" content="ApolloNvim je rychlá a lehká Neovim distribuce pro moderní Front-End vývoj. Postavená na Plug.vim, Coc a Lua. Dvě edice: Starter a FrontEnd." />
<meta name="author" content="Lukáš Kaňka" />
<meta name="keywords" content="ApolloNvim, Neovim, konfigurace, front-end, HTML, CSS, JavaScript, TypeScript, Lua, Coc, Plug.vim" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://apollo-nvim.lukan.cz/" />
<meta property="og:title" content="🚀 ApolloNvim | Neovim distribuce pro Front-End" />
<meta property="og:description" content="Rychlá, lehká a plně konfigurovatelná Neovim distribuce pro moderní Front-End vývoj. Postavená na Plug.vim, Coc a Lua." />
<meta property="og:image" content="img/apollonvim.jpg" />
<meta property="og:locale" content="cs_CZ" />
<meta property="og:site_name" content="ApolloNvim" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="🚀 ApolloNvim | Neovim distribuce pro Front-End" />
<meta name="twitter:description" content="Rychlá, lehká a plně konfigurovatelná Neovim distribuce pro moderní Front-End vývoj." />
<meta name="twitter:image" content="img/apollonvim.jpg" />
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<!-- Stylesheets -->
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/animations.css" />
<!-- Canonical -->
<link rel="canonical" href="https://apollo-nvim.lukan.cz/" />
<!-- Umami -->
<script defer src="https://analytics.lukan.cz/script.js" data-website-id="6bd59e17-6d55-483e-8e7a-8ec92fb4ede4"></script>
</head>
<body>
<!-- Loading Screen -->
<div id="loading-screen" class="loading-screen">
<div class="loading-screen__content">
<span class="loading-rocket">🚀</span>
<div class="loading-bar"><div class="loading-bar__fill"></div></div>
</div>
</div>
<!-- ===================== NAVBAR ===================== -->
<header class="navbar" id="navbar" role="banner">
<nav class="navbar__inner" aria-label="Hlavní navigace">
<a href="./index.html" class="navbar__logo" aria-label="ApolloNvim – Domů">
🚀 <span>ApolloNvim</span>
</a>
<ul class="navbar__links" id="nav-links" role="list">
<li><a href="./index.html" class="navbar__link active" aria-current="page">Domů</a></li>
<li><a href="html/instalace.html" class="navbar__link">Instalace</a></li>
<li><a href="html/pluginy.html" class="navbar__link">Pluginy</a></li>
<li><a href="html/tutorialy.html" class="navbar__link">Tutoriály</a></li>
<li><a href="html/novinky.html" class="navbar__link">Novinky</a></li>
<li><a href="html/zkratky.html" class="navbar__link">Zkratky</a></li>
</ul>
<button
id="hamburger"
class="navbar__hamburger"
aria-label="Otevřít menu"
aria-expanded="false"
aria-controls="mobile-menu"
>
<span></span><span></span><span></span>
</button>
</nav>
<div class="navbar__mobile" id="mobile-menu" hidden>
<ul role="list">
<li><a href="./index.html" class="active">Domů</a></li>
<li><a href="html/instalace.html">Instalace</a></li>
<li><a href="html/pluginy.html">Pluginy</a></li>
<li><a href="html/tutorialy.html">Tutoriály</a></li>
<li><a href="html/novinky.html">Novinky</a></li>
<li><a href="html/zkratky.html">Zkratky</a></li>
</ul>
</div>
</header>
<main id="main-content">
<!-- ===================== HERO ===================== -->
<section id="hero" class="hero" aria-labelledby="hero-title">
<!-- Particles canvas -->
<canvas id="particles-canvas" aria-hidden="true"></canvas>
<div class="hero__content">
<div class="hero__badge animate-on-scroll">
<span class="badge badge--purple">Verze 26.05</span>
</div>
<h1 id="hero-title" class="hero__title animate-on-scroll delay-1">
<span class="gradient-text">🚀 ApolloNvim</span>
</h1>
<p class="hero__subtitle animate-on-scroll delay-2" aria-live="polite">
<span class="typing-text" data-typewriter="Neovim distribuce pro moderní Front-End vývoj"></span>
</p>
<p class="hero__description animate-on-scroll delay-3">
Rychlý, lehký a plně konfigurovatelný.<br />
Postavený na <strong>Plug.vim</strong>, <strong>Coc</strong> a <strong>Lua</strong>.
</p>
<div class="hero__cta animate-on-scroll delay-4" role="group" aria-label="Hlavní akce">
<a href="html/instalace.html" class="btn-primary">
<span aria-hidden="true">🛠️</span> Začít instalaci
</a>
<a href="html/pluginy.html" class="btn-secondary">
<span aria-hidden="true">🔌</span> Prohlédnout pluginy
</a>
</div>
</div>
<!-- Scroll down indicator -->
<div class="hero__scroll" aria-hidden="true">
<span>Scroll</span>
<div class="hero__scroll-arrow"></div>
</div>
</section>
<!-- ===================== FEATURES ===================== -->
<section id="features" class="section section--alt" aria-labelledby="features-title">
<div class="container">
<header class="section-header">
<h2 id="features-title" class="section-title animate-on-scroll">
Proč ApolloNvim?
<span class="section-title__line"></span>
</h2>
<p class="section__subtitle animate-on-scroll">
Vše co potřebuješ pro moderní vývoj – přehledně na jednom místě.
</p>
</header>
<div class="feature-grid">
<article class="feature-card animate-on-scroll" role="listitem">
<div class="feature-icon" aria-hidden="true">⚡</div>
<h3 class="feature-card__title">Rychlost &amp; Lehkost</h3>
<p class="feature-card__body">Minimální nároky na systém, maximální výkon. Spustí se během okamžiku.</p>
</article>
<article class="feature-card animate-on-scroll delay-1" role="listitem">
<div class="feature-icon" aria-hidden="true">🎨</div>
<h3 class="feature-card__title">Krásný design</h3>
<p class="feature-card__body">19+ barevných témat. Výchozí Dracula téma pro příjemné prostředí při každé práci.</p>
</article>
<article class="feature-card animate-on-scroll delay-2" role="listitem">
<div class="feature-icon" aria-hidden="true">🤖</div>
<h3 class="feature-card__title">AI asistence</h3>
<p class="feature-card__body">Codeium – zdarma alternativa ke GitHub Copilotu. Také TabNine pro inteligentní doplňování.</p>
</article>
<article class="feature-card animate-on-scroll delay-3" role="listitem">
<div class="feature-icon" aria-hidden="true">🔌</div>
<h3 class="feature-card__title">Bohatá sada pluginů</h3>
<p class="feature-card__body">AutoSave, NERDTree, TreeSitter, Colorizer a mnoho dalšího. Vše přednastaveno a připraveno.</p>
</article>
<article class="feature-card animate-on-scroll" role="listitem">
<div class="feature-icon" aria-hidden="true">🌐</div>
<h3 class="feature-card__title">Front-End zaměření</h3>
<p class="feature-card__body">Plná podpora HTML, CSS, JS, TypeScript, PHP, TailwindCSS. Editor navržený pro webové vývojáře.</p>
</article>
<article class="feature-card animate-on-scroll delay-1" role="listitem">
<div class="feature-icon" aria-hidden="true">📖</div>
<h3 class="feature-card__title">Skvělá dokumentace</h3>
<p class="feature-card__body">Podrobné návody v češtině i angličtině. Začneš produktivně pracovat od prvního dne.</p>
</article>
</div>
</div>
</section>
<!-- ===================== EDITIONS ===================== -->
<section id="editions" class="section" aria-labelledby="editions-title">
<div class="container">
<header class="section-header">
<h2 id="editions-title" class="section-title animate-on-scroll">
Dvě edice pro každého
<span class="section-title__line"></span>
</h2>
<p class="section__subtitle animate-on-scroll">
Vyber si edici, která odpovídá tvým potřebám.
</p>
</header>
<div class="editions-grid">
<!-- Starter Edition -->
<article class="edition-card card animate-on-scroll" aria-labelledby="starter-title">
<div class="edition-card__badge">
<span class="badge badge--green">Pro začátečníky</span>
</div>
<h3 id="starter-title" class="edition-card__title">🚀 Starter Edition</h3>
<p class="edition-card__desc">
Zaměřená na práci s texty a Markdown. Ideální pro každého, kdo chce začít s Neovimem bez zbytečné složitosti.
</p>
<ul class="edition-card__features" role="list">
<li>✅ Podpora Markdown a MDX</li>
<li>✅ Zvýraznění syntaxe (TreeSitter)</li>
<li>✅ 19+ barevných témat vč. Dracula</li>
<li>✅ AutoSave – nikdy neztrácíš práci</li>
<li>✅ Minimalistické, rychlé prostředí</li>
<li>✅ NERDTree pro správu souborů</li>
</ul>
<a href="html/instalace.html" class="btn-secondary edition-card__btn" role="button">
🚀 Instalovat Starter
</a>
</article>
<!-- FrontEnd Edition -->
<article class="edition-card edition-card--highlighted card animate-on-scroll delay-1" aria-labelledby="frontend-title">
<div class="edition-card__badge">
<span class="badge badge--purple">⭐ Doporučeno</span>
</div>
<h3 id="frontend-title" class="edition-card__title">🌐 FrontEnd Edition</h3>
<p class="edition-card__desc">
Pro vývoj webů a webových aplikací. Inspirováno VSCode a WebStorm. Kompletní prostředí pro Front-End vývojáře.
</p>
<ul class="edition-card__features" role="list">
<li>✅ Vše ze Starter Edition</li>
<li>✅ LSP server přes Coc.nvim</li>
<li>✅ HTML, CSS, JS, TS, PHP, TailwindCSS</li>
<li>✅ AI: Codeium &amp; TabNine (zdarma)</li>
<li>✅ Emmet, Autopairs, Colorizer</li>
<li>✅ Git integrace (fugitive + gitsigns)</li>
</ul>
<a href="html/instalace.html" class="btn-primary edition-card__btn" role="button">
🛠️ Instalovat FrontEnd
</a>
</article>
</div>
</div>
</section>
<!-- ===================== STATS ===================== -->
<section class="section section--alt stats-section" aria-label="Statistiky projektu">
<div class="container">
<div class="stats-grid">
<div class="stat-item animate-on-scroll" aria-label="19 a více barevných témat">
<span class="stat-item__number" data-count="19" data-count-suffix="+">0+</span>
<span class="stat-item__label">Barevných témat</span>
</div>
<div class="stat-item animate-on-scroll delay-1" aria-label="20 a více pluginů">
<span class="stat-item__number" data-count="20" data-count-suffix="+">0+</span>
<span class="stat-item__label">Pluginů</span>
</div>
<div class="stat-item animate-on-scroll delay-2" aria-label="20 a více tutoriálů">
<span class="stat-item__number" data-count="20" data-count-suffix="+">0+</span>
<span class="stat-item__label">Tutoriálů</span>
</div>
</div>
</div>
</section>
<!-- ===================== TERMINAL PREVIEW ===================== -->
<section id="install-preview" class="section" aria-labelledby="terminal-title">
<div class="container">
<header class="section-header">
<h2 id="terminal-title" class="section-title animate-on-scroll">
Jednoduchá instalace
<span class="section-title__line"></span>
</h2>
<p class="section__subtitle animate-on-scroll">
Nastav si ApolloNvim ve třech krocích. Více detailů na stránce&nbsp;
<a href="html/instalace.html">Instalace</a>.
</p>
</header>
<div class="animate-on-scroll">
<div class="terminal-block" style="margin-bottom:16px;">
<div class="terminal-block__header">
<span class="terminal-block__dot terminal-block__dot--red"></span>
<span class="terminal-block__dot terminal-block__dot--yellow"></span>
<span class="terminal-block__dot terminal-block__dot--green"></span>
<span class="terminal-block__title">Krok 1 – Klonování repozitáře</span>
<button class="copy-btn" data-clipboard-text="git clone -b frontend https://codeberg.org/ApolloNvim/ApolloNvim2025.git ~/.config/nvim">Kopírovat</button>
</div>
<pre><code>git clone -b frontend https://codeberg.org/ApolloNvim/ApolloNvim2025.git ~/.config/nvim</code></pre>
</div>
<div class="terminal-block" style="margin-bottom:16px;">
<div class="terminal-block__header">
<span class="terminal-block__dot terminal-block__dot--red"></span>
<span class="terminal-block__dot terminal-block__dot--yellow"></span>
<span class="terminal-block__dot terminal-block__dot--green"></span>
<span class="terminal-block__title">Krok 2 – Instalace pluginů (uvnitř Neovimu)</span>
<button class="copy-btn" data-clipboard-text=":PlugInstall">Kopírovat</button>
</div>
<pre><code><span class="token-command">:PlugInstall</span></code></pre>
</div>
<div class="terminal-block">
<div class="terminal-block__header">
<span class="terminal-block__dot terminal-block__dot--red"></span>
<span class="terminal-block__dot terminal-block__dot--yellow"></span>
<span class="terminal-block__dot terminal-block__dot--green"></span>
<span class="terminal-block__title">Krok 3 – Instalace LSP serverů (FrontEnd Edition)</span>
<button class="copy-btn" data-clipboard-text=":CocInstall coc-html coc-css coc-tsserver coc-phpls coc-emmet coc-prettier coc-eslint">Kopírovat</button>
</div>
<pre><code><span class="token-command">:CocInstall</span> coc-html coc-css coc-tsserver coc-phpls coc-emmet coc-prettier coc-eslint</code></pre>
</div>
</div>
<div class="animate-on-scroll" style="margin-top:2rem;text-align:center;">
<a href="html/instalace.html" class="btn-primary">
📖 Kompletní průvodce instalací
</a>
</div>
</div>
</section>
<!-- ===================== COMMUNITY / LINKS ===================== -->
<section id="community" class="section section--alt" aria-labelledby="community-title">
<div class="container">
<header class="section-header">
<h2 id="community-title" class="section-title animate-on-scroll">
Komunita &amp; Zdroje
<span class="section-title__line"></span>
</h2>
<p class="section__subtitle animate-on-scroll">
Připoj se ke komunitě nebo si prohlédni zdrojový kód.
</p>
</header>
<div class="community-grid">
<a href="https://t.me/apollonvim" class="community-card card animate-on-scroll"
target="_blank" rel="noopener noreferrer"
aria-label="Telegram komunita ApolloNvim (otevře se v novém okně)">
<div class="community-card__icon" aria-hidden="true">✈️</div>
<h3 class="community-card__title">Telegram</h3>
<p class="community-card__desc">Připoj se do Telegram skupiny. Sdílej tipy, ptej se a buď součástí komunity.</p>
<span class="community-card__link">t.me/apollonvim →</span>
</a>
<a href="https://codeberg.org/ApolloNvim/ApolloNvim2025" class="community-card card animate-on-scroll delay-1"
target="_blank" rel="noopener noreferrer"
aria-label="Codeberg repozitář ApolloNvim2025 (otevře se v novém okně)">
<div class="community-card__icon" aria-hidden="true">🏔️</div>
<h3 class="community-card__title">Codeberg</h3>
<p class="community-card__desc">Zdrojový kód, issues a pull requesty. Otevřený a svobodný vývoj.</p>
<span class="community-card__link">codeberg.org/ApolloNvim →</span>
</a>
<a href="https://lukaskanka.cz/" class="community-card card animate-on-scroll delay-2"
target="_blank" rel="noopener noreferrer"
aria-label="Web autora Lukáše Kaňky (otevře se v novém okně)">
<div class="community-card__icon" aria-hidden="true">🧑‍💻</div>
<h3 class="community-card__title">Web autora</h3>
<p class="community-card__desc">Osobní web Lukáše Kaňky – autora ApolloNvim. Další projekty a články.</p>
<span class="community-card__link">lukaskanka.cz →</span>
</a>
</div>
</div>
</section>
</main>
<!-- ===================== FOOTER ===================== -->
<footer class="footer" role="contentinfo">
<div class="footer__inner">
<div class="footer__brand">
<span class="footer__logo">🚀 ApolloNvim</span>
<p>Neovim distribuce pro moderní Front-End vývoj.</p>
<p class="footer__made">Made with ❤️ and ⌨️</p>
</div>
<div class="footer__col">
<h4>Stránky</h4>
<ul>
<li><a href="./index.html">Domů</a></li>
<li><a href="html/instalace.html">Instalace</a></li>
<li><a href="html/pluginy.html">Pluginy</a></li>
<li><a href="html/tutorialy.html">Tutoriály</a></li>
<li><a href="html/novinky.html">Novinky</a></li>
<li><a href="html/zkratky.html">Zkratky</a></li>
</ul>
</div>
<div class="footer__col">
<h4>Komunita</h4>
<ul>
<li><a href="https://t.me/apollonvim" target="_blank" rel="noopener noreferrer">Telegram</a></li>
<li><a href="https://codeberg.org/ApolloNvim/ApolloNvim2025" target="_blank" rel="noopener noreferrer">Codeberg</a></li>
<li><a href="https://lukaskanka.cz/" target="_blank" rel="noopener noreferrer">Web autora</a></li>
</ul>
</div>
<div class="footer__col">
<h4>Projekty</h4>
<ul>
<li><a href="https://arch-linux.cz/" target="_blank" rel="noopener noreferrer">ArchLinux CZ</a></li>
<li><a href="https://searxng.cz/" target="_blank" rel="noopener noreferrer">SearXNG CZ</a></li>
<li><a href="https://endeavouros.cz/" target="_blank" rel="noopener noreferrer">EndeavourOS CZ</a></li>
</ul>
</div>
</div>
<div class="footer__bottom">
<p>Copyright &copy; 2024 - 2025 🚀 ApolloNvim / Lukáš Kaňka</p>
<span class="badge badge--purple">v25.04</span>
</div>
</footer>
<!-- ===================== SCRIPTS ===================== -->
<script src="js/particles.js"></script>
<script src="js/main.js"></script>
</body>
</html>
+647
View File
@@ -0,0 +1,647 @@
/**
* main.js — ApolloNvim Web Interaktivita
* Vanilla ES6+ JavaScript bez externích závislostí.
*/
"use strict";
// ════════════════════════════════════════════════════════════════════════════
// 8. LOADING SCREEN — fade-out při načtení stránky
// (registrujeme jako první, aby běžel co nejdříve)
// ════════════════════════════════════════════════════════════════════════════
function initLoadingScreen() {
const loader = document.getElementById("loading-screen");
if (!loader) return;
const hide = () => {
loader.classList.add("fade-out");
// Po dokončení animace element skryjeme úplně
loader.addEventListener(
"transitionend",
() => {
loader.style.display = "none";
},
{ once: true },
);
};
if (document.readyState === "complete") {
hide();
} else {
window.addEventListener("load", hide);
}
}
initLoadingScreen();
// ════════════════════════════════════════════════════════════════════════════
// Spuštění zbytku funkcí po načtení DOM
// ════════════════════════════════════════════════════════════════════════════
document.addEventListener("DOMContentLoaded", () => {
// ── Pořadí inicializace ──────────────────────────────────────────────────
initNavigation();
initHamburger();
initScrollAnimations();
initTabs();
initTypingEffects();
initCounters();
initCopyButtons();
initPluginFilters();
initTooltips();
initBackToTop();
});
// ════════════════════════════════════════════════════════════════════════════
// 1. NAVIGATION
// ════════════════════════════════════════════════════════════════════════════
function initNavigation() {
const navbar =
document.querySelector(".navbar") || document.querySelector("nav");
const hamburger =
document.querySelector(".hamburger") ||
document.querySelector(".menu-toggle");
const navMenu =
document.querySelector(".nav-menu") || document.querySelector(".nav-links");
const navLinks = document.querySelectorAll(".nav-links a, .nav-menu a");
// ── Třída "scrolled" při odrolování ──
if (navbar) {
const onScroll = () => {
navbar.classList.toggle("scrolled", window.scrollY > 50);
};
window.addEventListener("scroll", onScroll, { passive: true });
onScroll(); // Okamžitá inicializace
}
// ── Hamburger menu toggle ──
if (hamburger && navMenu) {
hamburger.addEventListener("click", () => {
const isOpen = navMenu.classList.toggle("open");
hamburger.classList.toggle("active", isOpen);
hamburger.setAttribute("aria-expanded", String(isOpen));
// Zabránit scrollování pozadí při otevřeném menu
document.body.classList.toggle("menu-open", isOpen);
});
}
// ── Smooth scroll pro anchor linky ──
document.querySelectorAll('a[href^="#"]').forEach((link) => {
link.addEventListener("click", (e) => {
const targetId = link.getAttribute("href");
if (targetId === "#") return;
const target = document.querySelector(targetId);
if (!target) return;
e.preventDefault();
const navHeight = navbar ? navbar.offsetHeight : 0;
const top =
target.getBoundingClientRect().top + window.scrollY - navHeight - 16;
window.scrollTo({ top, behavior: "smooth" });
// Zavření mobilního menu po kliknutí na odkaz
closeNavMenu(navMenu, hamburger);
});
});
// ── Zavření menu kliknutím mimo ──
document.addEventListener("click", (e) => {
if (!navMenu || !hamburger) return;
if (!navMenu.contains(e.target) && !hamburger.contains(e.target)) {
closeNavMenu(navMenu, hamburger);
}
});
// ── Aktivní stav nav linků dle URL ──
setActiveNavLink(navLinks);
}
/** Zavře mobilní navigation menu */
function closeNavMenu(navMenu, hamburger) {
if (!navMenu) return;
navMenu.classList.remove("open");
document.body.classList.remove("menu-open");
if (hamburger) {
hamburger.classList.remove("active");
hamburger.setAttribute("aria-expanded", "false");
}
}
/** Označí aktivní nav link dle aktuální URL */
function setActiveNavLink(links) {
const path = window.location.pathname;
links.forEach((link) => {
const href = link.getAttribute("href") || "";
// Přesná shoda nebo shoda části cesty (pro víceúrovňové URL)
const isActive =
href !== "#" &&
(path === href ||
path.endsWith(href) ||
(href !== "/" && path.includes(href)));
link.classList.toggle("active", isActive);
if (isActive) link.setAttribute("aria-current", "page");
});
}
// ════════════════════════════════════════════════════════════════════════════
// 2. SCROLL ANIMACE — Intersection Observer
// ════════════════════════════════════════════════════════════════════════════
function initScrollAnimations() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
// Odpojit observer po animaci (každý element se animuje jen jednou)
observer.unobserve(entry.target);
}
});
},
{
threshold: 0.1,
rootMargin: "0px 0px -40px 0px", // spustí se dříve, než element dosáhne dna okna
},
);
document
.querySelectorAll(".animate-on-scroll")
.forEach((el) => observer.observe(el));
}
// ════════════════════════════════════════════════════════════════════════════
// 3. TAB SYSTEM — CZ / EN přepínač (a jakékoli další záložky)
// ════════════════════════════════════════════════════════════════════════════
function initTabs() {
// Podpora více nezávislých tab skupin na stránce
document.querySelectorAll("[data-tab-group]").forEach((group) => {
const buttons = group.querySelectorAll(".tab-btn");
const contents = group.querySelectorAll(".tab-content");
const storageKey = `apollo-tab-${group.dataset.tabGroup}`;
/** Přepne na záložku s daným id */
const switchTab = (targetId, saveToStorage = true) => {
buttons.forEach((btn) => {
const active = btn.dataset.tab === targetId;
btn.classList.toggle("active", active);
btn.setAttribute("aria-selected", String(active));
});
contents.forEach((content) => {
if (content.dataset.tabContent === targetId) {
content.classList.add("active");
content.classList.remove("hidden");
// Fade-in efekt
content.style.opacity = "0";
requestAnimationFrame(() => {
requestAnimationFrame(() => {
content.style.opacity = "1";
});
});
} else {
content.classList.remove("active");
content.classList.add("hidden");
}
});
if (saveToStorage) {
try {
localStorage.setItem(storageKey, targetId);
} catch (_) {}
}
};
// Event listenery
buttons.forEach((btn) => {
btn.addEventListener("click", () => switchTab(btn.dataset.tab));
});
// Obnova uložené preference
let savedTab = null;
try {
savedTab = localStorage.getItem(storageKey);
} catch (_) {}
const firstBtn = buttons[0];
const initTab = savedTab || (firstBtn ? firstBtn.dataset.tab : null);
if (initTab) switchTab(initTab, false);
});
}
// ════════════════════════════════════════════════════════════════════════════
// 4. TERMINAL TYPING EFFECT
// ════════════════════════════════════════════════════════════════════════════
/**
* Animuje psaní textu do elementu znak po znaku.
* @param {HTMLElement} element - Cílový element
* @param {string} text - Text, který se "napíše"
* @param {number} speed - Prodleva mezi znaky v ms (default 60)
* @param {Function} callback - Volitelný callback po dokončení
*/
function typeWriter(element, text, speed = 60, callback = null) {
if (!element) return;
element.textContent = "";
element.style.borderRight = "2px solid currentColor"; // kurzor
let i = 0;
const tick = () => {
if (i < text.length) {
element.textContent += text.charAt(i);
i++;
setTimeout(tick, speed + Math.random() * 30); // mírná variabilita
} else {
// Blikající kurzor po dokončení
element.classList.add("typing-done");
if (typeof callback === "function") callback();
}
};
tick();
}
/** Aplikuje typing efekt na všechny elementy s atributem [data-typewriter] */
function initTypingEffects() {
const elements = document.querySelectorAll("[data-typewriter]");
if (!elements.length) return;
// Spustíme postupně s malým zpožděním pro každý element
elements.forEach((el, idx) => {
const text = el.dataset.typewriter || el.textContent;
const speed = parseInt(el.dataset.typeSpeed, 10) || 60;
const delay = parseInt(el.dataset.typeDelay, 10) || idx * 300;
el.textContent = ""; // Skryjeme původní text
setTimeout(() => typeWriter(el, text, speed), delay);
});
}
// ════════════════════════════════════════════════════════════════════════════
// 5. SMOOTH COUNTER ANIMATION
// ════════════════════════════════════════════════════════════════════════════
function initCounters() {
const counters = document.querySelectorAll("[data-count]");
if (!counters.length) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
observer.unobserve(entry.target);
animateCounter(entry.target);
});
},
{ threshold: 0.5 },
);
counters.forEach((el) => observer.observe(el));
}
/**
* Animuje čítač od 0 do cílové hodnoty.
* @param {HTMLElement} el - Element s atributem data-count
*/
function animateCounter(el) {
const target = parseInt(el.dataset.count, 10);
const duration = parseInt(el.dataset.countDuration, 10) || 1500; // ms
const suffix = el.dataset.countSuffix || "";
const start = performance.now();
const update = (now) => {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
// Ease-out cubic
const eased = 1 - Math.pow(1 - progress, 3);
const current = Math.floor(eased * target);
el.textContent = current.toLocaleString("cs-CZ") + suffix;
if (progress < 1) requestAnimationFrame(update);
else el.textContent = target.toLocaleString("cs-CZ") + suffix;
};
requestAnimationFrame(update);
}
// ════════════════════════════════════════════════════════════════════════════
// 6. COPY TO CLIPBOARD
// ════════════════════════════════════════════════════════════════════════════
function initCopyButtons() {
// Podpora pro explicitní tlačítka s třídou .copy-btn
document.querySelectorAll(".copy-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const targetSelector = btn.dataset.copyTarget;
const codeEl = targetSelector
? document.querySelector(targetSelector)
: btn.closest(".code-block, pre")?.querySelector("code") ||
btn.previousElementSibling;
if (!codeEl) return;
copyText(codeEl.textContent.trim(), btn);
});
});
// Automaticky přidá tlačítka ke všem <pre><code> blokům bez tlačítka
document.querySelectorAll("pre:not(.no-copy)").forEach((pre) => {
if (pre.querySelector(".copy-btn")) return; // Již má tlačítko
const btn = document.createElement("button");
btn.className = "copy-btn copy-btn--auto";
btn.textContent = "Kopírovat";
btn.setAttribute("aria-label", "Kopírovat kód");
pre.style.position = "relative";
pre.appendChild(btn);
btn.addEventListener("click", () => {
const code = pre.querySelector("code") || pre;
copyText(code.textContent.trim(), btn);
});
});
}
/**
* Zkopíruje text do schránky a ukáže vizuální feedback na tlačítku.
* @param {string} text - Text ke zkopírování
* @param {HTMLElement} btn - Tlačítko pro vizuální feedback
*/
function copyText(text, btn) {
navigator.clipboard
.writeText(text)
.then(() => {
const original = btn.textContent;
btn.textContent = "✓ Zkopírováno";
btn.classList.add("copied");
btn.disabled = true;
setTimeout(() => {
btn.textContent = original;
btn.classList.remove("copied");
btn.disabled = false;
}, 2000);
})
.catch(() => {
// Fallback pro starší prohlížeče
const ta = document.createElement("textarea");
ta.value = text;
ta.style.cssText = "position:fixed;top:-9999px;left:-9999px";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
btn.textContent = "✓ Zkopírováno";
btn.classList.add("copied");
setTimeout(() => {
btn.textContent = "Kopírovat";
btn.classList.remove("copied");
}, 2000);
});
}
// ════════════════════════════════════════════════════════════════════════════
// 7. THEME PARTICLE COLORS (při hover na sekce)
// ════════════════════════════════════════════════════════════════════════════
function initParticleTheme() {
if (!window.particleSystem) return;
// Mapování sekcí na palety barev
const sectionThemes = {
"#plugins": ["#bd93f9", "#ff79c6", "#ffffff"],
"#themes": ["#ff79c6", "#ffb86c", "#ffffff"],
"#installation": ["#50fa7b", "#8be9fd", "#ffffff"],
"#tutorials": ["#f1fa8c", "#ffb86c", "#ffffff"],
};
Object.entries(sectionThemes).forEach(([selector, colors]) => {
const section = document.querySelector(selector);
if (!section) return;
section.addEventListener("mouseenter", () => {
window.particleSystem.config.colors = colors;
});
section.addEventListener("mouseleave", () => {
// Obnovení výchozích barev
window.particleSystem.config.colors = [
"#ffffff",
"#8be9fd",
"#bd93f9",
"#ff79c6",
];
});
});
}
// Spustíme až po inicializaci particle systému (je v DOMContentLoaded v particles.js)
document.addEventListener("DOMContentLoaded", () => {
// Malé zpoždění pro jistotu, že particleSystem je vytvořen
setTimeout(initParticleTheme, 100);
});
// ════════════════════════════════════════════════════════════════════════════
// 9. TOOLTIP SYSTEM
// ════════════════════════════════════════════════════════════════════════════
function initTooltips() {
let tooltip = document.getElementById("tooltip-global");
// Vytvoření globálního tooltip elementu pokud neexistuje
if (!tooltip) {
tooltip = document.createElement("div");
tooltip.id = "tooltip-global";
tooltip.className = "tooltip";
tooltip.setAttribute("role", "tooltip");
tooltip.setAttribute("aria-hidden", "true");
document.body.appendChild(tooltip);
}
let hideTimer = null;
/** Zobrazí tooltip */
const show = (text, anchorEl) => {
clearTimeout(hideTimer);
tooltip.textContent = text;
tooltip.style.display = "block";
tooltip.removeAttribute("aria-hidden");
// Pozicování nad elementem
const rect = anchorEl.getBoundingClientRect();
const tRect = tooltip.getBoundingClientRect();
let left = rect.left + rect.width / 2 - tRect.width / 2 + window.scrollX;
let top = rect.top - tRect.height - 8 + window.scrollY;
// Zamezit přetečení přes pravý okraj
if (left + tRect.width > window.innerWidth - 8) {
left = window.innerWidth - tRect.width - 8;
}
if (left < 8) left = 8;
tooltip.style.left = `${left}px`;
tooltip.style.top = `${top}px`;
tooltip.classList.add("visible");
};
/** Skryje tooltip */
const hide = () => {
hideTimer = setTimeout(() => {
tooltip.classList.remove("visible");
tooltip.setAttribute("aria-hidden", "true");
}, 150);
};
// Event delegation — posloucháme na document
document.addEventListener("mouseover", (e) => {
const target = e.target.closest("[data-tooltip]");
if (target) show(target.dataset.tooltip, target);
});
document.addEventListener("mouseout", (e) => {
if (e.target.closest("[data-tooltip]")) hide();
});
// Klávesnicová přístupnost
document.addEventListener("focusin", (e) => {
const target = e.target.closest("[data-tooltip]");
if (target) show(target.dataset.tooltip, target);
});
document.addEventListener("focusout", (e) => {
if (e.target.closest("[data-tooltip]")) hide();
});
}
// ════════════════════════════════════════════════════════════════════════════
// 10. BACK TO TOP BUTTON
// ════════════════════════════════════════════════════════════════════════════
function initBackToTop() {
let btn = document.getElementById("back-to-top");
// Vytvoření tlačítka pokud neexistuje v HTML
if (!btn) {
btn = document.createElement("button");
btn.id = "back-to-top";
btn.className = "back-to-top";
btn.innerHTML = "&#8679;"; // ⇧
btn.setAttribute("aria-label", "Zpět nahoru");
btn.setAttribute("title", "Zpět nahoru");
document.body.appendChild(btn);
}
// Zobrazení/skrytí dle scroll pozice
const SHOW_THRESHOLD = 300;
const toggleVisibility = () => {
const visible = window.scrollY > SHOW_THRESHOLD;
btn.classList.toggle("visible", visible);
btn.setAttribute("aria-hidden", String(!visible));
};
window.addEventListener("scroll", toggleVisibility, { passive: true });
toggleVisibility();
// Scroll nahoru po kliknutí
btn.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
});
}
// ════════════════════════════════════════════════════════════════════════════
// UTILITY — veřejné API pro použití z HTML atributů nebo jiných skriptů
// ════════════════════════════════════════════════════════════════════════════
// ════════════════════════════════════════════════════════════════════════════
// 11. HAMBURGER MENU (pro HTML strukturu s .navbar__hamburger a .navbar__mobile)
// ════════════════════════════════════════════════════════════════════════════
function initHamburger() {
const hamburger = document.getElementById("hamburger");
const mobileMenu = document.getElementById("mobile-menu");
if (!hamburger || !mobileMenu) return;
hamburger.addEventListener("click", () => {
const isHidden = mobileMenu.hasAttribute("hidden");
if (isHidden) {
mobileMenu.removeAttribute("hidden");
hamburger.setAttribute("aria-expanded", "true");
hamburger.classList.add("open");
} else {
mobileMenu.setAttribute("hidden", "");
hamburger.setAttribute("aria-expanded", "false");
hamburger.classList.remove("open");
}
});
// Zavřít při kliknutí mimo menu
document.addEventListener("click", (e) => {
if (
!mobileMenu.hasAttribute("hidden") &&
!mobileMenu.contains(e.target) &&
!hamburger.contains(e.target)
) {
mobileMenu.setAttribute("hidden", "");
hamburger.setAttribute("aria-expanded", "false");
hamburger.classList.remove("open");
}
});
// Zavřít při kliknutí na odkaz v menu
mobileMenu.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", () => {
mobileMenu.setAttribute("hidden", "");
hamburger.setAttribute("aria-expanded", "false");
hamburger.classList.remove("open");
});
});
}
// ════════════════════════════════════════════════════════════════════════════
// 12. PLUGIN CATEGORY FILTER (pro pluginy.html)
// ════════════════════════════════════════════════════════════════════════════
function initPluginFilters() {
const filterContainer = document.getElementById("plugin-filters");
const pluginsGrid = document.getElementById("plugins-grid");
if (!filterContainer || !pluginsGrid) return;
const buttons = filterContainer.querySelectorAll(".filter-btn");
const cards = pluginsGrid.querySelectorAll(".plugin-card");
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
// Aktualizace aktivního tlačítka
buttons.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
const filter = btn.dataset.filter;
// Zobrazit / skrýt karty s animací
cards.forEach((card) => {
const category = card.dataset.category || "";
const match = filter === "all" || category === filter;
if (match) {
card.classList.remove("hidden");
// Krátké zpoždění pro plynulý efekt
requestAnimationFrame(() => {
card.style.opacity = "1";
card.style.transform = "";
});
} else {
card.classList.add("hidden");
}
});
});
});
}
window.ApolloNvim = {
typeWriter,
copyText,
};
+358
View File
@@ -0,0 +1,358 @@
/**
* particles.js — ApolloNvim Vesmírný Particle Systém
* Canvas-based animace hvězd s parallaxem, blikáním, spojeními a padajícími hvězdami.
*/
class ParticleSystem {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
// Konfigurace částic
this.config = {
count: 200,
minSize: 0.5,
maxSize: 2.5,
colors: ['#ffffff', '#8be9fd', '#bd93f9', '#ff79c6'],
connectionDist: 100, // px — maximální vzdálenost pro spojovací linky
driftSpeed: 0.25, // max px/frame náhodný drift
parallaxStrength: 0.04, // síla parallax efektu myši
blinkChance: 0.35, // podíl hvězd, které blikají
shootingInterval:[5000, 8000], // ms min/max mezi padajícími hvězdami
};
// Stav myši
this.mouse = { x: 0, y: 0, active: false };
// Pole částic a padající hvězdy
this.particles = [];
this.shootingStars = [];
this._animFrameId = null;
this._shootingTimer = null;
this.init();
}
// ─── Inicializace ─────────────────────────────────────────────────────────
init() {
this.resize();
this.createParticles();
this._bindEvents();
this._scheduleShootingStar();
this.animate();
}
/** Nastaví rozměry canvasu dle okna */
resize() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
/** Vytvoří pole hvězdičkových částic */
createParticles() {
this.particles = [];
const { count, minSize, maxSize, colors, blinkChance, driftSpeed } = this.config;
for (let i = 0; i < count; i++) {
const blinks = Math.random() < blinkChance;
this.particles.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
baseX: 0, // bude nastaveno níže
baseY: 0,
size: minSize + Math.random() * (maxSize - minSize),
color: colors[Math.floor(Math.random() * colors.length)],
// drift – každá hvězda má svůj vektor
vx: (Math.random() - 0.5) * driftSpeed,
vy: (Math.random() - 0.5) * driftSpeed,
// blikání
blinks,
opacity: blinks ? 0.3 + Math.random() * 0.7 : 0.6 + Math.random() * 0.4,
blinkSpeed: blinks ? 0.005 + Math.random() * 0.015 : 0,
blinkDir: Math.random() < 0.5 ? 1 : -1,
// parallax offset (akumulovaný)
offsetX: 0,
offsetY: 0,
});
// baseX/baseY = počáteční pozice (pro parallax výpočet)
this.particles[i].baseX = this.particles[i].x;
this.particles[i].baseY = this.particles[i].y;
}
}
// ─── Hlavní animační smyčka ───────────────────────────────────────────────
animate() {
this._animFrameId = requestAnimationFrame(() => this.animate());
const ctx = this.ctx;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Výpočet parallax posunu středu obrazovky
const cx = this.canvas.width / 2;
const cy = this.canvas.height / 2;
const mouseOffsetX = this.mouse.active
? (this.mouse.x - cx) * this.config.parallaxStrength
: 0;
const mouseOffsetY = this.mouse.active
? (this.mouse.y - cy) * this.config.parallaxStrength
: 0;
// ── Aktualizace a kreslení hvězd ──
for (const p of this.particles) {
// Drift
p.baseX += p.vx;
p.baseY += p.vy;
// Wrap-around přes okraje
if (p.baseX < -10) p.baseX = this.canvas.width + 10;
if (p.baseX > this.canvas.width + 10) p.baseX = -10;
if (p.baseY < -10) p.baseY = this.canvas.height + 10;
if (p.baseY > this.canvas.height + 10) p.baseY = -10;
// Parallax offset (každá hvězda reaguje trochu jinak dle velikosti)
const parallaxFactor = p.size / this.config.maxSize;
p.x = p.baseX + mouseOffsetX * parallaxFactor;
p.y = p.baseY + mouseOffsetY * parallaxFactor;
// Blikání opacity
if (p.blinks) {
p.opacity += p.blinkSpeed * p.blinkDir;
if (p.opacity >= 1.0) { p.opacity = 1.0; p.blinkDir = -1; }
if (p.opacity <= 0.15) { p.opacity = 0.15; p.blinkDir = 1; }
}
// Kreslení hvězdy jako malého hvězdičkového tvaru
this._drawStar(p);
}
// ── Spojovací linky ──
this.drawConnections();
// ── Padající hvězdy ──
this._updateShootingStars();
}
/** Nakreslí hvězdu (5-cípý tvar pro větší, kroužek pro malé) */
_drawStar(p) {
const ctx = this.ctx;
ctx.save();
ctx.globalAlpha = p.opacity;
ctx.fillStyle = p.color;
if (p.size >= 1.5) {
// Pěticípá hvězdička pro větší částice
this._drawStarShape(ctx, p.x, p.y, p.size * 2, p.size, 5);
} else {
// Jednoduchý kroužek pro malé hvězdičky
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
}
// Jemná záře kolem hvězdy
if (p.size > 1.2) {
const grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 4);
grd.addColorStop(0, this._hexAlpha(p.color, 0.3 * p.opacity));
grd.addColorStop(1, this._hexAlpha(p.color, 0));
ctx.fillStyle = grd;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * 4, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
/** Nakreslí pěticípou hvězdičku */
_drawStarShape(ctx, cx, cy, outerR, innerR, points) {
const step = Math.PI / points;
ctx.beginPath();
for (let i = 0; i < points * 2; i++) {
const r = i % 2 === 0 ? outerR : innerR;
const angle = i * step - Math.PI / 2;
const x = cx + Math.cos(angle) * r;
const y = cy + Math.sin(angle) * r;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
}
// ─── Spojovací linky ─────────────────────────────────────────────────────
drawConnections() {
const ctx = this.ctx;
const dist = this.config.connectionDist;
const ps = this.particles;
for (let i = 0; i < ps.length; i++) {
for (let j = i + 1; j < ps.length; j++) {
const dx = ps[i].x - ps[j].x;
const dy = ps[i].y - ps[j].y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < dist) {
const alpha = (1 - d / dist) * 0.25; // max 25% opacity
ctx.save();
ctx.globalAlpha = alpha;
ctx.strokeStyle = '#8be9fd';
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.moveTo(ps[i].x, ps[i].y);
ctx.lineTo(ps[j].x, ps[j].y);
ctx.stroke();
ctx.restore();
}
}
}
}
// ─── Padající hvězdy ─────────────────────────────────────────────────────
/** Naplánuje další padající hvězdu s náhodným zpožděním */
_scheduleShootingStar() {
const [min, max] = this.config.shootingInterval;
const delay = min + Math.random() * (max - min);
this._shootingTimer = setTimeout(() => {
this.createShootingStar();
this._scheduleShootingStar(); // naplánuje další
}, delay);
}
/** Vytvoří novou padající hvězdu */
createShootingStar() {
const angle = (15 + Math.random() * 25) * (Math.PI / 180); // 15°–40°
const speed = 6 + Math.random() * 8;
const length = 80 + Math.random() * 120;
this.shootingStars.push({
x: Math.random() * this.canvas.width * 0.8,
y: Math.random() * this.canvas.height * 0.3,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
length,
opacity: 1,
trail: [], // pole minulých pozic pro ocas
maxTrail: Math.floor(length / speed),
done: false,
});
}
/** Aktualizuje a kreslí všechny padající hvězdy */
_updateShootingStars() {
const ctx = this.ctx;
this.shootingStars = this.shootingStars.filter(s => !s.done);
for (const s of this.shootingStars) {
// Uloží pozici do ocasu
s.trail.push({ x: s.x, y: s.y });
if (s.trail.length > s.maxTrail) s.trail.shift();
// Pohyb
s.x += s.vx;
s.y += s.vy;
s.opacity -= 0.012;
if (s.opacity <= 0 || s.x > this.canvas.width + 50 || s.y > this.canvas.height + 50) {
s.done = true;
continue;
}
// Kreslení ocasu (gradient linka)
if (s.trail.length > 1) {
const grad = ctx.createLinearGradient(
s.trail[0].x, s.trail[0].y,
s.x, s.y
);
grad.addColorStop(0, `rgba(255,255,255,0)`);
grad.addColorStop(1, `rgba(255,255,255,${s.opacity})`);
ctx.save();
ctx.strokeStyle = grad;
ctx.lineWidth = 1.5;
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 6;
ctx.beginPath();
ctx.moveTo(s.trail[0].x, s.trail[0].y);
for (const pt of s.trail) ctx.lineTo(pt.x, pt.y);
ctx.lineTo(s.x, s.y);
ctx.stroke();
ctx.restore();
}
// Jasná tečka na špičce
ctx.save();
ctx.globalAlpha = s.opacity;
ctx.fillStyle = '#ffffff';
ctx.shadowColor = '#8be9fd';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(s.x, s.y, 1.5, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
// ─── Pomocné metody ───────────────────────────────────────────────────────
/** Převede HEX barvu na rgba string s danou průhledností */
_hexAlpha(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r},${g},${b},${alpha})`;
}
/** Naváže posluchače událostí */
_bindEvents() {
// Resize
window.addEventListener('resize', () => {
this.resize();
// Po resize přepočítá baseX/baseY aby hvězdy zůstaly proporcionálně
const scaleX = this.canvas.width / (this.canvas.width || 1);
const scaleY = this.canvas.height / (this.canvas.height || 1);
for (const p of this.particles) {
p.baseX = Math.random() * this.canvas.width;
p.baseY = Math.random() * this.canvas.height;
}
});
// Mouse parallax
window.addEventListener('mousemove', (e) => {
this.mouse.x = e.clientX;
this.mouse.y = e.clientY;
this.mouse.active = true;
});
// Dotyk (mobile)
window.addEventListener('touchmove', (e) => {
if (e.touches.length > 0) {
this.mouse.x = e.touches[0].clientX;
this.mouse.y = e.touches[0].clientY;
this.mouse.active = true;
}
}, { passive: true });
window.addEventListener('mouseleave', () => {
this.mouse.active = false;
});
}
/** Zastaví animaci a časovače */
destroy() {
if (this._animFrameId) cancelAnimationFrame(this._animFrameId);
if (this._shootingTimer) clearTimeout(this._shootingTimer);
}
}
// ─── Auto-init při načtení DOM ─────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
if (document.getElementById('particles-canvas')) {
window.particleSystem = new ParticleSystem('particles-canvas');
}
});