/* =============================================================================
   Find Your Dream Pontoon  ·  CSS  ·  v1.15.0
   -----------------------------------------------------------------------------
   Port of the Round-3 handoff exports approved 2026-08-07:
       3A Category Rail · 3B Clean Tab Bar · 3C Pill Grid + Accordion
       3D Spotlight Pager

   rev 1.15.0 — 2026-08-23
     • CROSS-FILE OWNERSHIP. This file no longer sets `top`, `max-height` or
       `overflow-y` on the slideout. It publishes ONE number, --fydp-tab-top,
       and style.css does the rest. See §SLIDEOUT at the foot of this file.

       The old arrangement reached into another component's box model at
       (1,0,1)+!important in order to beat ten (1,0,0)+!important `top` rules
       whose source order RUCSS could reorder. That reasoning was correct for
       its time and is now obsolete: style.css declares `top` exactly once,
       without !important, so there is nothing left to beat. Two files cannot
       race over a property only one of them sets.

     • THE max-height WAS ALSO WRONG, not merely out of scope. It measured
       `100dvh - var(--fydp-tab-top) - 16px` from the CONTAINER, while four
       landscape bands in style.css pulled #sidebar_slideout_content up a
       further -100px with a bare negative margin. It under-sized the panel by
       exactly that amount wherever both applied. style.css now measures from
       --slideout-panel-top, which is the card's real top edge.

     • THE `top` OVERRIDE WAS ALSO THE CAUSE OF A CROSS-PAGE BUG. Because this
       sheet is only enqueued where the shortcode renders, --fydp-tab-top is
       declared on / and undefined on /boat-model/… — so the slideout PANEL
       opened ~156px lower on FYDP pages than everywhere else. style.css now
       anchors the panel to a page-independent value and lets only the TAB
       follow this number, which is the only thing it was ever for.

     • The clearance value itself is UNCHANGED at 240px, as is the rev-1.9.0
       verification snippet. Nothing about the tab's resting position moves.

   rev 1.14.0 — 2026-08-15
     • THE DESKTOP CAPTION COMES OFF THE PHOTOGRAPH. Same treatment the 3C
       accordion has carried since 1.10.0, now applied at >=1025px as well, so
       the component has one card rather than two. No template change.

     • MEASURED FIRST. Overlay heights swept across all eight tabs, vw 1707,
       card 284 x 189:

           44px   26 of 30 cards   one-line title, no subtitle
           64px    2 of 30         one-line title + subtitle   (tab 0)
           65px    4 of 30         two-line title              (tabs 1, 3)

       The label covers 23% of the picture on most cards and 34% on six of them.
       Before 1.13.0 the worst case was 26%, because the card was 61px taller —
       but it was taller by cropping 23% off the width. Giving the picture back
       and then hiding a third of it under the label is not a trade worth
       keeping.

     • A TALLER CARD DOES NOT FIX THIS, which is why this rev is not simply a
       larger --fydp-card-min-h. .fydp-card__ov is bottom-anchored: it occupies
       the bottom 44 or 65px of whatever the card is, so growing the card grows
       the photo in the same proportion.

           3:2   card 189   caption covers 23% / 34%   crop  0.0%
           7:5   card 203   caption covers 22% / 32%   crop  6.6%
           4:3   card 213   caption covers 21% / 31%   crop 11.1%

       Three percentage points for a ninth of the photograph. The only way to
       stop the caption covering the picture is to take it off the picture.
       Dropping the 12px bottom padding — the other thing that was on the table
       — buys 4px and puts the type on the 6px corner radius.

     • ROW HEIGHT COMES BACK, which was the other thing 1.13.0 cost. Cards run
       219px (one line) to 240px (two lines, or one plus a subtitle) against the
       250px they were before 1.13.0. So the section is close to the height it
       had, with the photograph both complete and uncovered.

     • THE 21px SPREAD IS NOT LEVELLED, deliberately. `align-items: start` puts
       every photo on its row's top edge and the card is transparent below,
       so there is no box edge for the mismatch to show against — the reasoning
       that retired --fydp-acc-title-min on mobile in 1.12.0. --fydp-card-min-h
       remains if uniform cards are ever wanted, and it now means exactly that:
       with the ratio on the image rather than the card, raising the floor adds
       trailing space instead of cropping. 240px levels everything, at zero cost
       to the photograph. That is a different property from the one that was
       switched off in 1.13.0, in effect if not in name.

     • ALL FOUR DESIGNS, not just 3B. `.fydp-block .fydp-card` rather than
       `.fydp-block--clean-tab-bar .fydp-card`, because two card treatments in
       one component depending on which design is assigned is not a thing worth
       maintaining. 3A is the one to look at in QA: its cards are 403 x 269, so
       the caption covered 16-24% there rather than 34%, and it had the least
       wrong with it. To contain this to 3B instead, add --clean-tab-bar to the
       four selectors below and nothing else changes.

     • SPECIFICITY (0,3,0), not (0,2,0)-plus-source-order. These rules would win
       on order alone, being later in the file than the base card block, but
       RUCSS rewrites this stylesheet on every "Clear Used CSS" and order is not
       something to bet a layout on — see the 1.9.0 note. Every design carries
       .fydp-block, so the extra class costs nothing.

           VERIFY on desktop — the same sweep that sized this rev:
           (async()=>{const r=document.querySelector('[data-fydp]');
            const b=[...r.querySelectorAll('[data-fydp-block]')]
              .find(e=>e.offsetParent||e.getClientRects().length);
            const f=()=>new Promise(k=>requestAnimationFrame(
              ()=>requestAnimationFrame(k)));const per=[];
            for(let i=0;i<8;i++){AvalonFYDP.setActive(r,i);await f();
              const p=b.querySelector('.fydp-panel:not([hidden])');if(!p)continue;
              const c=[...p.querySelectorAll('.fydp-card')];if(!c.length)continue;
              const g=[...p.querySelectorAll('.fydp-card__img')];
              const q=c[0].getBoundingClientRect();
              const m=g[0].getBoundingClientRect();
              per.push({i,cardW:Math.round(q.width),
                cardH:[...new Set(c.map(e=>Math.round(
                  e.getBoundingClientRect().height)))],
                imgH:Math.round(m.height),
                imgTops:[...new Set(g.map(e=>Math.round(
                  e.getBoundingClientRect().top)))].length,
                scrim:getComputedStyle(c[0],'::after').content});}
            AvalonFYDP.setActive(r,0);
            console.log(JSON.stringify({vw:innerWidth,
              rev:getComputedStyle(r).getPropertyValue('--fydp-css-rev').trim(),
              design:b.getAttribute('data-fydp-design'),per}));})()

           `imgH` must equal cardW / 1.5 — 189 at a 284px track — in every tab,
           including tabs 3 and 5 where a boat has no photograph and the
           placeholder span holds the box. `imgTops` must read 1: every photo on
           the row's top edge, which is what makes the ragged card bottoms
           invisible. `cardH` will list two or three values per tab; that is
           correct, not a levelling failure. `scrim` must read "none".

   rev 1.13.0 — 2026-08-15
     • DESKTOP CARDS GO BACK TO A TRUE 3:2 AND THE PHOTOS STOP BEING CROPPED.
       One declaration: --fydp-card-min-h 250px -> 0px at >=1025px. No mobile
       change, no template change, no new mechanism.

     • THIS REVERSES A DECISION MADE IN 1.4.0 AND RESTATED SINCE, so the reason
       matters. The floor existed to match the outgoing
       `.custom-post-tabs .tab-post { min-height: 250px }` and avoid reading as
       a shrink beside the live site. But that legacy rule is
       `width: 23%; min-height: 250px; background-size: cover` — the same
       geometry, so PRODUCTION HAS ALWAYS CROPPED BY THE SAME ~23%. The number
       was inherited rather than designed, and matching it meant inheriting the
       crop. The approved 3B desktop comp draws `.card { aspect-ratio: 3/2 }`
       with no floor, so 0 is the comp.

     • THE CROP WAS NEVER A SETTING, it was arithmetic. At the 1252px container:

           card, 4-up with 32px gutters       289px wide
           a WHOLE 3:2 photo at that width    193px tall
           the old floor                      250px
           width a whole photo needs at 250   375px  ->  a 1596px container

       At 289 x 250 the 1024x683 source scaled to 375x250 and 86px was cut,
       22.9% of its width, 43px off each side. At 289 x 193 it fits exactly.
       Three arrangements were weighed and rejected: `object-fit: contain`
       (whole photo, but 28px letterbox bars), photo at 3:2 above a 57px solid
       strip (whole photo, height kept, but the bottom-anchored overlay then
       lands on flat colour with the scrim running over it), and a 3-up grid
       (396 x 264 — the only option that keeps 250px AND shows everything, at
       the cost of the comp's four columns and a 3+1 orphan row at limit=4).

     • 3A IS UNAFFECTED, which is worth knowing before looking for a change that
       is not there. Its rail layout leaves 838px for a 2-up grid, so its cards
       are 403px wide and 269px tall from `aspect-ratio` alone — already above
       the old floor, which therefore never bound on Category Rail. 3B,
       3C-desktop and 3D all run 4-up at 289px and were all clamped, so those
       three move together: rows 57px shorter, crop 22.9% -> 0.

     • --fydp-grid-gap STAYS AT 32px even though its justification is spent. 32
       was chosen in 1.4.0 because four ~23%-cropped frames ran hull-to-frame-
       edge and read as one band unless a wide gutter broke them up; with the
       water back around each boat, 24px would now do. Kept because the spacing
       between thumbnails was to be preserved.

           VERIFY on desktop, any tab:
           (()=>{const r=document.querySelector('[data-fydp]');
            const b=[...r.querySelectorAll('[data-fydp-block]')]
              .find(e=>e.offsetParent||e.getClientRects().length);
            const p=b.querySelector('.fydp-panel:not([hidden])');
            const c=p.querySelector('.fydp-card');
            const i=p.querySelector('img.fydp-card__img');
            const q=c.getBoundingClientRect();
            const box=q.width/q.height, nat=i.naturalWidth/i.naturalHeight;
            console.log(JSON.stringify({vw:innerWidth,
              rev:getComputedStyle(r).getPropertyValue('--fydp-css-rev').trim(),
              design:b.getAttribute('data-fydp-design'),
              cardW:Math.round(q.width),cardH:Math.round(q.height),
              boxRatio:+box.toFixed(3),imgRatio:+nat.toFixed(3),
              cropPct:+((1-Math.min(box,nat)/Math.max(box,nat))*100).toFixed(1),
              minH:getComputedStyle(c).minHeight,
              gap:getComputedStyle(p.querySelector('.fydp-grid'))
                .columnGap}));})()

           Expect cardW ~289, cardH ~193, boxRatio 1.500 against an imgRatio of
           1.499, `cropPct` 0.0, `minH` "0px", `gap` "32px". A boxRatio near
           1.156 with cropPct ~22.9 means a pre-1.13.0 file is still live. On
           Category Rail the same probe should read ~403 x 269 both before and
           after — that one is meant not to move.

   rev 1.12.0 — 2026-08-15
     • TWO NUMBERS, both set by looking at 1.11.0 on device. No new mechanism,
       no new selector, no template change. 3C accordion only.

           .fydp-card-sub padding-top   5px -> 1px
           --fydp-acc-row-gap           7px -> 10px   (back to where it was)

     • THE 1px IS THE TITLE-TO-SUBTITLE GAP on the categories that have one.
       5px read as a gutter between two separate things; 1px reads as a label
       and its qualifier, which is what they are.

       IT ALSO SHORTENS THE NO-SUBTITLE CARDS BY 4px, because this rule is the
       collapsed empty box as well as the populated one. The distance from the
       last line of text to the button therefore goes 15 -> 11 where the last
       row has no subtitle, against ~10 where it does. That is the two cases
       converging, which is the direction 1.11.0 was already travelling, but it
       was not separately asked for. To keep 1.11.0's 15px on the empty case
       while still tightening the populated one, one line does it:

           .fydp .fydp-block--pill-grid-accordion .fydp-card-sub:empty
               { padding-top: 5px; }

       :empty is exact here — the template renders <span class="fydp-card-sub">
       with no whitespace inside when the value is missing, so an unpopulated
       box matches and a populated one cannot.

     • BOTH ORIENTATIONS, though specified against portrait. The rule is block-
       scoped rather than media-scoped, as it has been since 1.11.0, so
       landscape tightens by the same 4px. Deliberate: the card widths are
       within 6px of each other (132 portrait / 138 landscape), the type sizes
       are identical, and the only subtitled category is acc 0 in both. A
       per-orientation split here would be a difference with no cause.

     • THE ROW GUTTER GOES BACK TO 10px. 1.11.0 took it to 7px to close a gap
       that the 1px padding has now closed by other means. The override is left
       in place at its inert value rather than deleted: the split between row
       and column gutters is the part worth keeping, since --fydp-grid-gap
       drives both axes and moving it would change the card width and every
       title wrap with it.

     • ARITHMETIC. Portrait vw 363, card 132 x 88, four cards, no subtitles,
       two-line titles: cell 128 -> 124, body ~344 -> ~339. Landscape vw 674,
       card 138 x 92: no-subtitle cell 132 -> 128; acc 0, which has subtitles,
       146 -> 142.

           VERIFY with the sweep in the rev-1.11.0 note below. Expected values
           change as follows:

               ctaGap   ~11 where the last row has no subtitle (was ~15)
                        ~10 where it does                      (unchanged)
               subH     contains 1  (collapsed empty box, was 5)
                        and ~15 on acc 0 only                  (was ~19)
               gap      ["10px","10px"] portrait — the two are equal again,
                        which is correct, not a sign the override failed.
                        Confirm by moving --fydp-acc-row-gap and re-reading.

   rev 1.11.0 — 2026-08-15
     • ON-DEVICE TUNING OF 1.10.0, plus one regression 1.10.0 introduced. 3C
       accordion only; 3A, 3B, 3C-desktop and 3D are untouched again.

     • THE TITLE FLOOR IS OFF. --fydp-acc-title-min goes 3.9em -> 1.3em portrait
       and 2.6em -> 0.6em landscape, both set by looking at the thing on a
       Galaxy S8+ and an iPhone SE. Both values are BELOW one line (1.3 x 11.5 =
       14.95px, which is exactly one line at line-height 1.3), so the
       reservation never engages and titles are natural height. 1.10.0 bought
       row-levelled subtitles with a blank line under every short title; on
       device that trade was the wrong way round, and the misalignment it was
       preventing is hard to see on a transparent caption with no box edge. The
       property stays as the tunable — 2.6em restores two lines, 3.9em three.

     • THE EMPTY SUBTITLE COLLAPSES, 20px -> 5px, and that is what moves the
       CTA rather than a margin on the CTA. `margin-top: -5px` on
       .fydp-cta-wrap--block was the request and was declined, because the
       condition it needs is not addressable in CSS. Measured on the deployed
       data:

           acc 0   subs [S S . .]   portrait rows: r1 = S S, r2 = . .
           acc 1-7      [. . . .]

       The stray gap appears when the LAST ROW carries no visible subtitle:
       portrait, that is all eight categories (acc 0's two subtitled boats are
       cards 1 and 2, so row 1); landscape, it is every category except acc 0,
       whose single row of four contains them. No flat value is right in both,
       a panel-level :has() gets portrait acc 0 backwards, and a PHP flag has
       the same granularity problem as the :has(). Collapsing the reservation
       answers it per cell, with nothing to get wrong, and it goes on answering
       it if subtitle="model" ever populates all thirty cards.

     • PORTRAIT ROW GUTTER 10px -> 7px, via --fydp-acc-row-gap. Longhands, not
       --fydp-grid-gap: that drives both axes, and 3px off the COLUMN gutter
       takes the card from 132px to ~133.5 and moves where every title wraps —
       which would invalidate the sweeps this section is sized from. Landscape
       is one row at limit=4, so there is no row gutter there to tighten.

     • REGRESSION FIXED, in cards.php rather than here. 1.10.0 moved
       aspect-ratio and --fydp-card-bg off .fydp-card and onto .fydp-card__img
       so the caption could sit below the photo. A boat with no featured image
       renders no <img> at all, so those cards lost their box entirely and
       collapsed to a bare title. Two are in that state now — "Catalina Cruise
       Rear Bench" and "GEOfish CRF". The template now emits an empty
       <span class="fydp-card__img fydp-card__img--empty"> on that branch, which
       picks up every rule below and restores the blank 3:2 box on mobile while
       leaving desktop pixel-identical. Setting the two thumbnails is still the
       real fix.

     • ARITHMETIC, for comparison against the sweep below. Portrait vw 363,
       card 132 x 88, a four-card category with no subtitles and two-line
       titles: cell 158 -> 128, body 407 -> ~344. Landscape vw 674, card
       138 x 92: no-subtitle cell 147 -> 132, body 210 -> ~195; acc 0, which
       has subtitles, 210 -> ~209, i.e. deliberately unmoved.

           VERIFY, portrait and landscape:
           (async()=>{const r=document.querySelector('[data-fydp]');
            const b=[...r.querySelectorAll('[data-fydp-block]')]
              .find(e=>e.offsetParent||e.getClientRects().length);
            const f=()=>new Promise(k=>requestAnimationFrame(
              ()=>requestAnimationFrame(k)));const o=[];
            for(let i=0;i<8;i++){AvalonFYDP.setActive(r,i);await f();
              const bd=b.querySelector('.fydp-acc.is-on .fydp-acc__body');
              if(!bd)continue;const g=bd.querySelector('.fydp-grid');
              const cl=[...bd.querySelectorAll('.fydp-cell')];
              const cta=bd.querySelector('.fydp-cta-wrap--block');
              const sub=c=>c.querySelector('.fydp-card-sub');
              const ink=cl.map(c=>{const s=sub(c);return (s&&s.textContent.trim()
                ?s:c.querySelector('.fydp-card__t'))
                .getBoundingClientRect().bottom;});
              const cs=getComputedStyle(g);
              o.push({i,n:cl.length,
                subs:cl.filter(c=>sub(c).textContent.trim()).length,
                subH:[...new Set(cl.map(c=>Math.round(
                  sub(c).getBoundingClientRect().height)))],
                imgH:Math.round(bd.querySelector('.fydp-card__img')
                  .getBoundingClientRect().height),
                gap:[cs.rowGap,cs.columnGap],
                ctaGap:Math.round(cta.getBoundingClientRect().top
                  -Math.max(...ink)),
                bodyH:Math.round(bd.getBoundingClientRect().height)});}
            AvalonFYDP.setActive(r,0);
            console.log(JSON.stringify({vw:innerWidth,
              orient:matchMedia('(orientation:portrait)').matches
                ?'portrait':'landscape',
              rev:getComputedStyle(r).getPropertyValue('--fydp-css-rev').trim(),
              per:o}));})()

           `ctaGap` is the distance from the last line of visible text to the
           button, and it is the number this rev exists to fix. Expect ~15 where
           the last row has no subtitle and ~10 where it does. Anything near 30
           means a pre-1.11.0 file is still live. `subH` must contain 5 (the
           collapsed empty box) and, on acc 0 only, ~19. `gap` must read
           ["7px","10px"] portrait — if both read 7px the column gutter moved
           and the card widths have shifted. `imgH` must stay cardW / 1.5 in
           every category including the two with no photograph.

   rev 1.10.0 — 2026-08-15
     • 3C ACCORDION ONLY: THE CARD TITLE MOVES OUT FROM ON TOP OF THE PHOTO AND
       SITS UNDER IT, ABOVE THE SUBTITLE. Nothing else changes. 3A, 3B, 3C-
       desktop and 3D keep the comp's scrim overlay exactly as ported, and on
       this site's shortcode config the accordion is the block that serves BOTH
       mobile slots — fydp_partial_for() folds portrait and landscape into one
       .fydp-block--pill-grid-accordion carrying both context classes — so the
       block selector alone is a precise "mobile, not desktop" scope and no
       media query is needed to express it.

     • WHY, in numbers rather than taste. Swept live, all eight categories:

           portrait  vw 363   card 132 x 88    title max 45px (3 lines)
           landscape vw 674   card 138 x 92    title max 30px (2 lines)

       A two-line title in the ported 8/9px overlay box is ~46px on an 88px
       photo. Over half the image, on the one form factor with the least of it.
       And the scrim cannot help at that size: it is
       rgba(3,7,9,0) 48% -> rgba(3,7,9,.9) 100%, drawn for a 300-410px desktop
       card where the label sits in the deep end; at 88px the same two lines
       start near the 50% stop, where the gradient is still mostly transparent.

     • FULL-BLEED, NOT INSET, and that is a measurement not a preference. The
       same sweep run at a 9px inset returned 45px in BOTH orientations —
       landscape's saving is entirely the 18px. The title also lines up with
       .fydp-card-sub, which never had a horizontal inset.

     • THE RESERVATION. --fydp-acc-title-min, 3.9em portrait / 2.6em landscape,
       declared on .fydp-card__t so the `em` resolves against that rule's own
       11.5px. Without a floor the subtitles in a row sit at different heights
       whenever the titles differ in line count — the identical problem
       .fydp-card-sub's 15px min-height already solves one element up. Untouched
       at 320 and below; the note beside the rule has the sweep and the number.

     • WHAT THIS COSTS. Portrait: cell 108 -> 158, open body 307 -> 407, list
       898 -> 998. Landscape: cell 112 -> 147, open body 175 -> 210, list
       643 -> 678. limit=4 and the accordion is single-open, so the worst case
       is two rows in portrait and one in landscape. Signed off in advance:
       "if .fydp-acc needs to be expanded, then that is ok."

           VERIFY after deploy, portrait and landscape, at your narrowest
           supported width — same sweep as before the change, so the numbers
           above are directly comparable:
           (async()=>{const r=document.querySelector('[data-fydp]');
            const b=[...r.querySelectorAll('[data-fydp-block]')]
              .find(e=>e.offsetParent||e.getClientRects().length);
            const f=()=>new Promise(k=>requestAnimationFrame(
              ()=>requestAnimationFrame(k)));const o=[];
            for(let i=0;i<8;i++){AvalonFYDP.setActive(r,i);await f();
              const bd=b.querySelector('.fydp-acc.is-on .fydp-acc__body');
              if(!bd)continue;const c=bd.querySelector('.fydp-card');
              const g=[...bd.querySelectorAll('.fydp-card__img')];
              const s=[...bd.querySelectorAll('.fydp-card-sub')];
              o.push({i,cardH:Math.round(c.getBoundingClientRect().height),
                imgH:Math.round(g[0].getBoundingClientRect().height),
                subTops:[...new Set(s.map(e=>Math.round(
                  e.getBoundingClientRect().top)))].length,
                bodyH:Math.round(bd.getBoundingClientRect().height)});}
            AvalonFYDP.setActive(r,0);
            console.log(JSON.stringify({vw:innerWidth,
              rev:getComputedStyle(r).getPropertyValue('--fydp-css-rev').trim(),
              per:o}));})()

           `subTops` counts DISTINCT subtitle y positions in the open body: 1
           for a single row, 2 for two rows. Anything higher means the floor is
           short and --fydp-acc-title-min needs a line adding. `imgH` must stay
           cardW/1.5 — if it does not, the aspect-ratio moved somewhere it
           should not have.

     • NOT A BUG FIX. The overlay is what the approved 3C export drew, at these
       same card sizes. This is a deliberate departure from the comp for the
       mini card, and the comp remains correct for every card that is 300px or
       wider.

   rev 1.9.0 — 2026-08-13
     • THE TAB IS A TOP LAYER AGAIN, AND THE TWO BOXES NO LONGER MEET.
       1.8.0 stopped the email tab covering the 3D pager arrow by painting the
       header OVER it (z-index 100 vs 99). That worked, and was rejected on
       sight: a position:fixed overlay disappearing behind page furniture is
       not a pattern anyone uses, and it does not read as intentional. Correct
       call. z-index is back to 30 and the whole 1.8.0 mechanism is gone —
       body.fydp-slideout-open, the stand-down rule and the JS state mirror.

     • THE CONSTRAINT, so this is not re-litigated a fourth time. .fydp-shead
       is position:sticky and full-bleed, so its box sweeps every y value in
       the viewport before it pins. The tab is position:fixed, so it sits at
       one y. A band that sweeps every y MUST cross a band that sits at one y.
       There is no CSS that makes them never intersect. The options are
       therefore only ever these three, and 1.7.x / 1.8.0 spent themselves
       discovering it the long way:

           tab in front   -> it covers the arrow for ~200px of scrolling
           tab behind     -> the tuck (1.8.0)   REJECTED
           tab moves      -> motion (1.7.x)     REJECTED

       What IS achievable, and what this rev ships, is no overlap in the
       RESTING state — while the header is pinned and the section is being
       read — with the tab in front throughout and a brief crossing on the way
       in, exactly as the tab already crosses the cards and the CTA. That
       crossing is unremarkable. The persistent overlap was the bug.

     • HOW: one static px `top` on portrait, +165px from where the thicket puts
       it, so the tab rests at [260, 420] and the pinned header tops out below
       it. Sized from a live sweep of all eight categories:

           vw 394   header 145 / 165 / 165 / 165 / 165 / 185 / 165 / 145
                    max 185, and the ARROW only moves half of any height
                    change because it is centred in .fydp-pager

       The header grows on narrower screens — the headline column is
       vw - 148, and the <h2> itself starts wrapping below ~375 — so the max
       is estimated at ~232 at vw 360 and ~252 at vw 320. 260 clears all
       three. That estimate is the one unmeasured number here, which is why it
       is a custom property: --fydp-tab-top, one line, one number.

           VERIFY at your narrowest supported width:
           (async()=>{const r=document.querySelector('[data-fydp]');
            const b=[...r.querySelectorAll('[data-fydp-block]')]
              .find(e=>e.offsetParent||e.getClientRects().length);
            const sh=b.querySelector('[data-fydp-shead]');
            const f=()=>new Promise(k=>requestAnimationFrame(
              ()=>requestAnimationFrame(k)));const h=[];
            for(let i=0;i<8;i++){AvalonFYDP.setActive(r,i);await f();
              h.push(Math.round(sh.getBoundingClientRect().height));}
            AvalonFYDP.setActive(r,0);
            const t=document.getElementById('sidebar_slideout_toggle')
              .getBoundingClientRect().top;
            console.log(JSON.stringify({vw:innerWidth,sheadMax:Math.max(...h),
              tabTop:Math.round(t),clear:Math.round(t)-Math.max(...h)}));})()

           `clear` must be positive. If it is not, raise --fydp-tab-top by the
           shortfall. Nothing else in this file depends on the value.

     • WHY `top`, WHEN 1.7.1 ARGUED AGAINST IT. That objection was to ANIMATING
       it — a layout pass per frame on a fixed element mid-scroll. A static
       declaration resolves once at layout and costs nothing. Using px also
       kills the vh dependence that forced 1.7.x to compute a delta in JS: the
       thicket's 7/8/10/20% all resolve against viewport height while the thing
       being cleared is a pixel count, which is why no percentage was ever
       going to be right.

     • THE SPECIFICITY, stated because this is the file where that lesson was
       learned. style.css owns `top` in five !important rules at (1,0,0)
       (lines 5892 / 6143 / 6234 / 6372 / 6514) and their source order relative
       to this file is NOT stable — RUCSS can reorder it on any "Clear Used
       CSS". `body #sidebar_slideout_container` is (1,0,1) with !important, so
       it wins outright in every delivery mode rather than winning a tie.

     • The open form panel would have run off the bottom of a short viewport
       once the container dropped 165px, so #sidebar_slideout_content gets a
       matching max-height and scrolls. 100dvh with a 100vh fallback.

     • Portrait only. Landscape returns to exactly its pre-1.7.0 behaviour
       (z-index 30, style.css's 27/35/37% top), per the standing scope.

   rev 1.8.0 — 2026-08-13
     • THE SLIDEOUT NO LONGER MOVES. AT ALL. 1.7.0 and 1.7.1 both tried to
       solve "the email tab covers the 3D pager's next arrow" by MOVING the
       tab. Measurement on the live site proved that cannot work, so the whole
       approach is withdrawn and replaced with four lines of paint order.

       THE PROOF, from getBoundingClientRect() on the affected device
       (viewport 853, container top 85.3 = 10%, toggle margin-top 10):

           tab       [95, 255]            160px tall, position:fixed
           shead     [T, T + 146]         146px tall, position:sticky
           arrow     [T + 68, T + 112]    44px, inside the shead

       .fydp-shead is STICKY, so it travels the full height of the viewport
       before it pins at top:0 — and a 44px arrow riding inside it sweeps from
       y≈853 down to y=68. A 44px object crossing the whole screen passes
       through ANY 160px fixed band. The only band it would miss is one
       entirely above y=120, and a 160px element does not fit there.
       => No fixed position for the tab avoids the arrow. None. The 1.7.x
          shift merely relocated the collision: it began at T=251 and ran to
          T=47, i.e. for 204px of scrolling, which is exactly the reported
          "it repositions and still covers the arrow."

       AND THE MOVE COULD NOT BE HIDDEN EITHER. Translating a 160px band by S
       past a fixed 146px window changes the exposed length by S wherever you
       fire it — checked at three trigger points, always ~79px of visible
       change. To hide a 64px shift the bar would have to be 160+64=224px
       tall. It is 146.

     • WHAT REPLACES IT: `z-index: 100` on the pinned .fydp-shead (was 30).
       The slideout container is z-index 99 in the ROOT stacking context, and
       a probe up the whole ancestor chain of .fydp-shead found no stacking
       context between it and <html> — no Elementor transform, no z-index on
       #fydp-sec-id, no will-change. So the two are siblings in the root
       context and 100 beats 99 outright.
         - The header is now painted OVER the tab, which answers the actual
           request ("never have the email signup over .fydp-shead") literally
           and at every scroll position, not just at the ends.
         - Hit-testing follows paint order, so the arrow is now TAPPABLE
           through the tab's column. That was broken at intermediate scroll
           positions in every version up to and including 1.7.1, even when it
           looked clear.
         - The shead is full-bleed with rgba(9,12,11,.95) + blur(10px) over a
           #000 tab, so the covered part of the tab does not read as "half
           hidden", it reads as a tab that starts at the bar's bottom edge.
       Nothing animates, nothing is measured, nothing is published to CSS.
       Deleted: --fydp-shead-h, --fydp-slideout-shift, .fydp-sec-inview,
       .fydp-slideout-offset, the `translate` rule, its transition restatement
       and its reduced-motion companion. See §SLIDEOUT.

     • ONE new state class: body.fydp-slideout-open, mirrored from the
       container's own .open/.closed by the FYDP JS. Required, not optional —
       open, the 310px form panel spans roughly y 85-585 and a raised header
       would slice straight across it. Contains "fydp" so the (.*)fydp(.*)
       RUCSS safelist pattern carries it, same as .fydp-sec-inview did.

     • ACCEPTED COST, recorded so nobody "fixes" it later: with the tab static
       at [95,255] and the pinned bar at [0,146], the top 51px of the tab
       (32%) sits behind the bar while the section is pinned, and during the
       crossing the bar briefly covers all but ~14px of it. That is the trade
       that was chosen over any motion at all.

   rev 1.7.1 — 2026-08-12  (SUPERSEDED by 1.8.0 — retained for the reasoning)
     • THE JOLT. v1.7.0 moved the slideout tab by overriding `top` and flipped
       it with an IntersectionObserver at threshold 0 on the .fydp root. Two
       faults, one symptom:
         - TIMING. threshold 0 with no rootMargin fires the moment the root's
           TOP edge touches the viewport BOTTOM — a full screen before any of
           this component is visible. On the live homepage that puts the flip
           in the middle of the "Explore The Avalon Lineup" grid (section
           1abdde7, which sits immediately above #fydp-sec-id / f9731d6), so
           the tab jumped 76px with nothing on screen to explain it. Same a
           full screen late on the way back up.
         - PROPERTY. `top` is a layout property. Animating it out of the jolt
           was not an option on a fixed element mid-scroll on a phone, so
           v1.7.0 snapped, which is what made it read as a jolt.
     • Fix, both halves:
         - rootMargin '0px 0px -70% 0px' collapses the observer's band to the
           top 30% of the viewport. The class now flips when the block's top
           edge ENTERS that band — i.e. as the section arrives at the top of
           the screen, with the header visibly sliding into place — and off
           again only once the block has fully scrolled past. The movement is
           now simultaneous with, and explained by, the section's own arrival.
         - `top` is no longer touched at all. The offset is applied with the
           independent `translate` property, which composes with the
           container's existing `transform: translateX()` instead of fighting
           it, is compositor-only, and needs no !important because nothing
           else on the site sets `translate` (verified: 0 declarations in
           style.css). The five-rule !important `top` thicket is left exactly
           as it was.
     • The shift is a DELTA computed in JS from the container's own resolved
       `top`, not an absolute position, so it lands correctly on every one of
       the portrait device queries (7% / 8% / 10% / 20%) without this file
       having to know which one matched. It latches to the session maximum and
       resets only on resize/orientationchange, so paging between categories
       cannot nudge the tab mid-scroll as the headline rewraps.
     • Option A (a static offset for the whole page) was considered and
       declined: the hero video's CTA sits under the tab at the top of the
       homepage, so the tab has to keep its normal position up there.

   rev 1.7.0 — 2026-08-11
     • THE PINK BORDER. hello-elementor/assets/css/reset.css (3.4.9) carries

           [type=button],[type=submit],button{
             background-color:transparent; border:1px solid #c36;
             border-radius:3px; color:#c36; padding:.5rem 1rem;
             text-align:center; white-space:nowrap; ... }

       at (0,1,1)/(0,0,1), and it is enqueued AFTER this file (the child's
       wp_enqueue_scripts callback is registered before the parent's, so every
       child asset prints first). v1.1.0 deliberately weakened this file's
       button reset to `:where(.fydp) button` = (0,0,1) so it could not
       outrank the component rules — but that made it TIE with hello's reset,
       and the later source won. Three symptoms on 3A, all one rule:
         - pink 1px top + right borders on .fydp-rail__item (its own rule
           declares only border-left and border-bottom, so the other two
           edges were left for the cascade to fill) plus border-radius:3px;
         - `white-space:nowrap` — never declared here at ALL, in any mode —
           so the long SEO category titles overflowed the 370px rail and
           spilled across the card grid (button{overflow:visible} too);
         - `text-align:center` on rail rows, against the 3A comp's left.
     • Fix is order-independent, because source order here is not stable:
       under RUCSS the used-CSS blob happened to place this file AFTER
       reset.css (pink suppressed, spill not), under ?nowprocket the raw link
       order applies (both symptoms). Any "Clear Used CSS" can flip that.
         1. Reset selector `:where(.fydp) button` (0,0,1) ->
            `.fydp [data-fydp-block] :where(button)` (0,2,0).
            (0,2,0) and not the more obvious (0,1,0) because hello's rule is a
            SELECTOR LIST and its `[type=button]` arm is itself (0,1,0) — every
            control this component renders is `<button type="button">`, so that
            is the arm that matches and a (0,1,0) reset would only have tied
            with it. Same reason `[type=button]:hover` (0,2,0) is now answered
            by a (0,3,0) hover arm on .fydp-rail__item, the one control whose
            hover rule did not previously restate its colour.
         2. That reset now also states `white-space: normal` and
            `border-radius: 0`, the two properties it had left unclaimed.
         3. Every FYDP <button> that wants a partial border now states
            `border: 0` first and then its longhands, so no edge is ever left
            to the cascade: .fydp-rail__item (3A), .fydp-tab (3B desktop —
            same latent bug, top/left/right), .fydp-acc__head (3C mobile —
            declared no border at all, so all four edges were exposed).
            .fydp-pill and .fydp-arrow already used the full shorthand and
            were never affected.
         4. .fydp-rail__item / __t / __c pinned to text-align:left per the 3A
            comp. Rail rows are now variable height as titles wrap — that is
            the comp's own behaviour and is intended.
       This survives the Hello "Reset CSS" toggle being switched off later:
       nothing here depends on that stylesheet existing.
     • NEW — sidebar slideout clearance. See §SLIDEOUT at the foot of this
       file. Below 1025px in portrait the pinned .fydp-shead puts the 3D
       pager's next-arrow at viewport y≈76-120, and the site-wide
       #sidebar_slideout_container tab occupies y≈94-254 (top:10% of 844 +
       the toggle's own margin-top:10px), so the tab sits on the arrow.
       Measured on iPhone 12 Pro portrait, 390x844.

   rev 1.6.0 — 2026-08-11
     • THE OVERLAP, FINALLY, FROM COMPUTED VALUES instead of pixel forensics.
       getBoundingClientRect() from the affected browser — identical on / and
       /?nowprocket, so every caching layer is out of the story: tracks
       `291px 291px 291px 291px`, `column-gap: 32px`, item x = 216 / 539 /
       862 / 1185 (a perfect 323px pitch) — but every item's USED WIDTH was
       375px. 375 = 250 × 3/2. Each card overflowed its 291px track by 84px,
       burying its own 32px gutter and sliding 52px UNDER its right-hand
       neighbour (later DOM paints on top); card 4 overhung the container by
       84px. The gutters have been rendering correctly, underneath the
       cards, ever since v1.5.0 actually reached the browser.
     • Root cause: the OTHER half of the transferred minimum (css-sizing-4
       §5.3). A definite min-height transfers through aspect-ratio as a
       transferred min-width that clamps any AUTO inline size — here
       250 × 3/2 = 375px. rev 1.3.0's minmax(0, 1fr) fixed TRACK sizing, and
       min-width: 0 removed the item's AUTOMATIC minimum — but the
       transferred constraint is neither of those things: it clamps an auto
       width even when min-width is 0.
     • Fix: `width: 100%` on .fydp-card. Transferred constraints only clamp
       auto sizes; an explicit width sidesteps the clamp. The item fills its
       291px grid area, aspect-ratio proposes 194px, min-height keeps it at
       250px, object-fit: cover absorbs the crop change. Also heals the 3-up
       clean-tab-bar landscape grid, whose ~296px tracks sat under the same
       375px clamp.
     • For the record: the Elementor background overlay was verified healthy
       in the same capture (opacity .75, position absolute, full height) —
       the bright imagery previously attributed to an undimmed section
       background was the cards' own overflow.

   rev 1.5.0 — 2026-08-10
     • THE GUTTER FIXES WERE NEVER REACHING THE BROWSER. Verified against the
       live server, not inferred: WP Rocket's Remove Unused CSS generated its
       used-CSS snapshot while v1.3.0 was the deployed file, removed this
       file's <link> from the page, and has served that frozen inline copy
       (618,922 bytes, `gap:var(--fydp-grid-gap)` — the v1.3.0 form) ever
       since. v1.4.0 sat on disk referenced by nothing. filemtime() versioning
       busts the min/1 cache, but the used-CSS snapshot regenerates ONLY on an
       explicit WP Rocket → File Optimization → "Clear Used CSS". Until that
       is run, every revision of this file — this one included — is invisible.
       DEPLOY ORDER: upload → Clear Used CSS → purge WP Rocket page cache →
       purge WP Engine → hard-refresh (Ctrl-F5).
     • The client-side captures showing a 0px gutter obey v1.1/v1.2 physics
       (card row overhangs the centred container by ~92px; tracks pinned at
       the 375px transferred minimum) — one generation staler again than even
       the served snapshot. Two independent layers of stale CSS are why four
       consecutive fixes "changed nothing": none of them ever arrived.
     • Legacy grid-column-gap / grid-row-gap / grid-gap aliases added ahead of
       the modern longhands (handoff §6, hypothesis 3 — the one cheap
       insurance still untried). Harmless in modern engines: the modern
       longhands re-win on source order.
     • --fydp-css-rev added so the revision a browser is ACTUALLY running is
       checkable — getComputedStyle(document.querySelector('.fydp'))
       .getPropertyValue('--fydp-css-rev') in the console, or grep the
       wpr-usedcss blob in view-source — instead of inferred from behaviour.
     • Boxed-1280 vs Full Width, for the record: the gutter is identical in
       both modes (32px). What differs is the CROP. At 1260px content the
       tracks land at ~291px; the 250px card floor beats 3:2 (which only
       needs 194px), so object-fit:cover crops the natively-3:2 1024×683
       photos ~22% horizontally and the boats run frame-edge to frame-edge —
       four wall-to-wall hulls read as one band even with a real gutter
       between them. At Full Width the tracks cross 375px, 3:2 takes over,
       and the water around each boat reappears. Height is to be kept, so the
       floor stays at 250px; the two levers remain --fydp-grid-gap (more air
       between cards) and --fydp-card-min-h (less crop, shorter card).
       Production reference, measured off the live legacy component: ~28px
       gutters on ~326×250 cards — 32px here meets it.

   rev 1.4.0 — 2026-08-10
     • GUTTER, ATTEMPT 3 — hardened against var() resolution failure.
       Screenshot forensics settled what is actually happening: the card
       corners ARE rounded and the cards ARE ~3:2, so `.fydp .fydp-card` lands
       and aspect-ratio applies — but the gutter measures ZERO, at both a boxed
       and a ~2590px-wide content width. A width-independent zero rules out the
       track-overflow theory in rev 1.3.0.
       Since no other stylesheet on the page sets a gap on .fydp-grid (all 64
       were scanned), the remaining mechanism is the `gap` declaration itself
       failing. `gap: var(--fydp-grid-gap)` with no fallback is invalid at
       computed-value time if that property does not resolve, and gap then
       falls back to its initial value — `normal`, i.e. 0. Exactly the symptom.
       So: `gap` shorthand replaced by the `column-gap` / `row-gap` longhands,
       each declared twice — a literal first, then the var() form WITH a
       fallback. Whatever eats the custom property, a real gutter survives.
     • Every other var() used for a layout-critical property gained a fallback
       for the same reason.

   rev 1.3.0 — 2026-08-10
     • FIXES THE MISSING GUTTER. Every card track is now minmax(0, 1fr) and the
       card carries min-width:0.

       `repeat(4, 1fr)` is shorthand for `repeat(4, minmax(auto, 1fr))`, and
       that `auto` floor is the item's automatic minimum size. A grid item with
       `aspect-ratio: 3/2` AND `min-height: 250px` has a TRANSFERRED minimum in
       the inline axis as well — 250 x 1.5 = 375px (CSS Sizing 3 §5.2.1). So
       each of the four tracks was pinned at 375px and could not shrink:
           4 x 375 + 3 x 32 = 1596px needed, inside a 1260px column
       The grid overflowed by 336px, the cards spilled past the container, and
       the row read as one solid band with no gutter.

       minmax(0, 1fr) removes the floor, so the tracks divide the space they
       actually have: (1260 - 96) / 4 = 291px, height still pinned at 250px by
       min-height, gutter visible again.

       This is also why setting the section to Full Width appeared to fix it —
       at 1900px of content there is room for the 1596px the grid was demanding,
       so nothing overflowed. The gutter was never the variable; it was 24px in
       both modes. And it is why v1.2.0's wider gutter changed nothing: it made
       the overflow worse, 1572px -> 1596px.

       Introduced in v1.1.0 with the 250px card floor; v1.0.0 had no min-height,
       hence no transferred minimum and correct gutters.

   rev 1.2.0 — 2026-08-10
     • Grid gutter is now --fydp-grid-gap, set per breakpoint, and the desktop
       value goes 24px -> 32px. Measured against the live section
       (.elementor-container max-width:1280px, section padding 70px 0 80px 0,
       elementor-column-gap-default 10px/side) the 4-up card lands at 297x250:
       min-height:250px beats aspect-ratio 3/2 at that width, so the photo takes
       a ~21% horizontal crop and the boats run edge to edge with no water
       around them, which is what made the row read as one continuous band.
       Switching the section to Full Width does NOT change the gutter — it
       widens the cards until 3:2 takes over and the crop disappears. Height was
       to be kept, so the gutter carries the separation instead. Both levers are
       now custom properties: raise --fydp-grid-gap for more air between cards,
       lower --fydp-card-min-h to trade height back for an uncropped photo.
     • Mobile headline: 12px in both orientations, with the portrait and
       landscape insets the brief specified. They were supplied as two blocks
       on the same `max-width:1024px` query with conflicting padding, so they
       are split across the orientation queries — otherwise the second block
       would simply overwrite the first for both orientations.

   rev 1.1.0 — 2026-08-10
     • SPECIFICITY REWRITE. Every component rule is now scoped under `.fydp`,
       matching the convention avalon-boat-features-specs.css already uses.
       v1.0.0 wrote bare `.fydp-x` selectors at (0,1,0), which lost to:
           .elementor-kit-12 h3        (0,1,1)  font-size:22px;font-weight:500
           button:hover, button:focus  (0,1,1)  background-color:#006991
       and, worse, to this file's OWN button reset `.fydp button` (0,1,1) —
       which stripped the border and fill off every pill, chip, rail item and
       arrow. Its `:hover` arm at (0,2,1) also beat `.fydp-pill.is-on` (0,2,0),
       so a freshly clicked pill lost its blue fill while the cursor sat on it,
       which is what made clicking look like it did nothing. Root-scoped rules
       sit at (0,2,0) or higher and clear all three. The reset is now
       `:where(.fydp) button` at (0,0,1), where it cannot outrank anything here.
     • Card subtitle restored — .fydp-card__s in the overlay (3A/3B/3C-desktop/
       3D) and .fydp-card-sub below the card (3C accordion), per the exports.
     • Card floor via --fydp-card-min-h so desktop thumbnails match the 250px of
       the outgoing `.custom-post-tabs .tab-post` instead of sitting ~30%
       shorter at a bare 3:2.
     • Landscape card type scaled to the 844 exports (13px title / 11.5px sub).

   Everything is scoped under .fydp. No class here collides with the legacy
   .custom-post-tabs / #tabs / .cat-tab-content / .tab-post tree, which is still
   bound by the global jQuery handler at assets/js/main.js:19 and shared with
   .model-tabs-navigation on the boat-model pages.

   TOKENS come from the child theme (style.css :root) with literal fallbacks.
   --primary-color #006991 already matches the comps exactly.

   THE STAGE BACKGROUND IS NOT HERE. The hero photo + rgba(9,12,11,.84/.88)
   scrim live on the Elementor section (#fydp-sec-id), which already carries
   them. Section padding is Elementor's too — this file adds no outer padding.

   FULL-BLEED STICKY HEADER. Below 1025px, 3B and 3D pin .fydp-shead. It has a
   background, a blur and a hairline, so it has to reach the viewport edges or
   it reads as a floating card with gaps beside it. margin-inline:
   calc(50% - 50vw) does that without this file needing to know what horizontal
   padding the Elementor section is carrying.

   WP ROCKET. Remove Unused CSS is on. This file and "(.*)fydp(.*)" are both on
   the safelist, which is what keeps the interaction states (.is-on, [hidden])
   from being stripped — they are not all present in the first paint.
   ============================================================================= */

