Design System
Component & Class Reference
app-container
Main content wrapper. Adds responsive horizontal padding.
Live Demo
This div has .app-container — notice the horizontal padding
Usage
<div class="app-container"> <!-- page content --> </div>
Expands to: px-6 sm:px-20 xl:px-15
Utility Classes
Non-typography, non-color reusable CSS classes defined in tailwind.css.
.no-scrollbar
Hides the scrollbar on a scrollable element while keeping it scrollable. Works across WebKit and Firefox.
<div class="no-scrollbar overflow-x-auto">...</div>
.bg-curve-size
Responsive background-image sizing helper: no-repeat, switching to bg-cover at xl. Intended for the project's curve/texture background images (see assets/images) — pair it with a bg-[url(...)] utility. Per-breakpoint image swapping isn't wired up yet, so there's no live demo here.
<div class="bg-curve-size bg-[url(...)]">...</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>
children:
Applies styles to all descendants of the element (& *).
<div class="children:text-green">Every child text is green</div>
active-class:
Targets elements with the .active class (e.g. Vue Router active links).
<NuxtLink class="active-class:font-bold">Nav Link</NuxtLink>
exact-active:
Targets elements with the .router-link-exact-active class, or any element containing one.
<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
Per the Two Moons responsiveness system, the root html font-size scales with viewport width (text-mobile sm:text-ipad-mini lg:text-ipad-pro xl:text-desktop 2xl:text-2xl), and every named class below is expressed in fixed rem sizes relative to that root — never in Tailwind's built-in text-sm/text-lg scale. Resize the window to see these scale proportionally.
Ginger — display
Font: ginger (custom display face). Main heading styles.
display-1
display-2
display-3
display-4
display-5
display-6
<h1 class="display-1">Hero Heading</h1> <h2 class="display-3">Section Heading</h2>
Ginger — emphasis
Font: ginger (custom display face). Used for <strong> emphasis inside ginger-styled copy.
Plain ginger text with emphasized strong text inside it
<p class="font-ginger text-green-light"> Plain text with <strong>emphasis</strong> </p>
Gilda Display — accent
Font: Gilda Display (serif). Uppercase tracked label face for buttons, nav items and eyebrows. Each class bundles uppercase and tracking-accent (0.3em) — override locally where a call site deviates.
accent-1 — 14px fixed
accent-2 — 13px → 14px (sm)
accent-3 — 12px → 13px (sm)
<h1 class="accent-1">Discover</h1> <button class="accent-2">Book now</button> <a class="accent-3">Nav item</a>
Lato — body, button, message
Font: Lato (sans-serif). Default body font set on <body>.
text-16 — default body copy
<p class="text-16">Body copy</p> <button class="text-button">Click me</button> <p class="text-message">Large quote or message</p>
Colors
The project palette, defined as Tailwind theme colors. Body text defaults to green-dark, swapping to light in dark mode.
light #F9F9F8
black #000000
white #ffffff
green #5B604A
green-light #848B6F
green-dark #414141
<section class="bg-green text-light"> <h2>Themed section</h2> </section>
Dark
Add .dark to any container to flip it onto the dark theme: it applies bg-green-dark and text-light, so everything inside reads correctly with no per-element changes. It also activates the dark: variant on every descendant — use it to override individual utilities only when the theme flip isn't enough.
Live Demo
Dark section
This whole block just has .dark on its wrapper. The background and body text colour come along automatically — this paragraph wasn't given a text colour.
This one adds dark:text-green-light, so it only turns green-light while inside a .dark context.
Usage
<!-- Flip a whole section onto the dark theme --> <section class="dark"> <h2>Heading</h2> <p>Body text is light automatically.</p> <!-- Override a single element only in dark context --> <p class="dark:text-green-light">Muted line</p> </section>
.dark expands to bg-green-dark text-light. The dark: variant matches &:where(.dark, .dark *) — the element itself when it has .dark, or any element nested inside one.
Breakpoints
Default Tailwind breakpoints, plus one project-specific addition. Each corresponds to a design artboard width — see CONTEXT.md.
| Prefix | Min width |
|---|---|
| (default) | 0px |
| sm: | 640px |
| lg: | 1024px |
| pro: | 1350px (84.375rem) — custom |
| xl: | 1280px |
| 2xl: | 1536px |
Reusable Vue Components
Components follow the shadcn philosophy: a single styled primitive whose look is driven by a variant prop, styles composed with cva (button-variants.ts) and merged with the cn() helper (app/lib/utils.ts).
AppButton
A polymorphic button primitive (renders as <button> by default, or anything via as / as-child). It carries the two button styles ported from the legacy ../swasti project. Colour comes from currentColor, so a button inherits its context — hover any of these.
| Prop | Type | Default | Notes |
|---|---|---|---|
| variant | 'line' | 'underline' | 'line' | Which ported style to render. |
| reverse | boolean | false | underline only — starts drawn, wipes out on hover. |
| as | string | Component | 'button' | Element/component to render as. |
| as-child | boolean | false | Merge onto the single child instead of a wrapper (underline variant only). |
| class | string | — | Extra classes, tailwind-merged over the variant. |
variant="line"
Uppercase Gilda label + a hairline that scales on hover while the letter-spacing opens up.
<AppButton variant="line">Discover more</AppButton>
variant="underline"
A hairline underline that starts drawn and wipes out on hover. Pass :reverse="false" for the opposite: start hidden and wipe in from the right on hover.
<AppButton variant="underline">Read our story</AppButton> <AppButton variant="underline" :reverse="false">Read our story</AppButton>
Polymorphic (as / as-child)
Render as a link. Use as for the line variant; either as or as-child for underline.
<AppButton :as="NuxtLink" to="/" variant="line">Home</AppButton> <AppButton as-child variant="underline"> <NuxtLink to="/">Home</NuxtLink> </AppButton>
On a dark surface
Colour is inherited via bg-current / currentColor, so no dark-mode props are needed.
AppCarousel
Hand-built against the shadcn-vue anatomy, wrapping embla-carousel-vue as the headless engine. Five auto-imported parts share context via carousel-context.ts. Drag/swipe, arrow keys, and the nav buttons all scroll the track; Previous disables on the first slide, Next on the last.
<AppCarousel class="w-full max-w-xl">
<AppCarouselContent>
<AppCarouselItem v-for="n in 5" :key="n" class="md:basis-1/2">
<div class="flex aspect-square items-center justify-center border border-current">
</div>
</AppCarouselItem>
</AppCarouselContent>
<AppCarouselPrevious />
<AppCarouselNext />
</AppCarousel>AppCalendar
A single composite wrapping the reka-ui Calendar primitives — no context file, no per-part wrappers, since Reka's CalendarRoot already provides state to every child. Prev/next page the months, keyboard arrows move the focused day, the selected day fills, today is underlined, and days outside the month dim.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
28 | 29 | 30 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 1 |
<script setup>
import { getLocalTimeZone, today } from '@internationalized/date'
const date = ref(today(getLocalTimeZone()))
</script>
<AppCalendar v-model="date" locale="en" />AppContentBlock
Prismic bridge stack: AppRichText (renders a rich-text field through PrismicRichText, mapping heading/paragraph/label/hyperlink nodes) over AppNavList (a repeatable group of Link fields, each through the AppLink bridge). CTAs resolve reusable-CTA documents transparently since they flow through the same bridge.
Stay With Us
Nestled in the hills, Swasti offers a quiet escape. Learn more about our rooms.
<AppContentBlock :text="slice.primary.body" :links="slice.items" centered />