Design System
Main content wrapper. Adds responsive horizontal padding and the red side borders that frame the layout.
Live Demo
This div has .app-container — notice the side borders and horizontal padding
Usage
<div class="app-container"> <!-- page content --> </div>
Expands to: px-5 sm:px-7.5 xl:px-9 border-x-5 border-secondary
Non-typography, non-color-scheme reusable CSS classes.
Circular button container. Uses Nikkei Line font, uppercase, aspect-ratio 1, and centers content.
<div class="elipse color-scheme-secondary w-24">Taco</div>
Animated underline that slides in from right on hover, slides out to left on leave. Use on links or interactive text.
<!-- Underline slides in on hover --> <a class="ui-underline-anim">Link text</a> <!-- Underline pre-shown, recedes on hover --> <a class="ui-underline-anim reverse">Link text</a>
Responsive aspect ratio: 9/20 on mobile → 3/4 on tablet → 16/9 on desktop.
<div class="aspect-screen">...</div>
Infinite horizontal scroll animation (20s linear loop). Use on a flex row of duplicated items inside an overflow-hidden container. Typically used via AppInfiniteCarousel.
<div class="overflow-hidden">
<div class="animate-slide-infinite flex gap-8 w-max">
<span>Item</span><span>Item</span>
<!-- duplicate items for seamless loop -->
<span>Item</span><span>Item</span>
</div>
</div>Extended Tailwind variants defined in tailwind.css.
tap:
Targets touch devices only (@media (hover: none)). Use for touch-specific styles.
<div class="opacity-0 tap:opacity-100">Touch only</div>
active-class:
Targets elements with the .active class (Vue Router active links).
<NuxtLink class="active-class:font-bold">Nav Link</NuxtLink>
exact-active:
Targets elements with .router-link-exact-active class.
<NuxtLink class="exact-active:underline">Nav Link</NuxtLink>
tight:
Targets landscape viewports under 800px height (phones in landscape).
<div class="py-20 tight:py-8">Section</div>
All typography classes use responsive viewport-relative sizing. Apply to any element.
Font: Bella Rose (serif). Use for hero decorative headings.
accent-tattoo-large-1
accent-tattoo-large-2
accent-tattoo-large-3
accent-tattoo-large-4
accent-tattoo-large-5
accent-tattoo-large-6
<h1 class="accent-tattoo-large-1">Heading</h1>
Font: Bella Rose (serif). Medium decorative headings.
accent-tattoo-1
accent-tattoo-2
accent-tattoo-3
accent-tattoo-4
<h2 class="accent-tattoo-1">Section Title</h2>
Font: Nikkei Line (sans-serif). Ultra-bold all-caps. Use for hero headings.
display-large-1
display-large-2
<h1 class="display-large-1">HERO HEADING</h1>
Font: Nikkei Line (sans-serif). Main heading styles, all-caps.
display-1
display-2
display-3
display-4
display-5
display-6
display-7
<h2 class="display-2">Section Heading</h2>
Font: Nikkei Line (sans-serif). Small caps for labels, metadata, UI elements.
display-small-1
display-small-2
display-small-3
display-small-4
display-small-5
display-small-6
display-small-7
<p class="display-small-3">LABEL TEXT</p>
Font: Helvetica (sans-serif). Primary reading text. Use for paragraphs, descriptions, UI copy.
body-lg — Large body text for intros and callouts
body — Standard body text for general content
body-s — Small body text for secondary content
body-xs — Extra small for captions, metadata
body-xxs — Micro text for legal, fine print
<p class="body">Standard paragraph text</p> <p class="body-s">Secondary information</p> <caption class="body-xxs">Fine print</caption>
Font: Palette Becker (serif). Decorative accent text for quotes, artistic callouts.
artistic-accent-lg — Larger artistic text
artistic-accent — Standard artistic accent
<blockquote class="artistic-accent-lg">"Quote text"</blockquote>
Apply to any container to set background and text color as a paired theme. Each class sets background-color and color together.
.color-scheme-main
Primary bg (#FFDEDE) + Secondary text (#EC1B26)
.color-scheme-secondary
Secondary bg (#EC1B26) + Primary text (#FFDEDE)
.color-scheme-secondary-accent
Secondary bg (#EC1B26) + Accent text (#F8EF54)
.color-scheme-accent
Accent bg (#F8EF54) + Secondary text (#EC1B26)
.color-scheme-accented-primary
Primary darker bg (#FCD5D5) + Secondary text
.color-scheme-accented-dark
Dark lighter bg (#ECD0D4) + Dark text (#3F5676)
.color-scheme-transparent
Transparent bg + secondary border, use on colored parent
.color-scheme-auto
Adapts to parent color scheme context
<section class="color-scheme-main"> <h2>Primary themed section</h2> </section> <!-- Nest color-scheme-auto inside a scheme parent --> <div class="color-scheme-secondary"> <div class="color-scheme-auto">Adapts automatically</div> </div>
All App-prefixed components. Auto-imported — no import needed in <script>.
Prismic-aware link component. Resolves internal vs external links, supports all AppButton variants. Use instead of a bare <a> when the href comes from Prismic.
| Prop | Type | Required | Description |
|---|---|---|---|
| field | LinkField | Yes | Prismic link field object |
| variant | string | No | Same options as AppButton variant |
| size | string | No | Same options as AppButton size |
<!-- Plain link (no styling) -->
<AppLink :field="slice.primary.cta_link" />
<!-- Styled as block button -->
<div class="color-scheme-main">
<AppLink :field="slice.primary.cta_link" variant="block">
Click Here
</AppLink>
</div>
<!-- Styled as underline text link -->
<AppLink :field="slice.primary.link" variant="underline-star-hover" />Decorative wrapper that places a star SVG at a corner. The parent must be relative.
| Prop | Type | Default | Options |
|---|---|---|---|
| as | string | "figure" | Any HTML tag |
| position | string | "left" | left | right |
| sizeClass | string | "w-16 aspect-square" | Tailwind width + aspect classes |
| positionClass | string | "top-0 -translate-y-1/2 -translate-x-1/2" | Tailwind position offset classes |
Live Demo
Content inside frame
<!-- Parent must be relative -->
<div class="relative w-80 h-40 color-scheme-secondary">
<AppStarFrame position="left" sizeClass="w-20 aspect-square">
<p>Content</p>
</AppStarFrame>
</div>
<!-- Star on right side -->
<div class="relative color-scheme-accent">
<AppStarFrame position="right" />
</div>Displays a live countdown to a target time. Renders nothing if target is in the past. Uses GMT+8 timezone.
| Prop | Type | Required | Description |
|---|---|---|---|
| targetTime | Date | number | Yes | Date object or millisecond timestamp |
Live Demo
<script setup> // 3 hours from now const target = new Date(Date.now() + 3 * 60 * 60 * 1000) </script> <template> <AppCountdown :targetTime="target" /> </template>
Displays "Opening in" or "Closing in" based on current GMT+8 time vs provided hours. Wraps AppCountdown.
| Prop | Type | Format | Description |
|---|---|---|---|
| openingTime | string | "6:00 PM" or "18:00" | Restaurant opening time (GMT+8) |
| closingTime | string | "11:00 PM" or "23:00" | Restaurant closing time (GMT+8) |
Live Demo
<AppTimeToOpenClose openingTime="6:00 PM" closingTime="11:00 PM" />
Infinite horizontal scrolling carousel. Use AppInfiniteCarouselHoverStop for the GSAP-powered version that pauses on hover. Content is duplicated in the slot — the component creates a second copy for the seamless loop.
| Prop | Type | Description |
|---|---|---|
| durationPerItem | number | Milliseconds per item (used to calculate total speed) |
| itemsLength | number | Number of items (used with durationPerItem) |
Live Demo — hover to pause
<!-- CSS-only version --> <AppInfiniteCarousel :items-length="5" :duration-per-item="3000"> <span>Item 1</span> <span>Item 2</span> </AppInfiniteCarousel> <!-- GSAP version with hover pause --> <AppInfiniteCarouselHoverStop :items-length="5" :duration-per-item="3000"> <span>Item 1</span> <span>Item 2</span> </AppInfiniteCarouselHoverStop>
Carousel specifically for text strings with star separators. Automatically expands the list to at least 10 items for a full-width loop.
| Prop | Type | Required |
|---|---|---|
| texts | KeyTextField[] | Yes |
| uniqueKey | string | Yes — for v-key generation |
Live Demo
<script setup>
const texts = ['Open Now', 'Best Tacos', 'Fresh Daily', 'Est. 2020']
</script>
<template>
<AppSectionTextInfiniteCarousel
:texts="texts"
unique-key="my-carousel"
/>
</template>Image gallery with navigation controls and optional autoplay. Smart lazy loading: loads image 1 on init, image 2 on hover, rest on interaction.
| Prop | Type | Required | Description |
|---|---|---|---|
| images | ImageType[] | Yes | Array of { url, alt? } objects |
| uniqueKey | string | Yes | Unique ID for key generation |
| autoplay | number | No | Autoplay interval in ms (e.g. 3000) |
| hideButtons | boolean | No | Hide prev/next buttons |
| overwriteNavigationClass | string | No | Custom class for nav container |
<script setup>
const images = [
{ url: '/images/dish-1.jpg', alt: 'Tacos' },
{ url: '/images/dish-2.jpg', alt: 'Burritos' },
{ url: '/images/dish-3.jpg', alt: 'Nachos' },
]
</script>
<template>
<AppImageCarousel
:images="images"
unique-key="menu-gallery"
:autoplay="4000"
/>
</template>Flexible video component supporting MP4, WebM, and YouTube. Lazy loads 500px before viewport entry. Source priority: WebM > MP4 > YouTube.
| Prop | Type | Default | Description |
|---|---|---|---|
| posterImage | ImageField | Required | Prismic image shown before video loads |
| mp4 | string | — | MP4 video URL |
| webm | string | — | WebM video URL (preferred) |
| youtubeUrl | string | — | YouTube video URL |
| playbackControl | boolean | number | — | true/false = play/pause; number = seek position |
| loop | boolean | true | Loop video playback |
| isLazyLoaded | boolean | true | Enable intersection observer lazy loading |
| isMuted | boolean | true | Mute video (required for autoplay) |
<!-- Self-hosted video (autoplay, muted, looped) --> <AppVideo :poster-image="slice.primary.poster" webm="/videos/hero.webm" mp4="/videos/hero.mp4" :playback-control="true" /> <!-- YouTube embed --> <AppVideo :poster-image="slice.primary.poster" youtube-url="https://youtu.be/VIDEO_ID" :playback-control="true" :loop="false" />
Renders Prismic rich text fields with custom heading and body styles. Supports Prismic label spans to apply any typography class (e.g. "Nikkei Line 60px" label → display-1 class). Links within rich text get underline animations.
| Prop | Type | Description |
|---|---|---|
| field | RichTextField | Prismic rich text field value |
<AppRichText :field="slice.primary.body" />
Renders a list of Prismic link fields as styled buttons. The Prismic link variant field controls which AppButton variant is used.
| Prop | Type | Description |
|---|---|---|
| links | Repeatable<LinkField> | Prismic repeatable link group |
| centered | boolean | Center-align the list |
| buttonSize | string | sm | md | full |
| buttonClass | string | Extra class for each button |
Prismic variant → AppButton variant mapping
"Block" → variant="block"
"Block Transparent" → variant="block" class="color-scheme-transparent"
"Block With Border Decoration" → variant="block-border-decoration"
"Underline" → variant="underline"
"Star Hover" → variant="star-hover"
"Underline With Star Hover" → variant="underline-star-hover"
<div class="color-scheme-main"> <AppNavList :links="slice.items" :centered="true" button-size="md" /> </div>
Combines AppRichText with an optional AppNavList below it. Supports max-width constraints. Use as the standard content + CTA pattern.
| Prop | Type | Default | Description |
|---|---|---|---|
| text | RichTextField | Required | Prismic rich text |
| links | Repeatable<LinkField> | null | Optional CTA links |
| as | any | "section" | Wrapper element tag |
| centered | boolean | false | Center text and links |
| maxWidth | string | null | "Max Width: 339px" | "400px" | "665px" | "845px" |
| containerClass | string | — | Class on content wrapper |
| navContainerClass | string | — | Class on nav list wrapper |
<AppContentBlock :text="slice.primary.body" :links="slice.items" :centered="true" max-width="Max Width: 665px" />
Creates a custom cursor that appears when hovering a zone. The cursor element teleports to #mouse-follow (provided by TheMouseLayer). Only shows on non-touch devices.
Slot API
#cursor — Content rendered inside the custom cursor
default — The hover zone content (receives mouseFollowEffect state)
<AppMouseFollowEffect>
<!-- Custom cursor content -->
<template #cursor>
<div class="elipse color-scheme-accent w-24">View</div>
</template>
<!-- Hover zone -->
<div class="relative overflow-hidden">
<img src="..." />
</div>
</AppMouseFollowEffect>