# elise-studio-components

## Thumbnail carousel galleries

Set `Gallery.layout` to `thumbnail-carousel` for a large photo with square
previous/next controls and a strip of five visible thumbnails below it. At desktop
widths (1024px and up), arrows appear on photo hover or keyboard focus when the
device has a fine, hover-capable pointer. Smaller tablet/mobile viewports and
touch-only devices hide the arrows; tappable thumbnails remain available alongside swipes. Swipe left/right on the main photo to navigate on touch screens;
vertical scrolling and pinch zoom remain available, and a tap opens the lightbox. Clicking
a thumbnail selects that photo; the active border and strip stay synchronized
with the arrows. Selecting a photo smoothly shifts its thumbnail to the left
edge of the strip, stopping at the end so the last five thumbnails stay filled.
Overflow supports touch scrolling; only the strip moves, not the surrounding
page. Reduced-motion preferences disable the smooth animation.
Focused thumbnails support Left/Right arrows and Home/End as well as native
Enter/Space activation.

This layout supports category filters and independent section galleries, static
images and live property media. `carouselAccentColor` controls the arrows and
selected border, defaulting to the site's primary theme color. Existing tile
height, image fit, radius, and responsive settings still apply; the default
stage aspect ratio is 4:3. Clicking the large photo opens the existing lightbox;
`lightboxThumbnails` controls only the overlay's optional thumbnail strip.

The editor and compiler receive the new layout through the generated component
schema (version 37). Release the package and update the consuming editor/compiler
versions before selecting this layout in production.

## Curated floor plan catalogs

`FloorplanCatalog` is a grouped, selector-and-accordion presentation of live
Elise floor plans. Each authored plan is a reference to an Elise floor plan id;
references missing from the authenticated property API are not rendered.
Authored JSON controls group order, marketing copy, actions, optional image
fallbacks, and an optional square-footage marketing label. API records remain
the source of identity, bed/bath values, availability, rent, and preferred
media. The component follows all cursor pages so a curated id is not lost
beyond the first response page, and stops safely if the API repeats a cursor.

The catalog uses Studio's keyed `sm` through `2xl` responsive contract and a
semantic `collapseAt` breakpoint. Actions reuse the canonical Button contract,
navigation stays router-aware, analytics emit canonical API floor plan ids, and
images use the shared accessible Lightbox. `FloorplanExplorer` remains unchanged
for uncurated listing grids and lists. A validated `compactPresentation` object
controls accordion typography, rhythm, panel insets, and action alignment below
the collapse breakpoint without relying on arbitrary classes or site templates.

The opt-in `desktopPresentation: "workspace"` mode integrates a bounded,
scrollable grouped selector with a stable summary-and-drawing canvas for larger
catalogs. Selector headings, counts, and API-backed plan details are separately
configurable. Below `collapseAt`, `compactDisclosure: "single"` turns the same
catalog into a globally single-open inline accordion; responsive detail columns
and connected panel styling remain authored presentation choices. The default
`split`/`perGroup` behavior and API contract are unchanged.

## Motion and background media

`BackgroundMedia` is a generic frame for decorative image or muted video
backgrounds. It accepts normal Studio children, so headings, buttons, and data
components remain independently editable. The optional `parallax` motion moves
only the media layer; overlay content and document layout stay fixed. Video is
paused outside the viewport and replaced by its poster or image when the visitor
requests reduced motion. If no still is authored, a paused first video frame is
used instead.

```json
{
  "type": "BackgroundMedia",
  "props": {
    "video": { "$content": "hero.video" },
    "poster": { "$content": "hero.poster" },
    "minHeight": "90vh",
    "contentPosition": "bottom-left",
    "motion": "parallax"
  },
  "children": [
    {
      "type": "Text",
      "props": { "content": { "$content": "hero.headline" }, "variant": "display" }
    }
  ]
}
```

