一键导入
composables
Project composables - useBlog, usePageSeo, useEnterAnimations, useBlogScroll, useGsapAnimations. Use when consuming blog/SEO/animation logic.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Project composables - useBlog, usePageSeo, useEnterAnimations, useBlogScroll, useGsapAnimations. Use when consuming blog/SEO/animation logic.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Global Pinia store (useAppStore) for theme and language with cookie persistence. Use when changing theme or language, or touching app/stores/app.ts.
Create/edit blog articles in EN/ES - frontmatter, collections, images, Markdown features. Use when writing content under content/{en,es}/blog/.
Auto-imported global and content components plus layouts (default/blog). Use when using/creating components or working with ContentRenderer.
CSS custom properties, utility classes and theme colors. Use when editing styles, working in app/assets/styles/*, writing <style scoped>, or touching colors, spacing or typography.
Static data - contact.ts, projects.ts, sidebar-items.ts. Use when displaying contact/projects info or adding navigation items.
GSAP + ScrollTrigger - animateInOnEnter, getMainScroller, useGsapAnimations. Use when adding enter/scroll animations.
| name | composables |
| description | Project composables - useBlog, usePageSeo, useEnterAnimations, useBlogScroll, useGsapAnimations. Use when consuming blog/SEO/animation logic. |
All project composables in
app/composables/. Auto-imported by Nuxt.
app/composables/useBlog.tsapp/composables/usePageSeo.tsapp/composables/useSiteSeoDefaults.tsapp/composables/useBlogScroll.tsapp/composables/useEnterAnimations.tsBlog logic. Uses the active locale internally.
const {
getPosts, // () => Promise<BlogPost[]> - all posts of the active locale, sorted
getPost, // (slug: string) => Promise<BlogPost | null> - falls back to EN if missing in ES
getAdjacentPosts, // (slug: string) => Promise<{ prev: BlogPost|null, next: BlogPost|null }>
findEquivalentPost, // (currentPath: string, toLocale: string) => Promise<BlogPost | null>
getRecentPosts, // (limit?: number) => Promise<BlogPost[]> - default 5
getPostsByTag, // (tag: string) => Promise<BlogPost[]> - case-insensitive partial search
getAllTags, // () => Promise<string[]> - unique tags, alphabetically sorted
} = useBlog()
interface BlogPost {
slug?: string
title: string
date: string // 'YYYY-MM-DD'
order?: number
lastmod?: string
author?: string
tags?: string[]
summary?: string
image?: string // path from /public
id?: string
path?: string
}
const { locale } = useI18n()
const { getPosts } = useBlog()
const { data: posts, pending } = await useAsyncData(
() => `blog-index-${locale.value}`,
() => getPosts(),
{ watch: [locale] }
)
// For a specific post:
const { data: post } = await useAsyncData(
() => `blog-post-${slug}-${locale.value}`,
() => getPost(slug),
{ watch: [locale] }
)
Configures all the SEO of a page (title, description, OG, Twitter).
// Static page - reads seo.pages.[pageKey].* from the locale
usePageSeo('home')
usePageSeo('me')
usePageSeo('projects')
usePageSeo('blog')
usePageSeo('resources')
// With dynamic options (overrides those from the locale)
usePageSeo('blog', {
title: () => post.value?.title, // string or () => string
description: () => post.value?.summary,
ogType: 'article', // 'website' | 'article', default: 'website'
image: () => post.value?.image,
keywords: () => post.value?.tags?.join(', '),
})
Only call in app/app.vue. Configures robots, canonical and global site defaults.
useSiteSeoDefaults() // no parameters
const { scrollToTop, getScrollTop, getScrollElement } = useBlogScroll()
scrollToTop() // smooth scroll to the top of the blog
getScrollTop() // number - current position
getScrollElement() // HTMLElement | Window - the blog scroller
Header pattern (label + title + description) + panels with GSAP.
const {
label, // ref<HTMLElement | null> - eyebrow label
titleEl, // ref<HTMLElement | null> - main title
descriptionEl, // ref<HTMLElement | null> - description/lead
setPanelRef, // (el: any) => void - use as :ref in v-for
resetPanelRefs, // () => void
setupEnterAnimations, // (options?: AnimationOptions) => void
cleanupEnterAnimations, // () => void
} = useEnterAnimations()
// AnimationOptions (all optional)
{
headerStart?: string // default: 'top 80%'
panelsStart?: string // default: 'top 80%'
headerTrigger?: HTMLElement | null
panelsTrigger?: HTMLElement | null
fadeHeader?: boolean // default: true - animates opacity
fadePanels?: boolean // default: true
}
<script setup>
const { label, titleEl, descriptionEl, setPanelRef, setupEnterAnimations, cleanupEnterAnimations } = useEnterAnimations()
onMounted(() => nextTick(() => setupEnterAnimations()))
onUnmounted(() => cleanupEnterAnimations())
</script>
<template>
<p ref="label" class="eyebrow">Label</p>
<h1 ref="titleEl">Title</h1>
<p ref="descriptionEl">Description</p>
<div v-for="item in items" :key="item.id" :ref="setPanelRef">...</div>
</template>
For custom animations without the header/panels pattern. Automatic cleanup in onBeforeUnmount.
const { setupAnimations, cleanup } = useGsapAnimations()
onMounted(() => {
nextTick(() => {
setupAnimations(() => {
// GSAP code here - automatically inside gsap.context()
const scroller = getMainScroller()
animateInOnEnter(document.querySelector('.my-el'), {
from: { opacity: 0, y: 30 },
to: { ...gsapDefaults, opacity: 1, y: 0 },
scroller,
})
})
})
})