/* ==========================================================================
   Selene Yachts Europe — design system
   ========================================================================== */
:root{
  --navy:#0e1a3c;
  --navy-900:#08122c;
  --navy-700:#16264f;
  --navy-500:#2b3f70;
  --gold:#b08d4f;
  --gold-light:#c9ab74;
  /* The gold above is tuned for dark grounds, where it measures ~6:1. On
     sand it only reaches 2.75:1, under the 3.0 that a meaningful graphic
     needs, so line icons on a light ground use this darker one (3.4:1) and
     still read as gold. */
  --gold-deep:#a07d3a;
  /* A third gold, one step down, so white type can sit on gold at all: --gold
     carries black at 6.8:1 but white at only 3.1:1 — enough for a graphic, not
     for type. This one takes white to 5.8:1 and black to 3.6:1, so the two are
     a pair: whichever ink a block uses picks its ground. The specification
     panel uses both — black masthead on --gold, white rows on this. */
  --gold-dark:#7d6129;
  --sand:#f4f1ec;
  --sand-2:#e9e4db;
  /* The only red on the site, and it has exactly one job: the pin on the
     contact map. Deep rather than pillar-box, so it sits on a greyscale map
     without shouting over the navy and gold everything else is made of. */
  --pin:#d0342c;
  --ink:#1b1f2a;
  --muted:#5d6474;
  --line:rgba(14,26,60,.14);
  --white:#fff;
  /* Bodoni Moda for headings, Inter for everything else. The fallbacks are
     chosen to fail gracefully rather than to pad the list: DIDOT is a real didone
     and ships on macOS/iOS, so an Apple visitor whose font request is blocked
     still gets high-contrast serif rather than Times. Playfair Display is in the
     stack as asked — it only applies to someone who has it installed locally,
     which is rare, but it costs nothing and it is the right next choice.
     A didone is a high-contrast face: its hairlines thin out at small sizes, so
     every place this site sets SERIF below about 1.4rem was re-weighted with it
     (see the notes at .spec-group dd, .spec-hero__n and .quote-card p). */
  --serif:"Bodoni Moda","Playfair Display",Didot,"Times New Roman",Georgia,serif;
  --sans:"Inter","Poppins","Helvetica Neue",Arial,sans-serif;
  --wrap:1240px;
  /* The vertical rhythm between stacked blocks, in one place — asked for as
     "make the windows of content a bit closer one to another". It was
     clamp(64px,9vw,120px) top and bottom, so two stacked sections put up to
     240px between the last line of one and the first of the next; on a 900px
     window that is a quarter of the screen of nothing. Down about 30%, which
     still reads as deliberate white space rather than a cramped page. Both
     .section and .cta-band read it, so they cannot drift apart — change the
     rhythm here, never in a component. */
  --sec-y:clamp(44px,6.2vw,84px);
  --nav-h:96px;
  --nav-h-solid:78px;
  /* offset used for #anchor jumps so the fixed header never covers the target */
  --anchor-offset:102px;
  /* the model page's jump bar sticks under the header, so its own height has to
     be added to that offset or every jump lands with the bar over the heading */
  --jump-h:54px;
  /* Entrance and exit: decelerating, no overshoot. Calm. */
  --ease:cubic-bezier(.22,.61,.36,1);
  /* Micro-interactions: a spring with a deliberately small overshoot (~4%).
     A pronounced bounce reads playful, which is wrong for this brand — this is
     just enough to feel physical rather than mechanical. */
  --spring:cubic-bezier(.34,1.46,.64,1);
  /* Press down: fast and flat, so the button feels like it meets your finger. */
  --press:cubic-bezier(.4,0,.2,1);
}
*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:var(--anchor-offset);-webkit-text-size-adjust:100%}
body{
  margin:0;font-family:var(--sans);color:var(--ink);background:var(--white);
  font-size:17px;line-height:1.7;-webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
/* every anchor target clears the fixed header */
main [id],section[id],[id].anchor{scroll-margin-top:var(--anchor-offset)}

/* ---------- accessibility ---------- */
.skip-link{
  position:absolute;left:16px;top:-100px;z-index:300;background:var(--gold);color:#fff;
  padding:12px 20px;font-size:.76rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  transition:top .2s var(--ease)
}
.skip-link:focus{top:16px}
:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.site-header :focus-visible,.mobile-nav :focus-visible{outline-color:var(--gold-light)}
.visually-hidden{
  position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0
}
/* brief highlight so you can see which section an #anchor landed on */
@keyframes anchor-flash{
  0%{box-shadow:inset 3px 0 0 var(--gold)}
  70%{box-shadow:inset 3px 0 0 var(--gold)}
  100%{box-shadow:inset 3px 0 0 rgba(176,141,79,0)}
}
.anchor-flash{animation:anchor-flash 1.9s var(--ease) 1}
/* HEADINGS. h5 and h6 join the rule: they were inheriting the body sans, which
   is not what "apply these globally" means, and the pre-owned and article pages
   both use an h5.
   font-optical-sizing:auto is free insurance — where the browser gets Bodoni
   Moda's variable font it picks the optical size for the rendered size, which is
   exactly what a didone needs to stop the hairlines disappearing on a heading and
   fattening on a caption. It does nothing at all when the face is static.
   letter-spacing drops from .005em to 0: Bodoni Moda is already a wide-set face,
   and the extra tracking that suited Cormorant's tight old-style caps read as
   loose on this one. line-height nudges up from 1.12 to 1.14 for the same reason —
   a taller x-height needs a hair more room. */
h1,h2,h3,h4,h5,h6{
  font-family:var(--serif);font-weight:400;line-height:1.14;margin:0 0 .5em;
  letter-spacing:0;font-optical-sizing:auto
}
/* THE FLOOR CAME DOWN FOR BODONI MODA, and it is a measurement, not a taste.
   The home page's H1 contains "CRAFTSMANSHIP." — fourteen characters of wide-set
   didone caps and the longest unbreakable word on the site. At the old
   clamp(2.6rem,...) floor it renders 370px wide; a 360px phone has 320px of
   content width, so the word was being CUT — silently, because body{overflow-x:
   hidden} clips it rather than showing a scrollbar. Measured across 360/390/414:
   2.35rem still overflows at 360, 2.2rem fits with 8px to spare, 2.1rem with 22.
   Taking 2.1rem and raising the vw term from 6 to 8.2 so the size climbs back to
   the old scale by about 560px instead of crawling there.
   NOTE FOR NEXT TIME: no suite can catch this. They all abort **fonts.g**, so
   they measure the fallback stack, and the fallback is narrower than Bodoni. This
   was found by rendering the real face from a local copy — see the note in
   build.py's FONTS. */
h1{font-size:clamp(2.1rem,8.2vw,4.6rem)}
h2{font-size:clamp(2rem,4vw,3.2rem)}
h3{font-size:clamp(1.4rem,2.2vw,2rem)}
p{margin:0 0 1.15em}
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:28px}
.wrap-narrow{max-width:820px;margin-inline:auto;padding-inline:28px}
.eyebrow{
  font-family:var(--sans);font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;
  color:var(--gold);font-weight:600;margin:0 0 1.1em;display:block
}
.eyebrow--light{color:var(--gold-light);text-shadow:0 1px 12px rgba(8,18,44,.75)}
.lead{font-size:1.18rem;color:var(--muted)}
.italic{font-style:italic}
.center{text-align:center}
.section{padding:var(--sec-y) 0}
.section--sand{background:var(--sand)}
.section--navy{background:var(--navy);color:#e8ecf6}
.section--navy h2,.section--navy h3{color:#fff}
.section--navy .lead{color:#b9c3da}
.rule{width:56px;height:1px;background:var(--gold);border:0;margin:0 0 28px}
.rule.center{margin-inline:auto}

/* ---------- buttons ---------- */
.btn{
  display:inline-block;font-family:var(--sans);font-size:.76rem;font-weight:600;
  letter-spacing:.18em;text-transform:uppercase;padding:16px 34px;border:1px solid var(--navy);
  color:var(--navy);background:transparent;cursor:pointer;
  /* explicit rather than `all`: transitioning everything animates properties we
     never intended to and costs more per frame */
  transition:background .35s var(--ease),color .35s var(--ease),
             border-color .35s var(--ease),transform .4s var(--spring),
             box-shadow .4s var(--ease)
}
.btn:hover{background:var(--navy);color:#fff;transform:translateY(-2px)}
.btn:active{transform:translateY(0) scale(.978);transition-duration:.09s;transition-timing-function:var(--press)}
.btn--gold{border-color:var(--gold);color:#fff;background:var(--gold)}
.btn--gold:hover{background:transparent;color:var(--gold);box-shadow:0 8px 22px rgba(176,141,79,.28)}
/* Sits on photographs, so it carries its own backdrop: over blown-out spray or
   a white hull an outline-only button disappears entirely. */
.btn--light{
  border-color:rgba(255,255,255,.8);color:#fff;
  /* 0.62 is the alpha at which white text still clears WCAG AA (4.5:1) even
     when the photograph behind is blown out to pure white — measured, not
     eyeballed. Below about 0.58 it fails over spray and white hulls. */
  background:rgba(8,18,44,.62);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px)
}
.btn--light:hover{background:#fff;color:var(--navy);backdrop-filter:none;box-shadow:0 10px 28px rgba(8,18,44,.3)}
.btn--sm{padding:12px 26px;font-size:.7rem}
.link-arrow{
  font-size:.76rem;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);
  display:inline-flex;align-items:center;gap:.6em
}
.link-arrow{transition:color .25s var(--ease)}
.link-arrow::after{content:"→";transition:transform .42s var(--spring)}
.link-arrow:hover::after,a:hover .link-arrow::after{transform:translateX(6px)}
.link-arrow:active::after{transform:translateX(3px);transition-duration:.09s}

/* ---------- header ---------- */
.site-header{
  position:fixed;inset:0 0 auto 0;z-index:100;height:var(--nav-h);
  display:flex;align-items:center;transition:background .4s var(--ease),height .4s var(--ease),box-shadow .4s var(--ease);
  background:linear-gradient(to bottom,rgba(8,18,44,.55),transparent)
}
.site-header.solid{background:#fff;box-shadow:0 1px 0 var(--line);height:78px}
.site-header .wrap{display:flex;align-items:center;justify-content:space-between;width:100%;gap:24px}
.logo{display:block;flex:0 0 auto}
.logo img{height:52px;width:auto;transition:height .4s var(--ease)}
.site-header.solid .logo img{height:44px}
.logo .l-dark{display:none}
.site-header.solid .logo .l-dark{display:block}
.site-header.solid .logo .l-light{display:none}

.nav{display:flex;align-items:center;gap:26px;margin:0;padding:0}
.nav > li{position:relative;list-style:none;display:flex;align-items:center;gap:2px}
.nav a.top{
  position:relative;
  display:flex;align-items:center;gap:.45em;font-size:.76rem;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;color:#fff;padding:12px 0;white-space:nowrap;
  transition:color .3s var(--ease)
}
/* the underline wipes in rather than snapping on as a border */
.nav a.top::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--gold);
  transform:scaleX(0);transform-origin:left center;transition:transform .34s var(--ease)
}
.nav a.top:hover::after,.nav li.current > a.top::after,.nav > li.open > a.top::after{transform:scaleX(1)}
.site-header.solid .nav a.top{color:var(--navy)}
.nav a.top:hover,.nav li.current > a.top,.nav > li.open > a.top{color:var(--gold)}
.site-header.solid .nav a.top:hover,.site-header.solid .nav li.current > a.top,
.site-header.solid .nav > li.open > a.top{color:var(--gold)}

/* the caret is its own button so the parent link still navigates to its page */
.nav-toggle{
  display:flex;align-items:center;justify-content:center;background:none;border:0;cursor:pointer;
  color:#fff;padding:12px 4px;line-height:1;transition:color .3s
}
.site-header.solid .nav-toggle{color:var(--navy)}
.nav-toggle:hover,.nav > li.open > .nav-toggle,
.site-header.solid .nav-toggle:hover,.site-header.solid .nav > li.open > .nav-toggle{color:var(--gold)}
.caret{
  display:block;width:9px;height:9px;
  /* 3px reads as a deliberate chevron rather than a hairline; at the old 1.5px
     it disappeared against the photography behind the transparent header. Note
     2.5px is no good: the browser reports it back as 2px at a device ratio of
     1, so half-pixel borders are not something to rely on. */
  border-right:3px solid currentColor;border-bottom:3px solid currentColor;
  border-radius:0 0 1px 0;
  transform:rotate(45deg) translate(-1px,-1px);transition:transform .3s var(--ease);opacity:1
}
.nav > li.open > .nav-toggle .caret{transform:rotate(225deg) translate(-1px,-1px)}

.submenu{
  position:absolute;top:100%;left:-24px;min-width:264px;background:#fff;border-top:2px solid var(--gold);
  box-shadow:0 24px 48px rgba(8,18,44,.16);padding:14px 0;margin:0;list-style:none;
  opacity:0;visibility:hidden;transform:translateY(10px) scale(.985);transform-origin:top left;
  /* visibility flips instantly on open (so items are focusable straight away)
     and is delayed on close (so the fade-out is still visible) */
  transition:opacity .28s var(--ease),transform .34s var(--spring),visibility 0s linear .34s
}
/* invisible bridge: keeps hover alive while the pointer travels into the panel */
.nav > li.has-sub::after{
  content:"";position:absolute;top:100%;left:-24px;right:-24px;height:14px;display:none
}
.nav > li.has-sub:hover::after{display:block}
/* no-JS fallback — pure CSS hover/focus */
html:not(.js) .nav > li:hover > .submenu,
html:not(.js) .nav > li:focus-within > .submenu,
.nav > li.open > .submenu{
  opacity:1;visibility:visible;transform:none;
  transition:opacity .28s var(--ease),transform .34s var(--spring),visibility 0s
}
.submenu li{list-style:none}
.submenu a{
  display:block;padding:9px 26px;font-size:.86rem;color:var(--navy);
  border-left:2px solid transparent;transition:background .2s,color .2s,border-color .2s
}
.submenu a{transition:background .2s var(--ease),color .2s var(--ease),
            border-color .2s var(--ease),padding-left .28s var(--spring)}
.submenu a:hover,.submenu a:focus-visible{background:var(--sand);color:var(--gold);padding-left:32px}
.submenu a[aria-current]{background:var(--sand);color:var(--gold);border-left-color:var(--gold);font-weight:500}
.submenu .group{
  padding:14px 26px 6px;font-size:.66rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);font-weight:600
}
.submenu .group:first-child{padding-top:2px}
.submenu .group a{color:inherit;display:inline;padding:0;border:0;background:none}
.submenu .group a:hover{color:var(--gold)}
/* keep the panel on screen for the right-hand menus */
.nav > li:nth-last-child(-n+2) > .submenu{left:auto;right:-24px}

/* ---------- the yachts panel: one column per range ---------- */
/* Fifteen models stacked in a single column would run off the bottom of the
   screen, so the ranges sit side by side. */
.submenu--mega{
  display:grid;grid-template-columns:repeat(4,minmax(172px,1fr));
  gap:0 8px;padding:22px 26px 20px;min-width:0;width:max-content;max-width:min(94vw,940px);
  /* opens rightwards from the Yachts item, which sits at the left of the bar —
     anchoring it to the right edge pushes 800px of panel off screen */
  left:-24px;right:auto
}
.submenu--mega .sm-col{display:block}
.submenu--mega .sm-col > ul{list-style:none;margin:0;padding:0}
.submenu--mega .group{padding:0 0 10px;margin-bottom:6px;border-bottom:1px solid var(--line)}
.submenu--mega .group:first-child{padding-top:0}
.submenu--mega a{padding:7px 10px 7px 8px;font-size:.84rem;white-space:nowrap}
.submenu--mega .sm-all a{
  margin-top:6px;font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold);font-weight:600
}

/* ================= the model page's head, and its jump bar =================
   Asked for after the Selene Americas 78 page: the ship's name at the top, the
   photograph below it, and a row of links that jump straight to a part of the
   page. It replaces the .hero--short the model pages opened with, where the name
   sat over the photograph — that markup is still there for the article pages.

   Two things follow from the change and both are improvements. The type is on a
   solid ground, so no scrim has to be tuned for the brightest picture any of
   nineteen boats might have — the fight that produced 1.88:1 breadcrumbs the
   first time a pale photograph arrived. And the lead photograph becomes an
   <img> with alt text instead of a CSS background, which is what it always
   should have been for the one picture the page is about. */
/* No header clearance here any more: the jump bar sits above this block and
   carries it (.jump--top below). This is the gap under the bar. */
.modelhead{background:var(--white);padding-top:clamp(22px,2.6vw,34px)}
.modelhead__inner{padding-bottom:clamp(20px,2.6vw,32px)}
/* The breadcrumb was written for dark grounds — its links are
   rgba(255,255,255,.7) and its current item --gold-light — so on this white band
   the first three items were invisible and the fourth measured 3.5:1. Both are
   re-picked for the ground, which is the rule this project keeps relearning:
   when a component moves ground, every colour pitched at the old one has to be
   chosen again. Navy links at 15:1, the current item --muted at 5.9:1, and the
   separators the same hairline the rest of the light pages use. */
.modelhead .crumbs{margin-bottom:16px;color:var(--muted)}
.modelhead .crumbs a{color:var(--navy)}
.modelhead .crumbs a:hover{color:var(--gold-deep)}
.modelhead .crumbs span{color:var(--navy);opacity:.32}
/* Centred, asked for as "center align the boat names when you click on one". The
   whole head block goes with the name, not the name alone: an <h1> centred over a
   left-aligned breadcrumb and a left-aligned strapline reads as a mistake rather
   than a decision. The two width caps stay — a 24ch name and a 58ch strapline are
   still the readable measure — so each needs margin-inline:auto to sit in the
   middle of the wrap instead of at its left edge. */
.modelhead__inner{text-align:center}
.modelhead h1{margin:0 auto .28em;max-width:24ch}
.modelhead__tag{color:var(--muted);font-size:1.1rem;line-height:1.6;
  max-width:58ch;margin:0 auto}
/* The breadcrumb needs nothing of its own: .crumbs is not a flex row, so a
   justify-content here would be a dead rule that looks like it is doing the
   work — the inherited text-align does it. */
/* The width of the text, and never cropped — both asked for, in that order.
   It started full-bleed with a max-height and object-fit:cover, which meant a
   wide window got a letterboxed 2.5:1 slice of a 3:2 photograph. Now it sits in
   the same .wrap the copy uses, and the only thing declared is the width: no
   aspect-ratio, no max-height, no object-fit, so the box takes the picture's own
   proportions whatever they are. That matters beyond the local sets — most of
   the fleet still hotlinks, and those frames are not all 3:2.

   height:auto is load-bearing, not tidiness: the <img> carries width/height
   attributes so the browser can reserve a box before the file lands, and those
   attributes arrive as a presentational height:1067px. Without height:auto the
   picture is sized by that number instead of by its own shape. */
/* Smaller, on instruction: at the full width of the wrap a 3:2 photograph came
   out 790px tall on a 1440px window, and the head took two scrolls to get past.
   Capped and centred under the (centred) name, so the name and the picture are
   one screen.
   Smaller again, on instruction — "it becomes blurry that big". That was not a
   taste note, it was a resolution note, and it is right: most of the fleet's
   lead photographs are hotlinked from the builder at 980px wide, so an 820px box
   on a 2x screen was asking for 1640px of picture from a 980px file and the
   browser was upscaling it. 680px is inside the source's own resolution at 1.44x
   and comfortably sharp; it also happens to be about 453px tall on a 3:2 file,
   which keeps the one-screen head. Raising this cap again means localising and
   re-exporting the sources first, not just changing the number.
   The cap is on the WIDTH and there is still no object-fit: the standing
   instruction is that a landing photograph is never cropped, only resized, so the
   box keeps the file's own proportions whatever they are. */
.modelhead__photo{
  display:block;width:100%;max-width:680px;height:auto;
  background:var(--sand-2)
}
/* PHOTOGRAPH LEFT, FIGURES RIGHT, on instruction: "the hero photo would be on the
   left and the 5 random specs below each hero would be on the right". The figures
   were a full-width band under the photograph and are now the second column of
   one row, so the head reads as a single block.
   The left track is minmax(0,680px): it may SHRINK but never grow, because 680 is
   a resolution cap rather than a taste — the smallest hero on the site is the
   yard's 980px frame for the 42 Europa, and 680 x 1.4 is 952. The right track
   takes whatever is left, which at the wrap's 1184px of content is about 470.
   align-items:stretch is what makes the panel exactly as tall as the photograph;
   the figures then spread inside it (see .spec-hero below), so the two columns end
   level without either being given a height. */
.modelhead__row{
  display:grid;
  grid-template-columns:minmax(0,680px) minmax(0,1fr);
  gap:clamp(22px,2.6vw,38px);
  align-items:stretch;
  padding-bottom:clamp(26px,3vw,44px)
}
.modelhead__media{display:flex;align-items:flex-start}
/* If the head photograph fails to load, main.js swaps it for a .ph placeholder —
   and a bare div is a flex child with no intrinsic size, so it would collapse to
   nothing and leave the panel beside an empty column. It gets the shape the
   photograph would have had. (In the gallery the FRAME is exempt from that swap
   for a different reason; see .gal--failed.) */
.modelhead__media .ph{width:100%;max-width:680px;aspect-ratio:3/2}
/* The photograph's own margin-bottom is gone — the row's padding does that job
   now, and a margin under a grid item would push its own column out of line with
   the panel beside it. */
@media (max-width:900px){
  /* Stacked, and the photograph goes back to centred under the centred name. */
  .modelhead__row{grid-template-columns:1fr;gap:clamp(20px,3vw,30px)}
  .modelhead__media{justify-content:center}
  .modelhead__photo{margin-inline:auto}
}

/* The bar is the first thing on the page now, so it — not .modelhead — clears
   the fixed header. margin, not padding: the bar is sticky, and padding here
   would travel with it and leave a gap under the header on the way down. */
.jump--top{margin-top:var(--nav-h)}
.jump{
  position:sticky;top:var(--nav-h-solid);z-index:60;
  background:var(--sand);border-bottom:1px solid var(--line)
}
/* One row, scrollable sideways on a narrow screen rather than wrapping into two
   rows that change the bar's height as you scroll past it. */
.jump__list{
  display:flex;gap:clamp(4px,1.6vw,26px);align-items:stretch;
  margin:0;padding:0;list-style:none;
  overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none
}
.jump__list::-webkit-scrollbar{display:none}
.jump__list a{
  display:flex;align-items:center;height:var(--jump-h);white-space:nowrap;
  font-family:var(--sans);font-size:.76rem;font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;color:var(--navy);text-decoration:none;
  border-bottom:3px solid transparent;transition:color .3s var(--ease),border-color .3s var(--ease)
}
/* The link text stays navy at 15:1 in every state. Gold is the sand-ground
   --gold-deep, which is 3.4:1 — fine for a 3px rule, which is a graphic needing
   3:1, and a fail for 12px text, which needs 4.5:1. So the state lives in the
   underline, not the colour: a hairline on hover, the gold rule plus a heavier
   weight for the section you are in. */
.jump__list a:hover{border-bottom-color:var(--line)}
.jump__list a[aria-current="true"]{font-weight:700;border-bottom-color:var(--gold-deep)}
.jump__list a:focus-visible{outline:2px solid var(--navy);outline-offset:-4px}

/* Landing a jump below the bar, on both paths.
   Two things scroll to an #anchor on this site and they do the arithmetic
   differently. `html{scroll-padding-top}` (102px) is what the browser uses on a
   fragment navigation, and it adds the target's own scroll-margin-top on top of
   that. main.js intercepts same-page anchor clicks and does its own sum instead,
   from the live header height — where scroll-margin plays no part at all.
   So the bar's height is added in both places: here as a scroll-margin that
   REPLACES the site-wide 102px (or the two would stack to 258), and in
   scrollToTarget() as the height of whatever sticky bar the page has.
   Measured both ways, script on and script off, in spec.js section 7. */
main .jump-target{scroll-margin-top:calc(var(--jump-h) + 2px)}
/* a zero-height hook for an anchor that has no element of its own to sit on —
   the CTA band is a shared component and cannot carry the id itself */
.jump-point{display:block;height:0;overflow:hidden}

@media (max-width:820px){
  .modelhead{padding-top:calc(var(--nav-h) + 18px)}
  .jump{top:var(--nav-h-solid)}
  .jump__list{gap:2px}
  .jump__list a{font-size:.7rem;padding:0 10px}
  .jump__list a:first-child{padding-left:0}
}

@media (max-width:1260px){
  .submenu--mega{grid-template-columns:repeat(4,minmax(150px,1fr));max-width:min(94vw,820px)}
  .submenu--mega a{font-size:.8rem;padding-inline:6px}
}

.nav-cta{display:flex;align-items:center;gap:18px;flex:0 0 auto}
.burger{display:none;background:none;border:0;padding:8px;cursor:pointer}
.burger span{
  display:block;width:26px;height:2px;background:#fff;margin:5px 0;
  transition:transform .4s var(--spring),opacity .2s var(--ease),background .3s
}
.burger[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.burger[aria-expanded="true"] span:nth-child(2){opacity:0;transform:scaleX(.4)}
.burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
/* the drawer is navy, so the bars must stay light while it is open */
.burger[aria-expanded="true"] span,.site-header.solid .burger[aria-expanded="true"] span{background:#fff}
.burger:active{transform:scale(.92);transition:transform .09s var(--press)}
.site-header.solid .burger span{background:var(--navy)}

/* ---------- mobile nav ---------- */
.mobile-nav{
  position:fixed;inset:0;z-index:99;background:var(--navy);padding:110px 28px 40px;overflow-y:auto;
  transform:translateX(100%);transition:transform .4s var(--ease);visibility:hidden
}
.mobile-nav.open{transform:translateX(0);visibility:visible}
.mobile-nav ul{list-style:none;margin:0;padding:0}
.mobile-nav li{list-style:none}
.mobile-nav > ul > li{border-bottom:1px solid rgba(255,255,255,.12)}
.mobile-nav a{display:block;padding:16px 0;color:#fff;font-size:1rem;letter-spacing:.1em;text-transform:uppercase}
/* parent row: link on the left, expand button on the right */
.mobile-nav .mrow{display:flex;align-items:center;justify-content:space-between;gap:10px}
.mobile-nav .mrow > a{flex:1 1 auto}
.msub-toggle{
  /* No border and no background: the arrow is the affordance, the box was just
     noise. The 44px box is still there as a touch target, it is simply not
     drawn — which is why the focus ring is defined explicitly below. */
  flex:0 0 auto;background:none;border:0;color:#fff;cursor:pointer;
  width:44px;height:44px;display:flex;align-items:center;justify-content:center;
  transition:color .25s var(--ease)
}
.msub-toggle:hover,.mobile-nav li.open > .mrow > .msub-toggle{color:var(--gold-light)}
.msub-toggle:focus-visible{outline:2px solid var(--gold-light);outline-offset:-4px}
.msub-toggle .caret{width:10px;height:10px;border-width:0 3px 3px 0}
.mobile-nav li.open > .mrow > .msub-toggle .caret{transform:rotate(225deg) translate(-1px,-1px)}
/* Sub-lists slide open. display:none cannot be animated, so they stay in the
   layout and are clipped by max-height, which main.js sets from scrollHeight so
   the distance is always exactly right. */
.mobile-nav .sub{
  overflow:hidden;max-height:0;opacity:0;padding-bottom:0;
  transition:max-height .42s var(--ease),opacity .3s var(--ease),padding-bottom .42s var(--ease)
}
.mobile-nav li.open > .sub{opacity:1;padding-bottom:10px}
.mobile-nav .sub a{
  padding:10px 0 10px 18px;font-size:.9rem;text-transform:none;letter-spacing:.02em;color:#b9c3da;
  transition:color .25s var(--ease),padding-left .28s var(--spring)
}
.mobile-nav .sub a:hover,.mobile-nav .sub a:active{padding-left:24px}
.mobile-nav .sub a:hover,.mobile-nav .sub a[aria-current]{color:var(--gold-light)}
.mobile-nav .sub a[aria-current]{box-shadow:inset 2px 0 0 var(--gold)}
.mobile-nav .group{padding:14px 0 4px;font-size:.66rem;letter-spacing:.2em;text-transform:uppercase;color:var(--gold-light)}
.mobile-nav > ul > li > a[aria-current],.mobile-nav .mrow > a[aria-current]{color:var(--gold-light)}
html:not(.js) .mobile-nav .sub{max-height:none;opacity:1;padding-bottom:10px}
html:not(.js) .msub-toggle{display:none}

/* ---------- hero ---------- */
.hero{
  position:relative;min-height:min(94vh,900px);display:flex;align-items:flex-end;
  background:var(--navy-900);overflow:hidden
}
.hero--short{min-height:min(62vh,620px)}
.hero__bg{
  position:absolute;left:0;right:0;top:-5%;height:110%;
  background-size:cover;background-position:center;transform:scale(1.03)
}
.hero__bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(to top,rgba(8,18,44,.82) 0%,rgba(8,18,44,.35) 45%,rgba(8,18,44,.5) 100%)
}
/* The model hero carries a taller block of type than the home page does — a
   two-line name, a line of copy AND the breadcrumbs, spanning roughly 30% to 70%
   of a 62vh hero — and the gradient above is at its thinnest exactly across that
   middle band.
   That was survivable while every model photograph was a dark hotlinked frame.
   The Selene 45's are ours and one of them is a pale boat under a white sky, and
   measured on the real pixels the breadcrumbs came out at 1.88:1 and the
   sub-headline at 4.25:1. Both failed.
   So the middle of the scrim is lifted, for the short hero only: the home page's
   was deliberately lightened for its darker clip and is left exactly as tuned. */
/* LIFTED A SECOND TIME, for the same reason and by a brighter picture. The 52's
   sixteen renders replaced its three hotlinked frames, and two article heroes
   borrow them: a sunset sky and a white hull on flat calm are brighter through
   the middle of the frame than anything this scrim had been shown, and the
   sub-headline measured 4.38:1 against the 4.5 it needs. 48%/100% go .62/.66 ->
   .70/.72. The 0% stop is untouched — the bottom was never the problem.
   This is the third time the same lesson has been paid for: A SCRIM IS ONLY
   TUNED FOR THE PHOTOGRAPHS IT HAS BEEN SHOWN, and the thinnest part of a
   bottom-anchored gradient is its middle, which is exactly where a sub-headline
   sits. spec.js measures it on the painted pixels of every article hero whose
   photograph is ours, so the next bright frame fails here rather than shipping. */
.hero--short .hero__bg::after{
  background:linear-gradient(to top,rgba(8,18,44,.88) 0%,rgba(8,18,44,.70) 48%,rgba(8,18,44,.72) 100%)
}
/* The home photograph is bright right where the headline sits — the boat's white
   superstructure runs through the middle of the frame. A scrim from the left
   gives the type a dark base while leaving the bow and the spray clear. */
.hero__bg--home{background-position:center 45%}

/* Decorative background clip. Sits inside .hero__bg so the parallax transform
   carries the video and its poster together, and the ::after scrim — which comes
   later in the DOM — still paints over the top. object-position matches the
   poster's background-position so nothing shifts as the video fades in. */
.hero__video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center 45%;
  opacity:0;transition:opacity 1s var(--ease);pointer-events:none
}
.hero__video.ready{opacity:1}
.hero__bg--home::after{
  background:
    /* top band: the sky above the horizon is bright, and the eyebrow sits in it */
    linear-gradient(to bottom,rgba(8,18,44,.62) 0%,rgba(8,18,44,.34) 22%,rgba(8,18,44,.06) 40%,transparent 52%),
    /* Left band, behind the headline. Solved against decoded video frames rather
       than the still, because the still is not what a visitor sees: the first
       clip's brightest frame took the headline to 2.86:1, under the 3.0 that AA
       wants for large text, while every screenshot of it looked fine.

       Re-solved for the current clip, which is darker on the left — open water
       rather than white spray — so the scrim could be lightened and still clear.
       Worst case is now 3.70:1 for the headline, checked at eight viewport sizes
       from 390px to 1920px, because the cover crop shifts with aspect ratio and
       a value solved at one size is not automatically safe at another. The
       far-right stop stays barely touched so the bow is not muddied.

       If you swap the clip again, re-measure. Do not eyeball it. */
    linear-gradient(to right,rgba(8,18,44,.82) 0%,rgba(8,18,44,.72) 30%,rgba(8,18,44,.35) 60%,rgba(8,18,44,.09) 100%),
    linear-gradient(to top,rgba(8,18,44,.86) 0%,rgba(8,18,44,.20) 55%,rgba(8,18,44,.42) 100%)
}
@media (max-width:820px){
  /* narrow viewports crop to the middle of the boat, where it is brightest, so
     use an even wash rather than a side scrim */
  .hero__bg--home::after{
    background:linear-gradient(to top,rgba(8,18,44,.9) 0%,rgba(8,18,44,.62) 55%,rgba(8,18,44,.66) 100%)
  }
}
.hero__inner{
  position:relative;z-index:2;width:100%;color:#fff;
  /* Reserve the fixed header's height. The hero bottom-aligns its content, so
     on a window shorter than the content the block used to overflow upward and
     the eyebrow ended up printed across the logo. Padding here makes the hero
     grow instead of overlapping — on tall windows it changes nothing, because
     the content is still pinned to the bottom. */
  padding-top:calc(var(--nav-h) + 28px);
  padding-bottom:clamp(56px,8vw,110px)
}
.hero h1{color:#fff;max-width:16ch;margin-bottom:.35em;text-shadow:0 2px 30px rgba(0,0,0,.35)}
.hero h1 em{font-style:italic;color:var(--gold-light)}
.hero p{max-width:56ch;color:#dfe5f2;font-size:1.1rem;text-shadow:0 1px 14px rgba(8,18,44,.7)}
/* The hero's opening sentence, promoted from <p> to <h2> so the page's first
   heading after the H1 carries a term someone would actually search for. Styled
   to match the body text it came from, because it is the same sentence — this is
   a markup change, not a copy change. */
.hero__sub{
  font-family:var(--sans);font-weight:400;font-size:1.1rem;line-height:1.7;
  max-width:56ch;color:#dfe5f2;margin:0 0 1.15em;letter-spacing:0;
  text-shadow:0 1px 14px rgba(8,18,44,.7)
}
.hero .btn{margin-top:14px}
/* ---------- page head (interior pages) ---------- */
.pagehead{
  background:var(--navy);color:#fff;padding:calc(var(--nav-h) + 70px) 0 70px;position:relative;overflow:hidden
}
.pagehead__bg{
  position:absolute;left:0;right:0;top:-5%;height:110%;
  background-size:cover;background-position:center;opacity:.4
}
.pagehead__bg::after{content:"";position:absolute;inset:0;background:linear-gradient(to right,rgba(8,18,44,.9),rgba(8,18,44,.55))}
.pagehead .wrap{position:relative;z-index:2}
.pagehead h1{color:#fff;margin-bottom:.2em}
.pagehead p{color:#c3cce0;max-width:60ch;margin:0}
.crumbs{font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--gold-light);margin-bottom:22px}
/* White in a hero, not gold. --gold-light's own luminance caps it near 4.6:1
   over anything but near-solid navy, and at 11.5px these need 4.5 — over the
   45's pale exterior the gold measured 2.83:1 even with the lifted scrim above,
   against 7.48:1 for white. The film-strip tags learned this first and the hero
   tag before them; this is the third time the answer has been "white over a
   photograph". */
.hero__inner .crumbs,.hero__inner .crumbs a{color:#fff}
.hero__inner .crumbs a:hover{color:var(--gold-light)}
.crumbs a{color:rgba(255,255,255,.7)}
.crumbs a:hover{color:var(--gold-light)}
.crumbs span{opacity:.5;margin:0 .5em}

/* ---------- split feature ---------- */
.split{display:grid;grid-template-columns:1fr 1fr;gap:clamp(32px,5vw,80px);align-items:center}
.split--rev .split__media{order:2}
.split__media{aspect-ratio:4/3;overflow:hidden;background:linear-gradient(135deg,#12224a 0%,#1d3163 45%,#2b3f70 100%)}
.split__media img,.split__media .ph{aspect-ratio:4/3;object-fit:cover;width:100%;height:100%}
/* THE SALES SPLIT'S RIGHT COLUMN IS THE FINISH SLIDER, not a photograph, and it
   has to give up BOTH of the things .split__media does for a photograph:
     * the 4:3 — the pair is 5:3 and cropping a COMPARISON to a different ratio is
       the one thing that would stop its two halves agreeing, which is all it is
       for;
     * the navy gradient — that is the branded ground a failed photograph shows
       through (the same device as .ph). With a 5:3 figure in a 4:3 box it showed
       as a navy band UNDER the frame with the caption sitting on it in --muted:
       grey on navy, unreadable. Caught by looking at the rendered page; no check
       would have said anything, because every element was present and correct.
   The figure brings its own frame, its own ratio and its own caption, so the
   container is just a grid cell here. */
.split__media--cmp{
  aspect-ratio:auto;background:none;overflow:visible;align-self:start
}
.split__media--cmp .cmp__img{aspect-ratio:auto}
.stat-row{display:flex;gap:44px;flex-wrap:wrap;margin:34px 0 0;padding:0;list-style:none}
.stat-row li{list-style:none}
.stat-row .n{font-family:var(--serif);font-size:2.8rem;line-height:1;color:var(--gold);display:block}
.stat-row .t{font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
.section--navy .stat-row .t{color:#9aa7c4}

/* ---------- range cards ---------- */
.range-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,3vw,42px)}
/* four ranges on the fleet page: two up, then one up on a phone */
.range-grid--four{grid-template-columns:repeat(2,1fr)}
.range-grid--four .range-card{min-height:400px}
@media (min-width:1100px){
  .range-grid--four{grid-template-columns:repeat(4,1fr);gap:clamp(16px,1.6vw,24px)}
  .range-grid--four .range-card{min-height:440px}
  .range-grid--four .range-card__body h3{font-size:1.5rem}
  .range-grid--four .range-card__body p{font-size:.9rem}
}
.range-card{position:relative;overflow:hidden;background:var(--navy-900);color:#fff;min-height:520px;display:flex;align-items:flex-end}
.range-card .ph,.range-card img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform 1s var(--ease)}
.range-card{transition:transform .5s var(--spring),box-shadow .45s var(--ease)}
.range-card:hover{--hov:translateY(-5px);box-shadow:0 28px 54px rgba(8,18,44,.22)}
.range-card:hover img{transform:scale(1.06)}
.range-card:active{
  --hov:translateY(-1px) scale(.995);
  transition-duration:.1s;transition-timing-function:var(--press)
}
/* The scrim, re-solved when the owner's own photographs arrived for three of
   these cards. It was .92 at 5% falling to .15 at 65%, tuned for the dark
   hotlinked frames it had been shown; over a sunset sky and two bright aerials
   the gold eyebrow measured 1.06–1.10:1 — invisible. Two changes, and both were
   needed: the eyebrow is white here (gold-light tops out around 4.6:1 over
   anything but near-solid navy, and no scrim that keeps a photograph readable
   gets there), and the wash is heavier across the band the type actually
   occupies. The thinnest part of a bottom-anchored gradient is its middle, which
   is exactly where this card's eyebrow sits. Measured on painted pixels. */
.range-card::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(to top,
    rgba(8,18,44,.95) 0%, rgba(8,18,44,.90) 30%, rgba(8,18,44,.78) 46%,
    rgba(8,18,44,.28) 66%, rgba(8,18,44,0) 90%)
}
.range-card .eyebrow,.range-card .eyebrow--light{color:#fff;text-shadow:0 1px 12px rgba(8,18,44,.8)}
/* The eyebrow is "N models" now — the lengths came out on instruction — so it
   fits on one line at the normal scale and needs nothing of its own. */
.range-card__body{position:relative;z-index:2;padding:clamp(28px,3vw,44px)}
.range-card h3{color:#fff;letter-spacing:.06em;text-transform:uppercase;font-size:1.7rem}
.range-card p{color:#cfd6e8;font-size:.98rem;max-width:44ch}

/* ---------- model grid ---------- */
.model-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,2.4vw,34px)}
.model-card{background:#fff;border:1px solid var(--line);display:flex;flex-direction:column;transition:box-shadow .4s var(--ease),transform .4s var(--ease)}
.model-card{
  transition:box-shadow .45s var(--ease),transform .5s var(--spring),border-color .35s var(--ease)
}
.model-card:hover{
  box-shadow:0 26px 50px rgba(8,18,44,.13);border-color:rgba(176,141,79,.5);
  --hov:translateY(-6px)
}
/* pressing anywhere in the card settles it back down */
.model-card:active{
  --hov:translateY(-1px) scale(.994);
  transition-duration:.1s;transition-timing-function:var(--press)
}
.model-card__media{position:relative;overflow:hidden;aspect-ratio:4/3;background:var(--navy-900)}
.model-card__media img,.model-card__media .ph{width:100%;height:100%;object-fit:cover;transition:transform .9s var(--ease)}
.model-card:hover .model-card__media img{transform:scale(1.06)}
/* "New" pill. Gold ground with navy ink: white on --gold is 3.1:1 and navy is
   5.5:1, which is the same pairing the buttons' hover state uses. It reads on the
   navy menu panel, on a white card and over a photograph, because it brings its
   own ground. */
.pill-new{
  display:inline-block;vertical-align:middle;margin-left:.5em;
  background:var(--gold);color:var(--navy-900);
  font-family:var(--sans);font-size:.58rem;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;padding:3px 7px;border-radius:2px;line-height:1.2;
  position:relative;top:-.08em
}
.model-card__body h3 .pill-new{font-size:.5rem}

.model-card__badge{
  position:absolute;top:16px;left:16px;z-index:2;background:rgba(14,26,60,.86);color:#fff;
  font-size:.6rem;letter-spacing:.18em;text-transform:uppercase;padding:7px 12px
}
.model-card__body{padding:26px 26px 28px;display:flex;flex-direction:column;flex:1}
.model-card__body h3{margin-bottom:.15em;font-size:1.75rem}
.model-card__len{font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--gold);margin-bottom:14px}
.model-card__body p{color:var(--muted);font-size:.95rem;flex:1}
.model-card__body .link-arrow{margin-top:14px}

/* ---------- the film strip: text over photograph ---------- */
/* The range pages list their boats as full-bleed panels rather than as a grid of
   cards: one photograph per boat, its name and a line about it set over the
   bottom-left corner, editorial rather than catalogue. It replaced the
   .model-card grid on those four pages — two listings of the same boats on one
   page would have been redundant, and would have broken the rule that no page
   repeats a photograph.
   Written in this stylesheet, with the site's own tokens, rather than in the
   Tailwind that yachts.html's hero uses: Tailwind here is a scoped exception
   that needs node to recompile, and this section wants nothing the tokens do not
   already give it. */
.filmstrip{display:block}
.filmcard{
  position:relative;display:flex;align-items:flex-end;overflow:hidden;
  /* tall enough to be the only thing on screen, capped so a large monitor does
     not turn one boat into two screenfuls */
  min-height:clamp(420px,66vh,700px);
  background:var(--navy-900);color:#fff;text-decoration:none;
  transition:box-shadow .5s var(--ease)
}
.filmcard + .filmcard{border-top:1px solid rgba(255,255,255,.10)}
.filmcard__media{position:absolute;inset:0;overflow:hidden}
.filmcard__media img,.filmcard__media .ph{
  width:100%;height:100%;object-fit:cover;
  /* the waterline, not the sky: these are wide photographs in a tall box */
  object-position:50% 56%;
  transition:transform 1.2s var(--ease)
}
/* THE HOVER ZOOM ON THE PHOTOGRAPH IS GONE, and this is the note rather than the
   rules, because deleting them is the fix.
   They were `.filmcard:hover .filmcard__media img{transform:scale(1.045)}` and an
   :active twin, written for the LARGE view — a full-width cinematic panel, where
   a slow 4.5% push made sense. That view was removed on instruction and the
   listings have been grid-only since: `<div class="filmstrip is-grid">` is the
   only thing on the site that carries .filmcard, on all four range pages.
   So the rules had no view left to belong to and kept firing in the grid, where
   the stated behaviour is the opposite — the CARD lifts (translateY(-6px) and a
   shadow) and the photograph holds still, because a card with a hard edge and a
   border should not have its picture creeping under it.
   film.js asserted exactly that and was mostly passing for the wrong reason: its
   hover did not always register, so the assertion often did not run at all. Now
   it waits for the lift before measuring, which is what turned this up.
   The reduced-motion block that used to cancel these went with them. */
/* Vignette, in two directions. Bottom-up carries the text block; left-to-right
   keeps the first words readable when the photograph is bright on that side.
   Both are needed: over a white hull in low sun the bottom gradient alone left
   the tag at 3.1:1. */
.filmcard__scrim{
  position:absolute;inset:0;z-index:1;
  background:
    linear-gradient(to top,rgba(8,18,44,.95) 0%,rgba(8,18,44,.80) 24%,
                    rgba(8,18,44,.34) 54%,rgba(8,18,44,.10) 100%),
    linear-gradient(to right,rgba(8,18,44,.70) 0%,rgba(8,18,44,.22) 58%,rgba(8,18,44,0) 100%)
}
.filmcard__body{
  position:relative;z-index:2;max-width:60ch;
  padding-block:clamp(34px,5vw,64px);
  /* the text lines up with .wrap on every other section, while the photograph
     runs edge to edge */
  padding-inline:max(28px,calc((100% - var(--wrap)) / 2 + 28px)) 28px
}
/* A second, tighter plate anchored to the text block itself, because the card's
   own vignette is anchored to the card. On a 700px panel the top line of text
   sits a third of the way up, where a bottom-up gradient has largely faded — and
   "readable regardless of the photograph's brightness" has to hold there too.
   This one always covers exactly the text, whatever the panel's height. */
.filmcard__body::before{
  content:"";position:absolute;z-index:-1;left:0;right:0;bottom:0;top:-64px;
  background:linear-gradient(to top,
    rgba(8,18,44,.80) 0%,rgba(8,18,44,.66) 62%,rgba(8,18,44,.34) 90%,rgba(8,18,44,0) 100%);
  /* Masked, not just narrow. The plate is a rectangle, and against a smooth sky
     its right edge read as a vertical seam down the middle of the photograph —
     obvious the moment you look for it. A second gradient in the background
     cannot fix that: two translucent layers composite, so the area to the right
     keeps the first layer's strength. A mask actually removes it. */
     The fade reaches zero exactly at the plate's own right edge, so there is no
     edge left to see — and the plate stays inside the body's box, because
     hanging it out to `right:-14vw` made the body itself report horizontal
     overflow. Invisible, since the card clips it, but a check that asks "does
     anything overflow" should not have to know which overflows are decorative. */
  -webkit-mask-image:linear-gradient(to right,#000 0%,#000 46%,transparent 100%);
          mask-image:linear-gradient(to right,#000 0%,#000 46%,transparent 100%)
}
.filmcard__tag{
  /* White, not --gold-light. The gold is the site's eyebrow colour and it was the
     first thing tried, but its own luminance caps it at about 4.6:1 over anything
     that is not nearly solid navy, and measured over eight photographs it came in
     between 3.2 and 4.7 — under the 4.5 a 10px line needs. Making the plate opaque
     enough to rescue it would have meant hiding the photograph behind the text,
     which is the opposite of the brief. White measures 6.7:1 on the same grounds,
     and the gold moved to the button's hover state, where it has a dark ground. */
  display:block;font-family:var(--sans);font-size:.66rem;letter-spacing:.22em;
  text-transform:uppercase;color:rgba(255,255,255,.86);margin-bottom:14px
}
.filmcard__body h3{
  /* was font-weight:300 — Cormorant had a light; Bodoni Moda's lightest is 400,
     so 300 would have been synthesised or silently rounded. Stated. */
  font-family:var(--serif);font-weight:400;line-height:1.06;color:#fff;
  font-size:clamp(1.9rem,3.6vw,3.1rem);margin:0 0 .5em
}
.filmcard__body p{
  color:rgba(255,255,255,.88);font-size:1rem;line-height:1.65;margin:0 0 28px;max-width:52ch
}
/* a span, not a nested <a>: the whole panel is the link, so a second anchor
   inside it would be a second tab stop to the same page */
.filmcard__cta{display:inline-block}
/* Gold on hover with navy type — 5.5:1, and it is where the range's gold now
   lives in this panel. White-on-gold, which is what .btn--gold does elsewhere on
   the site, measures 3.1:1 and is a known outstanding issue. */
.filmcard:hover .filmcard__cta{
  background:var(--gold);border-color:var(--gold);color:var(--navy);backdrop-filter:none;
  box-shadow:0 10px 28px rgba(8,18,44,.3)
}
.filmcard:focus-visible{outline:3px solid var(--gold-light);outline-offset:-3px}
@media (max-width:640px){
  .filmcard{min-height:clamp(380px,58vh,560px)}
  .filmcard__body p{font-size:.95rem;margin-bottom:24px}
}
/* ================= the model page specification =======================
   The boat's story on the left, its full specification on the right, side by
   side under the five headline figures. Restored: an editorial layout with a
   100vh hero, statement blocks and full-bleed breaks was built over this and
   reverted at the owner's request. */
/* Equal columns, because the four boxes have to be the same size — it was
   1.02fr / .98fr, a deliberate 23px lean toward the copy when the right-hand cell
   was a table nobody measured against it. With boxes, that lean is just two
   widths. */
.spec-split{
  display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(28px,3.4vw,48px);
  /* stretch, not start: the two boxes in a row end level, which is the "same
     size" the instruction was about. With start they ended ragged. */
  align-items:stretch
}
.spec-copy > :last-child{margin-bottom:0}

/* The panel. Gold ground, every word on it white -- the owner asked for the gold
   background first with black and white type, then for white only, so there is
   exactly one text colour in here and spec.js asserts that.
   --gold-dark #7d6129, not --gold: white on --gold is 3.1:1. The two cannot be
   chosen independently -- white needs a ground of luminance <= ~0.183 to clear
   4.5:1 and black needs >= ~0.175, and no gold in this palette sits in the gap,
   so the ink decides the gold. */
/* The panel is the footer's navy now, asked for in those words. It spent this
   project on --gold-dark for one reason — white type needs a ground of luminance
   <= ~0.183 to clear 4.5:1, and --gold-dark was the only gold that got there at
   5.8:1. --navy-900 is darker still (white reads about 17:1 on it), so the
   constraint that picked the gold is satisfied with room to spare and every white
   word on the panel gets better rather than worse. */
.spec-band{
  background:var(--navy-900);color:#fff;
  padding:clamp(30px,3vw,40px) clamp(26px,2.6vw,36px) clamp(24px,2.6vw,34px);
  position:relative
}
/* The white hairline that used to sit across the top of the panel is gone: the
   gold outline all four boxes now carry does that job, and two rules stacked at
   the same edge read as a mistake. (It existed because a gold rule on a gold
   ground was invisible — no longer the ground.) */
/* The masthead is set apart by a rule and by size, not by a second colour. */
.spec-band__head{
  margin-bottom:clamp(24px,2.4vw,32px);padding-bottom:20px;
  border-bottom:1px solid rgba(255,255,255,.3)
}
.spec-band__head .eyebrow{color:#fff;text-shadow:none;margin-bottom:.6em}
.spec-band__head h3{
  color:#fff;margin:0;font-size:clamp(1.35rem,1.9vw,1.7rem);line-height:1.2
}

/* One column, now that the table sits beside the copy rather than spanning the
   page. (When it was full width this was two columns balanced in Python -- CSS
   `columns: 2` left one side finishing 250px early, because it balances by
   flowing text and `break-inside: avoid` gave it nothing to flow.) */
/* The panel is the shorter of the two boxes in its row, and the row equalises
   them, so it would otherwise end with 400px of empty gold under the last row.
   Flex column plus space-between spreads the leftover BETWEEN the five groups
   instead — the panel fills its box and the groups breathe rather than the
   bottom being padded out. */
.spec-band{display:flex;flex-direction:column}
.spec-groups{
  display:flex;flex-direction:column;justify-content:space-between;
  flex:1 1 auto
}
.spec-group{margin:0 0 28px}
.spec-group:last-child{margin-bottom:0}
.spec-group > h3{
  font-family:var(--sans);font-size:.64rem;letter-spacing:.22em;text-transform:uppercase;
  color:#fff;margin:0 0 4px;padding-bottom:12px;
  border-bottom:1px solid rgba(255,255,255,.3)
}
.spec-group dl{margin:0}
/* flex-wrap, because "Accommodation" is a single 92px word and its value can run
   to a cabin count plus crew plus heads -- which squeezed the label to 79px and
   clipped it. A wrapped row puts the value on its own line, still right-aligned,
   rather than hyphenating a word nobody would hyphenate. */
.spec-group dl > div{
  display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:4px 18px;padding:11px 0;border-bottom:1px solid rgba(255,255,255,.22)
}
.spec-group dl > div:last-child{border-bottom:0;padding-bottom:0}
.spec-group dt{font-family:var(--sans);font-size:.8rem;color:#fff;flex:1 1 auto;min-width:0}
.spec-group dd{
  margin:0;text-align:right;font-family:var(--serif);font-size:1.1rem;color:#fff;
  font-variant-numeric:tabular-nums;flex:1 1 auto;min-width:0
}

/* ---------- four boxes, one size ---------- */
/* Asked for as "make them all the same sizes so its like in boxes of same sizes
   (exclude the gallery)". So the description, the enquiry form, Key features and
   the specification are one box each, identical in width, height and padding,
   whichever row they are in — and anything longer than the box scrolls inside it
   rather than making its box taller than the other three.
   Only above 900px: below that the split is one column and a fixed-height box
   with its own scrollbar on a phone is a worse page, not a tidier one. */
/* No box scrolls. The fixed height went out on instruction — "remove the scroll
   option ... so that the whole text will be visible without the scrolling option"
   — so each box is as tall as its own content and the two boxes in a ROW are
   equalised by the grid (align-items:stretch below) rather than by a number.
   Rows therefore differ in height, because a description and a twelve-item
   feature list are not the same length; what a reader sees side by side is still
   two boxes of one size. The type inside the boxes is a step down from the
   full-width scale to keep the taller ones sane — see the rules below. */
@media (min-width:901px){
  .spec-split > *{box-sizing:border-box}
}
/* THE OUTLINE. Asked for as "the golden color used throughout the site as an
   outline for these boxes" — the description, the specification and What you can
   get were named, and the enquiry card gets it too: three outlined boxes and one
   without would read as an oversight rather than a decision. --gold #b08d4f, the
   token the rest of the site uses on light and dark grounds alike. Decorative,
   not a control boundary, so 1.4.11's 3:1 does not apply to it — and the panel's
   own type is measured separately in spec.js either way.
   ONE weight on all four sides. It was 1px with a 2px top, and a 1px gold
   hairline against the navy panel read as a dark edge rather than a gold one —
   reported as "gold on top and black on the other sides". Same colour, same
   width, all round, and spec.js measures all four sides now, not two. */
.spec-split > *{border:2px solid var(--gold)}

/* The light boxes. The panel brings its own ground; these two match its padding
   exactly, so the four read as one set. */
.spec-copy{
  background:var(--white);
  padding:clamp(30px,3vw,40px) clamp(26px,2.6vw,36px) clamp(24px,2.6vw,34px)
}
.section--sand .spec-copy{background:var(--white)}

/* Inside a box the type steps down from the full-width scale. The boxes are as
   tall as their content now, and at the page's normal sizes the 66's feature list
   alone ran past 2,000px — this keeps a long box readable without making the
   reader scroll inside it. Nothing here changes a colour, so every contrast
   number measured elsewhere still holds. */
.spec-copy h2{font-size:clamp(1.5rem,2.4vw,2.05rem);line-height:1.24}
.spec-copy > p{font-size:.95rem;line-height:1.62;margin-bottom:.9em}
.spec-copy .feat{gap:16px clamp(20px,2.2vw,32px)}
.spec-copy .feat__i{padding-top:14px}
.spec-copy .feat__i h3{margin-bottom:7px}
.spec-copy .feat__i p{font-size:.88rem;line-height:1.55;margin:0}
.spec-band__head{margin-bottom:20px;padding-bottom:16px}
.spec-group{margin:0 0 22px}
.spec-group > h3{padding-bottom:10px}
.spec-group dl > div{padding:8px 0}
.spec-group dt{font-size:.78rem}
.spec-group dd{font-size:1.02rem}

/* THE ENQUIRY CARD IS AS TALL AS ITS FORM, on instruction: "you can make the
   enquire on the boat pages smaller no need to stretch it out that much".
   What it was doing, and it was worse than it sounds. The card stretched to match
   the box beside it (.spec-split is align-items:stretch, and "boxes of one size
   per row" was an earlier instruction), the form was flex:1 inside it, and the
   form is a GRID — so the spare height was distributed across every grid row.
   On the Selene 52, whose Key features column runs to 2,246px, that gave a 46px
   text input a 285px cell, a 251px note and a 265px button. Measured, not
   guessed: five fields at 285/285/286/286/336.
   So two changes, and both are needed — either alone leaves half the problem:
     * the card no longer stretches to the row (align-self:start below), which is
       the "smaller" he asked for;
     * the form's grid rows are min-content, so nothing inside it can absorb
       spare height even where a card IS stretched — which is still the case on
       the contact page, where the card is the page's main event.
   `margin-top:auto` on the note stays: on a stretched card it still pushes the
   note to the bottom, and on a content-sized one it does nothing. */
.enq-card{display:flex;flex-direction:column}
.enq-card .enq-form{flex:0 0 auto}
.enq-form{align-content:start;grid-auto-rows:min-content}
.enq-form .form-note{margin-top:auto}
/* Only inside a two-box row, and only the form. The specification panel in row
   one still stretches to match the description beside it — that pairing was
   asked for and reads as one component. This is the exception, not a new rule. */
.spec-split > .enq-card{align-self:start}

/* ---------- the general-arrangement drawings ---------- */
/* Asked for in the gallery's format, so the markup IS the gallery — build.py
   calls the same gallery() with kind='drawing'. Everything below is only the
   delta between a photograph and a line drawing; nothing here is a second
   component. (The .plans/.plan stack this replaced is gone: one frame,
   thumbnails beside it, arrows, lightbox and keyboard, like every boat's
   photographs.)

   Three differences, each with a reason:

   1. object-fit:contain, not cover. A deck seen from above is a wide, flat
      strip — the yard's run to 3:1 and flatter — and the gallery frame is 3:2
      with cover, which would slice the bow and stern off every one of them. A
      cropped photograph loses some water; a cropped general arrangement loses
      the cabin you were trying to look at.
   2. White ground and a hairline. They are line drawings on white, so on the
      page's own white an uncropped one with no edge reads as a photograph that
      failed to load. The padding keeps the drawing's own border off ours.
   3. No hover zoom. scale(1.03) on a contained drawing pushes it out past the
      padding and under the border — there is no overflow to absorb the growth
      the way a cover-fitted photograph has. The frame still opens in the
      lightbox on click, which is where a plan gets read at size. */
.gal--plans .gal__stage{
  /* Wider than 3:2 so a flat plan fills more of the box it is contained in, and
     held at this ratio on a phone too — the 560px rule that squares the photo
     frame up to 4:3 would waste half the height on a 3:1 drawing. This wins by
     specificity, not order, so it does not matter that that media query comes
     later in the file. */
  aspect-ratio:16/10;
  background:#fff;border:1px solid var(--sand-2);padding:clamp(8px,1vw,14px)
}
.gal--plans .gal__stage img,.gal--plans .gal__stage .ph{object-fit:contain}
.gal--plans .gal__stage:hover img{transform:none}
/* The zoom mark is a dark disc that was drawn over a photograph; on white, with
   the drawing's own margin showing through the corner, it needs to sit a little
   further in so it is not touching the hairline. */
.gal--plans .gal__stage::after{right:20px;bottom:20px}
.gal--plans .gal__thumb{
  aspect-ratio:16/10;
  background:#fff;border:1px solid var(--sand-2);padding:4px
}
.gal--plans .gal__thumb img{object-fit:contain}

/* ---------- the enquiry card beside a model's description ---------- */
/* Asked for as a form here instead of the specification panel, with the fields
   all the same size. Two things make that true rather than nearly true:
   one column, and a fixed control height — a <select> and a text input do not
   agree on their own height, and "roughly the same" reads as sloppy on a page
   this formal. The message box is the exception, and it is the same WIDTH. */
.enq-card{
  background:var(--sand);
  /* the same padding as .spec-copy and .spec-band: four boxes, one set */
  padding:clamp(30px,3vw,40px) clamp(26px,2.6vw,36px) clamp(24px,2.6vw,34px);
  /* Stated HERE as well as on .spec-split > *, because the card is used off a
     model page now — on the contact page, where there is no .spec-split to
     inherit an outline from. Same token, same width, so the model pages do not
     move and spec.js still reads one border across all four boxes. */
  border:2px solid var(--gold)
}
.enq-card__head{margin-bottom:18px;padding-bottom:14px;border-bottom:1px solid var(--sand-2)}
.enq-card__head h2,.enq-card__head h3{font-size:clamp(1.3rem,2vw,1.6rem);margin:0}
/* The contact page's card is the wide one and it is that page's main event, so
   its message box keeps the room it had before the form became one column. The
   WIDTH is untouched — matching widths was the whole instruction. */
#enquiry .enq-form .field textarea{min-height:132px}
/* The honeypot. Off-screen rather than display:none, because a robot that skips
   display:none inputs is exactly the robot it is there to catch; aria-hidden in
   the markup keeps it out of the accessibility tree. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
/* A failed send is the one line on the page the reader has to act on, so it is
   not the same grey whisper as the reassurance under a form that worked. */
.form-result--bad{color:var(--ink);font-weight:600}
/* One column, 13px apart. The box's height is no longer fixed and nothing
   scrolls, so the only job left here is the gap — see the note by .enq-card above
   for why grid-auto-rows had to be pinned to min-content. */
.enq-form{display:grid;grid-template-columns:1fr;gap:13px}
.enq-form .field{gap:7px}
.enq-form .field input,.enq-form .field select{height:46px;padding:0 16px;width:100%}
.enq-form .field textarea{min-height:96px;padding:12px 16px;width:100%}
.enq-form .btn{width:100%;text-align:center;margin-top:2px}
/* One line at this width, not two: at 690px the box is exactly the form's
   height and a second line of note was the one thing being clipped. */
.enq-form .form-note{margin:0;font-size:.78rem;line-height:1.5}

/* The popup. A native <dialog>, so it lands in the top layer and Escape closes
   it without a keyboard trap of our own making. It is the whole point of the
   instruction — submitting must not take the reader off the page. */
.pop{
  border:0;padding:clamp(26px,3vw,40px);max-width:min(92vw,460px);
  background:var(--white);color:var(--ink);box-shadow:0 30px 80px rgba(8,18,44,.28)
}
.pop::backdrop{background:rgba(8,18,44,.6)}
.pop h3{margin:0 0 12px}
.pop p{color:var(--muted);margin:0 0 22px}
.pop .btn{width:100%;text-align:center}

@media (max-width:900px){
  /* the second column drops under the first rather than squeezing into half a
     phone — the specification panel, and now the enquiry card too */
  .spec-split{grid-template-columns:1fr}
}

/* ---------- view switch: the same boats, large or as a grid ---------- */
/* Two presentations of ONE set of markup, not two sets. That matters for a
   reason beyond weight: rendering both would put every photograph on the page
   twice, and check-links.py fails a page that shows the same picture twice —
   and it should, because three boats in the lineup have exactly one photograph
   between them and could not have had a different one per view.
   So .filmcard is restyled rather than replaced. Large is the default, and it is
   what a visitor with scripting off gets, because the switch is hidden there. */
.viewswitch-row{
  display:flex;align-items:baseline;justify-content:space-between;gap:20px;
  flex-wrap:wrap;margin-bottom:clamp(22px,2.6vw,34px)
}
.viewswitch-row__label{
  font-family:var(--sans);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--muted)
}
/* no script, no switch: it would be a control that does nothing */
/* .viewswitch* lived here: a two-button Large/Grid control, hidden without
   JavaScript (html.js .viewswitch), navy on white when pressed. Removed with the
   switch itself — the range listings are grid-only now. The row that held it
   stays, because it also carries the model count. */

/* ---- grid view: the same panels, as cards ---- */
.filmstrip.is-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(310px,1fr));
  gap:clamp(20px,2.4vw,30px);
  /* The strip was full-bleed in the Large view this replaced, so it could butt
     the closing band with no gap and read as deliberate. As a grid of cards it
     cannot: the last row of cards was touching "Step aboard" on all four range
     pages. The bottom margin is the site's own section rhythm (--sec-y), so the
     gap here matches every other gap between blocks; the small top margin sets
     the grid off from the model-count line above it. */
  max-width:var(--wrap);margin-inline:auto;padding-inline:28px;
  margin-block:18px var(--sec-y)
}
.is-grid .filmcard{
  min-height:0;display:flex;flex-direction:column;align-items:stretch;
  background:#fff;border:1px solid var(--line);color:var(--ink);
  transition:box-shadow .45s var(--ease),transform .5s var(--spring),border-color .35s var(--ease)
}
.is-grid .filmcard + .filmcard{border-top:1px solid var(--line)}
.is-grid .filmcard:hover{
  box-shadow:0 26px 50px rgba(8,18,44,.13);border-color:rgba(176,141,79,.5);
  --hov:translateY(-6px)
}
.is-grid .filmcard__media{position:relative;inset:auto;aspect-ratio:4/3}
.is-grid .filmcard__media img,.is-grid .filmcard__media .ph{object-position:50% 50%}
/* the vignette and the text plate exist to make type readable over a photograph;
   in a card the type sits on white and both would just be grey haze */
.is-grid .filmcard__scrim,.is-grid .filmcard__body::before{display:none}
.is-grid .filmcard__body{
  position:static;max-width:none;flex:1;display:flex;flex-direction:column;
  padding:24px 26px 26px
}
/* --gold is the site's accent but measures 3.1:1 on white at this size, so the
   tag takes --muted here (5.9:1) and keeps the gold for the large view, where
   it sits on navy. */
.is-grid .filmcard__tag{color:var(--muted);margin-bottom:10px}
.is-grid .filmcard__body h3{color:var(--navy);font-size:1.55rem;margin-bottom:.35em}
.is-grid .filmcard__body p{color:var(--muted);font-size:.94rem;margin-bottom:20px;flex:1}
.is-grid .filmcard__cta{
  align-self:flex-start;background:transparent;color:var(--navy);
  border-color:var(--navy);backdrop-filter:none;padding:12px 24px;font-size:.66rem
}
.is-grid .filmcard:hover .filmcard__cta{
  background:var(--gold);border-color:var(--gold);color:var(--navy);box-shadow:none
}
@media (max-width:640px){
  .filmstrip.is-grid{grid-template-columns:1fr;padding-inline:22px}
}

/* The fleet comparison table ("Compare / The fleet at a glance") was removed
   from yachts.html on request, and its .fleet-table rules went with it. Deleting
   markup and leaving its CSS behind is how the site accumulated a .spec-table
   that nothing used; spec.js asserts both halves are gone. */

/* The editorial yacht page — 100vh hero, statement blocks, full-bleed breaks and
   the specification as light .yspec-card panels — was built over the dark gold
   specification panel and then reverted on request. Its rules went with it rather
   than being left behind; the panel is back, above. .btn-row survived both
   layouts, because both put the same pair of buttons in a half-width column. */
/* Two buttons in a half-width column: they used to sit inline separated by an
   nbsp, which in the narrower column wrapped them onto two ragged lines. */
.btn-row{display:flex;flex-wrap:wrap;gap:12px;margin-top:28px}
/* grow, so a button alone on a wrapped line fills that line rather than sitting
   at its own content width: two stacked buttons of 316px and 346px read as a
   mistake, two of equal width read as a pair */
.btn-row .btn{flex:1 1 auto;text-align:center}

/* the five headline figures */
/* ---------- the five headline figures ---------- */
/* Their own strip, above the description: the numbers a buyer compares first,
   before the prose rather than after it. On sand, between the dark hero above
   and the white copy below, which gives the page a light/dark rhythm instead of
   three dark blocks in a row. */
/* The width of everything else on the page, asked for: this band ran edge to
   edge while the boxes below it stop at .wrap, so the five figures sat wider than
   the page they belong to. Same max-width and the same gutter as .wrap, and it
   keeps its position directly under the photograph. The hairline becomes a full
   border for the same reason — a bottom rule alone on a band that no longer spans
   the window reads as an unfinished edge. */
/* THE FIVE HEADLINE FIGURES, now a panel beside the photograph rather than a band
   under it. What it was: .wrap's content width, one column per figure, hairlines
   between them, and a special rule for the fifth figure because it had no partner
   in a two-up phone grid. What it is: one column of rows, hairlines BETWEEN the
   rows, and no odd-one-out case to handle at any width — which is the layout
   telling the truth about the data (five unrelated figures, read top to bottom)
   rather than fighting it.
   Sand ground and the same 1px edge it always had, so it still reads as the same
   component in the same system; the max-width and the outer margin are gone
   because the grid track now decides its width. */
.spec-figures{
  background:var(--sand);border:1px solid var(--sand-2);
  padding:clamp(12px,1.5vw,20px) clamp(16px,1.8vw,26px);
  display:flex
}
/* Its inner .wrap is gone with the band — the panel is inside a .wrap already. */
.spec-hero{
  display:grid;grid-auto-flow:row;grid-template-columns:1fr;width:100%;
  /* SPREAD, not stacked at the top: the panel is as tall as the photograph beside
     it, and space-between distributes the five rows over that height so the two
     columns are level and the rows breathe. Same device as .spec-groups. */
  align-content:space-between
}
/* Each row is [icon] [label over value]: the icon spans both text rows and sits
   centred against them, which lines the five icons up in a column of their own. */
.spec-hero__i{
  display:grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:clamp(12px,1.2vw,16px);row-gap:2px;
  align-items:center;
  padding:clamp(11px,1.3vw,16px) 0;
  border-top:1px solid rgba(14,26,60,.10)
}
.spec-hero__i:first-child{border-top:0}
.spec-hero__ic{
  grid-column:1;grid-row:1 / span 2;align-self:center;
  width:clamp(24px,2.1vw,28px);height:clamp(24px,2.1vw,28px);
  color:var(--gold-deep);margin:0
}
/* Label ABOVE value. Both are placed explicitly rather than reordered with
   `order`, because the DOM is icon -> value -> label and an implicit flow would
   put the label second in the accessibility tree as well as visually. */
.spec-hero__t{
  grid-column:2;grid-row:1;
  display:block;margin:0;font-family:var(--sans);font-size:.62rem;
  letter-spacing:.2em;text-transform:uppercase;color:var(--muted)
}
.spec-hero__n{
  grid-column:2;grid-row:2;
  display:block;font-family:var(--serif);
  font-size:clamp(1.3rem,1.9vw,1.75rem);line-height:1.12;color:var(--navy);
  /* figures line up row to row instead of drifting */
  font-variant-numeric:tabular-nums;letter-spacing:-.01em
}

/* STACKED, so the panel is as wide as the page and a single column of five rows
   would leave two thirds of it empty. Two columns instead — which is what the
   old band did on a phone, and the reason its odd-one-out rule existed: the fifth
   figure has no partner, so it spans both. The hairlines change direction with
   the layout: a top rule on every row except the first pair, and a left rule down
   the second column. */
@media (max-width:900px){
  .spec-hero{
    grid-template-columns:1fr 1fr;
    column-gap:clamp(16px,3vw,28px);
    align-content:start
  }
  .spec-hero__i:nth-child(-n+2){border-top:0}
  .spec-hero__i:nth-child(even){
    border-left:1px solid rgba(14,26,60,.10);
    padding-left:clamp(16px,3vw,28px)
  }
  .spec-hero__i:last-child:nth-child(odd){grid-column:1 / -1;border-left:0;padding-left:0}
}
/* One column again on a phone: two columns of a serif figure and a letter-spaced
   label do not fit 350px without the labels wrapping. */
@media (max-width:560px){
  .spec-hero{grid-template-columns:1fr}
  .spec-hero__i:nth-child(2){border-top:1px solid rgba(14,26,60,.10)}
  .spec-hero__i:nth-child(even){border-left:0;padding-left:0}
}

@media (max-width:560px){
  .spec-group dl > div{gap:4px 14px}
  .spec-group dd{font-size:1.02rem}
}

/* ---------- gallery ---------- */
/* ---------- gallery: one frame, a strip of thumbnails under it ---------- */
/* It was a masonry-ish grid of every photograph at once, the first one twice the
   size of the rest. Twenty pictures of one boat is a wall; this is one at a time
   with the rest offered underneath, which is how a broker's page works and how
   somebody actually looks at a boat. */
/* The photograph you are looking at on the left, the others smaller on the
   right. The aside stretches to the row's height — which the frame sets — and the
   strip scrolls inside it, so a boat with seventeen photographs takes exactly as
   much page as a boat with three. */
/* Three quarters the photograph you clicked, one quarter the rest — asked for in
   those words. It was minmax(0,1fr) plus a clamped 150–220px strip, which came out
   at about 84/16 on a wide window and narrower still on a small one; a ratio holds
   at every width, and minmax(0,…) on both tracks is what stops a wide image from
   pushing the grid past its container. */
.gal{
  display:grid;grid-template-columns:minmax(0,3fr) minmax(0,1fr);
  gap:clamp(12px,1.4vw,20px);align-items:stretch
}
.gal--solo{grid-template-columns:minmax(0,1fr)}
.gal__main{min-width:0}
/* position:relative + inset:0 on the strip, because a percentage height on a
   grid item is not reliably resolvable — this is: the aside is stretched by the
   grid, and the strip fills it absolutely. */
.gal__aside{position:relative;min-height:0}
.gal__stage{
  display:block;position:relative;overflow:hidden;aspect-ratio:3/2;
  background:var(--navy-900);cursor:zoom-in
}
.gal__stage img,.gal__stage .ph{
  width:100%;height:100%;object-fit:cover;
  /* The swap is a cross-fade rather than a cut: main.js drops .is-in for one
     frame and puts it back, and this is what makes that read as a change of
     photograph instead of a flicker. */
  transition:opacity .34s var(--ease),transform .8s var(--ease)
}
/* Scoped to .gal--live, which main.js adds to a gallery at the same moment it
   adds .is-in to that gallery's frame. This rule used to be unscoped, and it was
   the single point of failure for every photograph on the site: the frame is held
   at opacity 0 so the swap can cross-fade, and ONLY script brings it back — so a
   main.js that failed to upload, or threw anywhere before the gallery loop, left
   every boat's main photograph invisible with its thumbnails still showing. Now
   nothing is hidden unless the code that unhides it has already run. */
.gal--live .gal__stage img:not(.is-in){opacity:0}
.gal__stage:hover img{transform:scale(1.03)}
.gal__stage::after{
  /* a zoom mark, so it is obvious the frame itself opens */
  content:"";position:absolute;right:16px;bottom:16px;width:34px;height:34px;
  background:rgba(8,18,44,.62) center/16px 16px no-repeat;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='7'/%3E%3Cpath d='M15.6 15.6 21 21M10.5 7.5v6M7.5 10.5h6'/%3E%3C/svg%3E");
  opacity:0;transition:opacity .3s var(--ease);pointer-events:none
}
.gal__stage:hover::after,.gal__stage:focus-visible::after{opacity:1}

/* ---- the arrows over the frame ---- */
.gal__frame{position:relative}
.gal__arrow{
  position:absolute;top:50%;transform:translateY(-50%);
  width:48px;height:48px;padding:0;border:0;border-radius:50%;
  display:grid;place-items:center;cursor:pointer;color:#fff;
  /* 62% navy over the brightest photograph the frame can hold still measures
     5.1:1 for the white chevron, so this is legible without a border and does
     not need one. Measured in photos.js on a real photograph, not assumed. */
  background:rgba(8,18,44,.62);
  transition:background .3s var(--ease),transform .3s var(--spring)
}
/* Visible without hovering, on purpose: a touch screen has no hover state, and
   an arrow you cannot see is not a control. */
.gal__arrow--prev{left:14px}
.gal__arrow--next{right:14px}
.gal__arrow:hover{background:rgba(8,18,44,.86)}
.gal__arrow:active{transform:translateY(-50%) scale(.94);transition-duration:.09s}
.gal__arrow:focus-visible{outline:3px solid #fff;outline-offset:2px}
/* No href behind them, so with the script blocked they would be dead controls.
   The thumbnail strip does the same job in that case. */
html:not(.js) .gal__arrow{display:none}
/* Smaller and tighter on a phone, where the frame is 263px tall. This block has
   to sit AFTER the base rules, not in the 560px media query further up the file:
   a media query does not raise specificity, so an earlier `.gal__arrow{}` in a
   media block loses to a later `.gal__arrow{}` outside one. The first version of
   this was 200 lines too high and simply never applied. */
@media (max-width:560px){
  .gal__arrow{width:40px;height:40px}
  .gal__arrow svg{width:18px;height:18px}
  .gal__arrow--prev{left:8px}
  .gal__arrow--next{right:8px}
}

.gal__cap{
  display:flex;align-items:baseline;gap:16px;margin:14px 0 0;
  font-family:var(--sans);font-size:.8rem;color:var(--muted)
}
.gal__n{
  font-variant-numeric:tabular-nums;letter-spacing:.14em;font-size:.68rem;
  text-transform:uppercase;color:var(--navy);flex:0 0 auto
}
.gal__t{min-width:0}

/* auto-fill on a minimum width, NOT a fixed column count. The fleet runs from
   one photograph to twenty — nine boats have three — and repeat(10, 1fr) sized
   every strip as though it held ten, so three photographs came out as three
   small pictures marooned at the left of an empty row. Sizing by the thumbnail
   instead means three fill three slots and twenty wrap onto two rows, at the
   same size either way. */
/* One column of small photographs down the right, scrolling inside the frame's
   height. It fills its stretched parent absolutely rather than being given a
   percentage height, and scroll-padding keeps a thumbnail brought into view by
   the arrow keys off the top edge. */
.gal__thumbs{
  list-style:none;margin:0;padding:0;
  position:absolute;inset:0;overflow-y:auto;overscroll-behavior-y:contain;
  display:grid;grid-template-columns:1fr;gap:8px;align-content:start;
  scroll-padding-block:8px;scrollbar-width:thin
}
.gal__thumb{
  display:block;position:relative;overflow:hidden;aspect-ratio:3/2;
  background:var(--navy-900);cursor:pointer;
  /* Unselected thumbnails sit back a little so the current one reads as current
     without needing a border that would eat into a 90px picture. */
  opacity:.62;transition:opacity .25s var(--ease),transform .3s var(--spring)
}
.gal__thumb img{width:100%;height:100%;object-fit:cover;display:block}
.gal__thumb:hover,.gal__thumb:focus-visible{opacity:1}
.gal__thumb:active{transform:scale(.96);transition-duration:.09s;transition-timing-function:var(--press)}
/* The current one: full strength, and a gold rule along the top drawn INSIDE the
   box with an inset shadow. A border would shift the picture by its own width
   every time the selection moved. */
.gal__thumb[aria-current="true"]{opacity:1;box-shadow:inset 0 3px 0 0 var(--gold)}

@media (max-width:820px){
  .gal__cap{font-size:.76rem;gap:12px}
  /* Below 820 there is no room for a column beside the frame, so the whole
     gallery stacks and the strip goes back to one swipeable row under the
     photograph — the gesture a phone expects. It leaves the absolute
     positioning behind for this, which is why the aside gets a height again. */
  .gal{grid-template-columns:minmax(0,1fr)}
  .gal__aside{position:static;margin-top:14px}
  .gal__thumbs{
    position:static;inset:auto;
    display:grid;grid-auto-flow:column;grid-auto-columns:112px;
    grid-template-columns:none;gap:8px;
    overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;
    scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;
    /* room for the focus ring on the first and last, which a clipped scroller
       would otherwise cut in half */
    padding:3px;margin-inline:-3px
  }
  .gal__thumb{scroll-snap-align:start}
}
@media (max-width:560px){
  .gal__thumbs{grid-auto-columns:96px;gap:6px}
  .gal__stage{aspect-ratio:4/3}
}
@media (prefers-reduced-motion:reduce){
  .gal__stage:hover img{transform:none}
}
.lightbox{
  /* kept in the layout and hidden with visibility, so opening and closing can
     be animated — display:none cannot be transitioned */
  position:fixed;inset:0;z-index:200;background:rgba(8,18,44,.95);display:flex;
  align-items:center;justify-content:center;padding:40px;cursor:zoom-out;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .3s var(--ease),visibility 0s linear .3s
}
.lightbox.open{
  opacity:1;visibility:visible;pointer-events:auto;
  transition:opacity .3s var(--ease),visibility 0s
}
.lightbox img{
  max-width:92vw;max-height:88vh;object-fit:contain;width:auto;
  transform:scale(.94);transition:transform .42s var(--spring)
}
.lightbox.open img{transform:none}
.lightbox button{position:absolute;top:24px;right:32px;background:none;border:0;color:#fff;font-size:2rem;cursor:pointer;line-height:1}

/* ---------- brokerage listings (pre-owned) ---------- */
.fleet-note{
  background:var(--sand);border-left:2px solid var(--gold);padding:16px 22px;
  /* sits below the model grids, which already carry their own bottom margin */
  margin:0;font-size:.94rem;color:var(--muted)
}
.fleet-note strong{color:var(--navy)}
.fleet-note a{color:var(--gold);font-weight:500;border-bottom:1px solid rgba(176,141,79,.4)}
.fleet-note a:hover{border-bottom-color:var(--gold)}

.listing-grid{align-items:stretch}
.model-card.listing .model-card__len{font-size:1.05rem;letter-spacing:.02em;text-transform:none}
.listing-facts{
  display:grid;grid-template-columns:1fr 1fr;gap:1px;margin:0 0 18px;padding:0;
  background:var(--line);border:1px solid var(--line)
}
.listing-facts > div{background:#fff;padding:12px 14px}
.listing-facts dt{
  font-size:.62rem;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin:0 0 3px
}
.listing-facts dd{margin:0;font-size:.92rem;color:var(--navy)}
.listing-links{
  display:flex;flex-wrap:wrap;gap:18px 24px;margin-top:auto;padding-top:18px;
  border-top:1px solid var(--line)
}
.listing-links .link-arrow{font-size:.68rem}

/* image placeholder (used when a remote photo fails to load) */
.ph{
  position:relative;
  background:linear-gradient(135deg,#12224a 0%,#1d3163 45%,#2b3f70 100%);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;
  color:rgba(255,255,255,.34);text-align:center;padding:20px;
  font-family:var(--serif);font-size:1.5rem;letter-spacing:.2em;text-transform:uppercase
}
.ph::before{
  content:"";width:38px;height:1px;background:var(--gold);opacity:.8
}
.ph::after{
  content:"Photograph";font-family:var(--sans);font-size:.6rem;letter-spacing:.24em;
  color:rgba(255,255,255,.32);text-transform:uppercase
}
/* In the drawings gallery it is not a photograph that failed. Same reason the
   arrows and thumbnails say "drawing" there: the component takes the noun as an
   argument precisely so it never tells the reader the wrong thing about what is
   missing. Only the visible word changes — main.js writes the accessible name,
   and it is already built from the drawing's own alt text. */
.gal--plans .ph::after{content:"Drawing"}

/* A FAILED FRAME, painted rather than replaced. main.js swaps a broken <img> for
   a .ph placeholder everywhere except three elements it must not detach — the
   lightbox, the guided-search hero and the gallery frame, which the gallery loop
   holds a reference to and writes every thumbnail click into. So the frame keeps
   its element and gets the placeholder's look from here: same gradient, same gold
   hairline, same word, and the image behind it hidden rather than removed. The
   class goes on and comes off per swap, so a boat with one dead hotlinked frame
   still shows every other photograph in the set.
   Position is on .gal__stage, which is the anchor the frame's image sits in. */
.gal--failed .gal__stage{
  background:linear-gradient(135deg,#12224a 0%,#1d3163 45%,#2b3f70 100%)
}
.gal--failed .gal__stage img{visibility:hidden}
.gal--failed .gal__stage::before{
  content:"";position:absolute;left:50%;top:calc(50% - 20px);transform:translateX(-50%);
  width:38px;height:1px;background:var(--gold);opacity:.8
}
/* NOTE the explicit reset. .gal__stage::after above is the zoom mark — a 34px
   box in the corner at opacity 0 until hover — so overriding `content` alone
   left this label invisible in a 34px square nobody would ever hover. Every
   property that rule sets has to be undone here, which is the argument for not
   reusing one pseudo-element for two jobs; it is reused because the frame must
   keep its <img>, so there are only two pseudos to go round. */
.gal--failed .gal__stage::after{
  content:"Photograph unavailable";
  position:absolute;left:50%;top:calc(50% + 6px);right:auto;bottom:auto;
  transform:translateX(-50%);width:auto;height:auto;background:none;
  opacity:1;pointer-events:none;white-space:nowrap;
  font-family:var(--sans);font-size:.62rem;letter-spacing:.24em;
  color:rgba(255,255,255,.42);text-transform:uppercase
}
.gal--failed .gal__stage:hover::after,
.gal--failed .gal__stage:focus-visible::after{opacity:1}
.gal--plans.gal--failed .gal__stage::after{content:"Drawing unavailable"}

/* ---------- article cards ---------- */
/* Photo, category, title, summary, "See more". The whole card lifts on hover,
   but only the title and the See more link are focusable: the media anchor is
   aria-hidden and tabindex -1, so a keyboard user gets one stop per card rather
   than three pointing at the same page. */
.art-grid{
  display:grid;grid-template-columns:repeat(3,1fr);gap:38px 32px
}
.art-grid--3{gap:32px 28px}
.art-card{
  display:flex;flex-direction:column;background:var(--white);
  transition:transform .5s var(--spring),box-shadow .5s var(--ease)
}
.art-card:hover{--hov:translateY(-6px);box-shadow:0 22px 48px rgba(8,18,44,.10)}
.art-card:active{--hov:translateY(-1px) scale(.995)}
.art-card__media{display:block;overflow:hidden;aspect-ratio:3/2;background:var(--sand-2)}
.art-card__media img,.art-card__media .ph{
  width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .8s var(--ease)
}
.art-card:hover .art-card__media img{transform:scale(1.04)}
.art-card__body{padding:24px 26px 28px;display:flex;flex-direction:column;flex:1 1 auto}
.art-card__body .date{
  font-family:var(--sans);font-size:.66rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--gold);margin-bottom:12px
}
.art-card__body h3{font-size:1.22rem;line-height:1.28;margin-bottom:.5em}
.art-card__body h3 a{color:var(--ink);text-decoration:none;transition:color .3s var(--ease)}
.art-card:hover .art-card__body h3 a{color:var(--gold)}
.art-card__body p{margin:0;color:var(--muted);font-size:.92rem;line-height:1.62}
.art-card__more{margin-top:auto;padding-top:20px;align-self:flex-start}
@media (max-width:1000px){.art-grid{grid-template-columns:repeat(2,1fr)}}
@media (max-width:640px){.art-grid{grid-template-columns:1fr}}

/* ---------- an article ---------- */
.hero--article h1{max-width:24ch;font-size:clamp(2rem,3.6vw,3.1rem)}
.art-meta{
  display:flex;flex-wrap:wrap;gap:10px 26px;padding-bottom:22px;margin-bottom:34px;
  border-bottom:1px solid var(--line);
  font-family:var(--sans);font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)
}
.art-meta span:first-child{color:var(--gold)}
.art-meta a{color:inherit}
.art-body p{margin-bottom:1.15em;font-size:1.03rem;line-height:1.78}
.art-body p:last-child{margin-bottom:0}
.art-body h2{font-size:1.5rem;margin:1.8em 0 .6em}
.art-list{margin:0 0 1.3em;padding:0;list-style:none}
.art-list li{position:relative;padding:8px 0 8px 22px;color:var(--muted)}
.art-list li::before{
  content:"";position:absolute;left:0;top:1.05em;width:9px;height:1px;background:var(--gold)
}
.art-video{margin:38px 0 0}
.art-video__frame{
  position:relative;aspect-ratio:16/9;background:var(--navy-900);overflow:hidden
}
.art-video__frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.art-video figcaption{
  margin-top:12px;font-family:var(--sans);font-size:.78rem;color:var(--muted);line-height:1.6
}
.art-source{
  margin-top:38px;padding:22px 24px;background:var(--sand);border-left:2px solid var(--gold);
  font-size:.9rem;line-height:1.7;color:var(--muted)
}

/* ---------- key features ---------- */
/* The raw .kit bullet list was removed with its section; this replaced it. Each
   feature is a name and a sentence, so the unit is a small block of type rather
   than a line item — which is why this is a grid of cards' worth of text and not
   a two-column list. */
.feat{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(268px,1fr));
  gap:clamp(22px,2.6vw,38px) clamp(26px,3vw,52px);align-items:start
}
.feat__i{
  /* A hairline over each one instead of a bullet: at three columns a bullet
     points at nothing, and the rule reads as a column edge. */
  border-top:1px solid var(--sand-2);padding-top:18px
}
.feat__i h3{
  font-family:var(--sans);font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--navy);margin:0 0 10px;font-weight:600;line-height:1.45
}
.feat__i p{
  margin:0;font-size:.92rem;line-height:1.7;color:var(--muted)
}
@media (max-width:560px){
  .feat{grid-template-columns:1fr;gap:20px}
  .feat__i{padding-top:14px}
}

/* ---------- the finish comparison slider ---------- */
/* Two photographs of one cabin, one revealed over the other. See the long note on
   build.compare_slider() for why this is CSS here rather than the inline styles it
   arrived with. In this site's tokens: square corners, no drop shadow, the sand
   hairline the other boxes use, --navy ink.
   NO transform on either layer. The upload scaled both by 1.05 to hide an 8px
   vertical misregistration between the two renders; the sources are aligned now
   (photos.py records the measurement), so nothing has to be pushed out of frame. */
.cmp{margin:0}
.cmp__frame{
  position:relative;aspect-ratio:5/3;overflow:hidden;
  border:1px solid var(--sand-2);background:var(--navy-900);
  /* the handle is dragged, so stop the browser panning the page under the finger */
  touch-action:pan-y
}
.cmp__img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center;display:block;
  /* a dragged image otherwise starts a native drag on the pointer */
  -webkit-user-drag:none;user-select:none
}
/* The TOP layer is clipped from the right; 50% until the script says otherwise,
   which is also exactly what a reader with no JavaScript should see. */
.cmp__img--a{clip-path:inset(0 50% 0 0)}
.cmp__line{
  position:absolute;top:0;bottom:0;left:50%;width:2px;margin-left:-1px;
  background:#fff;pointer-events:none
}
/* The knob is decorative — the range input under it is the control — so it is
   aria-hidden and it never takes a pointer event. */
.cmp__knob{
  position:absolute;top:50%;left:50%;width:44px;height:44px;
  transform:translate(-50%,-50%);border-radius:50%;
  background:#fff;color:var(--navy);
  display:flex;align-items:center;justify-content:center;pointer-events:none
}
.cmp__knob svg{width:20px;height:20px}
.cmp__tag{
  position:absolute;bottom:0;padding:7px 14px;
  background:rgba(8,18,44,.72);color:#fff;
  font-family:var(--sans);font-size:.68rem;font-weight:600;
  letter-spacing:.14em;text-transform:uppercase;pointer-events:none
}
.cmp__tag--a{left:0}
.cmp__tag--b{right:0}
/* THE CONTROL. Invisible but present and full-size, over the frame: the browser
   gives keyboard, touch and pointer dragging, and a screen reader gets a real
   slider with a name and a value. Both the track and the thumb are transparent —
   what a reader sees is the line and the knob above, moved from this input's
   value. Hidden until main.js grants .cmp--live, the same opt-in the gallery
   uses: a slider that cannot move is worse than no slider. */
.cmp__range{
  position:absolute;inset:0;width:100%;height:100%;margin:0;
  -webkit-appearance:none;appearance:none;background:transparent;
  display:none;cursor:ew-resize
}
.cmp--live .cmp__range{display:block}
.cmp__range::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:44px;height:44px;
  background:transparent;border:0;cursor:ew-resize
}
.cmp__range::-moz-range-thumb{width:44px;height:44px;background:transparent;border:0;cursor:ew-resize}
.cmp__range::-moz-range-track{background:transparent;border:0}
/* Focus lands on the input, so the ring has to be drawn on the frame around it. */
.cmp__range:focus-visible{outline:none}
.cmp__frame:focus-within{outline:2px solid var(--navy);outline-offset:2px}
/* Without the script there is no handle to grab, so it is not offered — the
   50/50 split and the two labels are the whole story then. */
.cmp:not(.cmp--live) .cmp__knob,
.cmp:not(.cmp--live) .cmp__line{opacity:.55}
.cmp__cap{
  margin:12px 0 0;font-family:var(--sans);font-size:.78rem;color:var(--muted);
  letter-spacing:.02em
}
.cmp--live .cmp__cap::after{content:""}
@media (max-width:560px){
  .cmp__knob{width:38px;height:38px}
  .cmp__tag{font-size:.62rem;padding:6px 10px;letter-spacing:.1em}
}
/* A dragged handle is a direct manipulation, not an entrance animation, so
   reduced motion has nothing to switch off here — the global rule at the end of
   this file already neutralises transitions and this component declares none. */

/* ---------- testimonials ---------- */
.quote{max-width:900px;margin-inline:auto;text-align:center}
.quote blockquote{
  font-family:var(--serif);font-size:clamp(1.5rem,2.9vw,2.35rem);line-height:1.4;font-style:italic;
  margin:0 0 26px
}
.quote cite{font-style:normal;font-size:.74rem;letter-spacing:.2em;text-transform:uppercase;color:var(--gold)}
.quote-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(22px,2.6vw,38px)}
.quote-card{background:#fff;padding:36px 32px;border-top:2px solid var(--gold)}
.quote-card p{font-family:var(--serif);font-size:1.15rem;font-style:italic;line-height:1.55;color:var(--ink)}
.quote-card cite{font-style:normal;font-size:.7rem;letter-spacing:.18em;text-transform:uppercase;color:var(--muted)}

/* ---------- feature triples ---------- */
/* Was written for the home page's "S3 Promise" band, which the owner removed.
   .triple is still the sales & service page's three-up, so the rule stays and
   only the name went. */
.triple{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(26px,3.4vw,56px)}
.triple h3{font-size:1.5rem;margin-bottom:.4em}
.triple .num{
  font-family:var(--serif);font-size:2.6rem;color:var(--gold);line-height:1;display:block;margin-bottom:14px
}
.feature-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:18px 34px}
.feature-list li{position:relative;padding-left:26px;color:inherit}
.feature-list li::before{content:"";position:absolute;left:0;top:.65em;width:9px;height:1px;background:var(--gold)}

/* ---------- news / events ---------- */
.card-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,2.4vw,34px)}
.news-card{background:#fff;border:1px solid var(--line);display:flex;flex-direction:column;transition:box-shadow .4s var(--ease)}
.news-card:hover{box-shadow:0 22px 44px rgba(8,18,44,.12)}
.news-card__media{aspect-ratio:16/10;overflow:hidden;background:var(--navy-900)}
.news-card__media img,.news-card__media .ph{width:100%;height:100%;object-fit:cover}
.news-card__body{padding:26px}
.news-card .date{font-size:.68rem;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);display:block;margin-bottom:10px}
.news-card h3{font-size:1.3rem;line-height:1.25}
.news-card p{color:var(--muted);font-size:.94rem}
.event-row{
  display:grid;grid-template-columns:150px 1fr auto;gap:32px;align-items:center;
  padding:30px 0;border-bottom:1px solid var(--line)
}
.event-row:first-of-type{border-top:1px solid var(--line)}
.event-row .when{font-family:var(--serif);font-size:1.5rem;color:var(--gold);line-height:1.2}
.event-row .when small{display:block;font-family:var(--sans);font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
.event-row h3{font-size:1.35rem;margin-bottom:.2em}
.event-row p{margin:0;color:var(--muted);font-size:.94rem}
/* A boat show needs two buttons: the organiser's ticketing and our own viewing
   diary. Stacked, so the pair never squeezes the description column. */
.event-row__cta{display:flex;flex-direction:column;gap:10px;align-items:stretch}
.event-row__cta .btn{text-align:center;white-space:nowrap}

/* ---------- Tech Talk ---------- */
/* Linked rows, not picture cards. These are the yard's technical articles, and
   pairing a stock yacht photograph with a piece about fuel polishing would be
   decoration pretending to be illustration. */
.tt{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.tt li{border-bottom:1px solid var(--line)}
.tt a{
  display:grid;grid-template-columns:150px 1fr;gap:6px 32px;
  padding:26px 0;text-decoration:none;color:inherit;
  transition:background .3s var(--ease),padding-left .3s var(--ease)
}
.tt a:hover,.tt a:focus-visible{background:var(--sand);padding-left:18px}
.tt__date{
  font-family:var(--sans);font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--muted);padding-top:.45em
}
.tt__title{
  font-family:var(--serif);font-size:1.35rem;line-height:1.25;color:var(--ink);
  transition:color .3s var(--ease)
}
.tt a:hover .tt__title,.tt a:focus-visible .tt__title{color:var(--gold)}
.tt__title .link-arrow{margin-left:12px;vertical-align:baseline}
.tt__ex{
  grid-column:2;margin:0;color:var(--muted);font-size:.94rem;line-height:1.65;max-width:74ch
}
/* the alias anchor for this section's previous id — no box, no space taken */
.anchor-alias{display:block;height:0;overflow:hidden;scroll-margin-top:var(--anchor-offset)}
@media (max-width:700px){
  .tt a{grid-template-columns:1fr;gap:8px}
  .tt__ex{grid-column:1}
  .tt a:hover,.tt a:focus-visible{padding-left:12px}
}

/* ---------- FAQ ---------- */
.faq{border-top:1px solid var(--line)}
.faq details{border-bottom:1px solid var(--line)}
.faq summary{
  cursor:pointer;padding:26px 44px 26px 0;position:relative;list-style:none;
  font-family:var(--serif);font-size:1.35rem;color:var(--navy)
}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"+";position:absolute;right:6px;top:24px;font-size:1.5rem;color:var(--gold);transition:transform .3s var(--ease)}
.faq details[open] summary::after{transform:rotate(45deg)}
.faq .answer{padding:0 44px 28px 0;color:var(--muted)}
.faq .answer p:last-child{margin-bottom:0}

/* ---------- forms ---------- */
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:22px}
.field{display:flex;flex-direction:column;gap:8px}
.field.full{grid-column:1/-1}
.field label{font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);font-weight:600}
.field input,.field select,.field textarea{
  font-family:var(--sans);font-size:1rem;padding:14px 16px;border:1px solid var(--line);background:#fff;
  color:var(--ink);border-radius:0;
  transition:border-color .3s var(--ease),box-shadow .3s var(--ease)
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:0;border-color:var(--gold);box-shadow:0 0 0 3px rgba(176,141,79,.16)
}
.field textarea{min-height:150px;resize:vertical}
.form-note{font-size:.82rem;color:var(--muted)}

.newsletter{display:flex;gap:12px;max-width:540px;margin-inline:auto;flex-wrap:wrap}
.newsletter input{flex:1;min-width:220px;padding:16px 18px;border:1px solid rgba(255,255,255,.28);background:transparent;color:#fff;font-size:1rem}
.newsletter input::placeholder{color:rgba(255,255,255,.5)}
.newsletter input:focus{outline:0;border-color:var(--gold)}

/* ---------- contact blocks ---------- */
.contact-grid{display:grid;grid-template-columns:1.25fr .75fr;gap:clamp(36px,5vw,72px)}
.info-block{margin-bottom:34px}
.info-block h4{font-family:var(--sans);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);margin:0 0 10px;font-weight:600}
.info-block p{margin:0;color:var(--ink)}
.info-block a:hover{color:var(--gold)}
/* ---------- map ---------- */
.map-wrap{position:relative}
/* The iframe's own positioning context, so the pin can be placed against the MAP
   rather than against .map-wrap — which on a phone is taller than the map,
   because .map-card stops being absolute and stacks underneath it. Without this
   the pin drifts below the map at exactly the width where it matters most. */
.map-frame{position:relative}
.map-embed{
  width:100%;aspect-ratio:16/9;border:0;display:block;
  /* Desaturated, so our own pin is the only colour on the map. Note what this
     does NOT do: a filter applies to the whole iframe, so it also greyed
     OpenStreetMap's own marker — which is why the marker is off the embed URL
     now and the pin below is drawn over the top instead. */
  filter:grayscale(1) contrast(1.05)
}
/* Sits at the centre of the map with its TIP on that point, because the embed's
   bbox is symmetric around LAT/LON — the office is the middle of the frame.
   pointer-events:none so it cannot swallow a drag on the map underneath. */
.map-pin{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-100%);
  z-index:1;pointer-events:none;line-height:0;color:var(--pin);
  filter:drop-shadow(0 3px 5px rgba(8,18,44,.45))
}
.map-pin svg{display:block;fill:currentColor}
/* The transparent layer that pins the pin. The embed is draggable, our pin is
   drawn over it and cannot follow it, so a dragged map left the pin marking the
   middle of the frame instead of the office. With the map fixed, the centre of
   the frame IS the office — the bbox is symmetric around LAT/LON. Directions and
   Larger map on the card are how a reader explores, and a bonus: the map no
   longer eats the page's scroll under the cursor.
   Declared inline in the markup as well: a transparent shield that only exists
   in a cached stylesheet is a map that pans again for a week. */
.map-shield{position:absolute;inset:0;z-index:1;background:transparent}
.map-card{
  position:absolute;z-index:2;top:26px;left:26px;max-width:302px;background:#fff;
  padding:26px 28px 22px;box-shadow:0 18px 40px rgba(8,18,44,.18);border-top:2px solid var(--gold)
}
.map-card .eyebrow{margin-bottom:.7em}
.map-card__addr{margin:0 0 16px;font-size:.94rem;line-height:1.6;color:var(--muted)}
.map-card__addr strong{color:var(--navy);font-weight:600}
.map-card__links{display:flex;flex-wrap:wrap;gap:10px 22px;margin:0}
.map-card__links .link-arrow{font-size:.68rem}
@media (max-width:820px){
  .map-embed{aspect-ratio:4/3}
  .map-card{position:static;max-width:none;margin:0;box-shadow:none;border-top-width:2px}
}
/* ---------- cta band ---------- */
.cta-band{
  /* the same rhythm as a .section: it is one of the stacked blocks */
  background:var(--navy);color:#fff;text-align:center;padding:var(--sec-y) 0;position:relative;overflow:hidden
}
.cta-band__bg{
  position:absolute;left:0;right:0;top:-5%;height:110%;
  background-size:cover;background-position:center;opacity:.3
}
.cta-band .wrap{position:relative;z-index:2}
.cta-band h2{color:#fff;max-width:20ch;margin-inline:auto}
.cta-band p{color:#c3cce0;max-width:56ch;margin-inline:auto}

/* ---------- footer ---------- */
/* Navy. It spent a version on --gold-dark and came back; #93a0bd is a blue-grey
   pitched at this navy, which is exactly why it turned muddy on gold. */
.site-footer{background:var(--navy-900);color:#93a0bd;padding:80px 0 0;font-size:.92rem}
/* three columns since the model lists came out: brand, links, company */
.footer-top{
  display:grid;grid-template-columns:1.5fr 1fr 1fr 1.1fr;
  gap:clamp(30px,3.6vw,56px);padding-bottom:56px
}
/* The dealer network. All four are presented alike and all four link to the
   contact page; the "Us" marker that used to single out the Adriatic entry was
   removed on the owner's instruction. */
.fdealers li{line-height:1.5}
.site-footer h4{
  font-family:var(--sans);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:#fff;
  margin:0 0 18px;font-weight:600
}
.site-footer ul{list-style:none;margin:0;padding:0}
.site-footer li{margin-bottom:10px}
.site-footer a:hover{color:var(--gold-light)}
.site-footer .flogo{height:56px;width:auto;margin-bottom:22px}
.site-footer /* The footer's contact slot is deliberately empty for now. :empty means it
   reserves no space at all, so a blank field cannot leave a gap in the footer —
   and the moment something is put back in it, it spaces itself. */
.fcontact{margin:0}
.fcontact:empty{display:none}
/* The office is named by its trading name with the registered company as a
   subtitle, on the contact page and on the map card. */
.info-block .sub,.map-card__sub{
  display:block;font-size:.82em;color:var(--muted);letter-spacing:.02em
}
.map-card__sub{margin-bottom:.35em}
.fabout{max-width:34ch;margin-bottom:20px}
.socials{display:flex;gap:14px;margin-top:6px}
.socials a{
  /* .36, not the .2 this started at: an outline is the whole of a 38px control's
     boundary and .2 white measures 1.8:1 on the footer's navy, under the 3:1
     WCAG 1.4.11 asks of one. Found while the footer was briefly gold, where the
     same .2 came out at 1.5:1; the fix applies to the navy just as well. */
  width:38px;height:38px;border:1px solid rgba(255,255,255,.36);display:flex;align-items:center;justify-content:center;
  transition:all .3s var(--ease)
}
.socials a:hover{background:var(--gold);border-color:var(--gold)}
.socials svg{width:16px;height:16px;fill:currentColor}
.footer-bottom{
  /* .20, up from the .1 this started at, which measured 1.3:1 and was not
     really there. A divider is decorative and owes no 3:1, but it should be
     visible if it is going to exist at all. */
  border-top:1px solid rgba(255,255,255,.2);padding:24px 0;display:flex;justify-content:space-between;
  gap:20px;flex-wrap:wrap;font-size:.82rem
}
.footer-bottom .legal{display:flex;gap:22px;flex-wrap:wrap}

/* ---------- scroll motion ---------- */
/* Gated behind html.js. Unconditionally hiding .reveal means that with
   JavaScript off every animated section on the site renders blank. */
html.js .reveal{
  opacity:0;transform:translateY(26px);
  transition:opacity .85s var(--ease),transform .85s var(--ease);
  transition-delay:calc(var(--rd,0) * 85ms)
}
html.js .reveal.in{opacity:1;transform:var(--hov,none)}

/* Yacht cards and brokerage listings come in from the side they sit on, so a
   wide grid reads as opening outward rather than rising as a block. Assigned by
   measured position in main.js, which means a single-column phone layout gets
   no sideways motion at all — sliding horizontally in a narrow viewport feels
   cramped, and there is no "side" to come from. */
html.js .reveal--left{transform:translate3d(-42px,14px,0)}
html.js .reveal--right{transform:translate3d(42px,14px,0)}
html.js .reveal--left.in,html.js .reveal--right.in{transform:var(--hov,none)}

/* the gold rule draws itself in under a heading */
html.js .reveal .rule,html.js .rule.reveal{
  width:0;transition:width .9s var(--ease) calc(var(--rd,0) * 85ms + 180ms)
}
html.js .reveal.in .rule,html.js .rule.reveal.in{width:56px}

/* editorial media: settles from a slight overscale as it arrives.
   NOT THE FINISH SLIDER. This is an entrance for a still photograph, and on an
   interactive control it is wrong twice over: for the first 1.5 seconds the two
   halves are drawn 7% larger than they will end up, so the seam a reader is
   dragging does not correspond to the pixels they think they are comparing — and
   an overscale is exactly the trick the supplied version used to HIDE a
   misregistration, which the sources are now aligned to avoid needing. A slider
   should be where it will stay from the first frame.
   Excluded by class rather than by adding a competing rule further down: a later
   override at equal specificity is how the vendored-stylesheet trap in this file
   already bit once. */
html.js .split__media img:not(.cmp__img),html.js .split__media .ph{
  transform:scale(1.07);transition:transform 1.5s var(--ease)
}
html.js .reveal.in .split__media img:not(.cmp__img),
html.js .split__media.reveal.in img:not(.cmp__img),
html.js .reveal.in .split__media .ph,html.js .split__media.reveal.in .ph{transform:none}

/* stat figures count up, so hold the width to stop the row twitching */
html.js .stat-row .n{font-variant-numeric:tabular-nums}

/* scroll position, as a hairline under the header */
.scroll-progress{
  position:absolute;left:0;right:0;bottom:0;height:2px;pointer-events:none;overflow:hidden
}
.scroll-progress span{
  display:block;width:100%;height:100%;background:var(--gold);
  transform:scaleX(var(--p,0));transform-origin:left center
}

/* ---------- responsive ---------- */
/* Six top-level items, a logo and the Enquire button fill the 1240px column.
   The phone number no longer fits alongside them, so it lives in the mobile
   drawer, the footer and the contact page instead of the header bar. Put
   Pre-Owned back inside the Yachts dropdown and there is room for it again. */
@media (max-width:1260px){
  .nav{gap:20px}
  .nav a.top{font-size:.72rem;letter-spacing:.12em}
  .site-header .wrap{gap:18px}
}
@media (max-width:1120px){
  :root{--anchor-offset:88px}
  .nav{display:none}
  .burger{display:block;flex:0 0 auto}
  .site-header .wrap{gap:12px}
  .logo img{height:42px}
  .site-header.solid .logo img{height:38px}
  .model-grid,.card-grid,.quote-grid,.triple{grid-template-columns:1fr 1fr}
  .footer-top{grid-template-columns:1fr 1fr;gap:36px}
}
@media (max-width:820px){
  body{font-size:16px}
  .split,.range-grid,.contact-grid,.form-grid,.feature-list{grid-template-columns:1fr}
  .split--rev .split__media{order:0}
  .gallery{grid-template-columns:1fr 1fr}
  .gallery a:first-child{grid-column:span 2;grid-row:span 1;aspect-ratio:16/10}
  .event-row{grid-template-columns:1fr;gap:14px;text-align:left}
  /* side by side once the row is a single column — there is width for it */
  .event-row__cta{flex-direction:row;flex-wrap:wrap}
  .range-card{min-height:400px}
}
@media (max-width:560px){
  .nav-cta .btn{display:none}
  .logo img,.site-header.solid .logo img{height:32px}
  .hero__inner{padding-bottom:48px}
  .model-grid,.card-grid,.quote-grid,.triple{grid-template-columns:1fr}
  .footer-top{grid-template-columns:1fr}
  .wrap,.wrap-narrow{padding-inline:20px}
  .stat-row{gap:28px}
}
@media (prefers-reduced-motion:reduce){
  *{animation:none!important;transition-duration:.01ms!important;transition-delay:0s!important}
  /* micro-interactions keep their colour changes but lose all movement */
  .btn:hover,.btn:active,.gallery a:active,
  .burger:active,.burger[aria-expanded="true"] span{transform:none!important}
  .model-card:hover,.model-card:active,.range-card:hover,.range-card:active{--hov:none}
  .link-arrow:hover::after,.link-arrow:active::after,a:hover .link-arrow::after{transform:none!important}
  .nav a.top::after{transform:scaleX(0)!important}
  .nav a.top:hover::after,.nav li.current > a.top::after,
  .nav > li.open > a.top::after{transform:scaleX(1)!important}
  .submenu a:hover,.mobile-nav .sub a:hover{padding-left:26px!important}
  .lightbox img{transform:none!important}
  html.js .reveal,.reveal{opacity:1!important;transform:none!important}
  html.js .reveal .rule,html.js .rule.reveal{width:56px!important}
  html.js .split__media img,html.js .split__media .ph{transform:none!important}
  /* no parallax offset, and no progress hairline */
  .hero__bg,.pagehead__bg,.cta-band__bg{transform:none!important}
  /* the clip is never loaded under reduced motion; keep it invisible regardless */
  .hero__video{display:none!important}
  .scroll-progress{display:none}
  html{scroll-behavior:auto}
}

/* ---------- the "Need help?" pill ---------- */
/* Fixed in the bottom corner of every page except the contact page itself and the
   two pages a form lands on (build.py's help_button decides that). It is an <a>,
   so it works with the script blocked and can be opened in a new tab.
   z-index 90: ABOVE the page and the sticky jump bar (60), BELOW the fixed
   navigation (100), the mobile drawer (99) and the lightbox (200). Getting that
   order wrong is how a floating button ends up over a modal — it has to be the
   thing that yields, not the thing that wins. */
.helpbtn{
  position:fixed;right:clamp(14px,2vw,26px);bottom:clamp(14px,2vw,26px);z-index:90;
  display:inline-flex;align-items:center;gap:9px;
  padding:12px 18px;
  background:var(--navy);color:#fff;border:1px solid var(--gold);
  font-family:var(--sans);font-size:.72rem;font-weight:600;
  letter-spacing:.14em;text-transform:uppercase;white-space:nowrap;
  box-shadow:0 10px 26px rgba(8,18,44,.26);
  transition:background .35s var(--ease),color .35s var(--ease),
             transform .4s var(--spring),box-shadow .4s var(--ease)
}
/* Navy on gold for the hover, which is the site's own pattern for a filled gold
   surface and measures 5.5:1 — white on --gold is 3.1:1 and would fail. */
.helpbtn:hover,.helpbtn:focus-visible{
  background:var(--gold);color:var(--navy);border-color:var(--gold);
  transform:translateY(-2px);box-shadow:0 14px 30px rgba(8,18,44,.3)
}
.helpbtn:active{transform:translateY(0)}
.helpbtn__ic{width:17px;height:17px;flex:0 0 auto}
@media (max-width:560px){
  /* Tighter, and clear of the gallery arrows and the strip on a phone. The label
     stays — an icon-only bubble is a guess, and the whole point is the words. */
  .helpbtn{padding:10px 14px;font-size:.66rem;letter-spacing:.12em;gap:7px}
  .helpbtn__ic{width:15px;height:15px}
}
@media print{.helpbtn{display:none}}