`Parallax` applies the same bounded, request-animation-frame scroll transform to
any child subtree. `StickyFrame` pins a subtree within its parent scroll range
and defaults to normal flow on mobile. Together with `Grid`, `Container`, and
`ScrollReveal`, these frames support sticky storytelling, independently moving
media, and layered editorial sections without site-specific JavaScript or CSS.
All motion is progressive enhancement: server output remains visible, unsupported
browsers retain a static layout, and operating-system reduced-motion preferences
disable continuous translation.

## Atlas authoring contract

The exported component schema includes optional `authoring` metadata for Atlas and
semantic validators. It describes component roles, breakpoint-aware props,
composition constraints, required context, relative performance cost, stable repair
hints, and recommended recipe IDs. Runtime rendering ignores this metadata.

The generated `dist/componentSchema.json` versions the component schema, authoring
contract, and diagnostic envelope independently. Diagnostics use a shared shape and
may include existing Studio design operations for Atlas to apply:

```json
{
  "code": "STICKY_MEDIA_OVERLAP",
  "severity": "error",
  "nodeId": "hero-media",
  "breakpoint": "sm",
  "message": "The transformed media can leave its sticky boundary.",
  "suggestedOperations": [
    {
      "type": "set_node_prop",
      "node_id": "hero-media",
      "prop_path": ["motion"],
      "value": "none"
    }
  ]
}
```

`validateAuthoringTree` is the shared, side-effect-free semantic validator used by
Atlas, preview, and compiler consumers. It checks required props, component nesting,
authoring contexts, incompatibilities, deprecations, and an optional normalized
performance budget without resolving or rendering the tree:

```ts
const { diagnostics, performanceCost } = validateAuthoringTree(page.tree, {
  availableContexts: ["property"],
  performanceBudget: 24,
  path: "pages[0].tree",
});
```

Errors represent invalid authoring decisions; warnings cover deprecations, unknown
props, and budget pressure. When a safe default exists, a diagnostic may include a
typed Studio design operation that Atlas can apply as a repair.

## Atlas composition recipes

The `@meetelise/studio-components/recipes` export and generated
`dist/authoringRecipes.json` provide versioned, validated section blueprints. Each
recipe declares its content fields, required site contexts, normalized performance
budget, and a normal Studio component tree using `$content` bindings. The initial
catalog includes editorial hero, split-media story, sticky-media story, and
availability/conversion compositions.

Recipes are build-validated against the canonical component schema and semantic
validator. A build fails if a recipe references an unknown component, an undeclared
content key, or a missing recipe ID. Atlas can therefore select a recipe, create its
declared content fields, and insert the tree through ordinary typed Studio operations.

## Amenity presentations

`AmenityList` keeps layout and ordering independent. Existing sites continue to
use the alphabetized icon-grid default, while `order="source"` preserves the
first occurrence of each amenity from either authored content or the Elise
property API. The opt-in `bulleted` layout emits semantic `ul`/`li` markup and
uses theme body typography. Its indentation, item spacing, and typography can
be adjusted through the validated `bulletPresentation` object without baking a
property's measurements into the shared component.

## Overlay navigation below preceding content

Use `Container display="grid" overlapChildren` to layer navigation over page
content while earlier content, such as a specials banner, stays in document flow:

```tsx
<Container maxWidth="none" paddingX="none" paddingY="none">
  <SpecialPromotion presentation="banner" />
  <Container display="grid" overlapChildren align="start"
    maxWidth="none" paddingX="none" paddingY="none">
    <Nav overlay positioning="layout" sticky />
    <Container maxWidth="none" paddingX="none" paddingY="none">
      <main>{/* Hero and all subsequent page sections */}</main>
      <footer>{/* Footer content */}</footer>
    </Container>
  </Container>
</Container>
```

`overlapChildren` applies only to grid containers. Keep all page content, including
the footer, in one child so sticky navigation follows the full page, and leave
ancestor overflow visible. Shared layouts can place their page `Slot` in that
content child. This composition is opt-in: `overlapChildren` defaults to `false`
and Nav's `positioning` defaults to `"auto"`.

## Navigation presentations

