Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
<template>
<div id="app" class="transition-colors duration-200">
<PageTransition>
<router-view />
</PageTransition>
<router-view v-slot="{ Component, route }">
<transition
:name="route.meta.transition || 'slide-left'"
mode="out-in"
>
<component :is="Component" :key="route.path" />
</transition>
</router-view>
</div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import PageTransition from '@/components/PageTransition.vue'
import { useTheme } from '@/composables/useTheme'

// Initialize theme on app start
const { initTheme } = useTheme()

// Initialize theme immediately
initTheme()

onMounted(() => {
// Re-init on mount to ensure it's applied
initTheme()
})
</script>
Expand All @@ -23,4 +31,59 @@ onMounted(() => {
#app {
min-height: 100vh;
}

/* Fade transition */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.25s ease;
}

.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

/* Slide transitions */
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}

.slide-left-enter-from {
opacity: 0;
transform: translateX(30px);
}

.slide-left-leave-to {
opacity: 0;
transform: translateX(-30px);
}

.slide-right-enter-from {
opacity: 0;
transform: translateX(-30px);
}

.slide-right-leave-to {
opacity: 0;
transform: translateX(30px);
}

/* Scale transition */
.scale-enter-active,
.scale-leave-active {
transition: all 0.25s ease;
}

.scale-enter-from {
opacity: 0;
transform: scale(0.95);
}

