
@layer lv-island {

/* THE PLATFORM BASE LAYER (Batch 4, 27 Sep 2026): defaults every V5 page gets, whatever the
   design. They live in this layer, the first one a V5 page declares (document.ts links this file
   before any other stylesheet) and so the lowest in its cascade: an unlayered rule the design
   writes always wins, whatever its specificity.

   No sideways scroll, ever, on a phone or a desktop (Michael's rule). overflow-x on the BODY, and
   clip rather than hidden:
   - On the body, never the root: while html's own overflow is visible (a design rarely sets it),
     the body's value is PROPAGATED to the viewport (CSS Overflow 3), and clip applied to the
     viewport is treated as hidden. So the viewport cannot be panned sideways, the body itself
     stays visible, and position:sticky keeps working against the viewport exactly as before.
     Setting it on html instead would stop that propagation, and a design's own
     body{overflow-x:hidden} (Shipwrights Cottage has one) would then turn the body into a scroll
     container of its own, which is what breaks a sticky header.
   - clip, not hidden, for the case where it is NOT propagated (a design that sets html's own
     overflow): clip never makes the body a scroll container, hidden would. overflow:clip is in
     every current engine (Safari and iOS 16 on, Chrome 90, Firefox 81); the hidden declaration
     before it is only what an older engine keeps.
   A design that says something else for the body (The Cedar Gable writes clip itself,
   Shipwrights Cottage hidden) wins outright, as everywhere in this file. */
body{overflow-x:hidden;overflow-x:clip}

/* Tap sizes in the platform's own pieces: every link and control the platform draws is at least
   44 by 44 CSS px to tap (the figure Apple and WCAG 2.5.5 give for a finger).
   - What the platform styles itself (the menu's summary and panel links, the cookie notice's two
     buttons, the default footer block, the lightbox's controls, the islands' fields and buttons)
     is simply that big: see each piece's own rule below.
   - A link the platform writes INTO the design's own markup, with the design's own look (the
     legal links, "Cookie settings", "Powered by Lettiva", the share links: platform-link.ts),
     must not be resized, or the design's footer moves. It carries an empty <lv-hit> instead,
     stretched here, invisibly, to at least 44px each way around the link's own centre (a link
     already that big gets nothing extra: min() keeps each edge at 0). position:relative on the
     link is what makes the link, not some outer box, the hit area's reference; both are plain
     layered declarations like everything else in this file. The one cost is honest: where two
     such links sit closer than 44px apart, the later one's hit area overlaps the edge of the
     earlier one's. A design's own gallery-link and portfolio-link are the design's to size
     (platform-link.ts says why), and the render tool measures them. */
a[data-lv-tap]{position:relative}
a[data-lv-tap]>lv-hit{position:absolute;top:min(0px, calc(50% - 22px));right:min(0px, calc(50% - 22px));bottom:min(0px, calc(50% - 22px));left:min(0px, calc(50% - 22px))}

/* Nothing upstream sets box-sizing: without this, an input at width:100% plus this file's own
   padding and border would overflow its .fields cell on a design with no global reset of its own. */
.island,.island *,.island *::before,.island *::after{box-sizing:border-box}

/* Photograph sizing floor (docs/SPEC-v5-site-runtime-2026-09-19.md): the renderer writes every
   <img>'s SOURCE pixel width/height as attributes so a guest's browser can reserve the right box
   before the file arrives (no layout shift), good, right up until a design sizes that same
   photograph with its own CSS width (100%, a grid track, whatever) and states no height of its
   own. Once that happens the attributes stop being layout-shift insurance and become a
   PRESENTATIONAL HINT instead (the one declaration left for height in the whole cascade), so
   the browser paints the photograph at its raw SOURCE height regardless of how narrow the box
   actually is: a 5971x3986 photograph once drew 3,986px tall wherever it landed (Forest House,
   binding this runtime to a finished design). Every future design hits this the same way, so the
   fix belongs here, in the platform floor every V5 page already loads, at this layer's own
   priority: an unlayered author rule, even a bare, unclassed img{height:400px}, still wins
   outright over a layered one regardless of specificity (this file's own header comment), so a
   design that genuinely wants a fixed-height crop keeps that power; this rule only supplies the
   sane default when the design asks for nothing else. Touches no island, .lv-map/.lv-video (both
   iframes, not <img>) or the lightbox (its own <img> is sized by an inline style, which already
   outranks anything here): a plain <img> is the only thing this selector can ever match. */
img{height:auto;max-width:100%}

/* Every photograph the platform draws from data-image (render-page.ts marks each with
   data-lv-slug), two base-layer defaults (Batch 4, 27 Sep 2026):
   - It keeps its own proportions. With the floor above, a photograph's box already has the
     photograph's own shape until a design sets both its width and its height; then, with no
     object-fit of the design's own, a browser STRETCHES the picture to the box (object-fit:fill),
     and the floor's own max-width:100% can do the same on a narrow phone to a design's fixed
     height. contain draws it whole, at its own proportions, inside whatever box it is given. A
     design that means to crop says so in its own CSS (object-fit:cover, naming its shape with
     data-frame for the resolution that needs) and wins outright, as everywhere in this file.
   - The subject stays whole: a manager's focal point (render-page.ts writes it as --lv-focal, only
     when one is on record) is the default object-position, so any crop centres on it. A design's
     own object-position wins. The Gallery page's tiles (.lv-gallery-grid img, below) crop with
     cover, and follow the focal point the same way. Only a photograph WITH a focal point gets an
     object-position at all: WebKit draws a cover crop given an explicit 50% 50% a sub-pixel apart
     from one left at the initial value (measured on Shipwrights Cottage's phone pages, 27 Sep
     2026), so a photograph with no focal point on record is left exactly as it always was.
   Not the logo or its footer variant (data-image="logo"/"logo-footer"): a design sizes a logo by
   one side (height, width auto), so it is never stretched, and contain on a box that already has
   the logo's shape only redraws it a sub-pixel apart (The Cedar Gable's header logo, 1,838 pixels
   at 2560, 27 Sep 2026); they carry no focal point either. The exception sits in :where(), so
   the rule keeps the weight of a plain img[data-lv-slug] and the Gallery tiles' own cover (the
   same weight, later) still wins. */
img[data-lv-slug]:where(:not([data-lv-slug="logo"],[data-lv-slug="logo-footer"])){object-fit:contain}
img[data-lv-slug][style*="--lv-focal"]{object-position:var(--lv-focal)}

.island{
background:var(--lv-ground, transparent);
color:var(--lv-ink, inherit);
font-family:var(--lv-font-body, inherit);
border:1px solid var(--lv-line, currentColor);
border-radius:var(--lv-radius, inherit);
padding:1.25rem;
}
/* Ground hooks: an island the design (or the renderer's own markup) marks as sitting on a dark
   surface gets dark UA chrome, the date picker above all. Both declarations live inside the
   layer, so any color-scheme the design sets itself, anywhere, already wins outright over
   these, they only fire when the design has not said otherwise. */
.island[data-ground="ink"],.island[data-dark]{color-scheme:dark}
/* The live price (the stay-price island) is INLINE: a few words a design places inside its own
   bar or sentence, in that bar's own type and colour. None of the box above applies to it: an
   inline element with a border and padding that wraps onto a second line draws a separate box on
   each line (seen on a phone, 30 Sep 2026, the bar's words overlapping). The design's own rules
   for .lv-price-figure, .lv-price-detail and .lv-price-split still win outright. */
.island[data-island="stay-price"]{background:none;border:0;border-radius:0;padding:0;color:inherit;font:inherit}

/* The booking calendar's own buttons (its React tree, platform/site-kit/runtime/booking-calendar.tsx,
   styled by the generated calendar stylesheet) are at least 44px tall to tap: the month arrows and
   day cells already are, "Try again" (when availability cannot load) and "Clear dates" were 32px
   and under. min-height only, because the calendar's utilities set height, never min-height, so
   this default is the one declaration there is. Never a min-width: seven day columns cannot each
   be 44px wide inside a phone's padding (7 x 44 is 308px before any padding at all), and forcing
   it would push the grid past the screen; a day cell is 44px tall and as wide as its column. */
[data-island="booking-calendar"] button{min-height:44px}

/* The enquiry form specifically (never every island): the platform scrolls this island into view
   on its own, both for a guest handed straight to it from the booking calendar on the same page
   (platform/site-kit/runtime/booking-calendar.tsx) and for one arriving with dates already in the
   URL (platform/site-kit/runtime/enquiry-form.ts). --lv-sticky-offset (CSS contract,
   src/lib/site-v5/contract.ts) is the height of a design's own fixed/sticky header, so that scroll
   lands the form's own heading clear of it instead of tucked underneath; 96px is a reasonable
   default for a design that sets nothing. */
[data-island="enquiry-form"]{scroll-margin-top:var(--lv-sticky-offset, 96px)}

.island-heading{
margin:0 0 1rem;
font-family:var(--lv-font-display, inherit);
font-weight:400;
font-size:1.375rem;
line-height:1.2;
color:var(--lv-ink, inherit);
}

.island form{display:grid;gap:1.25rem}

.island .fields{display:grid;gap:1rem;grid-template-columns:1fr}
@media (min-width:640px){
.island .fields{grid-template-columns:repeat(auto-fit,minmax(12.5rem,1fr))}
}

/* min-width:0 matters here, not just on the input inside it: a grid track's automatic minimum is
   its item's own min-content width (this .fields grid, two rules below), so a child wider than
   its track by nature - iOS Safari's native date control chief among them, see the input[type]
   rule below - stretches the WHOLE track, not just itself, unless the grid ITEM (this label) is
   told it is allowed to shrink past that content. Found on the owner's own iPhone, 21 Sep 2026. */
.island label{display:grid;gap:.375rem;font-size:.9375rem;color:var(--lv-ink, inherit);min-width:0}

.island input,.island select,.island textarea{
width:100%;
min-height:48px;
padding:.75rem .875rem;
font-size:16px;
line-height:1.4;
font-family:var(--lv-font-body, inherit);
color:var(--lv-ink, inherit);
background:var(--lv-field, transparent);
border:1px solid var(--lv-line, currentColor);
border-radius:var(--lv-radius, inherit);
}
/* iOS Safari's native date/time controls (found on the owner's own iPhone, 21 Sep 2026, on the
   enquiry form's Arrive/Leave fields): ~70px wider than every sibling field, poking past the
   form's own border, noticeably taller than a text input, and centring their value - all four
   despite the shared rule directly above already setting width/min-height/padding/font-size/
   line-height on every input. The cause is -webkit-appearance's own native chrome: it has an
   intrinsic minimum width, ignores width:100%, and centres its value via the internal
   ::-webkit-date-and-time-value pseudo-element (next rule) regardless of any text-align on the
   input itself. -webkit-appearance:none/appearance:none switches that chrome off, and the values
   below then simply match the shared rule above - never invented sizes of their own. Desktop
   Safari and WebKit under Playwright do not reproduce this, so it cannot be verified headlessly:
   it was fixed from the owner's own report, and a real iPhone is the only check. Never delete this as
   redundant with the shared rule above: without it, iOS ignores every one of these properties. */
.island input[type="date"],
.island input[type="time"],
.island input[type="datetime-local"],
.island input[type="month"]{
-webkit-appearance:none;
appearance:none;
display:block;
box-sizing:border-box;
width:100%;
min-width:0;
max-width:100%;
min-height:48px;
padding:.75rem .875rem;
font-size:16px;
line-height:1.4;
text-align:left;
}
/* The value text itself, inside iOS's own pseudo-element - ordinary text-align on the input above
   does not reach in here, which is why the value still rendered centred even once the box itself
   was the right size. min-height keeps an EMPTY date box (no value chosen yet) from collapsing
   shorter than its sibling fields on iOS, which it otherwise does once appearance:none removes
   the native chrome that used to hold its height open. */
.island input[type="date"]::-webkit-date-and-time-value,
.island input[type="time"]::-webkit-date-and-time-value,
.island input[type="datetime-local"]::-webkit-date-and-time-value,
.island input[type="month"]::-webkit-date-and-time-value{
text-align:left;
margin:0;
min-height:1.2em;
}
.island textarea{min-height:7.5rem;resize:vertical}
.island input:focus-visible,.island select:focus-visible,.island textarea:focus-visible{
outline:2px solid var(--lv-focus, currentColor);
outline-offset:2px;
}

/* The enquiry-form capability's success state hides .fields, every label and the submit button
   by setting the DOM [hidden] attribute (platform/site-kit/runtime/enquiry-form.ts), which relies
   on the user-agent stylesheet's own [hidden]{display:none}. This file's own "display" on those same
   selectors above is an AUTHOR rule, and an author rule, layered or not, always outranks a
   user-agent rule; without restating [hidden] ourselves, the capability would set the attribute
   and nothing would actually hide. */
.island [hidden]{display:none}

.island label.consent{display:flex;align-items:flex-start;gap:.6rem;min-height:44px;font-size:.9375rem}
.island label.consent input[type="checkbox"]{
width:22px;
height:22px;
min-height:0;
flex:none;
margin-top:.15em;
accent-color:var(--lv-accent, currentColor);
}
/* The enquiry form's occasion question without JavaScript: one radio row per choice (the consent
   rows' own look, a radio being the same size of control as their checkbox), under the question's
   words. With JavaScript the platform's own component replaces the whole block. */
.island label.consent input[type="radio"]{
width:22px;
height:22px;
min-height:0;
padding:0;
flex:none;
margin-top:.15em;
accent-color:var(--lv-accent, currentColor);
}
.island .island-legend{display:block;margin-bottom:.375rem;font-size:.9375rem;color:var(--lv-ink, inherit)}
.island label.consent a{color:currentColor;text-decoration:underline;text-underline-offset:.2em}

.island .btn{
display:inline-flex;
align-items:center;
justify-content:center;
gap:.5em;
min-height:52px;
padding:.9em 1.75em;
background:var(--lv-accent, currentColor);
color:var(--lv-accent-ink, inherit);
border:1px solid transparent;
border-radius:var(--lv-radius, inherit);
font-family:var(--lv-font-body, inherit);
font-size:1rem;
font-weight:600;
text-decoration:none;
cursor:pointer;
}
/* Hover and disabled each carry a second signal beyond colour (a lift and a shadow; reduced
   opacity and the cursor), a state told by colour alone is invisible to a colourblind or
   low-vision guest. */
.island .btn:hover:not(:disabled){
filter:brightness(1.08);
transform:translateY(-1px);
box-shadow:0 4px 12px color-mix(in srgb, var(--lv-accent, currentColor) 35%, transparent);
}
.island .btn:focus-visible{outline:2px solid var(--lv-focus, currentColor);outline-offset:2px}
.island .btn:disabled{cursor:not-allowed;opacity:.6}

.island .island-note{margin:0;font-size:.875rem;color:var(--lv-ink-soft, currentColor)}

/* The enquiry form's honeypot field (_hp) is the only thing on a V5 page that needs this class -
   there is no app stylesheet here to already define it. */
.island .visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* The lightbox (platform/site-kit/runtime/lightbox.ts) builds its overlay entirely from inline
   styles and appends it to <body>, outside any .island, an inline style attribute always
   outranks a stylesheet rule for the properties it sets, layer or not, so this file can only add
   what the script leaves unset: the design's own type, and a focus/hover cue for keyboard and
   low-vision guests the script does not draw itself. */
div[role="dialog"][aria-label="Photograph"]{font-family:var(--lv-font-body, inherit)}
div[role="dialog"][aria-label="Photograph"] figcaption{font-family:var(--lv-font-body, inherit)}
div[role="dialog"][aria-label="Photograph"] button{transition:opacity .15s ease}
div[role="dialog"][aria-label="Photograph"] button:hover{opacity:.75}
div[role="dialog"][aria-label="Photograph"] button:focus-visible{outline:2px solid var(--lv-focus, currentColor);outline-offset:3px}
/* "See the full gallery", the one link in the viewer: its buttons are 44px by their own inline
   style, this link a 44px-tall target here. :not([hidden]), because the script hides it by the
   attribute and this display would otherwise outrank the user agent's [hidden]{display:none}. */
div[role="dialog"][aria-label="Photograph"] a[data-gallery-cta]:not([hidden]){display:inline-flex;align-items:center;min-height:44px}

/* Every lightbox TRIGGER, in the page itself (never the overlay above, which is its own concern):
   a bare <img data-lightbox> (render-page.ts's markForLightbox, 21 Sep 2026 second pass -- an img
   marked in place, never wrapped) or a design's own <a data-lightbox> (data-image-link, already
   a real link with its own default cursor/focus ring, styled here too for one consistent look).
   LOOK, not structure -- a design restyles either with its own unlayered rule against the same
   selectors, exactly like every other hook in this file. */
a[data-lightbox],img[data-lightbox]{cursor:zoom-in}
a[data-lightbox]:focus-visible,img[data-lightbox]:focus-visible{outline:2px solid var(--lv-focus, currentColor);outline-offset:2px}

/* The platform's own required pieces when a design places none for them (SPEC marker vocabulary
   #9): small, quiet, centred, and reads whatever ground it lands on rather than bringing its own
   colour, so it never clashes with the footer the renderer appends it inside. Quiet, never too
   small to read or to tap: 14px words, and every link a 44px-tall target (27 Sep 2026: the
   render tool's phone checks, on the "Cookie settings" link this block now carries on every
   public page whose footer places no legal links of its own). */
.lv-platform-defaults{
text-align:center;
padding-block:1rem;
font-size:.875rem;
font-family:var(--lv-font-body, inherit);
color:inherit;
opacity:.85;
}
.lv-platform-defaults a{display:inline-flex;align-items:center;justify-content:center;min-height:44px;min-width:44px;padding-inline:.25rem;color:inherit;text-decoration:underline;text-underline-offset:.2em}

/* data-platform="map" / "video" (SPEC marker vocabulary #9): a keyless map embed and the
   owner's film, both lazy iframes the platform writes so a design never hand-authors one (the
   save-time sanitiser refuses a hand-written iframe outright). Sized by aspect-ratio, not a
   fixed height, so the frame reflows with its own width; a design changes the shape by setting
   --lv-frame-ratio, or with an unlayered rule on .lv-map/.lv-video, which outranks this layered
   one outright with no extra weight needed, same as every other rule in this file. */
.lv-map,.lv-video{
position:relative;
width:100%;
aspect-ratio:var(--lv-frame-ratio, 16 / 9);
}
.lv-map iframe,.lv-video iframe{
position:absolute;
inset:0;
width:100%;
height:100%;
border:0;
}

/* The platform-built Gallery page (gallery-page.ts's withGalleryPage): every photograph in the
   OWNER'S OWN ORDER, in the site's own header/footer/stylesheet. Round 2, 21 Sep 2026: a CSS
   multi-column "waterfall" was tried first and rejected the same day: columns fill DOWN each
   column before moving to the next, so with four columns the top row reads photograph 1, 9, 17,
   25, not 1, 2, 3, 4; the owner's own admin order (the entire point of this page) was lost. A
   flexbox JUSTIFIED-ROWS layout fills left to right, top to bottom, in source order, by
   construction: the same order the figures were written in is the same order a guest reads them
   in, on every viewport, no exception.
   Round 3, 21 Sep 2026: round 2's own justified rows CROPPED HARD (a first photograph measured
   720x220 on desktop, 3.3:1, the house losing its roof): it fixed the row HEIGHT and let
   flex-grow stretch WIDTHS to fill the row, so object-fit:cover ate the whole difference between
   a figure's own photograph and whatever width it was stretched to. The exact fix: each figure
   gets aspect-ratio:var(--ar) INSTEAD of a fixed height, and its flex-grow is proportional to
   --ar (any constant times --ar, here x100) while flex-basis stays proportional to --ar too (as
   round 2 already had it). Because both the share of a row's free space AND the starting width
   are proportional to the SAME --ar, every figure in a row ends at width = c x ar for the same c
   and, with aspect-ratio set, height = width / ar = c for every one of them: the whole row lands
   at one shared height by construction, nothing stretched, nothing cropped past what
   object-fit:cover trims to whole pixels (this is the same maths Flickr's and Google Photos' own
   justified grids use, and why their rows visibly differ in height from each other rather than
   all matching a fixed number). object-fit:cover on the img stays as a SUB-PIXEL safety net only,
   never the mechanism. --ar itself is CLAMPED server-side (gallery-page.ts, 0.6 to 2.4) so one
   oddly-shaped file (a phone panorama, a door frame shot in portrait) cannot make a whole row
   one photograph tall or a sliver wide.
   The ::after pseudo-element with an enormous flex-grow and explicit zero height absorbs whatever
   width is left over on the LAST row (a well-known flexbox technique) so a short final row of one
   or two photographs never inflates past the row height every other row settled on.
   --lv-gallery-row-h is still the one hook a design changes the TARGET row height with (never a
   guaranteed exact height any more, now that aspect-ratio decides the real one): smaller on a
   phone by default (a phone row holds one to two photographs), taller from 640px (three to five).
   .lv-gallery-grid's own gap, and figure's own margin, are STRUCTURAL and marked important, the same
   "same trap as this morning" reasoning site-menu's own block documents: a design's bare
   figure{margin:0} (an ordinary reset almost every design carries) already beat a plain layered
   margin-based gutter in the round-1 build, closing every gap between photographs to nothing; gap
   moves the gutter onto the CONTAINER, out of that fight entirely, and both are marked important
   defensively so neither a stray figure rule NOR an unlayered .lv-gallery-grid{gap:0} can ever
   silently zero it again. .lv-gallery-grid img's own width/height/object-fit is a DELIBERATE,
   documented second bare-img-adjacent rule (the "photograph sizing floor" section above is
   otherwise the only place this file touches <img>): a justified-row TILE must fill its own
   figure exactly for the grid to read as a grid, unlike an ordinary content photograph elsewhere,
   which keeps its own intrinsic proportions inside whatever box a design gives it.
   The page's FRAME (.lv-gallery, its title and its line under the title) is after this layer, outside
   it, for the cookie notice's reason: see "THE GALLERY PAGE'S FRAME, OUTSIDE THE LAYER" below. */
.lv-gallery-grid{
--lv-gallery-row-h: 140px;
display:flex;
flex-wrap:wrap;
gap:6px !important;
}
@media (min-width: 640px){.lv-gallery-grid{--lv-gallery-row-h: 220px}}
.lv-gallery-grid::after{
content:"";
flex-grow:9999;
height:0;
}
.lv-gallery-grid figure{
aspect-ratio:var(--ar, 1.5);
flex-grow:calc(var(--ar, 1.5) * 100);
flex-basis:calc(var(--lv-gallery-row-h, 140px) * var(--ar, 1.5));
margin:0 !important;
overflow:hidden;
}
.lv-gallery-grid img{
display:block;
width:100%;
height:100%;
object-fit:cover;
border-radius:var(--lv-radius, inherit);
}

/* The platform's document pages (.lv-legal: the access statement, the legal pages, the platform's
   own /terms) are styled OUTSIDE this layer, at the end of this file: see the note there. */

/* data-platform="menu" (SPEC marker vocabulary #9): the phone/overflow nav the platform renders
   as <details class="site-menu">, its links cloned from the header's own primary <nav>
   (src/lib/site-v5/render-page.ts's placeMenu). Structural selectors only, "header nav", never
   a design's own class name, because the platform cannot know what a design called its nav, and
   never depends on the design's own breakpoint either: the 900px figure below is a PLATFORM
   CONTRACT, not a guess at any one design's own layout break. It exists so "the menu is visible
   whenever the primary nav is hidden" is true BY CONSTRUCTION, for every design, not just ones
   that happen to also break at 900px.

   REAL-BROWSER DEFECT, 21 Sep 2026 (Shipwrights Cottage on a rig): a bare, unclassed rule for
   details/summary is completely ordinary in a free design (an amenities disclosure, a FAQ
   accordion), and it is UNLAYERED -- an unlayered rule always beats a layered one regardless of
   specificity, so details{border-top:1px solid #line-colour;margin-top:22px} and
   summary::after{content:" +"} reached the phone menu too: a stray plus/minus sign beside the
   hamburger, a rule and gap above it, and, the one that actually broke the menu rather than just
   its look, .site-menu{position:relative} made the <details> ITSELF the containing block for the
   absolutely positioned panel, so left:0;right:0 spanned only the width of the hamburger button
   (about 45px), not the header, and every link wrapped into fragments ("The / hou").
   Colour, spacing and type are legitimately a design's to restyle; POSITION, DISPLAY and SIZE are
   not -- the menu simply does not work if a design's bare element rule can move them. So this
   block is split in two: LOOK declarations stay plain, layered, freely overridable (the whole
   point of this file's own layer). STRUCTURAL declarations -- the ones a bare details/summary/
   nav/a rule could otherwise defeat and break the mechanism, not just its appearance -- are
   marked important. A cascade layer still obeys this ordering: an important declaration inside a
   layer beats an unlayered NORMAL declaration (a design's details{margin-top:22px} included);
   only an unlayered important declaration would beat it, and nothing here asks a designer to
   write one. Every important declaration below is named and justified at its own rule; there is
   no other important declaration in this file. */

/* STRUCTURAL: the menu's own visibility at each side of 900px, and the 7-links override
   (V4 parity), this IS the guarantee the whole piece exists for; a bare nav{display:...} or
   details{display:...} a design writes for something else entirely must never suppress it, or
   silently leave both the nav AND the menu hidden (or both showing) at some width. */
@media (max-width:899.98px){
header:not([data-menu-switch="design"]) nav:not(.site-menu):not(.site-menu-panel){display:none !important}
header:not([data-menu-switch="design"]) .site-menu{display:block !important}
/* A 360° TOUR is tall on a phone (Heritage Lodge, 23 Oct 2026: SkyNav's map, room cards and controls were crammed into the
   16:9 frame, 220px at 390px wide; Michael, "Doesn't look very nice on a mobile does it"). The renderer adds lv-video--tour
   beside lv-video when filmKind says tour; a FILM keeps 16:9 everywhere, a film in a 3:4 frame would be letterboxed. Same
   --lv-frame-ratio hook as the rule above, so a design that sets its own ratio (inherited from the frame's wrapper) still wins.
   Phone only: nothing changes from 900px up. */
.lv-video--tour{aspect-ratio:var(--lv-frame-ratio, 3 / 4)}
}
@media (min-width:900px){
header:not([data-menu-switch="design"]) .site-menu{display:none !important}
}
/* Seven links or more will not fit a header honestly at any width: the menu is used regardless
   of viewport (carries V4's own rule, platform/site-kit/css/chrome.ts's :has(...a:nth-child(7))).
   Higher selector specificity than the two rules above (an extra :has()), so it still wins
   against them at every width even though all three are now equally layered and important. */
header:not([data-menu-switch="design"]):has(nav:not(.site-menu):not(.site-menu-panel) a:nth-child(7)) nav:not(.site-menu):not(.site-menu-panel){display:none !important}
header:not([data-menu-switch="design"]):has(nav:not(.site-menu):not(.site-menu-panel) a:nth-child(7)) .site-menu{display:block !important}
/* A header carrying data-menu-switch="design" has MEASURED where its own links fit (The Cedar
   Gable, 22 Sep 2026: 8 links in one bar from 1240px, approved by Michael) and says so: none of
   the three switches above applies to it, and its own stylesheet decides, with ordinary rules, at
   which width the primary nav gives way to the menu. Opt-in and explicit, so no design loses the
   guarantee by accident; the render tool's bar and menu probes check the design kept it. */

/* The header stays on screen while a menu is open. LOOK, deliberately NOT important: a design
   may choose position:fixed instead (or its own scroll-driven transparent-to-solid bar), and an
   unlayered .top{position:fixed} already wins over this layered default with no help needed ,
   forcing it important would clobber that legitimate choice. Pure CSS cannot express "not static
   and not relative, whichever of sticky/fixed the design prefers" as a single guaranteed
   declaration; the render tool's rendered check (shoot.cjs) is the real backstop for a design
   that defeats this without supplying its own alternative. */
header:has(.site-menu){position:sticky;top:0;z-index:20}

/* STRUCTURAL: the <details> itself must never become a positioning context, or carry visual
   weight that belongs to a design's OWN accordion, not the platform's menu control.
   position:static is what makes the PANEL's containing block the header above, not this ~44px-
   wide button, the exact defect found on the rig (see this block's own header comment).
   margin/border are the two properties the same bare details{border-top:...;margin-top:22px}
   rule actually set; zeroing them is the direct fix, not a general "reset everything", padding,
   background and anything else a design's own details rule sets are left alone, still free to
   apply here exactly as any other design choice would. */
.site-menu{position:static !important;margin:0 !important;border:0 !important}

.site-menu>summary{
display:inline-flex;
align-items:center;
gap:.5rem;
padding:.5rem;
cursor:pointer;
color:var(--lv-ink, inherit);
}
/* STRUCTURAL: list-style is how Firefox draws a <summary> marker (list-style, not ::marker, on
   that one engine); min-width/min-height are the 44px tap-target floor. Padding/gap/cursor/colour
   above stay plain LOOK declarations. */
.site-menu>summary{list-style:none !important;min-width:44px !important;min-height:44px !important}
/* STRUCTURAL: three separate ways a browser (or a design's own accordion rule, e.g.
   summary::after{content:" +"}) can draw a marker on <summary>, Chrome/Safari's own
   pseudo-element, Firefox's ::marker, and generic ::before/::after content. All three must be
   fully suppressed or the platform's own three-bar icon (a real element, .menu-icon, not a
   pseudo-element) gets a design's accordion glyph sitting next to or instead of it. */
.site-menu>summary::-webkit-details-marker{display:none !important}
.site-menu>summary::marker{content:"" !important}
.site-menu>summary::before,.site-menu>summary::after{content:none !important}
.menu-icon{width:22px;height:16px;flex:none;display:inline-grid;gap:5px;align-content:space-between}
.menu-icon i{display:block;height:2px;background:currentColor;border-radius:2px;transition:transform .25s ease,opacity .2s ease}
.site-menu[open]>summary .menu-icon i:nth-child(1){transform:translateY(7px) rotate(45deg)}
.site-menu[open]>summary .menu-icon i:nth-child(2){opacity:0}
.site-menu[open]>summary .menu-icon i:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
/* Visually hidden by default; a design reveals it (e.g. beside the icon on a wide phone) with an
   ordinary unlayered rule against .menu-label, the same override pattern as everything else here. */
.menu-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

.site-menu-panel{
z-index:1;
display:flex;
flex-direction:column;
margin:0;
padding:0;
list-style:none;
background:Canvas;
color:CanvasText;
border-top:1px solid currentColor;
max-height:calc(100dvh - 4.5rem);
overflow-y:auto;
}
/* STRUCTURAL: position/left/right/top/width are the fix itself, the panel spans the FULL header
   (its containing block, now that .site-menu above is forced static) directly beneath it, never
   the ~45px the hamburger's own box would otherwise constrain it to, and never a size a design's
   bare nav{width:...} rule might set for some other <nav> on the page. overflow-y is the other
   half of "a long list stays reachable": a bare nav{overflow:hidden} (or similar) rule would
   silently cut it off rather than let it scroll. max-height is deliberately LEFT overridable, a
   platform-wide guess cannot know a given design's own header height or bottom dock (a fixed
   mobile booking bar, say); the figure above is a reasonable floor, and a design with its own
   fixed chrome to clear (see the Shipwrights rebind) sets a more exact one in its own stylesheet. */
.site-menu-panel{position:absolute !important;left:0 !important;right:0 !important;top:100% !important;width:auto !important;overflow-y:auto !important}
.site-menu-panel a{
align-items:center;
padding:.75rem 1.25rem;
color:inherit;
text-decoration:none;
}
/* STRUCTURAL: display/min-height/white-space/width are what keep a cloned link readable ON ONE
   LINE across the full panel width, a design's bare a{white-space:nowrap} (truncating instead
   of wrapping) or a narrower flex/inline-block default from some OTHER bare a{}/nav a{} rule
   is exactly the class of bug this whole change is a response to. Padding/colour/underline above
   stay plain LOOK declarations. */
.site-menu-panel a{display:flex !important;min-height:44px !important;white-space:normal !important;width:100% !important}
.site-menu-panel a[aria-current="page"]{font-weight:600;text-decoration:underline;text-underline-offset:.3em}

@media (prefers-reduced-motion:reduce){
.menu-icon i{transition:none}
}

/* The platform's cookie notice (src/lib/site-v5/consent-notice.ts; behaviour in
   platform/site-kit/runtime/consent.ts). Phone first: one slim row along the bottom of the screen,
   the one short sentence (two lines from 390px wide in all five designs' fonts, three below that in
   some) with two equal buttons beside it, the card
   hugging the buttons and 8px from the edges of the screen (13 Oct 2026: it was 122px tall, the
   sentence above the buttons, and with a dock under it the first screen lost about 190px; Cedar
   Gable's main picture keeps only 62px under its own "Check dates", so card and gap fit inside
   that). From 640px everything is the laptop's as before: roomier padding, type and margins, and
   the buttons a fixed minimum width.
   It looks like the site, not a stranger: its ground, ink, line, accent, radius and font are the
   design's own --lv-* properties. The one departure from this file's "neutral fallback" rule is
   the card's ground: a floating card cannot be transparent, so the system's own Canvas sits
   UNDER the design's ground (a gradient of --lv-ground over background-color:Canvas), seen only
   on a design that forgot to set one. It is shown only until the guest chooses and is removed
   from the page ([hidden]) the moment they do.
   It never covers the design's own bottom chrome (a phone's booking dock, a sticky ask bar):
   while it shows, platform/site-kit/runtime/consent-clearance.ts measures how far up the screen
   the chrome on the screen reaches and sets --lv-consent-lift on the notice, and the card
   sits that far up plus its usual gap (the dock's own height already holds its safe-area padding,
   so the lift replaces the safe-area inset rather than adding to it). Unset (no bar, or a dock
   still waiting below the screen), it sits on the safe area as before. The same script appends
   .lv-consent-room to the end of the body, as tall as the notice reaches above the bars'
   home, so the footer's last line can always be scrolled clear of it; both go the moment the
   guest chooses, and nothing of the design's moves.
   Decline and Accept are one and the same class, so they always share size, weight and colour
   (UK ICO: refusing must be as easy as accepting); a design's own bare button{} rule restyles
   both alike. STRUCTURAL, and so marked important: only where the card sits, that it stays on top
   while it is open, that [hidden] really hides it, that the sentence carries no margin of its own,
   and that the end-of-page room is exactly as tall as the script makes it. A design's bare
   div{position:relative}, div{padding:...} or p{margin:...} (unlayered, so it would otherwise win;
   Shipwrights' p{margin:0 0 1em} made its notice 15px taller than every other site's) must never
   drop the card into the page flow, bury it under a fixed booking bar, grow it past the room the
   runtime keeps for it, or pad the room.
   Only those structural declarations are here, in the layer (an important declaration in the first
   layer outranks every later layer's and every unlayered one). The notice's LOOK is just after
   this layer, outside it: see "THE COOKIE NOTICE'S LOOK, OUTSIDE THE LAYER" below for why. */
.lv-consent{position:fixed !important;left:.5rem !important;right:.5rem !important;bottom:calc(var(--lv-consent-lift, env(safe-area-inset-bottom, 0px)) + .5rem) !important;top:auto !important;z-index:2147483000 !important;max-width:40rem !important;margin:0 auto !important}
.lv-consent[hidden]{display:none !important}
.lv-consent-room{display:block !important;margin:0 !important;padding:0 !important;border:0 !important;min-height:0 !important}
.lv-consent-text{margin:0 !important}
@media (min-width: 640px){
.lv-consent{left:.75rem !important;right:.75rem !important;bottom:calc(var(--lv-consent-lift, env(safe-area-inset-bottom, 0px)) + .75rem) !important}
}

}