Nav keeps the inline, solid header and dropdown menu as its default. Sites may
opt into stacked or overlay layouts, compact links or equal-width tabs, separate
primary and utility actions, per-link desktop/mobile visibility, a scrolled logo,
an optional compact scroll effect, and a modal drawer. The existing `sticky`
flag controls viewport following while `overlay` controls whether the initial
header occupies document flow. `collapseAt` accepts a boolean or a shared
`sm`/`md`/`lg`/`xl`/`2xl` breakpoint (default: `md`). A breakpoint shows the
hamburger below that width and expands navigation at or above it. Set
`collapseAt: true` to use the hamburger at every width, or `collapseAt: false`
to keep navigation expanded at every width. Booleans select the layout mode;
users can still open and close the hamburger menu normally. Use JSON booleans,
not the strings `"true"` or `"false"`. Changing to `false` closes any open menu
and releases the drawer's scroll lock. Optional validated lengths control logo bounds,
link rhythm, content width, section padding, tab borders, and drawer width without
embedding a site template in the component. For sticky compact navigation, optional `topStyle`
and `scrolledStyle` objects can independently control validated logo height,
frame padding and gap, background color/opacity, divider, shadow, backdrop blur, and
foreground scheme. A bounded pixel threshold and whitelisted transition timing
provide a two-state CSS transition with an optional logo crossfade; reduced-motion
preferences disable that motion. Button-style links consume the canonical Button
presentation contract. Omitting the presentation fields preserves the existing
inline/dropdown behavior.

## Footer presentations

Footer keeps its columns-and-copyright output as the default. The optional
`property` presentation composes structured brand, address, contact, action,
badge, and legal content inside one semantic footer. Sections and responsive
grids follow the content that is present; actions reuse Button's canonical
presentation and routing behavior. `propertyLayout` and keyed
`responsivePropertyLayout` overrides use Studio's shared breakpoints and
validated CSS lengths, while colors come from the active theme.

## Responsive layout overrides

Container and Stack keep their token spacing as the default design language. For
layouts that need exact authored geometry, Container also accepts optional base
edge padding values and keyed `responsivePadding` overrides. Studio uses the same
mobile-first scale as Tailwind: `sm` (40rem), `md` (48rem), `lg` (64rem), `xl`
(80rem), and `2xl` (96rem). Stack and Grid use the same duplicate-free keyed
contract for authored gaps. Values are limited to simple CSS lengths with finite,
unit-specific bounds; malformed and extreme declarations are ignored. Container
bottom margin is signed so layout composition can intentionally overlap sections.

Button supports the same opt-in pattern for base/responsive font size, weight,
line-height, tracking, horizontal/vertical padding, border width, inset outline,
and bounded shadow presets. `responsiveTypography` can override font size, line
height, or signed letter spacing at the shared breakpoints. Focus presentation is
site-wide rather than per button: token themes may define `focus.ring.width`,
`focus.ring.offset`, and `focus.ring.color`. Unsafe values are ignored, and the
native focus indicator remains the fallback. Omitting presentation fields
preserves the existing variant/size output.

Background-image sections can opt into `bgAttachment` and
`responsiveBgAttachment` (`scroll` or `fixed`). Omitting both preserves the
browser default while allowing authored fixed-background behavior at any shared
breakpoint without runtime-authored CSS classes. Background position supports
edges and all four corners. `bgOverlayOpacity` controls the overlay layer
independently from its color and is bounded to the CSS opacity range.

## Coordinate-aware property maps

`PropertyMap` renders a property coordinate on the same map runtime used by
Studio's unit marketplace. Its default MapLibre style is served by Elise; an
installation may instead configure Mapbox or an alternate style through the
existing Studio runtime configuration. Provider credentials are platform
configuration and are never authored into site JSON.