.fydp {
	/* Which revision of this file the browser is actually running. After the
	   cache saga above, never again infer deployment from rendering:
	   getComputedStyle($0).getPropertyValue('--fydp-css-rev'). The RUCSS
	   safelist pattern (.*)fydp(.*) carries it into the used-CSS snapshot, so
	   it also dates the snapshot itself in view-source. */
	--fydp-css-rev:     "1.14.0";

	--fydp-font:        var(--body-font-family, "Gotham"), "Helvetica Neue", Helvetica, sans-serif;
	--fydp-font-bold:   "Gotham Bold", "Helvetica Neue", Helvetica, sans-serif;
	--fydp-primary:     var(--primary-color, #006991);
	--fydp-accent:      #228bcb;   /* active underline / rail edge / pager glyph */
	--fydp-accent-soft: #5685bc;   /* hover borders */
	--fydp-cta-hover:   #00344a;
	--fydp-text:        var(--white-color, #ffffff);
	--fydp-muted:       rgba(255, 255, 255, 0.68);
	--fydp-faint:       rgba(255, 255, 255, 0.45);
	--fydp-line:        rgba(255, 255, 255, 0.12);
	--fydp-card-bg:     #10161a;
	--fydp-shead-bg:    rgba(9, 12, 11, 0.95);

	/* Offset for a pinned site header. Nothing on Avalon is sticky today
	   (no elementor-sticky, and Max Mega Menu's toggle is static), so 0.
	   If that changes, set --fydp-sticky-top on #fydp-sec-id and every pinned
	   design follows. The JS reads this too, for scroll-into-view. */
	--fydp-sticky-top:  0px;

	/* Card floor. 0 by default so the 3:2 ratio governs; desktop raises it to
	   250px to match the outgoing .custom-post-tabs .tab-post min-height.
	   Lower it to trade height back for an uncropped 3:2 photo. */
	--fydp-card-min-h:  0px;

	/* Gutter between cards. Set per breakpoint below; raise it for more air. */
	--fydp-grid-gap:    24px;

	font-family: var(--fydp-font);
	color: var(--fydp-text);
}

.fydp *,
.fydp *::before,
.fydp *::after { box-sizing: border-box; }

/* ── Button reset ─────────────────────────────────────────────────────────────
   READ THE SELECTOR BEFORE CHANGING IT. It is pinned at (0,2,0) on purpose.

   The rule this has to clear is in hello-elementor/assets/css/reset.css:

       [type=button],[type=submit],button{ border:1px solid #c36;
         border-radius:3px; color:#c36; padding:.5rem 1rem;
         text-align:center; white-space:nowrap; ... }

   That is a SELECTOR LIST, and a rule's specificity for a given element is
   that of the most specific arm that matches it. Every control this component
   renders is `<button type="button">` — all five partials, no exceptions — so
   the arm that matches is `[type=button]`, an attribute selector at (0,1,0),
   NOT the bare `button` at (0,0,1). Anything at or below (0,1,0) here merely
   ties with it and hands the outcome back to source order, which is what
   went wrong in the first place. History:

     .fydp button                       (0,1,1)  v1.0.0 — too strong; beat
                                                 .fydp-pill.is-on and ate the
                                                 pills' border and fill
     :where(.fydp) button               (0,0,1)  v1.1.0-1.6.0 — too weak; lost
                                                 the tie to `button` on source
                                                 order and then lost outright
                                                 to `[type=button]`. The pink
                                                 borders, the 3px radius, the
                                                 centred rail text and the
                                                 nowrap spill all date from here
     .fydp :where(button)               (0,1,0)  considered and rejected — an
                                                 exact tie with [type=button]
     .fydp [data-fydp-block]
                        :where(button)  (0,2,0)  CORRECT. Outranks the
                                                 attribute arm outright, so the
                                                 outcome no longer depends on
                                                 which order WP Rocket happens
                                                 to assemble the two files in

   Every button this component renders lives inside a [data-fydp-block], so the
   extra hop costs no coverage. :where() keeps `button` itself weightless, which
   is what stops the pair from climbing to (0,2,1) and outranking the component
   rules.

   THE (0,2,0) TIE WITH THE COMPONENT RULES IS INTENDED. `.fydp .fydp-rail__item`
   and friends are also (0,2,0); they win because they are further down THIS
   file, and intra-file order is the one ordering guarantee that has held
   through every delivery mode (proven byte-level against the min/1 and RUCSS
   outputs). Do not move this block below the component sections. Rules that
   must win regardless — every .is-on and every :hover/:focus arm — are already
   (0,3,0).

   Two properties are declared here that this file had never claimed at any
   weight, so they leaked in no matter the ordering: `white-space` (hello's
   `nowrap` is what made the 3A rail titles spill out of the rail) and
   `border-radius` (its 3px). Components that genuinely want nowrap —
   .fydp-tab, .fydp-acc__c, .fydp-headline__count — say so themselves.

   One more of hello's arms is worth knowing about: `[type=button]:hover` is
   (0,2,0), which ties with the component base rules. Every control below
   therefore states its own colour and background in a (0,3,0) :hover/:focus
   arm rather than leaning on the base rule to hold through hover.
   --------------------------------------------------------------------------- */
.fydp [data-fydp-block] :where(button) {
	margin: 0;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: none;
	font: inherit;
	color: inherit;
	text-align: left;
	white-space: normal;
	cursor: pointer;
	-webkit-appearance: none;
	appearance: none;
	-webkit-tap-highlight-color: transparent;
}

.fydp :focus-visible {
	outline: 2px solid var(--fydp-accent);
	outline-offset: 2px;
}

/* ── Which block is visible ───────────────────────────────────────────────────
   Desktop is >=1025px, matching Elementor's viewport_lg for this site. Below
   that, orientation decides. A block serving two slots carries two context
   classes. The reveals tie at (0,2,0) with the hide rule, so they come second.
   --------------------------------------------------------------------------- */
.fydp .fydp-block { display: none; }

@media (min-width: 1025px) {
	.fydp .fydp-ctx--desktop { display: block; }
}
@media (max-width: 1024px) and (orientation: portrait) {
	.fydp .fydp-ctx--portrait { display: block; }
}
@media (max-width: 1024px) and (orientation: landscape) {
	.fydp .fydp-ctx--landscape { display: block; }
}

/* ── Section heading ──────────────────────────────────────────────────────── */
.fydp .fydp-title-wrap {
	border-left: 4px solid var(--fydp-primary);
	padding-left: 20px;
}

.fydp .fydp-title {
	margin: 0;
	padding: 0;
	font-family: var(--fydp-font-bold);
	font-weight: 700;
	font-size: 28px;
	line-height: 36px;
	text-transform: uppercase;
	color: var(--fydp-text);
}

/* ── Panels ───────────────────────────────────────────────────────────────── */
.fydp .fydp-panel { display: block; }
.fydp .fydp-panel[hidden] { display: none; }
.fydp .fydp-panel::before { content: none; }   /* only the <noscript> sheet turns this on */

/* ── Card grid ────────────────────────────────────────────────────────────── */
.fydp .fydp-grid {
	display: grid;
	/* minmax(0, …) not 1fr — see rev 1.3.0. A bare `1fr` keeps an `auto`
	   minimum, and the card's min-height transfers through its aspect-ratio
	   into a 375px inline minimum that pins the tracks open and overflows the
	   container. That was only the TRACK half of the clamp: the ITEM-level
	   transferred minimum sized every card to 375px inside its 291px track
	   until `width: 100%` on .fydp-card — see rev 1.6.0. */
	grid-template-columns: repeat(4, minmax(0, 1fr));
	/* Eight declarations, one gutter — deliberate, keep them all.
	   Oldest syntax first, so the most capable form the engine understands is
	   the one that wins on source order:
	     1. grid-* legacy aliases — pre-2018 engines that shipped grid before
	        the unified gap properties (handoff §6, hypothesis 3). In modern
	        engines these are plain aliases and are re-overridden below.
	     2. Modern longhands with LITERALS — the floor. Applies even if every
	        custom property on the page is eaten.
	     3. Modern longhands with var() + fallback — the tunable value.
	   Longhands rather than the `gap` shorthand throughout: a shorthand fed an
	   unresolvable var() is invalid at computed-value time and resets BOTH
	   axes to `normal` (0), which is precisely the class of failure this
	   component keeps meeting. */
	grid-column-gap: 32px;
	grid-row-gap: 32px;
	grid-column-gap: var(--fydp-grid-gap, 32px);
	grid-row-gap: var(--fydp-grid-gap, 32px);
	column-gap: 32px;
	row-gap: 32px;
	column-gap: var(--fydp-grid-gap, 32px);
	row-gap: var(--fydp-grid-gap, 32px);
	align-items: start;
}

.fydp .fydp-cell { display: block; }

.fydp .fydp-card {
	position: relative;
	display: block;
	overflow: hidden;
	border-radius: 6px;
	aspect-ratio: 3 / 2;
	min-height: var(--fydp-card-min-h, 0px);
	/* rev 1.6.0 — three legs, three DIFFERENT clamps, all required:
	     minmax(0, 1fr) on the grid → lets the TRACK shrink below content
	                                   (rev 1.3.0);
	     min-width: 0               → removes the item's AUTOMATIC minimum;
	     width: 100%                → sidesteps the TRANSFERRED minimum
	   (css-sizing-4 §5.3): a definite min-height transfers through
	   aspect-ratio as a min-width clamp on any *auto* inline size —
	   250 × 3/2 = 375px — regardless of min-width: 0. That is how every
	   card rendered 375px wide inside a 291px track and buried the gutters.
	   An explicit width is not auto, so the clamp never engages. */
	width: 100%;
	min-width: 0;
	background-color: var(--fydp-card-bg);
	text-decoration: none;
	color: inherit;
}
.fydp .fydp-card:hover,
.fydp .fydp-card:focus {
	text-decoration: none;
	color: inherit;
}

.fydp .fydp-card__img {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Bottom scrim. Locked in the comps: no shift, zoom or movement on hover. */
.fydp .fydp-card::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(rgba(3, 7, 9, 0) 48%, rgba(3, 7, 9, 0.9) 100%);
	pointer-events: none;
}

.fydp .fydp-card__ov {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 1;
	padding: 12px 14px;
}

.fydp .fydp-card__t {
	display: block;
	font-family: var(--fydp-font-bold);
	font-weight: 700;
	font-size: 15px;
	line-height: 1.35;
	color: var(--fydp-text);
}

/* Subtitle inside the overlay — 3A / 3B / 3C-desktop / 3D. */
.fydp .fydp-card__s {
	display: block;
	margin-top: 2px;
	font-size: 13px;
	line-height: 1.35;
	color: rgba(255, 255, 255, 0.78);
}

/* Subtitle below the card — 3C accordion only. min-height keeps the 2-up and
   4-up rows even when only some boats carry a floorplan value. */
.fydp .fydp-card-sub {
	display: block;
	min-height: 15px;
	margin-top: 5px;
	font-size: 10.5px;
	line-height: 1.35;
	color: rgba(255, 255, 255, 0.6);
}

/* ── CTA ──────────────────────────────────────────────────────────────────── */
.fydp .fydp-cta-wrap--center { text-align: center; margin-top: 44px; }
.fydp .fydp-cta-wrap--block  { margin-top: 10px; }

.fydp .fydp-cta {
	display: inline-block;
	padding: 10px 20px;
	border: 1px solid var(--fydp-primary);
	border-radius: 5px;
	background: var(--fydp-primary);
	color: var(--fydp-text);
	font-size: 12px;
	line-height: 1.6;
	letter-spacing: 1.8px;
	text-transform: uppercase;
	text-decoration: none;
	transition: background 0.25s, color 0.25s;
}
.fydp .fydp-cta:hover,
.fydp .fydp-cta:focus {
	background: #fff;
	color: var(--fydp-cta-hover);
	text-decoration: none;
}

.fydp .fydp-cta-wrap--block .fydp-cta {
	display: block;
	padding: 9px 12px;
	font-size: 11px;
	letter-spacing: 1.5px;
	text-align: center;
	transition: all 0.2s;
}
.fydp .fydp-cta-wrap--block .fydp-cta:hover,
.fydp .fydp-cta-wrap--block .fydp-cta:focus {
	background: #fff;
	color: var(--fydp-primary);
}

/* =============================================================================
   3A · CATEGORY RAIL
   ============================================================================= */
.fydp .fydp-rail-layout { margin-top: 38px; }

.fydp .fydp-rail { border-top: 1px solid var(--fydp-line); }

.fydp .fydp-rail__item {
	display: block;
	width: 100%;
	padding: 13px 16px;
	/* `border: 0` FIRST, then the two edges the comp actually draws. Without
	   the shorthand, border-top and border-right are never declared at this
	   weight and whatever the cascade last supplied for them survives — which
	   is how hello-elementor's `border: 1px solid #c36` put a pink hairline
	   along the top and right of every rail row (rev 1.7.0). Same pattern on
	   .fydp-tab and .fydp-acc__head. Keep the shorthand. */
	border: 0;
	border-left: 3px solid rgba(255, 255, 255, 0.15);
	border-bottom: 1px solid rgba(255, 255, 255, 0.08);
	background: transparent;
	/* The 3A comp left-aligns the row. Stated at (0,2,0) rather than relying
	   on the button reset, because this is a design decision, not a reset. */
	text-align: left;
	color: rgba(255, 255, 255, 0.62);
	transition: background 0.2s, border-color 0.2s, color 0.2s;
}
/* The comp changes only the fill on hover, so the colour is restated at its
   base value rather than left to the base rule: hello's `[type=button]:hover`
   is (0,2,0) and would otherwise tie with `.fydp .fydp-rail__item` and flip
   the row to #fff mid-hover depending on file order. */
.fydp .fydp-rail__item:hover,
.fydp .fydp-rail__item:focus {
	background: rgba(255, 255, 255, 0.05);
	color: rgba(255, 255, 255, 0.62);
}

.fydp .fydp-rail__item.is-on,
.fydp .fydp-rail__item.is-on:hover,
.fydp .fydp-rail__item.is-on:focus {
	border-left-color: var(--fydp-accent);
	background: rgba(255, 255, 255, 0.06);
	color: var(--fydp-text);
}
.fydp .fydp-rail__item.is-on .fydp-rail__t {
	font-family: var(--fydp-font-bold);
	font-weight: 700;
}

/* Both spans are block-level inside the row, so they take the row's
   text-align — restated here so a future change to the button reset cannot
   quietly re-centre them, and so the intent is greppable from the comp. The
   title wraps freely: at 370px the rail's content box is ~335px and the
   longest term name ("Find Your Perfect Fishing Pontoon Boat | Avalon Fishing
   Pontoon Boats") runs to two or three lines. Variable-height rows are the
   comp's own behaviour — do not reintroduce nowrap or a truncation. */
.fydp .fydp-rail__t {
	display: block;
	font-size: 14px;
	line-height: 1.5;
	text-align: left;
}
.fydp .fydp-rail__c {
	display: block;
	margin-top: 3px;
	font-family: var(--fydp-font);
	font-size: 11px;
	letter-spacing: 1px;
	text-transform: uppercase;
	text-align: left;
	color: rgba(255, 255, 255, 0.4);
}

@media (min-width: 1025px) {
	.fydp .fydp-block--category-rail .fydp-rail-layout {
		display: grid;
		grid-template-columns: 370px minmax(0, 1fr);
		/* Same hardening as .fydp-grid: legacy alias, then modern longhands.
		   All literals here, so no var() form is needed. */
		grid-gap: 44px;
		column-gap: 44px;
		row-gap: 44px;
	}
	/* The rail eats a lot of width, so the comp runs 2-up here. */
	.fydp .fydp-block--category-rail .fydp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* =============================================================================
   3B · CLEAN TAB BAR
   ============================================================================= */
.fydp .fydp-tabs {
	display: flex;
	gap: 2px;
	margin-top: 34px;
	border-bottom: 2px solid rgba(255, 255, 255, 0.1);
}

.fydp .fydp-tab {
	display: flex;
	align-items: baseline;
	gap: 7px;
	padding: 10px 17px 12px;
	margin-bottom: -2px;
	/* Shorthand first — see the note on .fydp-rail__item. This tab had the
	   same latent exposure on its top, left and right edges; it never showed
	   because 3B has only ever been assigned to mobile slots on this site,
	   and the mobile chip rule below restates the full border shorthand. */
	border: 0;
	border-bottom: 3px solid transparent;
	background: transparent;
	/* Deliberate here, unlike on the rail: the desktop underline row is a
	   single-line strip and its labels are the SHORT term labels. */
	white-space: nowrap;
	font-size: 15px;
	color: rgba(255, 255, 255, 0.6);
	transition: color 0.2s, border-color 0.2s;
}
.fydp .fydp-tab:hover,
.fydp .fydp-tab:focus {
	background: transparent;
	color: var(--fydp-text);
}
.fydp .fydp-tab.is-on,
.fydp .fydp-tab.is-on:hover,
.fydp .fydp-tab.is-on:focus {
	background: transparent;
	border-bottom-color: var(--fydp-accent);
	color: var(--fydp-text);
}
.fydp .fydp-tab.is-on .fydp-tab__label {
	font-family: var(--fydp-font-bold);
	font-weight: 700;
}
.fydp .fydp-tab__count { font-size: 11px; color: rgba(255, 255, 255, 0.42); }

.fydp .fydp-headline {
	display: flex;
	align-items: baseline;
	gap: 14px;
	margin-top: 28px;
}
.fydp .fydp-headline__title {
	margin: 0;
	font-family: var(--fydp-font-bold);
	font-weight: 700;
	font-size: 20px;
	line-height: 1.4;
	color: var(--fydp-text);
	text-wrap: pretty;
}
.fydp .fydp-headline__count {
	font-size: 13px;
	color: rgba(255, 255, 255, 0.5);
	white-space: nowrap;
}

.fydp .fydp-block--clean-tab-bar .fydp-panels { margin-top: 20px; }

/* =============================================================================
   3C · PILL GRID  (desktop)
   ============================================================================= */
.fydp .fydp-pills {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	margin-top: 34px;
}

.fydp .fydp-pill {
	display: inline-flex;
	align-items: baseline;
	padding: 9px 18px;
	border: 1px solid rgba(255, 255, 255, 0.28);
	border-radius: 999px;
	background: rgba(255, 255, 255, 0.04);
	color: var(--fydp-muted);
	font-size: 13.5px;
	line-height: 1.4;
	transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.fydp .fydp-pill:hover,
.fydp .fydp-pill:focus {
	background: rgba(255, 255, 255, 0.04);
	border-color: var(--fydp-accent-soft);
	color: var(--fydp-text);
}
.fydp .fydp-pill.is-on,
.fydp .fydp-pill.is-on:hover,
.fydp .fydp-pill.is-on:focus {
	background: var(--fydp-primary);
	border-color: var(--fydp-primary);
	color: var(--fydp-text);
}
.fydp .fydp-pill__count { margin-left: 8px; font-size: 11px; opacity: 0.75; }

.fydp .fydp-showing {
	margin-top: 26px;
	font-size: 12px;
	letter-spacing: 1.6px;
	text-transform: uppercase;
	color: var(--fydp-faint);
}

.fydp .fydp-block--pill-grid .fydp-panels { margin-top: 16px; }

/* =============================================================================
   3C · ACCORDION  (mobile form)
   ============================================================================= */
.fydp .fydp-acc-list {
	display: flex;
	flex-direction: column;
	gap: 10px;
	margin-top: 12px;
}

/* Mini-card overrides, declared once on the block. A custom property set on a
   descendant wins over the one on .fydp whatever the media query, so these do
   not need repeating per orientation.
   No card floor: a 172px-wide 2-up card forced to 250px would be portrait, not
   landscape. */
.fydp .fydp-block--pill-grid-accordion {
	--fydp-card-min-h: 0px;
	--fydp-grid-gap: 10px;
}

.fydp .fydp-acc {
	border: 1px solid var(--fydp-line);
	border-radius: 8px;
	overflow: hidden;
	background: rgba(10, 14, 13, 0.55);
	transition: border-color 0.2s;
}
.fydp .fydp-acc.is-on { border-color: rgba(34, 139, 203, 0.65); }

.fydp .fydp-acc__head {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	padding: 13px 14px;
	/* This header declared no border at all, so all four edges were exposed —
	   a pink #c36 box inside every accordion row, clipped only by the
	   .fydp-acc wrapper's overflow:hidden. The comp draws the border on the
	   wrapper, never on the header. */
	border: 0;
	border-radius: 0;
	background: transparent;
	text-align: left;
	color: rgba(255, 255, 255, 0.62);
	transition: color 0.2s;
}
.fydp .fydp-acc__head:hover,
.fydp .fydp-acc__head:focus {
	background: transparent;
	color: var(--fydp-text);
}
.fydp .fydp-acc.is-on .fydp-acc__head { color: var(--fydp-text); }
.fydp .fydp-acc.is-on .fydp-acc__t {
	font-family: var(--fydp-font-bold);
	font-weight: 700;
}

.fydp .fydp-acc__t { flex: 1; font-size: 13.5px; line-height: 1.45; }
.fydp .fydp-acc__c { font-size: 11px; color: var(--fydp-faint); white-space: nowrap; }

.fydp .fydp-acc__ic {
	flex: none;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 22px;
	border: 1px solid rgba(255, 255, 255, 0.3);
	border-radius: 50%;
	font-size: 13px;
	line-height: 1;
	color: var(--fydp-accent);
}
/* Glyph is CSS, not markup, so the +/- can never desync from aria-expanded. */
.fydp .fydp-acc__ic::before { content: "+"; }
.fydp .fydp-acc.is-on .fydp-acc__ic::before { content: "\2212"; }

.fydp .fydp-acc__body { padding: 2px 12px 14px; }

/* ── Mini cards: caption BELOW the photo  (rev 1.10.0) ────────────────────────
   Every other design keeps the comp's scrim overlay. This one does not, because
   at accordion size the overlay stops being a caption and becomes a lid: the
   card is 132px wide at vw 363 (measured), so the photo is 88px tall and a
   two-line 11.5px title in an 8/9px box is ~46px of it. The 3C export was drawn
   with the same numbers, so this is a deliberate departure from it, not a
   correction of the port.

   The scrim is the other half of the argument. It runs
   rgba(3,7,9,0) 48% -> rgba(3,7,9,.9) 100%, tuned for a 300-410px desktop card
   where the label sits in the deep end. Scaled to 88px the same two lines start
   around the 50% stop, where the gradient is still mostly transparent — so the
   top line lands on raw photograph. On this site's studio blacks and aerial
   water that is the least legible text the component renders anywhere.

   FOUR CHANGES, and the reason each one is needed:

     .fydp-card       loses `aspect-ratio` so the anchor sizes to its content
                      (photo + caption) instead of being locked to 3:2, and
                      gives up the fill, radius and clip, which now belong to
                      the image.
     .fydp-card__img  takes the 3:2 ratio, the 6px radius and --fydp-card-bg,
                      and comes out of the absolute layer so a static sibling
                      can follow it.
     ::after          off. With no text over the photo the scrim is a vignette
                      the comp never asked for.
     .fydp-card__ov   static, full-bleed, 5px below the photo — the same rhythm
                      .fydp-card-sub already uses, so photo / title / subtitle
                      read as one column.

   THE TRANSFERRED MINIMUM CANNOT COME BACK HERE — rev 1.6.0's trap, checked
   rather than assumed. It needs a definite min-height transferring through an
   aspect-ratio into an AUTO inline size. After this block the anchor has
   `aspect-ratio: auto` so it transfers nothing, and the image has an explicit
   `width: 100%` (not auto) plus no min-height of its own, so there is nothing
   to transfer and nothing for it to clamp. The block's --fydp-card-min-h is
   already 0px besides.

   `display: block` on .fydp-card__ov IS LOAD-BEARING. It is a <span>, and its
   block-level behaviour today comes only from `position: absolute`. Take that
   away without restating display and it reverts to inline, where the
   `margin-top` below silently does nothing.

   Real assets are 1024x683 (1.4993), so `object-fit: cover` at 3:2 crops
   nothing visible and the width/height attributes still describe the box.
   ------------------------------------------------------------------------- */
.fydp .fydp-block--pill-grid-accordion .fydp-card {
	aspect-ratio: auto;
	overflow: visible;
	border-radius: 0;
	background-color: transparent;
}

.fydp .fydp-block--pill-grid-accordion .fydp-card__img {
	position: static;
	inset: auto;
	width: 100%;
	height: auto;
	aspect-ratio: 3 / 2;
	object-fit: cover;
	border-radius: 6px;
	background-color: var(--fydp-card-bg);
}

.fydp .fydp-block--pill-grid-accordion .fydp-card::after { content: none; }

.fydp .fydp-block--pill-grid-accordion .fydp-card__ov {
	display: block;
	position: static;
	inset: auto;              /* the base rule's left/right/bottom, now inert */
	z-index: auto;
	margin-top: 5px;
	padding: 0;
}

/* THE TITLE FLOOR — kept as a tunable, but TURNED OFF as of 1.11.0.

   1.10.0 reserved three lines portrait / two landscape so the subtitles in a
   row would sit at one height. Reviewed on device and rejected: the blank line
   under short titles cost more than the misalignment it bought, and the
   misalignment is barely legible anyway on a transparent caption with no box
   edge to expose it. Set by measurement-on-device to 1.3em / 0.6em.

   BOTH VALUES ARE NO-OPS, and that is the intended state — worth knowing before
   anyone "fixes" them. 1.3 x 11.5px = 14.95px, which is exactly one line at
   `line-height: 1.3`, so a one-line title already meets it; 0.6em is far under.
   The floor is therefore off in both orientations and titles are natural
   height, one to three lines. Anything ABOVE 1.3em starts reserving again:
   2.6em = two lines, 3.9em = three, 5.2em = four.

   Stated in `em` rather than px so it tracks the font-size on this very rule.
   A px value would need re-deriving on any type change, and this is the number
   most likely to be nudged.

   For reference, the sweep 1.10.0 was sized from, titles at full card width:
   portrait vw 363 max 45px (3 lines), landscape vw 674 max 30px (2 lines). The
   same sweep at a 9px inset returned 45px in BOTH orientations, which is why
   the caption is still full-bleed. */
.fydp .fydp-block--pill-grid-accordion {
	--fydp-acc-title-min: 1.3em;   /* <= 1.3em means "no floor" — see above */
	--fydp-acc-row-gap:  10px;     /* portrait row gutter — consumed below */
}

.fydp .fydp-block--pill-grid-accordion .fydp-card__t {
	font-size: 11.5px;
	line-height: 1.3;
	min-height: 1.3em;                                /* literal floor */
	min-height: var(--fydp-acc-title-min, 1.3em);     /* the tunable */
}

/* THE EMPTY SUBTITLE COLLAPSES.  This is what puts the CTA where it belongs,
   and it replaces the obvious fix rather than joining it.

   The symptom: on a category whose boats carry no floorplan value the button
   sits ~20px further from the last line of text than it does on one that has
   them, which reads as a stray gap rather than as spacing. The cause is right
   here — .fydp-card-sub reserves `min-height: 15px` plus a 5px margin whether
   or not it has anything in it.

   The obvious fix, `margin-top: -5px` on .fydp-cta-wrap--block, was declined
   because the condition it needs is not one CSS can address. Measured on the
   deployed data:

       acc 0  subs [S S . .]   portrait rows: r1 = S S, r2 = . .
       acc 1-7            [. . . .]

   So the gap appears whenever the LAST ROW has no visible subtitle — which in
   portrait is all eight categories (acc 0's two subtitled boats are cards 1 and
   2, i.e. row 1), and in landscape is every category except acc 0, whose single
   row of four contains them. One flat -5px cannot be right in both places, a
   panel-level :has() gets portrait acc 0 backwards, and a PHP flag has the same
   panel-level granularity as the :has(). Rows are not addressable.

   Collapsing the reservation answers it per cell instead, with no condition to
   get wrong: an empty subtitle becomes a 5px spacer, a populated one is
   unchanged to within a pixel, and the CTA's own 10px margin stays put.

       empty      0 content + 5 padding                     =  5px  (was 20)
       populated  14.175 (10.5 x 1.35) + 5 padding          = 19.2px (was 20)

   PADDING, NOT MARGIN, and not a style preference. A margin on an empty block
   self-collapses and then escapes through .fydp-card and .fydp-cell, so whether
   the 5px survives at all depends on grid-item margin rules. Padding is inside
   the border box and cannot collapse, so the number is the number. `min-height:
   0` is safe alongside it: box-sizing is border-box here, and 0 is below the
   5px of padding, so it never clamps.

   It also survives subtitle="model". If those boat_model term fields turn out
   populated and all 30 cards gain a value, this rule simply stops applying,
   where a hard-coded -5px would then be 15px too tight on every category.

   PADDING VALUE: 1px as of 1.12.0, was 5px. See that rev's note. */
.fydp .fydp-block--pill-grid-accordion .fydp-card-sub {
	min-height: 0;
	margin-top: 0;
	padding-top: 1px;
}

/* =============================================================================
   3D · SPOTLIGHT PAGER
   ============================================================================= */
.fydp .fydp-pager {
	position: relative;
	margin-top: 18px;
	padding: 0 58px 8px;
}
.fydp .fydp-pager__pos { text-align: center; }
.fydp .fydp-pager__pos .fydp-mut + .fydp-mut { margin-top: 1px; }

.fydp .fydp-mut {
	display: block;
	font-size: 10.5px;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.5);
}

.fydp .fydp-pager__head { text-align: center; margin-top: 10px; }
.fydp .fydp-pager__head .fydp-headline__title { font-size: 14px; }
.fydp .fydp-pager__head .fydp-headline__count {
	margin-top: 3px;
	font-size: 11px;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fydp-faint);
}

.fydp .fydp-arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 1px solid rgba(255, 255, 255, 0.32);
	border-radius: 50%;
	background: transparent;
	font-size: 22px;
	line-height: 1;
	color: var(--fydp-text);
	transition: border-color 0.2s, color 0.2s;
}
.fydp .fydp-arrow:hover,
.fydp .fydp-arrow:focus {
	background: transparent;
	border-color: var(--fydp-accent);
	color: var(--fydp-accent);
}
.fydp .fydp-arrow--prev { left: 0; }
.fydp .fydp-arrow--next { right: 0; }

.fydp .fydp-block--spotlight-pager .fydp-panels { margin-top: 16px; }

/* =============================================================================
   DESKTOP  (>=1025px)
   ============================================================================= */
@media (min-width: 1025px) {
	/* THE CARD FLOOR IS OFF as of 1.13.0. It was 250px from 1.4.0 to 1.12.0,
	   matching the outgoing `.custom-post-tabs .tab-post { min-height: 250px }`
	   so the new component would not read as a shrink beside the live site.

	   That reversed because the thing being matched was cropping too. The
	   legacy rule is `width: 23%; min-height: 250px; background-size: cover` —
	   the identical geometry, so production has always cut the same ~23%. The
	   250px was inherited, not designed, and matching it meant inheriting the
	   crop with it. The approved 3B desktop comp draws
	   `.card { ... aspect-ratio: 3/2 }` with no floor at all, so 0 is the comp,
	   not a departure from it.

	   The arithmetic, at the 1252px Elementor container:

	       card, 4-up with 32px gutters      289px wide
	       a WHOLE 3:2 photo at that width   193px tall
	       the old floor                     250px
	       width a whole photo needs at 250  375px  -> a 1596px container

	   So 250px of complete photograph was never available here. At 289 x 250
	   `object-fit: cover` scaled the 1024x683 source to 375x250 and cut 86px,
	   22.9% of its width, 43px off each side. At 289 x 193 it fits exactly and
	   the crop is zero. Rows lose 57px; that is the whole cost.

	   WHAT MOVES AND WHAT DOES NOT. 3B, 3C-desktop and 3D all run 4-up at 289px
	   and were all clamped by the floor, so all three change together. 3A does
	   NOT: its rail layout leaves 838px for a 2-up grid, so its cards are 403px
	   wide and 269px tall from `aspect-ratio` alone — above the old floor, which
	   therefore never bound. Nothing about 3A is different today.

	   --fydp-grid-gap STAYS AT 32px, and its own justification is now spent:
	   32 was chosen because four ~23%-cropped frames ran hull-to-frame-edge and
	   read as one band unless a wide gutter broke them up. With the water back
	   around each boat that pressure is gone and 24px would do — but the
	   spacing between thumbnails was to be preserved, so it is.

	   KEEP `width: 100%` ON .fydp-card. With the floor at 0 there is no definite
	   min-height to transfer through `aspect-ratio` into an auto inline size, so
	   the 1.6.0 gutter bug cannot fire whatever the width says. That makes the
	   declaration a guard rather than a fix — and it is the guard that has to
	   survive if anyone ever puts the floor back. */
	.fydp {
		--fydp-card-min-h: 0px;

		/* 24px -> 32px in 1.4.0. See the note above: the crop it was
		   compensating for is gone, but the gutter is kept as-is. */
		--fydp-grid-gap: 32px;
	}

	/* ── Caption below the photograph (rev 1.14.0) ────────────────────────────
	   The same treatment .fydp-block--pill-grid-accordion has carried since
	   1.10.0, now on the desktop designs too. One card, one rule, both
	   breakpoints.

	   WHY, from the sweep rather than from taste. Overlay heights across all
	   eight tabs at vw 1707, card 284 x 189:

	       44px   26 of 30 cards   one-line title, no subtitle
	       64px    2 of 30         one-line title + subtitle   (tab 0)
	       65px    4 of 30         two-line title              (tabs 1, 3)

	   So the caption covers 23% of the photograph on most cards and 34% on six
	   of them. Before 1.13.0 the worst case was 64/250 = 26%, because the card
	   was 61px taller — but it was taller by cropping 23% off the width, which
	   is what 1.13.0 stopped doing. Shortening the card gave the picture back
	   and took a third of it away again with the label.

	   AND A TALLER CARD CANNOT FIX IT, which is the part worth writing down.
	   .fydp-card__ov is bottom-anchored, so it always occupies the bottom 44 or
	   65px of whatever the card is. Growing the card grows the photo with it,
	   in the same proportion:

	       3:2   card 189   caption covers 23% / 34%     crop 0%
	       7:5   card 203   caption covers 22% / 32%     crop 6.6%
	       4:3   card 213   caption covers 21% / 31%     crop 11.1%

	   Three percentage points for a ninth of the picture. The only way to stop
	   the caption covering the photograph is to take it off the photograph.

	   WHAT IT COSTS: nothing, and the row height comes back. Cards run 219px
	   (one line) to 240px (two lines, or one plus a subtitle) against the 250px
	   they were before 1.13.0 — so the section returns to roughly the height it
	   had, with the photograph both complete and uncovered.

	   The 21px spread between short and tall cards is deliberately not levelled.
	   `align-items: start` on .fydp-grid puts every photo on the row's top edge
	   and the card is transparent below it, so there is no box edge for the
	   mismatch to show against — the same reasoning that retired
	   --fydp-acc-title-min on mobile in 1.12.0. --fydp-card-min-h is still here
	   if uniform cards are ever wanted, and it now means exactly that: with the
	   ratio on the image rather than the card, raising the floor adds trailing
	   space instead of cropping. 240px levels everything.

	   Scoped .fydp-block .fydp-card at (0,3,0) rather than relying on being
	   later in the file than the (0,2,0) base. RUCSS rewrites this stylesheet on
	   every "Clear Used CSS" and source order is not something to bet a layout
	   on — see the 1.9.0 note. Every design carries .fydp-block, so the extra
	   class costs nothing and matches.

	   `display: block` on .fydp-card__ov IS LOAD-BEARING — it is a <span> whose
	   block behaviour comes only from `position: absolute`. Removing that
	   without restating display reverts it to inline, where `margin-top` does
	   nothing. Same trap as 1.10.0.

	   The transferred minimum (css-sizing-4 §5.3, the 1.6.0 gutter bug) cannot
	   fire: the card has `aspect-ratio: auto` so it transfers nothing, and the
	   image carries an explicit `width: 100%` rather than an auto inline size.
	   --fydp-card-min-h is 0 here besides. */
	.fydp .fydp-block .fydp-card {
		aspect-ratio: auto;
		overflow: visible;
		border-radius: 0;
		background-color: transparent;
	}

	.fydp .fydp-block .fydp-card__img {
		position: static;
		inset: auto;
		width: 100%;
		height: auto;
		aspect-ratio: 3 / 2;
		object-fit: cover;
		border-radius: 6px;
		background-color: var(--fydp-card-bg);
	}

	/* The scrim goes with it. It exists to make white type legible over a
	   photograph; with no type over the photograph it is a vignette the comps
	   never drew. */
	.fydp .fydp-block .fydp-card::after { content: none; }

	.fydp .fydp-block .fydp-card__ov {
		display: block;
		position: static;
		inset: auto;
		z-index: auto;
		margin-top: 10px;
		padding: 0;
	}

	/* 3D has no desktop comp. Left unconstrained the pager cluster stretches
	   across the full 1252px container and the arrows end up a screen apart,
	   so cap it and centre it rather than inventing a new layout. */
	.fydp .fydp-block--spotlight-pager .fydp-pager {
		max-width: 640px;
		margin-inline: auto;
		margin-top: 28px;
	}
	.fydp .fydp-block--spotlight-pager .fydp-pager__head .fydp-headline__title { font-size: 20px; }
}

/* =============================================================================
   MOBILE  (<=1024px)
   =============================================================================
   Shared across every design once the viewport drops below Elementor's
   viewport_lg: smaller heading, pinned header for 3B/3D, tighter rhythm.
   ============================================================================= */
@media (max-width: 1024px) {

	.fydp .fydp-title-wrap { padding-left: 14px; }
	.fydp .fydp-title { font-size: 20px; line-height: 27px; }

	/* Pinned header — 3B and 3D only. Full-bleed so the bar's fill, blur and
	   hairline reach the viewport edges whatever padding the Elementor section
	   carries; the 16px is put back as internal padding. */
	.fydp .fydp-block--clean-tab-bar .fydp-shead,
	.fydp .fydp-block--spotlight-pager .fydp-shead {
		position: sticky;
		top: var(--fydp-sticky-top, 0px);

		/* 30, as it was through 1.7.x. 1.8.0 briefly raised this to 100 so the
		   header would paint OVER the email tab; that removed the overlap but
		   put a fixed overlay behind page furniture, which is not a pattern
		   users read correctly. The tab is a top layer again and the clash is
		   answered by keeping the two boxes apart — see §SLIDEOUT. */
		z-index: 30;

		margin-inline: calc(50% - 50vw);
		padding-inline: 16px;
		padding-block: 8px 12px;
		background: var(--fydp-shead-bg);
		-webkit-backdrop-filter: blur(10px);
		backdrop-filter: blur(10px);
		border-bottom: 1px solid rgba(255, 255, 255, 0.08);
	}

	/* Chips replace the desktop underline tabs. */
	.fydp .fydp-tabs {
		flex-wrap: wrap;
		gap: 7px;
		margin-top: 12px;
		border-bottom: 0;
	}
	.fydp .fydp-tab {
		padding: 7px 12px;
		margin-bottom: 0;
		border: 1px solid rgba(255, 255, 255, 0.28);
		border-radius: 999px;
		background: rgba(255, 255, 255, 0.04);
		gap: 5px;
		font-size: 12px;
		color: var(--fydp-muted);
		transition: background 0.2s, border-color 0.2s, color 0.2s;
	}
	.fydp .fydp-tab:hover,
	.fydp .fydp-tab:focus {
		background: rgba(255, 255, 255, 0.04);
		border-color: var(--fydp-accent-soft);
		color: var(--fydp-text);
	}
	.fydp .fydp-tab.is-on,
	.fydp .fydp-tab.is-on:hover,
	.fydp .fydp-tab.is-on:focus {
		background: var(--fydp-primary);
		border-color: var(--fydp-primary);
		color: var(--fydp-text);
	}
	.fydp .fydp-tab__count { font-size: 10.5px; opacity: 0.7; color: inherit; }

	.fydp .fydp-headline { display: block; margin-top: 8px; }
	.fydp .fydp-headline__title { font-size: 12px; }
	.fydp .fydp-headline__count {
		display: block;
		margin-top: 3px;
		font-size: 11px;
		letter-spacing: 1px;
		text-transform: uppercase;
		color: var(--fydp-faint);
	}

	.fydp .fydp-block--clean-tab-bar .fydp-panels,
	.fydp .fydp-block--spotlight-pager .fydp-panels { margin-top: 14px; }

	.fydp .fydp-pager { margin-top: 12px; padding-bottom: 6px; }
	.fydp .fydp-pager__head { margin-top: 8px; }

	/* 3A has no mobile comp: the rail stacks above the cards. */
	.fydp .fydp-block--category-rail .fydp-rail-layout { display: block; margin-top: 20px; }
	.fydp .fydp-block--category-rail .fydp-panels { margin-top: 20px; }

	/* 3C desktop pills, if assigned to a mobile slot. */
	.fydp .fydp-pills { margin-top: 14px; gap: 7px; }
	.fydp .fydp-pill { padding: 7px 12px; font-size: 12px; }
	.fydp .fydp-showing { margin-top: 14px; font-size: 11px; }

	.fydp .fydp-cta-wrap--center { margin-top: 24px; }
	.fydp .fydp-cta { padding: 9px 16px; font-size: 11px; letter-spacing: 1.5px; }

	.fydp .fydp-card__t { font-size: 14px; }
	.fydp .fydp-card__s { font-size: 12px; }
}

/* ── Portrait ─────────────────────────────────────────────────────────────── */
@media (max-width: 1024px) and (orientation: portrait) {
	.fydp { --fydp-grid-gap: 12px; }
	.fydp .fydp-block--spotlight-pager { --fydp-grid-gap: 14px; }

	.fydp .fydp-grid { grid-template-columns: minmax(0, 1fr); }
	.fydp .fydp-block--category-rail .fydp-grid { grid-template-columns: minmax(0, 1fr); }
	.fydp .fydp-block--pill-grid-accordion .fydp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

	/* ROW GUTTER ONLY. Currently 10px, which is what --fydp-grid-gap gives it
	   anyway — 1.11.0 tried 7px and 1.12.0 put it back after the subtitle
	   padding dropped to 1px and made the tightening unnecessary. The override
	   stays even though it is inert at this value, because the SPLIT is the
	   point: it is what makes the row gutter safe to move at all.

	   The COLUMN gutter must not move with it. It is what sets the card width,
	   and at vw 363 a 3px change there takes the card from 132px to ~133.5 and
	   shifts where every title wraps, invalidating the sweeps this section is
	   sized from. Hence longhands rather than --fydp-grid-gap, which drives
	   both axes. Literal first, then the var, for the same reason the base
	   .fydp-grid rule doubles its declarations: an unresolvable var() leaves
	   the literal standing instead of falling back to `normal`.

	   Portrait only, as specified. Landscape is 4-up with limit=4, so it has a
	   single row and no row gutter to tighten — raise `limit` above 4 and it
	   would inherit --fydp-grid-gap, not this. */
	.fydp .fydp-block--pill-grid-accordion .fydp-grid {
		grid-row-gap: 10px;
		grid-row-gap: var(--fydp-acc-row-gap, 10px);
		row-gap: 10px;
		row-gap: var(--fydp-acc-row-gap, 10px);
	}

	/* Portrait insets, per brief. The chips wrap to several rows here, so the
	   headline needs a little right padding to keep the long category names
	   off the edge of the pinned bar. */
	.fydp .fydp-headline__title {
		padding-left: 4px;
		padding-right: 10px;
	}
	.fydp .fydp-headline__count { padding-left: 4px; }
}

/* ── Landscape ────────────────────────────────────────────────────────────── */
@media (max-width: 1024px) and (orientation: landscape) {
	.fydp { --fydp-grid-gap: 12px; }

	.fydp .fydp-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.fydp .fydp-block--category-rail .fydp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.fydp .fydp-block--pill-grid-accordion .fydp-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }

	/* Also a no-op, and more emphatically than portrait's 1.3em: 0.6 x 11.5 =
	   6.9px, well under a single 14.95px line. Titles are natural height here.
	   Set by measurement on device; see the floor note in the 3C accordion
	   section for what raising it would buy. */
	.fydp .fydp-block--pill-grid-accordion { --fydp-acc-title-min: 0.6em; }

	.fydp .fydp-card__t { font-size: 13px; }
	.fydp .fydp-card__s { font-size: 11.5px; }

	/* Landscape inset, per brief. Wider bar, so a single 8px inset on both the
	   title and the count is enough to line them up under the chip row. */
	.fydp .fydp-headline__title { padding-left: 8px; }
	.fydp .fydp-headline__count { padding-left: 8px; }
}