/* THE COOKIE NOTICE'S LOOK, OUTSIDE THE LAYER (23 Oct 2026). Its padding, card, type and the two
   buttons (the structural part, and what the notice is for, are in the layer above, beside the rest
   of its comment). Inside the layer the look lost on all 12 BHHL houses: a site ported from V2
   carries Tailwind's preflight in @layer base, declared after lv-island and so ahead of it whatever
   its specificity, and the port (scripts/v5/v2-port/port-css.cjs) spares only .island from it. The
   card lost its padding and border and the buttons their colour, padding and radius: the sentence
   ran into the card's edge and Decline and Accept read as plain words. Every V5 page draws this
   notice outside any island and outside any design, so it cannot count on a design's layers
   leaving it alone; the document pages below left the layer for the same reason on 15 Oct.
   Every selector is wrapped in :where(), so each rule weighs NOTHING: unlayered, it beats any
   layer a design brings, and it still loses to EVERY unlayered rule a design writes (the site's
   sheet is linked after this one), a bare button{} or p{} included, exactly as it did inside the
   layer. So on a design with no layers of its own (every hand-built V5 design) the notice is
   drawn exactly as before. island-css.test.ts pins that every rule here is a zero-weight
   .lv-consent rule and nothing is important; scripts/v5/render-tool/test/base-layer.cjs proves
   the look in WebKit and Chromium on a page that carries a V2 port's preflight. */
