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-5 sm:px-17 lg:px-21.5 xl:px-30.5 pro:px-33.5 2xl:px-37.5
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.15em) — 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>
Manrope — body, button, message
Font: Manrope (sans-serif), self-hosted. The default body font, set on <body> at text-18, dropping to text-16 from sm up. One variable file with a 200–800 wght axis, but the design uses two stops on it: 400 everywhere by default, and font-bold retuned to 600.
text-18 — default body copy on mobile
text-16 — default body copy from sm up
<p class="text-18 sm: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
off-white #F4F3F2
green-mid #757768
rose #9C5F62
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 declare that the ground under it is dark. It carries the inherited text-light and nothing else — painting is yours, so give the container its own background. It activates the dark: variant on every descendant, which is how headings, form fields and button chips invert without being told individually.
Use .dark-until-lg instead when the ground is dark on a phone and hands back to light from lg — a class can't be toggled per breakpoint, so the variant carries the media query. Everything inside inverts below lg and reverts above it, together. See docs/adr/0014.
Live Demo
Dark section
This block has .dark plus its own bg-green-dark. The body text colour comes along automatically — this paragraph wasn't given one.
This one adds dark:text-green-light, so it only turns green-light while inside a .dark context.
Usage
<!-- Declare a dark ground, and paint it yourself --> <section class="dark bg-green"> <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> <!-- Green on a phone, light from lg --> <section class="dark-until-lg">…</section>
.dark carries only text-light; .dark-until-lg carries max-lg:text-light. The dark: variant has two arms — it matches &:where(.dark, .dark *) at any width, and &:where(.dark-until-lg, .dark-until-lg *) only below lg.
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' | 'boxed' | 'hairline' | 'link' | 'line' | Which ported style to render. |
| reverse | boolean | false | link 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 (link variant only). |
| class | string | — | Extra classes, tailwind-merged over the variant. |
variant="underline"
The house call-to-action: a solid green-light chip with an uppercase Gilda label — 16px at 0.1em tracking, 20px of padding left and right, 10px top and bottom. Inverts to cream inside a .dark (green) section — see “On a dark surface” below.
<AppButton variant="underline">Contact us</AppButton>
variant="boxed"
A white outline over a 15% white tint rather than a fill, and the one button at 18px rather than 16px. For surfaces that are not .dark-classed but still sit over something dark — the header, whose background changes under it as the page scrolls. Hovering clears the tint.
<AppButton variant="boxed">Book now</AppButton>
variant="line"
The outlined half of the pair: a 10% green-light wash under a label and rule in the same colour at full strength — otherwise the same chip as underline. Hovering fills it in, so the two read as one set. This is the default variant. On a dark ground it swaps green-light for light throughout, keeping every one of those relationships — including a hover that still lands exactly on underline.
<AppButton variant="line">Contact us</AppButton>
variant="hairline"
Uppercase Gilda label (16px, 0.1em) + a hairline that scales on hover while the letter-spacing opens up. The “Discover more ———” treatment. What line was before it became a chip, and the only pre-chip button look still in use: the footer keeps it by choice for its nav and signup button, and AppDiscButton needs it for the label centred inside the disc.
<AppButton variant="hairline">Discover more</AppButton>
variant="link"
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. What underline was before it became a chip; for links that are not buttons — inline hyperlinks in rich text, and the footer's legal row.
<AppButton variant="link">Read our story</AppButton> <AppButton variant="link" :reverse="false">Read our story</AppButton>
Polymorphic (as / as-child)
Render as a link. Use as for the hairline variant; either as or as-child for link.
<AppButton :as="NuxtLink" to="/" variant="hairline">Home</AppButton> <AppButton as-child variant="link"> <NuxtLink to="/">Home</NuxtLink> </AppButton>
On a dark surface
No dark-mode props are needed — the ground is read from context. hairline and link inherit colour via bg-current / currentColor, so the flag's text-light is all they need. Both chips carry their own dark: inversion, since a green box or a green outline on green would disappear.
The same four inside .dark-until-lg: identical below lg, back to the light-ground treatment above it. Resize to check both.
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 |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 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 | 2 | 3 | 4 | 5 |
<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 />