/* ── Reduced motion ───────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
	.fydp * { transition: none !important; }
}

/* =============================================================================
   SLIDEOUT  (§SLIDEOUT — rewritten 1.9.0; 1.7.x and 1.8.0 machinery deleted)
   =============================================================================
   THE COLLISION
   -------------
   The site-wide email-signup slideout (#sidebar_slideout_container, rendered on
   wp_footer by inc/sidebar_form.php) is position:fixed at the right edge with a
   z-index of 99. Its visible 45x160 tab (#sidebar_slideout_toggle, which adds
   its own margin-top:10px) landed on the 3D pager's next-category arrow, and
   then sat on the pinned header for as long as the section was on screen.

   Measured live, vw 394 / vh 853, portrait:

       container top   10% of 853  = 85.3     (style.css line 6234)
       tab                           [95, 255]
       .fydp-shead pinned            [0, 145]  short category
                                     [0, 185]  longest category
       .fydp-arrow--next             [68, 112] .. [88, 132]

   WHY THE TAB IS SIMPLY PARKED LOWER, AND NOTHING CLEVER HAPPENS
   --------------------------------------------------------------
   .fydp-shead is sticky and full-bleed: its box sweeps every y value in the
   viewport before it pins at top:0. The tab is fixed: it sits at one y. A band
   that sweeps every y must cross a band that sits at one y — so "these two
   never overlap" is not a thing CSS can express, and three revisions were spent
   proving it. Painting the header over the tab (1.8.0) removed the overlap by
   hiding the tab, which is not a pattern users read. Moving the tab on scroll
   (1.7.x) removed it by introducing motion. Insetting the header's content
   (tried and discarded 1.9.0) knocked the pager off the title rule's axis by
   45/2 = 23px and did not even clear the <h2>, which is in .fydp-title-wrap,
   not .fydp-pager.

   So: the tab is given one static resting position that the pinned header
   cannot reach, it stays in front at all times, and it crosses the header once
   on the way in exactly as it crosses the cards and the CTA. That crossing is
   ordinary. The resting overlap was the bug, and the resting overlap is gone.

   THE NUMBER
   ----------
   --fydp-tab-top is where the visible TAB rests. As of 1.15.0 this file does
   not set `top` itself — style.css reads the number as

       --slideout-tab-top: calc(var(--fydp-tab-top, 10dvh) + 10px)

   and derives the tab's offset from the card's own top edge. 240 + 10 -> tab
   rests at [250, 410], clearing a header of up to 250px. Live max is 185 at
   vw 394; estimated ~232 at 360 and ~252 at 320, since the headline column is
   vw-148 and the <h2> starts wrapping below ~375. Raise the number if the check
   in the rev-1.9.0 note at the top of this file reports a negative `clear`.
   Nothing else depends on it.

   WHY px AND NOT A PERCENTAGE
   ---------------------------
   The thing being cleared is a pixel count that depends on viewport WIDTH,
   while a percentage on a fixed box resolves against viewport HEIGHT. No
   percentage is correct on two devices at once. That is what forced 1.7.x to
   compute a delta in JS. A px value has no such problem and needs no JS.

   WHY THERE IS NO !important HERE ANY MORE      (changed in 1.15.0)
   ----------------------------------------
   Until 1.15.0 this file carried

       body #sidebar_slideout_container { top: var(--fydp-tab-top, 250px) !important }

   at (1,0,1) specifically to beat ten (1,0,0)+!important `top` rules in
   style.css whose relative source order RUCSS could reorder — the pink-border
   saga (rev 1.7.0) turned on exactly that hazard. Those ten rules are gone.
   style.css now declares `top` once, from a custom property, with no
   !important. There is nothing left to out-specify, so publishing a plain
   custom property is both sufficient and honest about ownership.

   WHY THE PANEL NO LONGER FOLLOWS THIS NUMBER   (changed in 1.15.0)
   -------------------------------------------
   This value exists for exactly one purpose: pushing the TAB clear of the
   pinned .fydp-shead. The panel is a fixed overlay that only exists while open
   and has nothing to clear. Because this stylesheet is enqueued only where the
   shortcode renders, letting one `top` drive both meant the panel opened ~156px
   lower on / than on /boat-model/… — a page-dependence that had no cause other
   than the two being coupled. style.css anchors the panel independently and
   lets only the tab read this number.

   WHAT IS NOT HERE ANY MORE
   -------------------------
   From 1.7.x:  --fydp-shead-h · --fydp-slideout-shift · .fydp-sec-inview ·
                .fydp-slideout-offset · translate: 0 var(...) · the transition
                restatement · the prefers-reduced-motion companion
   From 1.8.0:  z-index:100 on .fydp-shead · body.fydp-slideout-open · the
                stand-down rule · the JS MutationObserver state mirror
   From 1.15.0: `top` on #sidebar_slideout_container · `max-height` and
                `overflow-y` on #sidebar_slideout_content · the !important that
                carried them
   No JavaScript in this component touches the slideout in any way.
   ============================================================================= */
@media (max-width: 1024px) and (orientation: portrait) {

	/* THE one tunable, and now the ONLY thing this file publishes to the
	   slideout. See the rev-1.9.0 note at the top of this file for the sweep it
	   was sized from and the console check that confirms it.

	   Declared on <body> — not on #sidebar_slideout_container — because it is a
	   fact about THIS PAGE ("a pinned header occupies the top 240px here"), not a
	   property of the slideout. style.css reads it with a fallback, so pages that
	   do not render this component are unaffected:

	       --slideout-tab-top: calc(var(--fydp-tab-top, 10dvh) + 10px)

	   Do not add `top`, `max-height`, `overflow`, `overflow-y` or `margin` for
	   #sidebar_slideout_container / _content / _scroll below. Those belong to
	   style.css §SLIDEOUT and having them in two files is what rev 1.15.0
	   removed. */
	body {
		--fydp-tab-top: 240px;
	}
}