:where(.lv-consent){
padding:.25rem .25rem .25rem .625rem;
display:grid;
grid-template-columns:minmax(0, 1fr) auto;
align-items:center;
gap:.5rem;
background-color:Canvas;
background-image:linear-gradient(var(--lv-ground, transparent), var(--lv-ground, transparent));
color:var(--lv-ink, inherit);
border:1px solid var(--lv-line, currentColor);
border-radius:var(--lv-radius, inherit);
font-family:var(--lv-font-body, inherit);
font-size:.875rem;
line-height:1.3;
box-shadow:0 8px 28px color-mix(in srgb, currentColor 20%, transparent);
box-sizing:border-box;
}
:where(.lv-consent-text){text-wrap:balance}
:where(.lv-consent-actions){display:grid;grid-template-columns:1fr 1fr;gap:.5rem}
:where(.lv-consent-btn){
min-height:44px;
margin:0;
padding:.5rem;
width:100%;
font-family:inherit;
font-size:.875rem;
font-weight:600;
line-height:1.2;
background:var(--lv-accent, currentColor);
color:var(--lv-accent-ink, inherit);
border:1px solid var(--lv-accent, currentColor);
border-radius:var(--lv-radius, inherit);
cursor:pointer;
box-sizing:border-box;
}
:where(.lv-consent-btn:focus-visible){outline:2px solid var(--lv-focus, currentColor);outline-offset:2px}
:where(.lv-consent-btn[aria-pressed="true"]){text-decoration:underline;text-underline-offset:.2em}
@media (min-width: 640px){
:where(.lv-consent){grid-template-columns:1fr auto;gap:.625rem 1.25rem;padding:.875rem 1rem;font-size:.9375rem;line-height:1.4}
:where(.lv-consent-actions){grid-template-columns:repeat(2, minmax(7.5rem, 1fr))}
:where(.lv-consent-btn){padding:.625rem 1rem;font-size:.9375rem}
}

