Design System

Component & Class Reference

app-container

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

Utility Classes

Non-typography, non-color-scheme reusable CSS classes.

.elipse

Circular button container. Uses Nikkei Line font, uppercase, aspect-ratio 1, and centers content.

Taco
Menu
Go
<div class="elipse color-scheme-secondary w-24">Taco</div>

.ui-underline-anim

Animated underline that slides in from right on hover, slides out to left on leave. Use on links or interactive text.

Hover me (default)Hover me (reverse)
<!-- 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>

.aspect-screen

Responsive aspect ratio: 9/20 on mobile → 3/4 on tablet → 16/9 on desktop.

aspect-screen
<div class="aspect-screen">...</div>

.animate-slide-infinite

Infinite horizontal scroll animation (20s linear loop). Use on a flex row of duplicated items inside an overflow-hidden container. Typically used via AppInfiniteCarousel.

TacoBurritoQuesadillaTacoBurritoQuesadilla
<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>

Custom Tailwind Variants

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>

Typography System

All typography classes use responsive viewport-relative sizing. Apply to any element.

Bella Rose — Accent Tattoo Large

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>

Bella Rose — Accent Tattoo

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>

Nikkei Line — Display Large

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>

Nikkei Line — Display

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>

Nikkei Line — Display Small

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>

Helvetica — Body

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>

Palette Becker — Artistic Accent

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>

Color Scheme Classes

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>

AppButton

The primary interactive element. Auto-routes to block or text sub-components based on variant. Color is inherited from parent color scheme — always wrap in a color scheme class.

Props

PropTypeDefaultOptions
variantstring"block"block | block-border-decoration | underline | star-hover | underline-star-hover
sizestring"default"full | default | md | sm
asany"button"Any HTML tag or component
asChildbooleanfalseMerge with child element via Primitive
classstringAdditional CSS classes

Block Variants

Filled button with padding. Color comes from applied color scheme class.

variant="block" — sizes: default / md / sm / full

variant="block-border-decoration" — adds inner outline border

<div class="color-scheme-main">
  <AppButton variant="block">Button</AppButton>
  <AppButton variant="block" size="md">Medium</AppButton>
  <AppButton variant="block" size="sm">Small</AppButton>
  <AppButton variant="block" size="full">Full Width</AppButton>
</div>

<div class="color-scheme-secondary">
  <AppButton variant="block-border-decoration">With Border</AppButton>
</div>

Text Variants

Text-only buttons. Wrap in a color scheme for correct text color.

underline

star-hover

underline-star-hover

<div class="color-scheme-main">
  <AppButton variant="underline">Underline</AppButton>
  <AppButton variant="star-hover">Star Hover</AppButton>
  <AppButton variant="underline-star-hover">Both</AppButton>
</div>

Color Scheme Combinations

Buttons inherit color from their container. Mix and match.

color-scheme-main

color-scheme-secondary

color-scheme-accent

Rendering as Other Elements

Use as to render the button as a different HTML element while keeping all styles.

<!-- Default: renders as <button> -->
<AppButton variant="block">Button</AppButton>

<!-- Render as anchor tag -->
<AppButton variant="block" as="a" href="/menu">Go to Menu</AppButton>

<!-- Render as NuxtLink (use AppLink instead for Prismic links) -->
<AppButton variant="block" as="NuxtLink" to="/menu">Go to Menu</AppButton>

Reusable Vue Components

All App-prefixed components. Auto-imported — no import needed in <script>.

AppLink

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.

PropTypeRequiredDescription
fieldLinkFieldYesPrismic link field object
variantstringNoSame options as AppButton variant
sizestringNoSame 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" />

AppStarFrame

Decorative wrapper that places a star SVG at a corner. The parent must be relative.

PropTypeDefaultOptions
asstring"figure"Any HTML tag
positionstring"left"left | right
sizeClassstring"w-16 aspect-square"Tailwind width + aspect classes
positionClassstring"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>

AppCountdown