.scale-leave-to {
opacity: 0;
transform: scale(1.05);
}
</style>
12 changes: 5 additions & 7 deletions src/components/CodeBlock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,15 @@ import 'prismjs/components/prism-javascript'
import 'prismjs/components/prism-typescript'
import 'prismjs/components/prism-python'
import 'prismjs/components/prism-go'
import 'prismjs/components/prism-markup' // Must be loaded before PHP
import 'prismjs/components/prism-markup-templating' // Required for PHP
import 'prismjs/components/prism-php'
import 'prismjs/components/prism-java'
import 'prismjs/components/prism-c'
import 'prismjs/components/prism-cpp'
import 'prismjs/components/prism-csharp'
import 'prismjs/components/prism-css'
import 'prismjs/components/prism-scss'
import 'prismjs/components/prism-markup'
import 'prismjs/components/prism-yaml'
import 'prismjs/components/prism-docker'
import 'prismjs/components/prism-sql'
Expand Down Expand Up @@ -241,13 +242,10 @@ const codeClasses = computed(() => [

const highlightCode = () => {
if (codeElement.value) {
// Ensure the language is loaded
// Check if the language is available
if (!Prism.languages[detectedLanguage.value] && detectedLanguage.value !== 'text') {
try {
require(`prismjs/components/prism-${detectedLanguage.value}`)
} catch (e) {
console.warn(`Language ${detectedLanguage.value} not available in Prism.js`)
}
console.warn(`Language ${detectedLanguage.value} not available in Prism.js`)
return
}

Prism.highlightElement(codeElement.value)
Expand Down
25 changes: 24 additions & 1 deletion src/components/DataSources.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
<a :href="source.url" target="_blank" rel="noopener noreferrer"
class="flex w-full h-full items-center justify-center hover:scale-110 transition-transform duration-300 p-2"
:title="source.alt">
<img :src="source.image" :alt="source.alt" class="max-h-8 sm:max-h-10 md:max-h-12 w-auto object-contain" loading="lazy">
<img :src="source.image" :alt="source.alt"
class="max-h-8 sm:max-h-10 md:max-h-12 w-auto object-contain"
:class="getImageDarkModeClass(source.image)"
loading="lazy">
</a>
</div>
</div>
Expand Down Expand Up @@ -55,4 +58,24 @@ const dataSources: DataSource[] = [
url: 'https://hack.co.id'
}
]

const getImageDarkModeClass = (imagePath: string): string => {
// Images that need background removal (non-transparent white backgrounds)
if (imagePath.includes('inacovid')) {
return 'image-remove-bg'
}

// Images with government logos that need selective typography color inversion
if (imagePath.includes('dinkes-sulteng')) {
return 'image-gov-logo'
}

// Images that should remain unchanged in dark mode
if (imagePath.includes('sulteng-lawan-covid')) {
return ''
}

// Default: apply standard logo dark mode treatment for other logos
return 'logo-dark-mode'
}
</script>
123 changes: 79 additions & 44 deletions src/components/Navigation.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<template>
<nav ref="navbar" class="fixed w-full top-0 z-50 transition-all duration-500 ease-out" :class="navbarClass">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<nav ref="navbar" class="fixed w-full top-0 left-0 z-50 transition-all duration-500 ease-out px-4 sm:px-6 lg:px-8 pt-4" :class="navbarClass">
<div class="max-w-7xl mx-auto rounded-2xl transition-all duration-500 ease-out" :class="innerContainerClass">
<div class="flex justify-between items-center h-16 px-4 sm:px-6 lg:px-8">
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center space-x-3">
<img src="/pico-api-logo.webp" alt="PICO API Logo" class="h-10 w-auto">
<router-link to="/" class="text-2xl font-bold gradient-text">PICO SulTeng</router-link>
<img src="/pico-api-logo.webp" alt="PICO API Logo" class="h-10 w-auto logo-dark-mode">
<router-link to="/" class="text-2xl font-bold gradient-text flex items-center">PICO SulTeng</router-link>
</div>
</div>

Expand All @@ -14,22 +14,19 @@
<div class="ml-10 flex items-baseline space-x-4">
<router-link
to="/"
class="text-gray-600 hover:text-pico-blue px-3 py-2 rounded-md text-sm font-medium transition-colors"
:class="{ 'text-gray-900': $route.path === '/' }"
:class="[navLinkClass, { [activeNavLinkClass]: $route.path === '/' }]"
>
{{ t('nav.home') }}
</router-link>
<router-link
to="/docs"
class="text-gray-600 hover:text-pico-blue px-3 py-2 rounded-md text-sm font-medium transition-colors"
:class="{ 'text-gray-900': $route.path === '/docs' }"
:class="[navLinkClass, { [activeNavLinkClass]: $route.path === '/docs' }]"
>
{{ t('nav.documentation') }}
</router-link>
<router-link
to="/api"
class="text-gray-600 hover:text-pico-blue px-3 py-2 rounded-md text-sm font-medium transition-colors"
:class="{ 'text-gray-900': $route.path === '/api' }"
:class="[navLinkClass, { [activeNavLinkClass]: $route.path === '/api' }]"
>
{{ t('nav.apiReference') }}
</router-link>
Expand All @@ -38,41 +35,26 @@
</a>
<!-- Theme & Language Controls -->
<div class="flex items-center space-x-2 ml-4">
<!-- Theme Toggle -->
<ThemeToggle />

<!-- Language Toggle -->
<button
@click="toggleLanguage"
class="flex items-center space-x-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-pico-sky transition-colors bg-white/80 backdrop-blur-sm dark:bg-gray-800/80 dark:border-gray-600 dark:hover:border-pico-sky"
class="flex items-center justify-center px-3 py-2 rounded-lg border border-gray-200 hover:border-gray-300 transition-all duration-200 bg-white hover:bg-gray-50 shadow-sm dark:bg-gray-800 dark:border-gray-600 dark:hover:bg-gray-700"
:aria-label="`Switch to ${locale === 'en' ? 'Indonesian' : 'English'} language`"
>
<svg class="w-4 h-4 text-gray-600 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"></path>
</svg>
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">{{ locale === 'en' ? 'ID' : 'EN' }}</span>
<span class="ml-1.5 text-sm font-medium text-gray-700 dark:text-gray-200">{{ locale === 'en' ? 'EN' : 'ID' }}</span>
</button>

<!-- Theme Toggle -->
<ThemeToggle />
</div>
</div>
</div>

<!-- Mobile menu button -->
<div class="md:hidden flex items-center space-x-2">
<!-- Theme Toggle for Mobile -->
<ThemeToggle variant="compact" />

<!-- Language Toggle for Mobile -->
<button
@click="toggleLanguage"
class="flex items-center space-x-1 px-2 py-1.5 rounded-lg border border-gray-200 hover:border-pico-sky transition-colors bg-white/80 backdrop-blur-sm dark:bg-gray-800/80 dark:border-gray-600"
:aria-label="`Switch to ${locale === 'en' ? 'Indonesian' : 'English'} language`"
>
<svg class="w-3 h-3 text-gray-600 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"></path>
</svg>
<span class="text-xs font-medium text-gray-700 dark:text-gray-200">{{ locale === 'en' ? 'ID' : 'EN' }}</span>
</button>

<div class="md:hidden">
<!-- Hamburger button -->
<button
@click="toggleMobileMenu"
Expand Down Expand Up @@ -113,8 +95,7 @@
<!-- Mobile menu -->
<div
id="mobile-menu"
class="fixed top-0 right-0 z-50 w-64 h-full bg-white shadow-lg transform transition-transform duration-300 ease-in-out md:hidden"
:class="mobileMenuOpen ? 'translate-x-0' : 'translate-x-full'"
:class="[mobileMenuClass, mobileMenuOpen ? 'translate-x-0' : 'translate-x-full']"
role="navigation"
:aria-hidden="!mobileMenuOpen"
>
Expand All @@ -123,24 +104,21 @@
<router-link
to="/"
@click="closeMobileMenu"
class="block text-gray-600 hover:text-pico-blue px-3 py-2 rounded-md text-base font-medium border-b border-gray-100 transition-colors"
:class="{ 'text-gray-900': $route.path === '/' }"
:class="[mobileNavLinkClass, { [activeMobileNavLinkClass]: $route.path === '/' }]"
>
{{ t('nav.home') }}
</router-link>
<router-link
to="/docs"
@click="closeMobileMenu"
class="block text-gray-600 hover:text-pico-blue px-3 py-2 rounded-md text-base font-medium border-b border-gray-100 transition-colors"
:class="{ 'text-gray-900': $route.path === '/docs' }"
:class="[mobileNavLinkClass, { [activeMobileNavLinkClass]: $route.path === '/docs' }]"
>
{{ t('nav.documentation') }}
</router-link>
<router-link
to="/api"
@click="closeMobileMenu"
class="block text-gray-600 hover:text-pico-blue px-3 py-2 rounded-md text-base font-medium border-b border-gray-100 transition-colors"
:class="{ 'text-gray-900': $route.path === '/api' }"
:class="[mobileNavLinkClass, { [activeMobileNavLinkClass]: $route.path === '/api' }]"
>
{{ t('nav.apiReference') }}
</router-link>
Expand All @@ -152,6 +130,27 @@
>
{{ t('nav.liveApi') }}
</a>

<!-- Mobile Controls Section -->
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-700 space-y-4">
<!-- Language Toggle for Mobile -->
<div class="flex items-center justify-between px-3 py-2">
<span class="text-gray-700 dark:text-gray-300 text-sm font-medium">Language</span>
<button
@click="toggleLanguage"
class="flex items-center justify-center px-3 py-1.5 rounded-lg border border-gray-200 hover:border-gray-300 transition-all duration-200 bg-white hover:bg-gray-50 shadow-sm dark:bg-gray-800 dark:border-gray-600 dark:hover:bg-gray-700"
:aria-label="`Switch to ${locale === 'en' ? 'Indonesian' : 'English'} language`"
>
<span class="text-sm font-semibold text-gray-600 dark:text-gray-300">{{ locale === 'en' ? 'EN' : 'ID' }}</span>
</button>
</div>

<!-- Theme Toggle for Mobile -->
<div class="flex items-center justify-between px-3 py-2">
<span class="text-gray-700 dark:text-gray-300 text-sm font-medium">Theme</span>
<ThemeToggle variant="compact" />
</div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -184,14 +183,50 @@ const mobileMenuOpen = ref(false)

const navbarClass = computed(() => {
if (props.variant === 'solid') {
return 'bg-white shadow-sm'
return '!pt-0'
}

return ''
})

// Mobile menu background should always be solid
const mobileMenuClass = computed(() =>
'fixed top-0 right-0 z-50 w-64 h-full bg-white dark:bg-gray-900 shadow-lg transform transition-transform duration-300 ease-in-out md:hidden'
)

const innerContainerClass = computed(() => {
if (props.variant === 'solid') {
return 'bg-white dark:bg-gray-900 shadow-sm dark:shadow-gray-800/50 !rounded-none'
}

return isScrolled.value
? 'bg-white/10 dark:bg-gray-900/10 backdrop-filter backdrop-blur-[20px] backdrop-saturate-[180%] shadow-2xl border border-white/20 dark:border-gray-800/20 md:rounded-2xl'
: 'bg-white/0 dark:bg-gray-900/0 border border-gray-200/0 dark:border-gray-800/0 md:rounded-2xl'
})

// Dynamic text color classes based on glass state
const navLinkClass = computed(() => {
const baseClasses = 'px-3 py-2 rounded-md text-sm font-medium transition-all duration-200'

if (props.variant === 'solid') {
return `${baseClasses} text-gray-600 dark:text-gray-300 hover:text-pico-blue dark:hover:text-pico-sky`
}

return isScrolled.value
? 'bg-white/95 backdrop-blur-lg shadow-lg border-b border-gray-200/50'
: 'bg-transparent'
? `${baseClasses} text-gray-900 dark:text-white hover:text-pico-blue dark:hover:text-pico-sky font-semibold`
: `${baseClasses} text-gray-600 dark:text-gray-300 hover:text-pico-blue dark:hover:text-pico-sky`
})

const activeNavLinkClass = computed(() => {
return isScrolled.value && props.variant !== 'solid'
? 'text-gray-900 dark:text-white font-bold'
: 'text-gray-900 dark:text-white'
})

// Mobile navigation classes (always solid background)
const mobileNavLinkClass = 'block text-gray-600 dark:text-gray-300 hover:text-pico-blue dark:hover:text-pico-sky px-3 py-2 rounded-md text-base font-medium border-b border-gray-100 dark:border-gray-700 transition-colors'
const activeMobileNavLinkClass = 'text-gray-900 dark:text-white'

// Scroll handler
const handleScroll = () => {
scrollY.value = window.scrollY
Expand Down
Loading