/* THE GALLERY PAGE'S FRAME, OUTSIDE THE LAYER (23 Oct 2026). The platform's own Gallery page
   (gallery-page.ts): its frame, its title and the line under it. Found the same day as the cookie
   notice, by the same cause: on all 12 BHHL houses (ported from V2, so carrying Tailwind's
   preflight in @layer base) the frame lost its padding and centring and the title its size, so the
   title sat in body type against the screen's left edge with the photographs running to it. Zero
   weight, as above: every design's own .lv-gallery rules (Villa Perdido, Serenity, Velero, Heritage
   Lodge and The Cedar Gable each write some) still win exactly as they did, and a design with no
   layers of its own draws the page as before. The grid itself stays in the layer: its gap and its
   figures' margin are important there, and a port's preflight leaves the rest of it alone. */
:where(.lv-gallery){
max-width:88rem;
margin-inline:auto;
padding-block:clamp(4.5rem, 7vw, 5.5rem) 1rem;
padding-inline:1.25rem;
background:var(--lv-ground, transparent);
color:var(--lv-ink, inherit);
}
:where(.lv-gallery h1){
margin:0 0 .35rem;
font-family:var(--lv-font-display, inherit);
font-weight:400;
font-size:clamp(1.375rem, 1.15rem + 1.2vw, 1.875rem);
line-height:1.15;
color:var(--lv-ink, inherit);
}
:where(.lv-gallery>p){
margin:0 0 1.25rem;
font-family:var(--lv-font-body, inherit);
color:var(--lv-ink-soft, currentColor);
}