Displays a live countdown to a target time. Renders nothing if target is in the past. Uses GMT+8 timezone.

PropTypeRequiredDescription
targetTimeDate | numberYesDate 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>

AppTimeToOpenClose

Displays "Opening in" or "Closing in" based on current GMT+8 time vs provided hours. Wraps AppCountdown.

PropTypeFormatDescription
openingTimestring"6:00 PM" or "18:00"Restaurant opening time (GMT+8)
closingTimestring"11:00 PM" or "23:00"Restaurant closing time (GMT+8)

Live Demo

<AppTimeToOpenClose
  openingTime="6:00 PM"
  closingTime="11:00 PM"
/>

AppInfiniteCarousel / HoverStop

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.

PropTypeDescription
durationPerItemnumberMilliseconds per item (used to calculate total speed)
itemsLengthnumberNumber of items (used with durationPerItem)

Live Demo — hover to pause

TACOSBURRITOSQUESADILLASENCHILADASNACHOS
TACOSBURRITOSQUESADILLASENCHILADASNACHOS
<!-- 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>

AppSectionTextInfiniteCarousel

Carousel specifically for text strings with star separators. Automatically expands the list to at least 10 items for a full-width loop.

PropTypeRequired
textsKeyTextField[]Yes
uniqueKeystringYes — for v-key generation

Live Demo

  • Best Tacos in Town
  • Fresh Every Day
  • Open Late
  • Come Hungry
  • Best Tacos in Town
  • Fresh Every Day
  • Open Late
  • Come Hungry
<script setup>
const texts = ['Open Now', 'Best Tacos', 'Fresh Daily', 'Est. 2020']
</script>

<template>
  <AppSectionTextInfiniteCarousel
    :texts="texts"
    unique-key="my-carousel"
  />
</template>

AppImageCarousel

Image gallery with navigation controls and optional autoplay. Smart lazy loading: loads image 1 on init, image 2 on hover, rest on interaction.

PropTypeRequiredDescription
imagesImageType[]YesArray of { url, alt? } objects
uniqueKeystringYesUnique ID for key generation
autoplaynumberNoAutoplay interval in ms (e.g. 3000)
hideButtonsbooleanNoHide prev/next buttons
overwriteNavigationClassstringNoCustom 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>

AppVideo

Flexible video component supporting MP4, WebM, and YouTube. Lazy loads 500px before viewport entry. Source priority: WebM > MP4 > YouTube.

PropTypeDefaultDescription
posterImageImageFieldRequiredPrismic image shown before video loads
mp4stringMP4 video URL
webmstringWebM video URL (preferred)
youtubeUrlstringYouTube video URL
playbackControlboolean | numbertrue/false = play/pause; number = seek position
loopbooleantrueLoop video playback
isLazyLoadedbooleantrueEnable intersection observer lazy loading
isMutedbooleantrueMute 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"
/>

AppRichText

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.

PropTypeDescription
fieldRichTextFieldPrismic rich text field value
<AppRichText :field="slice.primary.body" />

AppNavList

Renders a list of Prismic link fields as styled buttons. The Prismic link variant field controls which AppButton variant is used.

PropTypeDescription
linksRepeatable<LinkField>Prismic repeatable link group
centeredbooleanCenter-align the list
buttonSizestringsm | md | full
buttonClassstringExtra 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>

AppContentBlock

Combines AppRichText with an optional AppNavList below it. Supports max-width constraints. Use as the standard content + CTA pattern.

PropTypeDefaultDescription
textRichTextFieldRequiredPrismic rich text
linksRepeatable<LinkField>nullOptional CTA links
asany"section"Wrapper element tag
centeredbooleanfalseCenter text and links
maxWidthstringnull"Max Width: 339px" | "400px" | "665px" | "845px"
containerClassstringClass on content wrapper
navContainerClassstringClass on nav list wrapper
<AppContentBlock
  :text="slice.primary.body"
  :links="slice.items"
  :centered="true"
  max-width="Max Width: 665px"
/>

AppMouseFollowEffect

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>