Authors control center, zoom, responsive height, the property marker, and an
optional list of nearby-place markers. Every marker can use an image or safe
custom/category color, a structured information card, and a generated or custom
Google Maps view/directions action. When nearby places are present the map fits
the property and visible places by default; authors may opt back into manual
center and zoom. Only one card opens at a time and it remains attached to its
coordinate while the visitor pans or zooms, flips and clamps inside the map
viewport, supports phone, email, and Google Maps actions, and can be dismissed
and reopened. Responsive values use Studio's keyed `sm` through `2xl` contract.
`MapEmbed` remains the lightweight iframe option and retains its existing output
and TypeScript `markers` input.

## Contact form presentations

`ContactForm` keeps its existing comfortable, stacked lead form as the default
and continues to submit through Elise's authenticated `/v1/prospect` endpoint.
Opt-in grid layout, one-to-three columns, compact density, grouped names,
configurable fields, labels, and requiredness are independent authoring choices.
The native date input remains the default; `usText` accepts editable numeric or
punctuated input, formats it on blur, validates the calendar date, and sends the
existing ISO `move_in_date` payload.

Base and keyed responsive values can control row, column, and label-to-control
gaps. A bounded control-presentation object covers minimum heights, padding,
border, radius, and label typography without embedding a site template. Submit
buttons consume the canonical Button presentation contract and add independent
base/responsive alignment. Invalid authored CSS values are ignored, fields keep
theme colors and visible focus, and validation errors identify and focus the
affected control.

## Property SEO template tokens

A `building_template: true` page may use the following compile-time tokens in
plain string fields. Resolution is recursive, so the same tokens work in the
page path, search/social metadata, and nested `seo.structuredData` strings.

| Token | Merged content source |
| --- | --- |
| `{{property.name}}` | `building.name` |
| `{{property.slug}}` | `vars.slug` |
| `{{property.city}}` | `building.city` |
| `{{property.state}}` | `building.state` |
| `{{property.neighborhood}}` | `building.neighborhood` |
| `{{property.address}}` | `building.address` |
| `{{property.shortDescription}}` | `building.short_description`, then `building.tagline` |
| `{{property.amenitySummary}}` | `building.amenity_summary` |
| `{{property.bedroomRange}}` | `building.bedroom_range` |
| `{{site.name}}` | `site.name`, then merged `meta.name` / `meta.title`, then raw site name metadata |

The existing `{{slug}}` syntax remains an alias for `vars.slug`. A token whose
source is missing stays visibly unresolved so publish-time SEO checks can report
the missing data. Runtime `{$data: "..."}` bindings are never interpolated.

Property hero images are intentionally not string tokens. Author an image or
social-image field with the building-scoped object binding
`{$content: "building.image"}`; the resolver converts its `AssetValue` to that
building's CDN URL.

## Typography theme tokens

Typography is configured by role so a bounded token set can cover every Text
variant. All tokens are optional. Without an authored role override, each
variant retains its built-in responsive size, weight, line-height, and tracking.

| Role | Family | Scale | Weight | Line height | Tracking |
| --- | --- | --- | --- | --- | --- |
| Heading (`display`, `h1`–`h3`) | `font.heading` | `font.heading.scale` | `font.heading.weight` | `font.heading.line-height` | `font.heading.tracking` |
| Body (`subtitle`, `body`, `body-lg`, `caption`, `overline`) | `font.body` | `font.body.scale` | `font.body.weight` | `font.body.line-height` | `font.body.tracking` |

Scale is a unitless multiplier such as `"1.1"`. Weight and line-height accept
CSS values such as `"600"` and `"1.4"`; tracking accepts a CSS letter-spacing
value such as `"-0.02em"`. All values may use the existing `{$token}` alias
shape, and building-mode overrides inherit through `ThemeScope`.

Individual `Text` nodes can override base font size, weight, line height,
letter spacing, transform, and alignment. The keyed `responsiveTypography`
object uses the same `sm` through `2xl` contract as layout and Button, with
optional font-size, line-height, and signed letter-spacing values at each
minimum viewport. Values are validated and bounded before rendering; invalid
authored values are ignored without removing the variant defaults.

Body Google fonts include the regular italic face in addition to the upright
weight range, allowing semantic emphasis to use the selected family without
requesting every italic weight. Duplicate stylesheet requests are suppressed.