/* THE PLATFORM'S DOCUMENT PAGES, OUTSIDE THE LAYER (15 Oct 2026). A published document
   (legal-pages.ts): the access statement, a captured legal document, the platform's own /terms
   page and the terms it appends to a designed terms page. The owner's text in the site's own
   header, footer and fonts, an ordinary reading page: no form, no capability script, so nothing
   here needs a [hidden]/[data-ground] hook.

   NOT in @layer lv-island, on purpose (the only other things outside it are the cookie notice's look
   and the Gallery page's frame, above). This frame is the
   platform's own page, not a piece placed inside a design, and inside the layer it lost to every
   design: measured on the live sites at 390 wide, 15 Oct 2026, a site ported from V2 carries
   Tailwind's preflight in @layer base (declared after lv-island, so it wins whatever its
   specificity) and zeroed every margin and padding here (the text ran to the screen's edge, the
   title in body size, no space between paragraphs), and a hand-written design's own unlayered
   p{margin:0}, ul{list-style:none;padding:0} and h2{font-size:...} won the same way (The Cedar
   Gable's terms showed each section heading larger than the page title). Unlayered, every rule
   here is scoped to .lv-legal and weighs a class and an element, so it beats a layer and a
   design's bare element rules, and nothing else on any page; a design that means to restyle this
   page still can, with its own .lv-legal rule, which comes later in the cascade (the site sheet
   is linked after this one). Every value still reads the --lv-* tokens. island-css.test.ts pins
   that nothing but these rules, the cookie notice's look and the Gallery page's frame ever leaves
   the layer. */
.lv-legal{
max-width:68ch;
margin-inline:auto;
padding-block:clamp(6rem, 12vw, 9rem) clamp(4rem, 8vw, 6rem);
padding-inline:1.25rem;
background:var(--lv-ground, transparent);
color:var(--lv-ink, inherit);
overflow-wrap:anywhere;
}
.lv-legal h1{
margin:0 0 1.5rem;
padding-bottom:1rem;
border-bottom:1px solid var(--lv-line, currentColor);
font-family:var(--lv-font-display, inherit);
font-weight:400;
font-size:clamp(1.75rem, 1.35rem + 2vw, 2.5rem);
line-height:1.15;
color:var(--lv-ink, inherit);
}
.lv-legal-body{
font-family:var(--lv-font-body, inherit);
font-size:clamp(1rem, .95rem + .2vw, 1.0625rem);
line-height:1.7;
color:var(--lv-ink-soft, currentColor);
}
.lv-legal-body p{margin:0 0 1.25rem}
.lv-legal-body h2{
margin:2.5rem 0 .75rem;
font-family:var(--lv-font-display, inherit);
font-weight:600;
font-size:clamp(1.375rem, 1.2rem + .8vw, 1.75rem);
line-height:1.25;
color:var(--lv-ink, inherit);
}
.lv-legal-body h3,.lv-legal-body h4{
margin:1.75rem 0 .5rem;
font-family:var(--lv-font-body, inherit);
font-weight:600;
font-size:1.0625rem;
line-height:1.4;
color:var(--lv-ink, inherit);
}
.lv-legal-body ul,.lv-legal-body ol{margin:0 0 1.25rem;padding:0 0 0 1.375rem}
.lv-legal-body ul{list-style:disc}
.lv-legal-body ol{list-style:decimal}
.lv-legal-body li{margin:0 0 .375rem;padding:0}
.lv-legal-body li::marker{color:var(--lv-accent, currentColor)}
.lv-legal-body li>ul,.lv-legal-body li>ol{margin:.375rem 0 0}
.lv-legal-body li>p{margin:0}
.lv-legal-body strong,.lv-legal-body b{font-weight:600;color:var(--lv-ink, inherit)}
.lv-legal-body em,.lv-legal-body i{font-style:italic}
.lv-legal-body blockquote{margin:0 0 1.25rem;padding:0 0 0 1rem;border-left:2px solid var(--lv-line, currentColor)}
.lv-legal-body>:first-child{margin-top:0}
.lv-legal-body>:last-child{margin-bottom:0}
.lv-legal-body a{color:var(--lv-accent, currentColor);text-decoration:underline;text-underline-offset:.2em}
.lv-legal-body a:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:var(--lv-radius, inherit)}

/* THE SPECIAL OFFERS PAGE (21 Oct 2026, special-offers-page.ts). It uses the document frame above
   (.lv-legal: the owner's words in the site's own header, footer and fonts) and adds only the parts
   an offer has. Every selector here is a DESCENDANT of .lv-legal on purpose: that keeps these rules
   inside the one unlayered block of this file, so a ported design's Tailwind preflight cannot zero
   them as it zeroed the legal pages, and island-css.test.ts's rule that nothing else ever leaves the
   layer stays exactly as strict as it was. Structure only, every value a --lv-* token, no colour
   written literally and nothing marked important: a design that means to restyle this page still
   wins with its own .lv-offers rule, which comes later in the cascade. */
.lv-legal .lv-offers-count{
margin:0 0 .5rem;
font-size:.8125rem;
letter-spacing:.14em;
text-transform:uppercase;
color:var(--lv-accent, currentColor);
font-variant-numeric:tabular-nums;
}
.lv-legal .lv-offers-ask{margin:2rem 0 0}
.lv-legal .lv-offers-ask a{color:var(--lv-accent, currentColor);text-decoration:underline;text-underline-offset:.2em}
.lv-legal .lv-offers-list{
margin:3rem 0 0;
padding:0;
list-style:none;
display:grid;
gap:2rem;
}
.lv-legal .lv-offer{
margin:0;
/* A clamp, not a @media: every rule in this block is a plain rule on purpose, so the only at-rules
   outside the layer stay the cookie notice's one @media, and island-css.test.ts's count stays as
   strict as it was. */
padding:clamp(1.5rem, 1rem + 2vw, 2rem);
border:1px solid var(--lv-line, currentColor);
border-radius:var(--lv-radius, inherit);
background:var(--lv-ground, transparent);
}
.lv-legal .lv-offer-name{
margin:0 0 .75rem;
font-family:var(--lv-font-display, inherit);
font-weight:400;
font-size:clamp(1.375rem, 1.1rem + 1vw, 1.75rem);
line-height:1.2;
color:var(--lv-ink, inherit);
}
.lv-legal .lv-offer-stay{
margin:0 0 .75rem;
font-size:.9375rem;
color:var(--lv-ink-soft, currentColor);
font-variant-numeric:tabular-nums;
}
.lv-legal .lv-offer-price{
margin:0 0 1rem;
font-size:1.0625rem;
color:var(--lv-ink, inherit);
font-variant-numeric:tabular-nums;
}
.lv-legal .lv-offer-now{font-weight:600}
.lv-legal .lv-offer-saving{color:var(--lv-accent, currentColor);font-weight:600}
.lv-legal .lv-offer-words{margin:0 0 1rem}
.lv-legal .lv-offer-includes{
margin:0 0 1rem;
padding:0 0 0 1.375rem;
list-style:disc;
}
.lv-legal .lv-offer-includes li{margin:0 0 .25rem;padding:0}
.lv-legal .lv-offer-includes li::marker{color:var(--lv-accent, currentColor)}
.lv-legal .lv-offer-action{margin:0}
.lv-legal .lv-offer-action a{
display:inline-flex;
align-items:center;
justify-content:center;
min-height:44px;
min-width:44px;
padding:.6875rem 1.375rem;
background:var(--lv-accent, currentColor);
color:var(--lv-accent-ink, inherit);
border:1px solid var(--lv-accent, currentColor);
border-radius:var(--lv-radius, inherit);
font-family:var(--lv-font-body, inherit);
font-size:.9375rem;
text-decoration:none;
box-sizing:border-box;
}
.lv-legal .lv-offer-action a:focus-visible{outline:2px solid var(--lv-focus, currentColor);outline-offset:2px}
