/* 
 * Shared CSS for New Design (ND) Components
 * Typography: Assistant
 * Primary Colors: #1935CA (Blue), #E8841A (Orange), #D32F2F (Red)
 */

:root {
    /* Brand Colors */
    --nd-color-primary-blue: #1935CA;
    --nd-color-accent-orange: #E8841A;
    --nd-color-cta-red: #D32F2F;
    --nd-color-white: #FFFFFF;
    --nd-color-black: #000000;
    --nd-color-text-dark: #252525;
    --nd-color-text-light: #5A5A5A;
    --nd-color-bg-light: #F7F7F7;

    /* Typography Settings */
    --nd-font-family: 'Assistant', sans-serif;
    
    --nd-h1-desktop: 48px;
    --nd-h1-mobile: 28px;
    
    --nd-h2-desktop: 36px;
    --nd-h2-mobile: 24px;
    
    --nd-h3-desktop: 24px;
    --nd-h3-mobile: 18px;
    
    --nd-body-desktop: 16px;
    --nd-body-mobile: 14px;
    
    --nd-counter-desktop: 60px;
    --nd-counter-mobile: 36px;
}

/* Base resets within component wrappers */
.nd-component-wrapper {
    font-family: var(--nd-font-family);
    color: var(--nd-color-text-dark);
    direction: rtl; /* Enforce RTL as default for ND components */
    text-align: right;
    box-sizing: border-box;
}

.nd-component-wrapper * {
    box-sizing: inherit;
}

/* Typography Utility Classes */
.nd-title-h1 {
    font-size: var(--nd-h1-desktop);
    font-weight: 700;
    color: var(--nd-color-primary-blue);
    margin: 0 0 20px 0;
}

.nd-title-h2 {
    font-size: var(--nd-h2-desktop);
    font-weight: 700;
    color: var(--nd-color-primary-blue);
    margin: 0 0 16px 0;
}

.nd-title-h3 {
    font-size: var(--nd-h3-desktop);
    font-weight: 700;
    color: var(--nd-color-primary-blue);
    margin: 0 0 12px 0;
}

.nd-body-text {
    font-size: var(--nd-body-desktop);
    font-weight: 400;
    line-height: 1.5;
    margin: 0 0 16px 0;
}

/* Button Utility Classes */
.nd-btn {
    display: inline-block;
    padding: 12px 24px;
    font-size: 16px;
    font-weight: 500;
    text-decoration: none;
    border-radius: 4px;
    transition: all 0.3s ease;
    cursor: pointer;
    text-align: center;
}

.nd-btn-primary {
    background-color: var(--nd-color-primary-blue);
    color: var(--nd-color-white);
    border: 2px solid var(--nd-color-primary-blue);
}

.nd-btn-primary:hover {
    background-color: transparent;
    color: var(--nd-color-primary-blue);
}

.nd-btn-red {
    background-color: var(--nd-color-cta-red);
    color: var(--nd-color-white);
    border: 2px solid var(--nd-color-cta-red);
}

.nd-btn-red:hover {
    background-color: transparent;
    color: var(--nd-color-cta-red);
}

/* Responsive Overrides */
@media screen and (max-width: 991px) {
    .nd-title-h1 { font-size: var(--nd-h1-mobile); }
    .nd-title-h2 { font-size: var(--nd-h2-mobile); }
    .nd-title-h3 { font-size: var(--nd-h3-mobile); }
    .nd-body-text { font-size: var(--nd-body-mobile); }
    .nd-btn { font-size: 14px; padding: 10px 20px; }
}

/* Standard-desktop scale compensation (client-reported, 2026-07-26).
   Every component's desktop CSS is built to Figma's exact 1920px frame
   (fixed max-widths like 1860px/1512px/1440px, fixed-px flex children),
   and the only other breakpoints are tablet (<=991px) and mobile
   (<=767px) -- there's no tier at all for real desktop/laptop viewports
   narrower than ~1920px (1366/1440/1536/1600px are all common and get
   zero adaptation). At 100% browser zoom on those screens the fixed-
   width content doesn't fit: no side margins, everything reads oversized,
   hero overflows the fold. Client confirmed 80% browser zoom already
   looks correct, which is the actual mechanism (it enlarges the
   effective CSS-pixel viewport back toward ~1920px) -- so this
   compensates the same way, automatically, for every screen in that gap.
   `zoom` (not `transform: scale`) is used deliberately: it reflows layout
   like real browser zoom rather than just repainting, and unlike
   `transform` it does not create a new containing block for
   `position: fixed` descendants -- important since the sticky header is
   `position: fixed` + JS-driven (see nd-shared.js). This is a targeted
   compensation patch, not a fluid redesign of every component. */
@media screen and (min-width: 992px) and (max-width: 1919px) {
    body { zoom: 0.8; }
}

/* --- ND Hero Carousel Overhaul --- */
/* This section had a batch of orphaned selectors (.slick-active x2,
   .variant-white, .nd-hero-carousel-container) with no {} body left over
   from a past edit, plus two stray standalone } braces with nothing to
   close. Individually near-harmless-looking, but the stray/dangling
   fragments desynced the CSS parser's brace balance for the rest of the
   file: confirmed live that the browser's parsed CSSOM (document.styleSheets)
   was silently missing .nd-content-marker's own rule (further down this
   file) even though the raw deployed file still contained it as valid-
   looking text -- the parser had already lost its place before reaching
   it. Removed the dead fragments; every rule that still had real
   declarations (.nd-hero-content-inner, .nd-hero-title, etc., and the
   .variant-blue/.variant-white *descendant* selectors just below, which
   are real complete rules, not dangling) is unchanged below. */
.nd-hero-content-inner { display: flex; flex-direction: column; align-items: center; gap: 20px; text-align: center; max-width: 491px; padding: 40px; position: relative; z-index: 2; }

.nd-hero-title { font-size: 60px; font-weight: 400; line-height: 1.2; margin: 0; font-family: 'Assistant', sans-serif; }
.variant-blue .nd-hero-title, .variant-blue .nd-hero-subtitle { color: #ffffff; }
.variant-white .nd-hero-title, .variant-white .nd-hero-subtitle { color: #000000; }

.nd-hero-subtitle { font-size: 24px; font-weight: 400; line-height: 1.4; margin: 0; font-family: 'Assistant', sans-serif; }

.nd-hero-buttons { display: flex; gap: 12px; margin-top: 10px; }

.nd-btn-solid-white { background-color: #ffffff; color: var(--nd-color-primary-blue); font-size: 20px; font-weight: 700; padding: 16px 32px; border-radius: 12px; text-decoration: none; display: inline-block; transition: all 0.3s ease; }
/* color pinned explicitly on :hover -- the parent theme's legacy generic
   `a:hover { color: ... }` rule (jafi/css/style_dynamic_ms_id_1.css) has
   higher specificity (element+pseudo-class) than this button's own BASE
   (non-hover) single-class color rule, so without an explicit :hover color
   here the legacy rule silently won on hover, turning the text blue/teal
   instead of staying its intended color. Confirmed live. */
.nd-btn-solid-white:hover { color: var(--nd-color-primary-blue); transform: translateY(-3px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); }

.nd-btn-solid-blue { background-color: var(--nd-color-primary-blue); color: #ffffff; font-size: 16px; font-weight: 700; padding: 16px 32px; border-radius: 12px; text-decoration: none; display: inline-block; transition: all 0.3s ease; }
.nd-btn-solid-blue:hover { color: #ffffff; opacity: 0.9; transform: translateY(-3px); }

.nd-btn-outline-blue { background-color: transparent; border: 1px solid var(--nd-color-primary-blue); color: var(--nd-color-primary-blue); font-size: 16px; font-weight: 700; padding: 16px 32px; border-radius: 12px; text-decoration: none; display: inline-block; transition: all 0.3s ease; }
.nd-btn-outline-blue:hover { color: var(--nd-color-primary-blue); background-color: rgba(0,42,186,0.05); transform: translateY(-3px); }

/* Pairs with .nd-btn-solid-white the same way .nd-btn-outline-blue pairs
   with .nd-btn-solid-blue -- needed once Hero Carousel V2's secondary CTA
   stopped being locked to the white variant only (client-reported: no way
   to add a 2nd button on blue-variant slides). White border/text reads on
   the blue box the same way the blue outline reads on a white box. */
.nd-btn-outline-white { background-color: transparent; border: 1px solid #ffffff; color: #ffffff; font-size: 16px; font-weight: 700; padding: 16px 32px; border-radius: 12px; text-decoration: none; display: inline-block; transition: all 0.3s ease; }
.nd-btn-outline-white:hover { color: #ffffff; background-color: rgba(255,255,255,0.12); transform: translateY(-3px); }

@media screen and (max-width: 991px) {
    .nd-hero-content-box svg { display: none !important; }
    .nd-hero-title { font-size: 40px; }
    .nd-hero-subtitle { font-size: 18px; }
}

/* --- ND Content Marker (Phase 3 redesign) ---
   Was two independent flex columns (orange stat block + white description
   block) on a solid blue section background. Figma (node 608:3709,
   fileKey nHzxgbwDah4lMBIYGp4oN3) shows a single flowing, centered
   paragraph instead: plain text, a highlighted middle segment rendered as
   white text on a blue rounded-pill background, then more plain text. */
/* Vertical padding is 70px, not the 100px originally measured off Figma
   (node 808:1396's consistent inter-section gap) — Niri confirmed the gap
   live once the parser-desync bug hiding this rule was fixed, and 100px
   read as too big in practice. Horizontal 20px is unrelated and untouched. */
.nd-content-marker { padding: 70px 20px; }
/* render_html()'s own top-level div reuses "nd-content-marker" as its
   class too (same collision as Damka/Puzzle/etc.), nested directly inside
   the auto-generated .nd-component-wrapper.nd-content-marker outer div --
   the bare rule above applies to BOTH, doubling the padding on every side.
   Niri originally flagged only the vertical doubling as excess on mobile
   (140px top/bottom), so horizontal was left alone at the time ("unrelated
   and untouched" above) -- but the client has now separately flagged the
   text block itself as narrower than the component below it on mobile,
   and live measurement confirms the same doubling on the horizontal axis
   too: .nd-content-marker-line starts at 40px from the screen edge (both
   wrappers' 20px stacking) while ND 5 Content Promo's own card starts at
   20px (no such collision there). Zeroing all four sides here, not just
   top/bottom, leaves the inner div's own single 20px/70px intact and
   matches the 5 Content Promo card's actual left/right inset. Desktop is
   untouched, since only mobile was flagged either time. */
@media screen and (max-width: 767px) {
    .nd-component-wrapper.nd-content-marker { padding: 0; }
}
.nd-content-marker-line {
    /* 1457px, not 1300px: Figma node 608:3709's own paragraph is measured
       at exactly this width — Niri confirmed live the text reads too
       narrow/wraps too early at 1300px. */
    max-width: 1457px;
    margin: 0 auto;
    text-align: center;
    font-family: 'Assistant', sans-serif;
    font-size: 40px;
    font-weight: 400;
    /* 1.35, not Figma's own 1.2: client-requested a slightly larger gap
       between wrapped lines (2026-07-27) -- this is a deliberate deviation
       from Figma's measured value, not a re-derivation of it. The
       .nd-cm-highlight pill below is sized off this same value (padding
       calculated against 1.2's resulting 48px line box), so its pill is
       now slightly taller than Figma's exact 58px measurement as a direct
       consequence -- expected, not a separate bug, since the highlight
       pill is meant to track whatever the line height actually is. */
    line-height: 1.35;
    color: #000000;
}
/* box-decoration-break:clone gives each wrapped line of the highlighted
   span its own independent rounded pill (matches Figma's two separate
   rounded-rect backgrounds behind the two wrapped lines of that span,
   without hand-computing per-line rect positions). */
.nd-cm-highlight {
    /* #002ABA, not var(--nd-color-primary-blue) (#1935CA) — Figma node
       608:3709 confirms this exact blue, matching the Hero Carousel V2's
       blue variant (also #002ABA), not the site's generic blue variable. */
    background-color: #002ABA;
    color: #ffffff;
    /* 999px, not a hand-computed radius: guarantees a full pill cap at
       any box height, so it can't under-shoot again the way the original
       32px did once the box grew past 64px tall. */
    border-radius: 999px;
    /* 5px vertical, not 10px: Figma's own highlight rects measure 58px
       tall against 40px text. line-height:1.2 above (48px) + 5px top/bottom
       (10px total) = 58px, an exact match — the previous 10px padding on
       top of a taller 1.5 line-height (80px total) overshot Figma's
       actual proportions, not just its border-radius. Horizontal padding
       (24px) is unchanged: Figma doesn't expose a clean way to isolate
       per-span text width to verify it precisely, flagged as a judgment
       call rather than a measured value. */
    padding: 5px 24px;
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}
/* Sequential reveal (opening -> highlighted -> closing), Phase 3c item 3.
   .nd-fade-in-group on the paragraph triggers all three spans together
   via the shared IntersectionObserver in nd-shared.js; each span's own
   transition-delay staggers them into a reading-order sequence. Note:
   CSS transforms don't apply to plain `display:inline` boxes (spec
   behavior, not a bug) — .nd-cm-highlight in particular can't become
   inline-block without breaking the per-line-pill effect above, so all
   three spans only animate opacity here, not .nd-fade-in's translateY
   slide. Consistent across all three rather than sliding two and not the
   third.
   A right-to-left clip-path wipe was tried here (per a later request) and
   reverted: confirmed live (not just in theory) that clip-path on a
   wrapped multi-line `display:inline` element paints incorrectly in
   Chromium — text got silently truncated even at rest with the fully-
   revealed inset(0 0 0 0%) value forced with no transition at all, so
   it's not a stuck-mid-animation issue, the reference box computation
   itself breaks across wrapped inline fragments. Reproduced on both the
   box-decoration-break:clone highlight AND the plain closing span once it
   wraps, so it's not specific to the pill technique either. A real
   right-to-left wipe across wrapped lines needs a JS-based per-line-split
   approach instead of a pure CSS one — flagged as a follow-up, not
   attempted here to avoid shipping the confirmed-broken clip-path version. */
.nd-cm-opening.nd-fade-in { transition-delay: 0ms; }
.nd-cm-highlight.nd-fade-in { transition-delay: 200ms; }
.nd-cm-closing.nd-fade-in { transition-delay: 400ms; }
@media screen and (max-width: 991px) {
    .nd-content-marker-line { font-size: 28px; }
    .nd-cm-highlight { padding: 3px 16px; border-radius: 999px; }
}
/* 767px (not 600px): matches this pass's phone breakpoint and Figma's own
   393px mobile frame (node 409:12828), which measures the line at 20px. */
@media screen and (max-width: 767px) {
    .nd-content-marker-line { font-size: 20px; }
}

/* --- ND Damka + ND Damka Radio ---
   Shared CTA button (.nd-dr-cta): background/label colors come from each
   component's own cta_color/text_color inline style, confirmed against
   Figma nodes 366:5948 and 839:7311 (both show a solid-fill pill button,
   #002ABA background + white label, NOT the card's title/description
   color — those keep their own existing .nd-title-h2/.nd-body-text
   styling untouched). */
/* Sized to Figma node 1027:1657 (1920px full frame, 30px inset each side ->
   1860px max-width): text column 898px, image 867px, remainder as the gap.
   flex:0 1/0 0 (not flex:1/1) so the image holds its measured size instead
   of stretching to fill whatever the old, too-narrow 1200px container left
   over -- that's what made the image render undersized before. */
.nd-damka-inner { display: flex; gap: 60px; align-items: center; justify-content: space-between; max-width: 1860px; margin: 0 auto; padding: 60px 20px; }
/* In an RTL flex row (this whole wrapper is `direction: rtl` via
   .nd-component-wrapper) the FIRST DOM child (.nd-damka-content) sits at
   the visual right by default, and row-reverse swaps that -- so the
   default 'image-right' state (no reverse) was actually rendering the
   image on the left, and 'image-left' (reversed) put it on the right.
   Confirmed live/reported by Niri. Reverse now applies to 'image-right'
   instead, which is what actually moves the (DOM-second) image to the
   visual right. */
/* min-width:992px gate (not unconditional): confirmed live on Product Page
   that this 2-class rule was beating the 991px block's plain
   ".nd-damka-inner { flex-direction: column-reverse }" on specificity
   alone regardless of source order, keeping image-right instances in row
   layout on phone. With flex-direction stuck at row, .nd-damka-image's
   width (an auto flex-basis, sized only by its own width:100% img child --
   a circular reference with no other constraint) collapsed to 0, which is
   why the image wasn't rendering at all on mobile, not a visibility/
   animation bug. */
@media screen and (min-width: 992px) {
    .nd-damka-image-right .nd-damka-inner { flex-direction: row-reverse; }
}
/* display:flex + a real gap (not individual child margins) -- .nd-title-h2/
   .nd-body-text were both explicitly zeroed out for this component (see
   below) leaving only .nd-dr-cta's own 16px margin-top between description
   and button, and NO gap at all between title and description. Figma
   (node 839:10096) shows a uniform 28px between all three regardless of
   viewport -- confirmed against the mobile node specifically, but nothing
   about a 0px title-to-description gap could have been correct at any
   width, so this is fixed unconditionally, not mobile-scoped. */
/* align-items:flex-start (not the flex column default of stretch): without
   it, the CTA button's own inline-block sizing was overridden by the flex
   item default cross-axis stretch, expanding it to the container's full
   width instead of hugging its own text like Figma shows -- confirmed live
   this is exactly what broke it, and that flex-start (not flex-end) is
   what actually keeps it pinned to the visual right under this wrapper's
   direction:rtl (flex-start = inline-start = right in RTL). */
.nd-damka-content { flex: 0 1 898px; display: flex; flex-direction: column; align-items: flex-start; gap: 28px; }
.nd-damka-content .nd-dr-cta { margin-top: 0; }
/* Client-requested (2026-07-27): a second CTA button. .nd-damka-content
   is a flex COLUMN, so the two buttons (both plain .nd-dr-cta children)
   need their own row wrapper to sit side by side instead of stacking --
   same pattern as ND Content Editor's own .nd-ce-buttons. */
.nd-damka-buttons { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.nd-damka-image { flex: 0 0 867px; max-width: 100%; }
/* Client-requested (2026-07-27) editor toggle, desktop only ("mobile
   stays similar" per the client's own wording -- deliberately no
   matching rule in the 991px/767px blocks below). Halves the image's own
   flex-basis.
   justify-content:center on .nd-damka-inner, not flex:1 1 0 on
   .nd-damka-content (2026-08-03, client-reported the image was still
   landing far from the text): the original fix made content GROW to
   fill the width the image gave up, but content's own text still hugs
   one side of that now-much-wider box (align-items:flex-start) -- the
   box grew, the visible text didn't, so the gap just moved from
   "next to the image" to "inside the content column, between the text
   and the image" instead of actually closing. Keeping content at its
   normal fixed 898px basis and centering the whole (now-narrower) pair
   within the row instead puts any leftover width outside the pair, on
   both edges, so image and text stay exactly 60px apart (the row's own
   gap) regardless of how much the image shrank. */
@media screen and (min-width: 992px) {
    .nd-damka-small-image .nd-damka-image { flex: 0 0 433px; }
    .nd-damka-small-image .nd-damka-inner { justify-content: center; }
}
.nd-damka-image img, .nd-damka-image .nd-media-img { width: 100%; aspect-ratio: 867 / 722; object-fit: cover; border-radius: 32px; box-shadow: 0 0 20px rgba(161,161,161,0.25); display: block; }
.nd-damka-image video, .nd-damka-image .nd-media-embed { width: 100%; aspect-ratio: 867 / 722; border-radius: 32px; box-shadow: 0 0 20px rgba(161,161,161,0.25); overflow: hidden; }
.nd-damka-image .nd-media-embed iframe { width: 100%; height: 100%; border: 0; }
/* line-height:1.05, not the shared .nd-title-h2 default (browser
   "normal", ~1.2) -- client-requested (2026-08-03) tighter spacing
   between a wrapped title's own lines. Same value/reasoning already used
   for ND Puzzle's CTA title. The 28px gap to .nd-body-text right below
   (.nd-damka-content's own flex gap) is unchanged -- confirmed against
   Figma node 839:7311 (title box height 78 ends exactly 28px before the
   description starts), so that part was already Figma-accurate. */
.nd-damka-content .nd-title-h2 { font-size: 60px; font-weight: 700; color: #002ABA; line-height: 1.05; margin: 0; }
/* font-family set explicitly (not left to inherit from .nd-component-wrapper):
   confirmed live the parent theme has a direct rule targeting bare <p> tags
   with its own legacy font (Trade Gothic) that wins over an inherited value
   regardless of specificity -- same reason every other component's own
   text classes in this file (.nd-hub-card-desc, .nd-testimonial-desc, etc.)
   all set this explicitly instead of relying on inheritance. */
.nd-damka-content .nd-body-text { font-family: 'Assistant', sans-serif; font-size: 20px; font-weight: 400; color: #000000; margin: 0; line-height: normal; max-width: 703px; }
@media screen and (max-width: 991px) {
    .nd-damka-inner { flex-direction: column-reverse; padding-bottom: 0; }
    .nd-damka-content, .nd-damka-image { flex: 0 0 auto; max-width: 100%; }
    .nd-damka-content .nd-title-h2 { font-size: var(--nd-h2-mobile); }
    .nd-damka-content .nd-body-text { font-size: 18px; max-width: 100%; }
}
/* Phone (node 839:10096): 28px/16px, not the 991px tablet block's
   24px/18px. */
@media screen and (max-width: 767px) {
    .nd-damka-content .nd-title-h2 { font-size: 28px; }
    .nd-damka-content .nd-body-text { font-size: 16px; }
}

/* Compound selector (not bare .nd-damka-radio): render_html()'s own
   top-level div reuses "nd-damka-radio" as its class too, so it's nested
   directly inside the auto-generated .nd-component-wrapper.nd-damka-radio
   outer div -- both carry the same class name. A bare .nd-damka-radio
   rule would apply to both; padding specifically must only land on the
   true outer wrapper (the compound match) or the card would get double
   side-inset instead of a single 20px gutter from the viewport edge. */
.nd-component-wrapper.nd-damka-radio { margin-top: 70px; padding: 0 20px; }

/* ND Damka Radio's card IS .nd-dr-inner (bg/radius/shadow live directly on
   the flex row, no extra wrapper div needed) — sized to Figma node
   366:5948: 1860px max width, 31px top/bottom inset (31 + 543 image height
   + 31 = 605px card height), text column held near Figma's 537px measure
   and centered, image locked to Figma's 1002x543 ratio via aspect-ratio +
   object-fit so the surrounding flex box can't stretch/distort it. */
.nd-dr-inner {
    display: flex; flex-direction: row-reverse; align-items: center; justify-content: space-between;
    gap: 60px; max-width: 1860px; margin: 0 auto; background: #fff; border-radius: 32px;
    box-shadow: 0 0 20px rgba(161,161,161,0.25); padding: 31px 60px; overflow: hidden;
}
/* flex:1 1 0 on both this and .nd-dr-media below (not the old fixed
   537px/1002px bases) -- client-reported (2026-07-27) that below 1920px
   the image was taking up most of the card, and the math confirms why:
   .nd-dr-media's flex-shrink:0 meant it never gave up any of its fixed
   1002px even as the container narrowed, so it grew from ~65% of the row
   (Figma's own 1002:537 ratio, at the reference 1920px width) toward
   nearly the whole row at anything narrower. Equal flex-grow with
   min-width:0 (needed for either side to actually shrink below its own
   content's automatic minimum -- same flex gotcha documented elsewhere
   in this file) locks both sides to a genuine 50/50 split at every
   desktop width, per the client's explicit "cap it at half" ask. */
.nd-dr-content { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 20px; text-align: center; }
.nd-dr-content .nd-title-h2 { font-size: 60px; font-weight: 700; color: #000000; text-align: center; margin: 0; }
/* font-family added explicitly -- client-reported (2026-07-27) this text
   rendered in a different font from the title above it. It was silently
   falling through to whatever the ambient/legacy cascade provides
   instead of Assistant -- every other component's own text rule in this
   file sets font-family explicitly rather than relying on inheritance
   for exactly this reason (a more specific legacy selector further up
   the cascade can win over a plain inherited value); this rule was just
   missing it. */
.nd-dr-content .nd-body-text { font-family: 'Assistant', sans-serif; font-size: 24px; font-weight: 400; color: #000000; text-align: center; margin: 0; line-height: 1.3; }
.nd-dr-media { flex: 1 1 0; min-width: 0; max-width: 100%; }
.nd-dr-media .nd-media-img, .nd-dr-media video { width: 100%; aspect-ratio: 1002 / 543; object-fit: cover; border-radius: 32px; box-shadow: 0 0 16.5px rgba(109,109,109,0.25); display: block; }
.nd-dr-media .nd-media-embed { width: 100%; aspect-ratio: 1002 / 543; border-radius: 32px; box-shadow: 0 0 16.5px rgba(109,109,109,0.25); overflow: hidden; }
.nd-dr-media .nd-media-embed iframe { width: 100%; height: 100%; border: 0; }

/* Reveal animation: Figma's two static frames for this node show the image
   starting shifted toward the text column (covering it), then settling
   into its resting slot — read as a slide-to-reveal, not a fade, so the
   image keeps full opacity throughout and only its position animates.
   Percentage transform (not px) keeps the shift proportional to the
   image's own rendered width at any viewport size. Desktop only: below
   992px the layout stacks vertically (image above/below text rather than
   beside it), where a horizontal cover-slide no longer reads correctly,
   so mobile falls back to the standard vertical .nd-fade-in instead. */
@media screen and (min-width: 992px) {
    .nd-dr-media.nd-fade-in { opacity: 1; transform: translateX(-79%); transition: transform 0.8s ease-out; }
    .nd-dr-media.nd-fade-in.is-visible { transform: translateX(0); }
}

@media screen and (max-width: 991px) {
    .nd-dr-inner { flex-direction: column-reverse; padding: 24px; gap: 24px; border-radius: 24px; }
    .nd-dr-content { flex: 0 1 auto; max-width: 100%; }
    .nd-dr-content .nd-title-h2 { font-size: var(--nd-h2-mobile); }
    .nd-dr-content .nd-body-text { font-size: 18px; }
    .nd-dr-media { flex: 0 0 auto; width: 100%; }
}
/* Phone (node 829:2475): 28px/16px, not the 991px tablet block's
   24px/18px. */
@media screen and (max-width: 767px) {
    .nd-dr-content .nd-title-h2 { font-size: 28px; }
    .nd-dr-content .nd-body-text { font-size: 16px; }
}

.nd-dr-cta {
    display: inline-block; text-decoration: none; font-weight: 700; font-size: 20px;
    padding: 16px 32px; border-radius: 12px; margin-top: 16px; transition: opacity 0.3s ease;
}
.nd-dr-cta:hover { opacity: 0.85; }
/* Phone (node 839:10101 and equivalents on ND Content Editor/Damka Radio,
   all sharing this one button): 16px, not the desktop 20px -- had no
   mobile override at all until now, found while auditing Product Page's
   own 3 Damka instances against Figma. */
@media screen and (max-width: 767px) {
    .nd-dr-cta { font-size: 16px; }
}

/* Staggered reveal, same idiom as Content Marker's opening/highlight/closing
   spans: .nd-fade-in-group on the outer wrapper triggers every .nd-fade-in
   descendant together via the shared IntersectionObserver in nd-shared.js,
   each one's own transition-delay sequences them (text first, media
   slightly after). */
.nd-damka-content.nd-fade-in { transition-delay: 0ms; }
.nd-damka-image.nd-fade-in { transition-delay: 200ms; }

/* Directional variants for ND Damka's image block only (its layout toggle
   actually has two sides to mirror; Damka Radio has no toggle, so its
   media block just uses the standard vertical .nd-fade-in). Combined with
   .nd-fade-in (for the shared is-visible toggle), not a replacement for it. */
.nd-fade-in.nd-fade-in-dir-right { transform: translateX(30px); }
.nd-fade-in.nd-fade-in-dir-right.is-visible { transform: translateX(0); }
.nd-fade-in.nd-fade-in-dir-left { transform: translateX(-30px); }
.nd-fade-in.nd-fade-in-dir-left.is-visible { transform: translateX(0); }

/* --- ND Power Counter Numbers ---
   --nd-pc-color is set inline per-instance (editor colorpicker) and drives
   the number, label, AND description together, per the brief's explicit
   field spec. Note for future reference: Figma's own final/resting frame
   for this component (node 366:5224, "Property 1=Frame 62" — the actual
   end state; node 808:1403 / "Frame 61" is Figma's own digit-strip mockup
   of the count-up motion, not a real state) hardcodes the description as
   plain black instead of the shared orange, which conflicts with the
   brief's explicit "one color controls number + label + description"
   instruction. Implemented per the brief's explicit text (single shared
   color) since it reads as a deliberate field-behavior decision, not just
   a loose description of the screenshot — flagged in the build report
   rather than silently overridden. Number and its unit label ARE the same
   large bold size in both Figma frames (one 60px/bold/tight-tracking
   block, label stacked directly under the number with no visible gap);
   the description's 24px/regular-weight sizing below is matched from the
   real Frame 62 state. */
.nd-power-counter { margin-top: 70px; padding: 0 20px; }
.nd-pc-title { font-size: var(--nd-h2-desktop); font-weight: 700; color: var(--nd-color-primary-blue); text-align: center; margin: 0 0 32px 0; }
.nd-pc-grid { display: flex; justify-content: space-between; align-items: center; max-width: 1860px; width: 100%; margin: 0 auto; padding: 40px 100px; }
/* Client-requested (2026-07-27): with only 2-3 real items, the base
   rule's justify-content:space-between pushes them to the far edges of
   the 1860px container with one huge gap between, instead of reading as
   evenly spaced. :not(:has(nth-of-type(4))) matches only when a real 4th
   .nd-pc-item ISN'T present (collect_item() skips empty/hidden items
   entirely, so this reflects the real rendered count, not just a CSS
   guess) -- the untouched base rule above still governs the real 4-item
   case, matching Figma's own edge-to-edge tuning for that state. Mirrors
   ND 5 Content Promo's own unconditional center+gap approach, just
   scoped here to the fewer-items case specifically rather than applied
   unconditionally, since Power Counter's 4-item state was already
   correct and center+gap alone would have pulled it in from the edges. */
.nd-pc-grid:not(:has(.nd-pc-item:nth-of-type(4))) { justify-content: center; gap: 100px; }
.nd-pc-item { text-align: center; max-width: 260px; }
.nd-pc-num, .nd-pc-text { font-size: var(--nd-counter-desktop); font-weight: 700; letter-spacing: -2px; text-transform: uppercase; line-height: 1; color: var(--nd-pc-color); }
.nd-pc-desc { font-size: 24px; font-weight: 400; line-height: 1.3; margin-top: 24px; color: var(--nd-pc-color); }
@media screen and (max-width: 991px) {
    .nd-pc-grid { flex-wrap: wrap; justify-content: center; gap: 40px; padding: 40px 24px; }
    .nd-pc-num, .nd-pc-text { font-size: var(--nd-counter-mobile); letter-spacing: -1px; }
    .nd-pc-desc { font-size: 18px; }
}
/* Phone (node 409:14677): a real 2x2 grid, not a wrapped/centered row --
   Figma measures the gap itself (8px columns, 23px rows), not just a
   smaller font size on the same layout. */
@media screen and (max-width: 767px) {
    .nd-pc-grid { display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 8px; row-gap: 23px; padding: 24px 16px; }
    .nd-pc-item { max-width: none; }
    .nd-pc-num, .nd-pc-text { font-size: 40px; letter-spacing: -1.33px; }
    /* min-height reserves 3 lines' worth (16px * 1.3 line-height * 3),
       even when a given item's real description only wraps to 1-2 lines
       -- client-requested (2026-07-27) so every item in the 2x2 grid
       above stays the same height and aligned with its row-mate instead
       of each row's height collapsing to its shortest description. */
    .nd-pc-desc { font-size: 16px; margin-top: 12px; min-height: 62.4px; }
}

/* --- ND Form Section (JoinUs) --- */
/* Rebuilt per Figma 839:7362 (fileKey nHzxgbwDah4lMBIYGp4oN3): a single
   stacked, centered column inside one blue rounded card (heading +
   subtitle above, form fields below) -- NOT the old 2-column side-by-side
   flex this component shipped with previously. That old shape only ever
   got responsive polish applied to it during mobile QA; it was never
   actually rebuilt against the real design until this pass.

   Compound selector (not bare .nd-form-section): render_html()'s own
   top-level div reuses "nd-form-section" as its class too, nested
   directly inside the auto-generated .nd-component-wrapper.nd-form-section
   outer div -- same collision as Damka Radio/Puzzle/Testimonial elsewhere
   in this file. A bare .nd-form-section rule would apply the margin to
   both. */
/* 0 20px: the section-level gutter between the card and the viewport/
   container edge -- separate from the card's OWN 174px internal padding
   below. QA flagged this as still missing (the card was going fully
   edge-to-edge with no outer margin at all), matching the same
   `.nd-component-wrapper` side-padding convention already used by other
   full-bleed components in this file (e.g. .nd-accordion). */
/* Full reset, not just margin/padding: the OUTER wrapper carries the same
   bare "nd-form-section" class as the INNER card div (render_html()'s own
   top-level element), so it was ALSO picking up background:#002ABA,
   border-radius:32px, display:flex, max-width:1860px and margin:0 auto
   from the bare .nd-form-section rule below. Since the wrapper's blue
   background painted identically behind/around the (also blue) inner
   card, the 20px gutter existed structurally but was visually invisible
   -- both boxes were the same color, so nothing looked inset. This is
   the exact collision the comment above has warned about since the first
   pass on this component; this time it bit more than just margin. */
.nd-component-wrapper.nd-form-section {
    margin-top: 70px; padding: 0 20px;
    background: transparent; border-radius: 0; box-shadow: none;
    display: block; max-width: none; margin-left: 0; margin-right: 0;
}
/* Exact values as given directly from Figma's own inspector panel for
   node 839:7362's outer container: display:flex, width:1860px,
   padding:80px 174px, flex-direction:column, justify-content:center,
   align-items:center, gap:40px, radius:32px, bg:#002ABA. width:1860px is
   treated as max-width (not a literal fixed width) since this card sits
   in a real responsive page, not Figma's fixed canvas -- centered via
   margin:0 auto once the viewport exceeds that width. */
.nd-form-section {
    position: relative; overflow: hidden; box-sizing: border-box;
    display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 40px;
    width: 100%; max-width: 1860px; margin: 0 auto;
    background-color: #002ABA; padding: 80px 174px; border-radius: 32px;
}
.nd-fs-pattern { position: absolute; inset: -52.66% 72.46% -27.13% -15.27%; pointer-events: none; }
.nd-fs-pattern svg { width: 100%; height: 100%; display: block; }
/* This wrapper is now just a centered content column inside the flex
   card above (its own display:flex/gap were removed -- the 40px gap
   Figma specifies lives on the OUTER container per the values above,
   between this whole block and the row; the 20px between the heading and
   subtitle themselves stays on .nd-fs-heading, that's a separate, deeper
   layer of the same design). */
.nd-form-section-inner { position: relative; z-index: 1; width: 100%; max-width: 1512px; margin: 0 auto; text-align: center; }
.nd-fs-heading { display: flex; flex-direction: column; gap: 8px; width: 100%; margin: 0 0 40px 0; }
/* letter-spacing/text-transform explicitly reset here: the PARENT theme's
   own global `h2, h3 { letter-spacing:1px; text-transform:uppercase; }`
   rule has nothing of mine to compete with on those two properties (my
   rule never set them), so it was winning by default -- not a specificity
   loss like the submit-button bug, just an omission. Figma specifies
   neither, and the added tracking was visibly changing this text's
   rhythm compared to the design (this is what QA flagged as "typography
   not like Figma"). font-weight is 400 (regular), NOT 700 -- confirmed
   directly from Figma's own type spec; the shared .nd-title-h2 class
   defaults to 700 site-wide, so that has to be overridden back down
   specifically for this heading. */
.nd-fs-heading .nd-title-h2 { font-size: 40px; font-weight: 400; color: #ffffff; margin: 0; letter-spacing: normal; text-transform: none; line-height: normal; }
.nd-fs-heading .nd-body-text { font-size: 24px; font-weight: 400; color: #ffffff; margin: 0; letter-spacing: normal; text-transform: none; line-height: normal; }
/* The actual text sits in a nested <p> (wpb_js_remove_wpautop() wraps the
   raw WPBakery content in one) -- the parent theme has a bare `p { color:
   #4d4d4d }` rule that applies directly to that inner <p>, and an explicit
   rule always wins over inheriting the outer div's white regardless of
   specificity math. This is what QA saw as grey subtitle text. */
.nd-fs-heading .nd-body-text p { color: #ffffff; margin: 0; }

/* Fields sit directly on the blue card as translucent bordered boxes --
   confirmed via Figma's own design context (bg rgba(255,255,255,0.1),
   border 1px solid white, radius 12px, white text) -- NOT solid white
   pills, which is what the first pass here built from eyeballing a
   compressed screenshot. Only the submit button is solid white.
   direction:ltr on the row is deliberate: it pins the physical
   left-to-right pixel order (submit, name, phone, email) to match Figma
   exactly regardless of the page's ambient RTL context -- the CF7 form
   itself lists [submit] FIRST in its own tag order for the same reason
   (see setup_cf7.py). Individual inputs get direction:rtl back so Hebrew
   placeholder text still reads/aligns correctly. align-items:center
   guards against any future field-height mismatch (a real intl-tel-input
   plugin bug caused ~50px of misalignment here until that field was
   dropped for a plain validated tel input -- see fix_joinus_phone.py). */
.nd-fs-row { display: flex; align-items: center; direction: ltr; gap: 12px; width: 100%; max-width: 909px; margin: 0 auto; }
/* CF7's own shortcode output wraps everything in <div class="wpcf7"><form>...
   -- display:contents on both collapses those wrappers out of the flex/grid
   flow so the actual <p>/field elements become direct flex (or grid) items
   of the row/grid container itself, instead of one opaque flex item. */
.nd-fs-row .wpcf7, .nd-fs-row form,
.nd-fs-grid .wpcf7, .nd-fs-grid form { display: contents; }
/* Confirmed live: QA's "no sent indicator" report traced to the parent
   theme shipping a blanket, unconditional `.wpcf7-response-output {
   display: none; }` (no state class scoping at all) -- CF7 core's OWN
   stylesheet correctly hides this element only while
   form.init/.resetting/.submitting, meaning it should become visible
   again the instant a real response (sent/failed/invalid) comes back,
   but the theme's rule overrides that outcome permanently regardless of
   state. The two rules below replicate CF7's own intended behavior
   (hidden only during those 3 transient states) at higher specificity
   than the theme's blanket override, scoped to these 3 form containers
   rather than changing the global rule site-wide. flex:1 1 100% +
   order:99 (the acceptance-checkbox trick already used on Newsletter)
   makes it its own full-width banner line below the fields instead of
   squeezing into the row's own flex sizing. Colors adapt per card
   background: white card (.nd-fs-grid) gets the card's own text-dark/
   cta-red; blue cards (.nd-fs-row, .nd-fs-row-narrow) get white/success-
   tinted text so it stays legible against #002ABA. */
/* !important on BOTH the base "show" rule and the state-scoped "hide"
   rule below: confirmed live the parent theme's own `.wpcf7-response-
   output { display: none; }` is ITSELF marked !important, so a normal-
   priority override here (the first deployed attempt at this fix) never
   won regardless of specificity -- !important always beats a normal
   declaration outright, it isn't a specificity contest. Both of mine now
   carry !important too, so the tie is broken by specificity between the
   two (the state-scoped selector below is more specific, so it still
   correctly wins during init/resetting/submitting). */
.nd-fs-row .wpcf7-response-output, .nd-fs-row-narrow .wpcf7-response-output,
.nd-fs-grid .wpcf7-response-output {
    display: block !important; flex: 1 1 100%; order: 99; margin: 8px 0 0 0; padding: 12px 16px;
    border-radius: 8px; font-family: var(--nd-font-family); font-size: 14px; text-align: center;
    direction: rtl; box-sizing: border-box;
}
/* grid-column:1/-1 is required for the .nd-fs-grid case specifically --
   flex:1 1 100%/order:99 above only affects flex layout (.nd-fs-row/
   -narrow); .nd-fs-grid is a CSS Grid, where those two properties do
   nothing. Without an explicit grid-column, this element auto-placed
   into a new 3rd implicit COLUMN (grid-auto-flow:column, both explicit
   columns already full across all 3 rows) sized by grid-auto-columns'
   default of "auto" -- with no room left in the row's 100% width, that
   implicit column rendered stacked on top of/overlapping the existing
   fields instead of appearing below them, which is what QA saw as
   "errors rendered behind the text fields". Spanning the full width
   forces the auto-placement algorithm to open a new ROW instead. */
.nd-fs-grid .wpcf7-response-output { grid-column: 1 / -1; }
.nd-fs-row form.init .wpcf7-response-output, .nd-fs-row form.resetting .wpcf7-response-output, .nd-fs-row form.submitting .wpcf7-response-output,
.nd-fs-row-narrow form.init .wpcf7-response-output, .nd-fs-row-narrow form.resetting .wpcf7-response-output, .nd-fs-row-narrow form.submitting .wpcf7-response-output,
.nd-fs-grid form.init .wpcf7-response-output, .nd-fs-grid form.resetting .wpcf7-response-output, .nd-fs-grid form.submitting .wpcf7-response-output {
    display: none !important;
}
.nd-fs-row .wpcf7-response-output, .nd-fs-row-narrow .wpcf7-response-output { background: rgba(255, 255, 255, 0.15); color: #ffffff; border: 1px solid rgba(255, 255, 255, 0.4); }
.nd-fs-grid .wpcf7-response-output { background: rgba(0, 42, 186, 0.06); color: var(--nd-color-text-dark); border: 1px solid rgba(0, 0, 0, 0.15); }
.nd-fs-row .wpcf7.wpcf7-mail-sent-ok .wpcf7-response-output, .nd-fs-row-narrow .wpcf7.wpcf7-mail-sent-ok .wpcf7-response-output { background: rgba(76, 175, 80, 0.25); border-color: rgba(76, 175, 80, 0.6); }
.nd-fs-grid .wpcf7.wpcf7-mail-sent-ok .wpcf7-response-output { background: rgba(76, 175, 80, 0.12); color: #1b5e20; border-color: rgba(76, 175, 80, 0.5); }
.nd-fs-row .wpcf7-form-control-wrap { flex: 1; min-width: 0; display: block; }
/* position:relative anchors the submit button's own .wpcf7-spinner (now
   position:absolute -- see that rule's comment) to this <p>, not to some
   further-up ancestor. Harmless on the other 3 fields' <p> tags. */
.nd-fs-row p { margin: 0; flex: 1; min-width: 0; position: relative; }
/* The parent theme's own `.wpcf7 form.wpcf7-form p { margin-bottom: 20px; }`
   (specificity 0,2,2) beats the plain rule above (0,1,1) on that one
   longhand property -- fully qualifying the real DOM nesting here
   (.nd-fs-row > .wpcf7 > form.wpcf7-form > p) reaches 0,3,2, which wins.
   Left uncorrected this added 20px under EVERY field/button in the row,
   uniformly, so it didn't show up as relative misalignment between
   fields, only as unwanted extra height inside the card. */
.nd-fs-row .wpcf7 form.wpcf7-form p { margin-bottom: 0; }
.nd-fs-row input[type="text"], .nd-fs-row input[type="email"], .nd-fs-row input[type="tel"] {
    width: 100%; height: 53px; line-height: 53px; box-sizing: border-box; direction: rtl; text-align: right;
    background: rgba(255, 255, 255, 0.1); border: 1px solid #ffffff; border-radius: 12px; padding: 0 16px;
    font-family: var(--nd-font-family); font-size: 16px; color: #ffffff; margin: 0;
}
.nd-fs-row input[type="text"]::placeholder, .nd-fs-row input[type="email"]::placeholder, .nd-fs-row input[type="tel"]::placeholder { color: #ffffff; opacity: 1; }
/* .wpcf7-submit qualifier is deliberate, not decorative: confirmed live
   that the PARENT theme's own stylesheet.min.css ships
   `input.wpcf7-form-control.wpcf7-submit:not([disabled])` at specificity
   (0,3,1), which beats a plain `.nd-fs-row input[type="submit"]` (0,2,1)
   and was silently winning on color (rendering dark grey instead of this
   design's white/blue button) despite loading first in the cascade --
   this wasn't a load-order tie, the parent rule is genuinely more
   specific. Adding .wpcf7-submit here brings this to (0,3,1), tied with
   the parent's rule, and nd-shared.css's later enqueue position (priority
   15 vs. the parent's default) wins that tie. box-sizing/text-align/
   padding/margin are ALSO explicitly reset here for the same reason --
   the parent theme's own .wpcf7-submit rule was supplying its own
   padding (0 23px) and text-align:left, neither of which I had a
   competing declaration for, so they leaked through and were the direct
   cause of the button text sitting left-aligned instead of centered. */
/* line-height:53px (matching height, vertical padding 0) rather than
   height:53px + padding:16px 32px: with box-sizing:border-box the two
   should be equivalent, but this is the same vertical-centering mechanism
   used on the text/email/tel fields above (line-height == box height,
   horizontal-only padding) so all 4 controls in the row line up
   identically regardless of any remaining browser default line-height on
   inputs vs. this Submit button's own default rendering -- QA flagged the
   two as visibly misaligned even after box-sizing/height were already
   equal, which pointed at line-height, not the box model, as the cause. */
.nd-fs-row input.wpcf7-submit[type="submit"] {
    width: 100%; height: 53px; line-height: 53px; box-sizing: border-box; border: none; border-radius: 12px; cursor: pointer;
    background: #ffffff; color: #002ABA; margin: 0; padding: 0 32px; text-align: center;
    font-family: var(--nd-font-family); font-size: 16px; font-weight: 700;
    transition: background 0.3s, color 0.3s;
}
.nd-fs-row input.wpcf7-submit[type="submit"]:hover { background: var(--nd-color-accent-orange); color: var(--nd-color-white); }
.nd-fs-row .wpcf7-not-valid-tip { direction: rtl; text-align: right; color: #FFE0B2; font-size: 13px; margin-top: 4px; }
/* CF7 core injects <span class="wpcf7-spinner"> right after the submit
   button and toggles it between visibility:hidden (idle) and
   visibility:visible (actively submitting) via its own JS -- it never
   toggles `display`. An earlier pass here used display:none to fix a
   real layout bug (visibility:hidden still reserves its own 24px-tall
   inline-block layout box, which is what made the submit button's <p>
   render taller than the other 3 fields and sit visibly higher in the
   row), but that also permanently hid the one bit of "your click
   registered" feedback a visitor gets while the AJAX request is in
   flight. position:absolute removes it from the row's layout flow
   entirely (fixing the original bug) while leaving CF7's own JS free to
   still show/hide it normally during a real submission. */
/* top/left explicit, not left to the browser's static-position fallback:
   confirmed live that position:absolute with no offsets keeps the
   spinner at whatever position it would have held in normal flow as an
   inline element immediately after the submit button -- combined with
   CF7 core's own default spinner margin, that resolved to a nonsensical
   offset (measured live at left:-72px), reading as "the spinner appears
   in a weird position" rather than lining up with the button at all.
   left:16px/top:50%+translateY(-50%) pins it just inside the button's
   own left edge (physically leftmost in this direction:ltr row/grid,
   i.e. "the left side of the button text" QA asked for), vertically
   centered regardless of the button's line-height. */
.nd-fs-row .wpcf7-spinner, .nd-fs-grid .wpcf7-spinner {
    position: absolute; top: 50%; left: 16px; margin: 0; transform: translateY(-50%);
}
/* 1300px (not 991px): with the card's literal 174px side padding, a
   fixed 909px row only fits without overflowing once the container is
   at least 909 + 174*2 = 1257px wide -- wrapping to 2-per-row needs to
   kick in before that gap, not at the generic 991px tablet breakpoint
   most other components use. */
@media screen and (max-width: 1300px) { .nd-fs-row { flex-wrap: wrap; } .nd-fs-row > p, .nd-fs-row .wpcf7-form-control-wrap { flex: 1 1 calc(50% - 6px); } }

/* Phone (node 839:10901, same "Component 46" box also seen on Hub Page):
   had no 767px coverage at all before this pass -- .nd-title-h2/
   .nd-body-text were silently falling back to the shared generic mobile
   vars (24px/14px) instead of this design's real 28px/16px, and the
   desktop 80px/40px paddings were never reduced for a 393px viewport.
   margin-top: 36px (not 0) mirrors the same real gap already confirmed
   elsewhere on this page (ND Hub Programs' own standalone-title margin) --
   the 70px desktop/tablet spacing rhythm reads as too much at this scale
   everywhere it's been checked, and this component's own Figma gap from
   the section above it measures the same ~36px. Mobile node 839:10901
   stacks every field full-width in a single column (not the 2-per-row
   wrap used at tablet width above) -- confirmed directly against that
   node's own layer tree, not assumed from the desktop row. */
@media screen and (max-width: 767px) {
    .nd-component-wrapper.nd-form-section { margin-top: 36px; }
    /* Padding/gap/type sizes below are exact matches confirmed via Figma's
       design context for node 839:10901, not estimated. Decorative pattern
       is desktop-only -- that node has no Group/pattern layer at all. */
    .nd-form-section { padding: 40px 12px; border-radius: 32px; }
    .nd-fs-pattern { display: none; }
    .nd-form-section-inner { gap: 20px; }
    .nd-fs-heading .nd-title-h2 { font-size: 28px; }
    .nd-fs-heading .nd-body-text { font-size: 16px; }
    /* direction:rtl only affects the row axis, not column stacking order,
       so the DOM order (submit first, for the desktop pixel trick -- see
       setup_cf7.py) would otherwise stack as submit/name/phone/email here.
       Figma 839:10901 stacks name/phone/email/submit (submit LAST) --
       explicit order overrides fix that without changing the shared CF7
       tag order the desktop row depends on. */
    .nd-fs-row { flex-direction: column; direction: rtl; max-width: none; }
    .nd-fs-row > p, .nd-fs-row .wpcf7-form-control-wrap { flex: 1 1 auto; }
    .nd-fs-row p:has(input[name="your-name"])  { order: 1; }
    .nd-fs-row p:has(input[name*="your-phone"]) { order: 2; }
    .nd-fs-row p:has(input[name="your-email"]) { order: 3; }
    .nd-fs-row p:has(input[type="submit"])     { order: 4; }
}

/* --- ND Join Us Extended --- */
/* White card, per Figma 383:7052 (exact values pulled from Figma's own
   design context this time, not a screenshot estimate -- see JoinUs's
   own QA round for the list of mistakes that approach caused). Decorative
   photo one side, the volunteer-signup form's 2-column field grid the
   other. Wrapper fully reset for the same bare-classname collision reason
   as .nd-form-section's wrapper -- this component doesn't reuse its own
   base class for the painted card (.nd-jue-card is a distinct class), so
   it isn't actually at risk here, but the padding/margin still needs to
   live on the wrapper, not the card, to match the site-wide gutter
   convention QA confirmed on JoinUs. */
.nd-component-wrapper.nd-join-us-extended { margin-top: 70px; padding: 0 20px; }
/* direction:ltr is deliberate: the page's ambient RTL direction was
   flipping DOM order (photo first, form second in the markup, matching
   Figma) into photo-on-the-right/form-on-the-left -- the opposite of
   Figma, where the photo sits physically on the LEFT. Forcing ltr here
   pins physical left-to-right to DOM order regardless of ambient
   direction, same fix already applied to .nd-fs-row for the same reason. */
.nd-jue-card { display: flex; direction: ltr; gap: 35px; max-width: 1578px; margin: 0 auto; background: var(--nd-color-white); border-radius: 32px; box-shadow: 0 0 20px rgba(161, 161, 161, 0.25); padding: 40px; box-sizing: border-box; }
.nd-jue-photo { flex: 1; min-width: 0; height: 433px; border-radius: 28px; overflow: hidden; box-shadow: 0 0 16.5px rgba(109, 109, 109, 0.25); }
.nd-jue-photo .nd-media, .nd-jue-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nd-jue-content { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 40px; }
/* font-weight:400 (regular, not the shared .nd-title-h2 default of 700) and
   the letter-spacing/text-transform reset are the same two corrections
   QA needed on JoinUs's heading -- the parent theme's bare `h2, h3` rule
   adds 1px tracking + uppercase with nothing here to compete with it. */
.nd-jue-content .nd-title-h2 { font-size: 40px; font-weight: 400; color: var(--nd-color-text-dark); text-align: center; margin: 0; letter-spacing: normal; text-transform: none; line-height: normal; }

/* Dropdowns + submit in one (physical-left) column, text inputs in the
   other (physical-right) -- direction:ltr for the same pixel-exact reason
   as .nd-fs-row (see that block's comment); the CF7 form's own tag order
   (volunteer-area, region, submit, then name/phone/email) is what actually
   produces this column split via grid-auto-flow:column (3 rows per column).
   Fields sit on a WHITE card here (unlike the blue-card .nd-fs-row), so
   they're bordered outline boxes rather than solid white pills -- border
   is BLACK per Figma (not the #ccc grey the first pass here guessed),
   radius 12px (not 8px). row-gap 12px between the 3 rows in each column,
   column-gap 20px between the two columns -- both confirmed exact. */
/* grid-template-columns: 1fr 1fr is required, not cosmetic -- without an
   explicit column size, an auto-flow grid sizes each column from its own
   content's intrinsic width, and since every field independently claims
   width:100% of ITS OWN cell, the two columns could resolve to different
   widths from each other (the actual cause of fields/select/submit not
   all reading as "the same size" QA flagged). Two equal fractions forces
   both columns -- and therefore every field in them -- to the same width,
   matching Figma's two equal 355.75px-wide columns. */
/* minmax(53px, auto), not a fixed 53px: confirmed live that a fixed row
   track height doesn't grow for a field's own validation tip (an extra
   ~26px of content inside that field's <p>, below the 53px input/select
   box) -- with the track locked at 53px, the tip visually bled down into
   the NEXT row's track and overlapped whatever field sits there, which is
   what QA saw as "errors rendered behind the text fields". minmax lets
   each row grow to fit its tallest cell (across both columns) while still
   defaulting to 53px when nothing needs the extra space. */
.nd-fs-grid { display: grid; direction: ltr; grid-auto-flow: column; grid-template-columns: 1fr 1fr; grid-template-rows: repeat(3, minmax(53px, auto)); gap: 12px 20px; width: 100%; }
.nd-fs-grid p { margin: 0; position: relative; }
/* .wpcf7-form-control qualifier is the actual fix here, not decoration:
   confirmed live that <select> specifically was NOT getting this rule's
   border/radius/padding at all -- the parent theme ships
   `select.wpcf7-form-control.wpcf7-select { border:0; padding:15px; }` at
   specificity (0,2,1), while a bare `.nd-fs-grid select` is only (0,1,1)
   and genuinely loses (not a load-order tie like the text/email/tel
   fields below, which already had a same-specificity `[type="..."]`
   attribute to tie-break on load order). Adding .wpcf7-form-control here
   brings every one of these to (0,2,1), tying with the parent rule, and
   this stylesheet's later enqueue position wins that tie -- same fix
   applied consistently to text/email/tel too so none of them are
   depending on the attribute-selector coincidence. padding:16px (all
   sides, not 0 16px) and no explicit line-height, matching Figma's own
   literal box model (height:53px + padding:16px, single line centers
   naturally in the remaining 21px) instead of the line-height:53px trick
   used on the blue-card .nd-fs-row fields. */
.nd-fs-grid input.wpcf7-form-control[type="text"], .nd-fs-grid input.wpcf7-form-control[type="email"], .nd-fs-grid input.wpcf7-form-control[type="tel"], .nd-fs-grid select.wpcf7-form-control {
    width: 100%; height: 53px; box-sizing: border-box; direction: rtl; text-align: right;
    background: var(--nd-color-white); border: 1px solid #000000; border-radius: 12px; padding: 16px;
    font-family: var(--nd-font-family); font-size: 16px; color: var(--nd-color-text-dark); margin: 0;
    -webkit-appearance: none; appearance: none;
}
/* background-size:20px 20px is required, not cosmetic -- a background-
   image SVG with no explicit CSS size renders at the browser's own
   fallback intrinsic size (not necessarily its viewBox dimensions),
   which is what made the chevron look oversized/bolder than Figma's real
   20x20 icon: the stroke itself was still 1.5px in SVG-space, but scaled
   up along with the whole oversized icon it read as thicker on screen. */
.nd-fs-grid select.wpcf7-form-control {
    background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none'%3E%3Cpath d='M5 7.5L10 12.5L15 7.5' stroke='%23000000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: left 16px center; background-size: 20px 20px; padding-left: 44px; cursor: pointer;
}
/* .wpcf7-submit qualifier + box-sizing/line-height/padding/text-align all
   explicit for the same parent-theme specificity reasons documented on
   .nd-fs-row's submit button above (color/padding/text-align leak,
   0,3,1 vs 0,2,1 specificity) -- applying the same fix here up front
   instead of re-discovering it through another QA round. */
.nd-fs-grid input.wpcf7-submit[type="submit"] {
    width: 100%; height: 53px; line-height: 53px; box-sizing: border-box; border: none; border-radius: 12px; cursor: pointer;
    background: #002ABA; color: var(--nd-color-white); margin: 0; padding: 0 32px; text-align: center;
    font-family: var(--nd-font-family); font-size: 16px; font-weight: 700;
    transition: background 0.3s;
}
.nd-fs-grid input.wpcf7-submit[type="submit"]:hover { background: var(--nd-color-accent-orange); }
.nd-fs-grid .wpcf7-not-valid-tip { direction: rtl; text-align: right; color: var(--nd-color-cta-red); font-size: 13px; margin-top: 4px; }
/* Same two universal CF7/parent-theme bugs fixed on .nd-fs-row, applied
   here too rather than waiting to rediscover them: CF7's own spinner span
   is visibility:hidden (not display:none) and still reserves 24px next to
   the submit button, and the parent theme's `.wpcf7 form.wpcf7-form p`
   rule adds an unwanted 20px bottom margin under every field. Spinner
   positioning itself is the single shared .nd-fs-row, .nd-fs-grid rule
   above -- not repeated here. */
.nd-fs-grid .wpcf7 form.wpcf7-form p { margin-bottom: 0; }

@media screen and (max-width: 991px) {
    .nd-jue-card { flex-direction: column; }
}
/* Mobile (node 835:3310): photo on top, then heading, then every field
   stacked in ONE column in the order name/phone/email/volunteer-area/
   region/submit -- confirmed directly against that node's own layer tree
   (same "Rectangle 4331"/photo layer as the desktop node, just restacked),
   NOT the same left/right order the desktop 2-column grid uses. :has()
   reorders each field's own <p> without needing to change the underlying
   CF7 tag order that the desktop grid depends on. Exact values (card
   radius 20 not 32, photo radius 20, heading 20px not 40px) pulled from
   Figma's design context for this node directly. */
@media screen and (max-width: 767px) {
    .nd-component-wrapper.nd-join-us-extended { margin-top: 36px; padding: 0 12px; }
    .nd-jue-card { padding: 20px; border-radius: 20px; gap: 20px; box-shadow: 0 0 20px rgba(161, 161, 161, 0.25); }
    .nd-jue-photo { height: auto; aspect-ratio: 731.5 / 433; border-radius: 20px; }
    .nd-jue-content { gap: 20px; }
    .nd-jue-content .nd-title-h2 { font-size: 20px; }
    .nd-fs-grid { display: flex; flex-direction: column; direction: rtl; grid-template-rows: none; gap: 12px; }
    .nd-fs-grid p:has(input[name="your-name"])  { order: 1; }
    .nd-fs-grid p:has(input[name="your-phone"]) { order: 2; }
    .nd-fs-grid p:has(input[name="your-email"]) { order: 3; }
    .nd-fs-grid p:has(select[name="volunteer-area"]) { order: 4; }
    .nd-fs-grid p:has(select[name="region"])         { order: 5; }
    .nd-fs-grid p:has(input[type="submit"])          { order: 6; }
}

/* --- ND Newsletter --- */
/* Same blue-card family as .nd-form-section, per Figma 839:7397 (exact
   design-context values, not a screenshot estimate) -- heading, a
   narrower row (email + submit only, 700px fixed vs JoinUs's 909px), then
   the required consent checkbox as its own line below. Card carries its
   own distinct class (.nd-nl-card, not the "nd-newsletter" base slug the
   wrapper also uses) specifically to avoid the bare-classname collision
   class-nd-form-section.php's wrapper needed a full property reset for. */
.nd-component-wrapper.nd-newsletter { margin-top: 70px; padding: 0 20px; }
.nd-nl-card { position: relative; overflow: hidden; box-sizing: border-box; background-color: #002ABA; padding: 80px 174px; border-radius: 32px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 40px; width: 100%; max-width: 1860px; margin: 0 auto; }
.nd-nl-inner { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 20px; width: 100%; text-align: center; }
/* font-weight:400 + letter-spacing/text-transform reset: same fix as
   every other heading in this build -- the shared .nd-title-h2 default is
   700, and the parent theme's bare `h2, h3` rule adds 1px tracking +
   uppercase with nothing here to compete with it. */
.nd-nl-title { font-size: 40px; font-weight: 400; color: #ffffff; margin: 0; letter-spacing: normal; text-transform: none; line-height: normal; }
/* Mobile-only subtitle (node 872:3045 has one, desktop 839:7397 doesn't --
   see class-nd-newsletter.php's docblock) -- hidden by default, only
   shown under the 767px breakpoint below. */
.nd-nl-mobile-subtitle { display: none; color: #ffffff; margin: 0; letter-spacing: normal; text-transform: none; }

.nd-fs-row-narrow { width: 700px; max-width: 100%; margin: 0 auto; flex-wrap: wrap; }
/* email is a FIXED 478px per Figma (not flex:1 like the submit button
   next to it) -- confirmed directly in the design context, not assumed
   symmetry with the submit button's own flex-[1_0_0]. */
.nd-fs-row-narrow p:has(input[name="your-email"]) { flex: 0 0 478px; max-width: 100%; }
/* The acceptance field's own <p> breaks out of the submit+email row onto
   its own full-width line -- flex-basis:100% forces the wrap point since
   the row above it is already full at 2 items. */
.nd-fs-row-narrow p:has(.wpcf7-acceptance) { flex: 1 1 100%; order: 99; }
/* Consent line: gap-7, justify-center, checkbox AFTER the text in DOM
   order -- confirmed this needs NO direction:ltr override (unlike the
   card/row structures elsewhere in this build): under the page's own
   ambient RTL, DOM-last already renders physically leftmost, which is
   exactly where Figma puts the checkbox relative to the text. */
.nd-fs-row-narrow .wpcf7-acceptance { display: flex; align-items: center; justify-content: center; gap: 7px; text-align: center; color: #ffffff; font-size: 16px; }
.nd-fs-row-narrow .wpcf7-acceptance a { color: #ffffff; text-decoration: underline; }
/* Custom checkbox: 16x16, white 1px border, 4px radius, transparent fill
   -- appearance:none strips the native browser checkbox. This does NOT
   use a .wpcf7-form-control qualifier the way every other field fix in
   this build does: confirmed live that CF7's acceptance tag puts that
   class on the WRAPPING <span> (.wpcf7-form-control.wpcf7-acceptance),
   never on the actual <input type="checkbox"> itself -- a selector
   requiring it on the input matched nothing at all, so NONE of this
   rule's properties were applying; the checkbox was rendering under a
   completely different site-wide rule instead (`app.css`'s bare
   `[type="checkbox"] { appearance:none; }`, with no size/border/radius of
   its own, which is what made it look wrong/oversized). Specificity here
   (2 classes + 1 attribute + 1 element) already comfortably beats that
   bare attribute selector without needing a matching class at all. A
   checked-state fill isn't specified anywhere in Figma's static export
   (no "checked" variant exists) -- filling white on check is a reasonable
   functional default, not a guess at content. */
/* border uses !important -- confirmed live that a site-wide `app.css`
   ships `[type="checkbox"] { border-style: none !important; ... }`.
   !important always wins over a normal-priority rule regardless of
   specificity (this is a different mechanism from every other
   specificity fix in this build -- those were all decided by selector
   specificity/load order; this one needs the same weapon to compete at
   all). Only border needs it -- width/height/border-radius/appearance
   all came from ordinary (non-!important) app.css rules and already won
   on specificity alone. */
.nd-fs-row-narrow .wpcf7-acceptance input[type="checkbox"] {
    -webkit-appearance: none; appearance: none; width: 16px; height: 16px; flex-shrink: 0;
    border: 1px solid #ffffff !important; border-radius: 4px; background: transparent; margin: 0; cursor: pointer;
    position: relative; vertical-align: middle;
}
.nd-fs-row-narrow .wpcf7-acceptance input[type="checkbox"]:checked { background: #ffffff; }
/* The solid grey/white square QA was actually seeing wasn't the input's
   own background at all -- app.css generates a `::before` pseudo-element
   on every checkbox (content:"", 30x30, background rgb(238,238,238)) as
   its own fake custom-checkbox box, layered on top of/around the real
   input. The real input already has the correct transparent bg + white
   border from the rule above; this leftover pseudo-box was what actually
   rendered. No !important needed here (app.css's ::before rule isn't
   marked important, unlike its border-style rule) -- this selector's own
   specificity already wins. */
.nd-fs-row-narrow .wpcf7-acceptance input[type="checkbox"]::before { display: none; }
/* Same story as ::before, but for the checkmark: app.css draws it via a
   FontAwesome glyph on :checked::after, sized/positioned for ITS OWN
   30x30 fake box (font-size:23px, top:6px, left:8px) -- scaled down here
   to actually fit inside this design's real 16x16 box instead of
   overflowing past its edges, which is what QA saw as "the v mark is too
   big and outside the box". position:relative on the checkbox itself
   (added above) is what anchors this ::after correctly. */
/* top/left confirmed live: the glyph's own rendered box is 11x11 inside
   the 16x16 checkbox, so (16-11)/2 = 2.5px centers it on both axes --
   the earlier -3px/1px was a sign error from guessing a proportional
   scale-down instead of measuring the actual rendered glyph box. */
.nd-fs-row-narrow .wpcf7-acceptance input[type="checkbox"]:checked::after {
    font-size: 11px; top: 2.5px; left: 2.5px; color: #002ABA;
}
.nd-fs-row-narrow .wpcf7-not-valid-tip { direction: rtl; text-align: center; color: #FFE0B2; font-size: 13px; margin-top: 4px; width: 100%; }
/* Spinner-hide and stray-margin-reset already covered by .nd-fs-row's own
   rules above -- render_html() applies BOTH classes to this row, so no
   need to duplicate them here. */

@media screen and (max-width: 767px) {
    .nd-component-wrapper.nd-newsletter { margin-top: 36px; padding: 0 12px; }
    .nd-nl-card { padding: 40px 12px; border-radius: 32px; gap: 20px; }
    .nd-nl-inner { gap: 20px; }
    .nd-nl-title { font-size: 28px; }
    .nd-nl-mobile-subtitle { display: block; font-size: 16px; }
    /* Mobile node 872:3045 stacks email BEFORE submit -- the opposite of
       the desktop row's DOM order (CF7's own tag order lists [submit]
       first, for the desktop direction:ltr trick -- see setup_cf7.py).
       Explicit order here reorders visually without touching that tag
       order the desktop row depends on.

       align-items:stretch override is required, not redundant: the base
       .nd-fs-row rule sets align-items:center for desktop's row (so the
       submit button/fields center vertically against each other) -- but
       once flex-direction flips to column here, align-items switches to
       controlling the CROSS axis, which is now horizontal/width. Center
       made every field size to its own intrinsic content width and
       center itself instead of stretching full-width, which is exactly
       why the submit button and email field read as different widths on
       mobile. Stretch (the normal flex default) restores full-width
       fields regardless of each one's own content size. */
    .nd-fs-row-narrow { flex-direction: column; align-items: stretch; direction: rtl; width: 100%; }
    .nd-fs-row-narrow p:has(input[name="your-email"]) { flex: 1 1 auto; order: 1; }
    .nd-fs-row-narrow p:has(input[type="submit"]) { order: 2; }
    .nd-fs-row-narrow p:has(.wpcf7-acceptance) { order: 3; }
}

/* --- ND CF7 Forms --- */
.nd-cf7-form .wpcf7-form-control-wrap { display: block; margin-bottom: 16px; }
.nd-cf7-form input[type="text"], .nd-cf7-form input[type="email"], .nd-cf7-form input[type="tel"], .nd-cf7-form textarea, .nd-cf7-form select { width: 100%; padding: 12px 16px; border: 1px solid #ccc; border-radius: 4px; font-family: var(--nd-font-family); font-size: 16px; box-sizing: border-box; }
.nd-cf7-form input[type="submit"] { background: var(--nd-color-accent-orange); color: var(--nd-color-white); border: none; padding: 12px 24px; font-size: 16px; font-weight: 700; border-radius: 4px; cursor: pointer; transition: background 0.3s; width: 100%; }
.nd-cf7-form input[type="submit"]:hover { background: var(--nd-color-primary-blue); }
.nd-cf7-inline { display: flex; gap: 8px; }
.nd-cf7-inline .wpcf7-form-control-wrap { margin-bottom: 0; flex: 1; }
.nd-cf7-inline input[type="submit"] { width: auto; }

/* --- Animations & Micro-Interactions --- */
/* Generic Hover Lift */
.nd-hover-lift { transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1), box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }
.nd-hover-lift:hover { transform: translateY(-6px); box-shadow: 0 15px 35px rgba(0,0,0,0.15); }
/* Buttons */
.nd-btn:hover { transform: scale(1.03); }
/* Fade In on Scroll (Intersection Observer) */
.nd-fade-in { opacity: 0; transform: translateY(30px); transition: opacity 0.8s ease-out, transform 0.8s ease-out; }
.nd-fade-in.is-visible { opacity: 1; transform: translateY(0); }
/* Accordion Animation
   max-height on .active is only a no-JS fallback now — nd-shared.js sets
   an exact inline max-height (element.scrollHeight) on open/close instead
   of guessing a fixed number. That switch happened when Answer became a
   real textarea_html rich field (FAQ): rich HTML (multiple paragraphs,
   lists) has no predictable length the way the old nl2br'd plain text
   did, so a fixed guess (previously 500px) would clip longer answers. */
/* ND Accordion (FAQ) Title and ND Accordion Item are two independent,
   non-nested elements (see class-nd-accordion.php's docblock for why) --
   each gets its own top-level .nd-component-wrapper, so the side gutter is
   applied to BOTH wrappers rather than once to a shared parent. .nd-acc-item
   itself carries its own bottom margin/centering so a run of items reads
   as one continuous list purely through spacing, with no shared flex
   parent tying them together. */
.nd-component-wrapper.nd-accordion { margin-top: 70px; padding: 0 20px; }
.nd-component-wrapper.nd-accordion-item { padding: 0 20px; }
.nd-acc-title-heading { font-family: 'Assistant', sans-serif; font-size: 60px; font-weight: 700; color: #002ABA; letter-spacing: -2px; text-transform: uppercase; text-align: center; max-width: 1100px; margin: 0 auto 40px auto; }
.nd-acc-item { width: 100%; max-width: 1100px; margin: 0 auto 20px auto; background: #ffffff; border-radius: 32px; box-shadow: 0 0 20px rgba(161,161,161,0.25); overflow: hidden; }
/* Last item in a consecutive run shouldn't leave a trailing 20px gap
   before whatever section comes next on the page. */
.nd-component-wrapper.nd-accordion-item:last-of-type .nd-acc-item { margin-bottom: 0; }
.nd-acc-title { padding: 40px; gap: 35px; cursor: pointer; display: flex; align-items: center; }
/* Client-requested (2026-08-03): tighten the gap between the question and
   its answer specifically when open -- .nd-acc-title's own 40px padding
   is unconditional (same in both states, matching Figma's closed look),
   so the question row's own bottom padding was the full 40px right
   before the answer starts too. Only reduced when .active, so the
   closed-state spacing (already Figma-confirmed) is untouched. */
.active .nd-acc-title { padding-bottom: 24px; }
.nd-acc-question { margin: 0; font-family: 'Assistant', sans-serif; font-size: 24px; font-weight: 700; color: #000000; text-align: right; flex: 1; }
.nd-acc-icon { width: 20px; height: 20px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: transform 0.3s; }
.nd-acc-icon svg { width: 100%; height: 100%; display: block; }
.active .nd-acc-icon { transform: rotate(180deg); }
.nd-acc-content { max-height: 0; padding: 0 40px; opacity: 0; transition: all 0.4s ease; overflow: hidden; }
/* Bottom padding 96px, not 48px -- client-requested (2026-07-27, then
   again 2026-08-03 that 64px still wasn't enough) more room at the end
   of an open answer. The last-child margin-zero rules below are
   untouched (still needed to avoid the editor's own last paragraph
   doubling up its margin on top of this padding); this simply raises
   the padding itself, at every breakpoint (proportionally below too),
   rather than reintroducing that double-margin problem. */
.active .nd-acc-content { max-height: 2000px; padding: 0 40px 96px 40px; opacity: 1; }
.nd-acc-content-inner { font-family: 'Assistant', sans-serif; font-size: 18px; color: #000000; text-align: right; }
.nd-acc-content-inner > *:last-child { margin-bottom: 0; }
/* Repeated directly on the nested <p> the rich text editor generates --
   same parent-theme bare-<p>-targets-Trade-Gothic issue fixed for Damka
   and Content Editor's own body text. */
.nd-acc-content-inner p { font-family: 'Assistant', sans-serif; font-size: 18px; color: #000000; margin: 0 0 12px 0; }
/* Belt-and-suspenders on top of the >*:last-child rule above (higher
   specificity, guaranteed to win): whatever the rich editor's last element
   actually is, it shouldn't add its own trailing margin on top of the
   container's own bottom padding. */
.nd-acc-content-inner p:last-child { margin-bottom: 0; }

@media screen and (max-width: 991px) {
    .nd-acc-title-heading { font-size: var(--nd-h2-mobile); }
    .nd-acc-title { padding: 24px; gap: 20px; }
    .active .nd-acc-title { padding-bottom: 14px; }
    .nd-acc-question { font-size: 20px; }
    .active .nd-acc-content { padding: 0 24px 72px 24px; }
}
/* Phone (node 1027:5494): 24px/16px padding (not the 991px tablet block's
   24px-all-around), 20px radius (not 32px), question 16px (not 20px).
   letter-spacing -1px on the title, not the base rule's -2px: same fix,
   same reasoning as every other component's own standalone title in this
   file (ND Hub Programs, ND Content Editor) -- -2px was tuned against
   the 60px desktop size, disproportionately tight left unchanged at this
   much smaller 28px. Client-reported (2026-07-27). ND Accordion Item's
   own question text (.nd-acc-question) has no letter-spacing set at any
   size and nothing restricting it to one line, so it needed no matching
   fix -- there was nothing actually broken on that half of the request. */
@media screen and (max-width: 767px) {
    .nd-acc-title-heading { font-size: 28px; letter-spacing: -1px; }
    .nd-acc-item { border-radius: 20px; }
    .nd-acc-title { padding: 24px 16px; gap: 35px; }
    .active .nd-acc-title { padding-bottom: 14px; }
    .nd-acc-question { font-size: 16px; }
    .active .nd-acc-content { padding: 0 16px 56px 16px; }
    .nd-acc-content { padding: 0 16px; }
}

/* --- ND Generic CF7 Form Box Styles --- */
/* FormAssembly-era selectors (.wFormContainer/.oneField, .wForm, .reqMark)
   removed -- FormAssembly was reversed back to Contact Form 7 project-wide
   (see setup_cf7.py), nothing on this site renders FormAssembly markup
   anymore. .nd-fs-form-box itself now only backs ND Hub Programs' tab-3
   form slot (.nd-hub-form-box, a plain single-column stacked box) -- the
   standalone JoinUs/JoinUsExtended/Newsletter components style their own
   fields directly (.nd-fs-row / .nd-fs-grid below) and no longer carry
   this class, to avoid colliding with these generic rules. */
.nd-fs-form-box .wpcf7-form-control-wrap { display: block; margin-bottom: 16px; }
.nd-fs-form-box input[type="text"], .nd-fs-form-box input[type="email"], .nd-fs-form-box input[type="tel"], .nd-fs-form-box textarea, .nd-fs-form-box select { width: 100%; padding: 12px 16px; border: 1px solid #ccc; border-radius: 4px; font-family: 'Assistant', sans-serif; font-size: 16px; box-sizing: border-box; }
/* input.wpcf7-submit qualifier: confirmed live that the parent theme's own
   stylesheet.min.css ships input.wpcf7-form-control.wpcf7-submit:not([disabled])
   at specificity (0,3,1), beating a plain input[type="submit"] rule on
   color. Matching that specificity here so this box's intended orange
   submit button doesn't silently render in the parent's default grey. */
.nd-fs-form-box input.wpcf7-submit[type="submit"], .nd-fs-form-box button[type="submit"] { background: var(--nd-color-accent-orange); color: var(--nd-color-white); border: none; padding: 12px 24px; font-size: 16px; font-weight: 700; border-radius: 4px; cursor: pointer; transition: background 0.3s; width: 100%; }
.nd-fs-form-box input.wpcf7-submit[type="submit"]:hover, .nd-fs-form-box button[type="submit"]:hover { background: var(--nd-color-primary-blue); }

/* --- Inline Newsletter Form (Footer/General) --- */
.nd-form-inline { display: flex; gap: 8px; align-items: stretch; }
.nd-form-inline .wpcf7-form-control-wrap { margin-bottom: 0; flex: 1; }
.nd-form-inline input[type="submit"], .nd-form-inline button[type="submit"] { width: auto; height: 100%; }

/* ND Sticky Button — round floating homepage button, per Figma node
   839:7495 (fileKey nHzxgbwDah4lMBIYGp4oN3): a 100x100 circle, light-blue
   fill, bold centered 2-line text, no icon. Two Figma variants differ only
   in border weight/opacity (thin translucent vs. thick solid) — treated as
   idle vs. :hover since Figma doesn't label which is which. Positioning
   (fixed, bottom-right, slides up from bottom:-100px on scroll) is kept
   from the original — that part isn't part of this redesign, only the
   button's own shape/style is.

   Scoped to `a.nd-sticky-trigger` specifically, not the bare class: the
   ND_Component base class wraps every component's output in a div whose
   class is derived from its base slug (nd_sticky_trigger -> class
   "nd-sticky-trigger") — a naming collision with this component's own
   button class that predates Phase 3. Left unscoped, that wrapper div
   would also render as an invisible duplicate fixed circle sitting over
   the real link and eating its clicks. */
a.nd-sticky-trigger {
    position: fixed;
    bottom: -100px;
    left: 24px;
    /* Not 999: after re-parenting to <body> (nd-shared.js), this competes
       directly against the theme's own .wrapper div at the root stacking
       context — confirmed live that .wrapper is position:relative with
       z-index:1000, exactly one higher, which was enough to render the
       entire page (including the footer) back on top of this button. */
    z-index: 100000;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100px;
    height: 100px;
    padding: 0 10px;
    border-radius: 50%;
    background: #6FC8F4;
    border: 1px solid rgba(111, 200, 244, 0.61);
    box-shadow: 0 0 5px rgba(85, 85, 85, 0.25);
    color: #000000;
    font-family: 'Assistant', sans-serif;
    font-weight: 700;
    font-size: 16px;
    line-height: 1.3;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: bottom 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55), transform 0.3s ease, border-color 0.3s ease;
}
a.nd-sticky-trigger.is-visible { bottom: 24px; }
a.nd-sticky-trigger:hover { border: 4px solid #6FC8F4; transform: scale(1.05); }
/* Phone (node 839:10937 and equivalents): 80x80, 12px from the edge --
   smaller than desktop's 100x100/24px. */
@media screen and (max-width: 767px) {
    a.nd-sticky-trigger { width: 80px; height: 80px; left: 12px; font-size: 14px; }
    a.nd-sticky-trigger.is-visible { bottom: 12px; }
}

/* --- Phase 4: Top Menu & Header --- */
/* Normal document flow by default (NOT fixed/sticky): the header reserves
   its own height at the top of the page, so everything below it (including
   the hero) starts right after it instead of being hidden underneath an
   always-fixed bar. This also fixes a pre-existing site-wide mismatch: the
   header's real rendered height (~124px) was taller than the body's old
   manual 70px compensation, so every page (not just the hero) had ~54px of
   its top content hidden under the header before this change.
   CSS `position:sticky` was tried first and rejected: `.wrapper_inner` (the
   header's direct parent, theme-wide) has `overflow:hidden`, which breaks
   sticky positioning in every browser (confirmed live — the header just
   scrolled away with the page instead of pinning). Changing that
   `overflow:hidden` site-wide was judged too risky to touch for this task,
   so "sticky after scroll" is done with a small JS scroll-listener instead
   (nd-shared.js) that swaps in `.nd-header-fixed` once scrollY > 0, backed
   by a spacer element (.nd-header-spacer) so content doesn't jump when the
   header leaves the flow.
   Padding is 16.5px 24px, not a rounder number: per Figma (node 808:1427,
   1920px reference frame) the header's total height is 95px, and its
   tallest child (the logo, 62px) is centered inside it — (95 - 62) / 2 =
   16.5px top/bottom. Horizontal 24px matches Figma's edge padding exactly. */
.nd-site-header { position: relative; z-index: 1000; background: #ffffff; padding: 16.5px 24px; transition: transform 0.4s ease, background 0.4s ease; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
.nd-site-header.nd-header-fixed { position: fixed; top: 0; left: 0; right: 0; }
.nd-header-spacer { display: none; }
.nd-header-spacer.is-active { display: block; }
/* No flex-direction:row-reverse (was here, labeled "RTL Support"): this
   container's `direction` computes to `ltr` (confirmed live — nothing in
   its ancestor chain sets dir="rtl" or direction:rtl), so row-reverse
   didn't add RTL support, it flipped the DOM order backwards from the
   markup's own documented intent (header-override.php: "Left: Search,
   Accessibility, Donate" / "Right: Logo") — confirmed live the header was
   rendering logo-left, donate-right, the opposite of both comments. DOM
   order is tools -> nav -> brand; default `row` alone already lays that
   out left-to-right exactly as documented, no reversal needed. */
/* max-width:1920px, not 1440px: confirmed via Figma metadata for node
   808:1427 (the 1920px reference frame) that the donate cluster starts at
   x:24 and the logo cluster ends at x:1896 -- a 24px inset each side (the
   same 24px already in .nd-site-header's own padding), not centered
   inside a smaller sub-container. The previous 1440px cap left a large,
   unintended empty gap between the true viewport edges and where
   tools/logo actually sat -- client flagged this directly ("logo needs to
   reach the far right, donate the far left, using the full width of the
   nav bar"). 1920px (not literally 100%) still caps it from stretching
   indefinitely on ultrawide monitors, consistent with every other ND
   component's own 1860-1920px desktop cap. */
.nd-header-container { max-width: 1920px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; }
.nd-header-brand { display: flex; align-items: center; }
.nd-logo-img { height: 62px; width: auto; }

/* No flex-direction:row-reverse on .nd-nav-list: per Figma (node 808:1427),
   this list computes `direction:ltr` same as .nd-header-container did
   (confirmed live — no dir="rtl"/direction:rtl anywhere in its ancestor
   chain), and the 5 real menu items render left-to-right in plain DOM
   order (Figma's own generated markup has no reversal or dir="rtl" on this
   container). row-reverse was flipping the wp-admin configured order
   backwards from that — same bug as the header container fix (commit
   64cc02d). Left the fixed 40px gap + centered layout as-is: Figma's
   1042px-wide test frame shows even ~137.5px gaps via justify-content
   space-between, but that number is a byproduct of that mockup's specific
   5 test labels at 1920px, not a spacing value worth hard-coding — a fixed
   gap that scales with however many real items end up in this menu is more
   robust than reproducing one mockup's exact distribution. */
.nd-header-nav { flex: 1; display: flex; justify-content: center; }
.nd-nav-list { list-style: none; margin: 0; padding: 0; display: flex; gap: 40px; align-items: center; }
.nd-nav-list a { text-decoration: none; color: #000000; font-family: 'Assistant', sans-serif; font-weight: 600; font-size: 20px; transition: color 0.3s; }
.nd-nav-list a:hover { color: #e7342b; }

.nd-header-tools { display: flex; align-items: center; }
.nd-tools-inner { display: flex; align-items: center; gap: 40px; }
.nd-header-search-acc { display: flex; align-items: center; gap: 18px; cursor: pointer; }
.nd-aa-text { font-family: 'Assistant', sans-serif; font-weight: 400; font-size: 20px; color: #000000; }
.nd-search-icon { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; }

.nd-btn-donate { background-color: #e7342b; color: #ffffff; border-radius: 12px; padding: 16px 32px; display: flex; align-items: center; gap: 4px; text-decoration: none; transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); }
.nd-btn-donate span { font-family: 'Assistant', sans-serif; font-weight: 700; font-size: 20px; }
.nd-btn-donate:hover { transform: scale(1.03); color: #ffffff; }
.nd-btn-donate-heart { width: 20px; height: 20px; display: block; }

/* Mobile Nav Toggle */
.nd-mobile-toggle { display: none; background: none; border: none; flex-direction: column; gap: 5px; cursor: pointer; padding: 10px; margin-right: 16px; }
.nd-mobile-toggle span { display: block; width: 24px; height: 2px; background: #000000; transition: 0.3s; }
/* Hamburger -> X once the menu is open (nd-shared.js toggles .is-active on
   click) -- a small, standard affordance Figma doesn't separately spec
   (no "menu open" frame exists anywhere in this file), same reasoning as
   the mobile-nav donate button below. */
.nd-mobile-toggle.is-active span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nd-mobile-toggle.is-active span:nth-child(2) { opacity: 0; }
.nd-mobile-toggle.is-active span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
/* top is a CSS var (nd-shared.js sets --nd-header-height from the header's
   own real outerHeight, same measurement the sticky-header spacer already
   uses), not a second hardcoded number: a literal 86px here previously
   drifted out of sync with the header's actual height (measured ~124px
   live, now 95px after the Figma padding fix) with no way to notice until
   someone opened the mobile menu and saw the gap/overlap. The 95px
   fallback matches the current Figma-derived height, for the brief moment
   before JS sets the real value. */
.nd-mobile-nav { position: fixed; top: var(--nd-header-height, 95px); left: 0; right: 0; background: #ffffff; padding: 20px; box-shadow: 0 10px 20px rgba(0,0,0,0.1); transform: translateY(-150%); transition: transform 0.4s ease-in-out; z-index: 999; }
.nd-mobile-nav.is-open { transform: translateY(0); }
/* Repeats the header's own donate button inside the dropdown -- see
   header-override.php's comment on why (mobile hides .nd-header-tools
   entirely, but donate needs to stay reachable somewhere on mobile). */
.nd-mobile-nav-donate { justify-content: center; margin-bottom: 20px; }
.nd-mobile-nav-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 16px; text-align: right; }
.nd-mobile-nav-list a { font-size: 20px; font-weight: 600; color: #000000; text-decoration: none; }

@media (max-width: 1100px) {
  .nd-header-nav { display: none; }
  .nd-mobile-toggle { display: flex; }
  /* Tablet range (768-1100px): .nd-header-tools still shows here (only
     hidden below, at the 767px phone breakpoint), just tightened up now
     that the nav no longer needs the room. */
  .nd-tools-inner { gap: 16px; }
  .nd-btn-donate { padding: 12px 16px; }
}
@media (max-width: 767px) {
  /* Figma's mobile header (node 829:2387 and equivalents on the hub/
     product mobile frames) is just the logo + hamburger, edge-to-edge
     16px padding, no search/Aa/donate row -- the asymmetric top/bottom
     padding is a direct match of the measured coordinates (logo and
     hamburger both vertically center at y=64 in a 90px-tall frame with
     0 bottom inset, not a typo). */
  .nd-site-header { padding: 38px 16px 0 16px; }
  .nd-logo-img { width: 143px; height: auto; }
  .nd-header-tools { display: none; }
  .nd-mobile-toggle { margin-right: 0; }
  /* .nd-header-nav/.nd-header-tools are both hidden above, leaving
     .nd-header-brand as the only visible child of .nd-header-container --
     its own space-between (below) is what actually spreads logo/hamburger
     to opposite edges now; .nd-header-container's own justify-content has
     nothing left to distribute between with only one visible child. */
  .nd-header-brand { width: 100%; justify-content: space-between; }
}
/* No body padding-top offset needed any more: .nd-site-header is `sticky`,
   not `fixed`, so it reserves its own space in the document flow instead of
   requiring a manually-guessed compensation value below. */

  
  
  /* --- ND Hero Carousel V2 --- */

  /* The parent theme's legacy ".content.content_top_margin" wrapper adds
     100px of top MARGIN (jafi/css/stylesheet.min.css, !important) meant for
     pages with a non-fixed classic header. ND pages already compensate for
     the fixed nav via body{padding-top:70px}, so that legacy 100px is pure
     extra gap above the hero — confirmed live via getComputedStyle (padding
     was already 0 from the rule below, but margin-top was still 100px,
     which is the actual visible gray gap between the nav and the hero).
     Both scoped with :has() so this only affects pages that actually
     contain the ND hero — every other (legacy) page keeps its normal
     100px top margin untouched. */
  .content.content_top_margin:has(.nd-hero-v2-container) { padding-top: 0 !important; margin-top: 0 !important; }

  .nd-hero-v2-container { position: relative; width: 100%; height: 770px; overflow: hidden; direction: rtl; }
  .nd-hero-v2-slider { position: relative; width: 100%; height: 770px; }

  /* Hand-rolled crossfade (nd-shared.js), NOT Slick's fade:true. Slick 1.8.1's
     own fade-mode positioning has a confirmed, unresolved bug in this RTL
     setup: it miscalculates each non-first slide's "left" (observed as far
     as -3698px, 2 full slide-widths, on a 2-slide carousel), pushing every
     slide but the first off-screen. Traced it to .slick-track itself sitting
     at the wrong position (RTL block right-aligns a track wider than its
     container by default) - forcing left:0 on individual slides didn't fix
     it, and forcing the track's own position broke layout further. Rather
     than keep fighting Slick's internal RTL+fade math, this component does
     its own opacity crossfade: every slide is absolutely positioned to fill
     the container (inset:0), with only .is-active visible. This also
     eliminates the earlier "slide collapses to 1px" cascade-order bug
     entirely, since height now comes from filling the parent via inset:0
     rather than through any Slick-dependent DOM structure. */
  .nd-hero-v2-slider .nd-hero-v2-slide-wrapper { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; z-index: 1; transition: opacity 1s ease; }
  .nd-hero-v2-slider .nd-hero-v2-slide-wrapper.is-active { opacity: 1; z-index: 2; }

  .nd-hero-v2-media-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; overflow: hidden; }

  /* Desktop/mobile media split (Phase 2, Part B). The legacy-fallback path in
     render_slide_media() puts BOTH classes on one wrapper for old slides
     that only have the single "media" field — harmless since only one of
     the two @media rules below is ever active at a time. */
  .nd-hero-v2-media-desktop, .nd-hero-v2-media-mobile { position: absolute; inset: 0; width: 100%; height: 100%; }
  .nd-hero-v2-media-mobile { display: none; }
  @media screen and (max-width: 767px) {
      .nd-hero-v2-media-desktop { display: none; }
      .nd-hero-v2-media-mobile { display: block; }
  }

  .nd-hero-v2-bg, .nd-hero-v2-media-layer video, .nd-hero-v2-media-layer .nd-media-img { width: 100%; height: 100%; background-size: cover; background-position: center; object-fit: cover; display: block; }
  .nd-hero-v2-media-layer .nd-media-embed, .nd-hero-v2-media-layer .nd-media-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

  /* Client-reported (2026-07-27) this reads darker than Figma. Checked the
     actual masked photo asset pulled directly from Figma (node 360:2544,
     "Rectangle 4323", the hero's own background photo layer) -- it's the
     naturally bright, ungraded source photo with no separate dark gradient
     rectangle anywhere in that node's tree. This overlay isn't a
     mistranslation of a real Figma layer, then; it reads as something
     added defensively during the build (most likely for the breadcrumb
     text's own legibility on Hub/Product pages, where it sits directly on
     the raw photo with no card behind it, unlike the homepage where
     breadcrumbs are suppressed entirely). Halved rather than removed:
     matches "too dark vs Figma" without dropping that legibility margin
     entirely for whatever photo an editor uploads elsewhere -- flagged as
     a judgment call, confirm live whether this needs to go lighter still. */
  .nd-hero-v2-overlay-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2; background: linear-gradient(0deg, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0.1) 100%); }

  /* No longer flex-centered — the box is placed at a fixed point (see
     .nd-hero-v2-box below) matching Figma's actual coordinates. Centering
     here was the root cause of QA "box position is incorrect": every slide
     rendered dead-center on the slide regardless of box_position. */
  .nd-hero-v2-content-layer { position: absolute; inset: 0; z-index: 3; pointer-events: none; }

  /* Breadcrumbs: top-right, per Figma node 1027:1548. Chevron uses a plain
     CSS border-triangle, not the Figma icon asset (unverified pixel match,
     flagged in report). */
  .nd-hero-v2-breadcrumbs { position: absolute; top: 28px; right: 29px; z-index: 4; display: flex; align-items: center; gap: 5px; font-family: 'Assistant', sans-serif; font-size: 16px; }
  .nd-hero-v2-breadcrumbs a, .nd-hero-v2-breadcrumbs .nd-breadcrumb-current { color: #000000; text-decoration: none; }
  .nd-hero-v2-breadcrumbs .nd-breadcrumb-current { font-weight: 700; }
  /* rotate(135deg), not 45deg: a border-right+border-bottom corner at
     rotate(45deg) is the standard "chevron pointing down" recipe (the
     common dropdown-arrow trick) -- confirmed live that's exactly what
     was rendering here instead of a left-pointing separator. 135deg
     turns the same corner to point left, correct for a RTL breadcrumb
     trail (this affects both Hub Programs' and Hero Carousel V2's
     breadcrumbs, which share this rule -- a downward separator was never
     correct in either context, unlike the color/order differences
     elsewhere in this file which ARE intentionally per-component). */
  .nd-hero-v2-breadcrumbs .nd-breadcrumb-sep { display: inline-block; width: 6px; height: 6px; border-right: 2px solid #000000; border-bottom: 2px solid #000000; transform: rotate(135deg); }
  /* ND Hub Programs reuses this same breadcrumb markup/class (see
     class-nd-hub-programs.php's docblock), but Figma node 839:7477
     (get_design_context) has it in solid white, not black -- scoped to
     .nd-hub-top rather than changed on the shared rule above so Hero
     Carousel V2's own already-approved black breadcrumb is untouched. */
  .nd-hub-top .nd-hero-v2-breadcrumbs a,
  .nd-hub-top .nd-hero-v2-breadcrumbs .nd-breadcrumb-current { color: #ffffff; }
  .nd-hub-top .nd-hero-v2-breadcrumbs .nd-breadcrumb-sep { border-right-color: #ffffff; border-bottom-color: #ffffff; }

  /* Mobile (node 1027:5614): white text (not black), smaller, closer to
     the corner -- the hero's own dark gradient overlay already makes white
     the readable choice here too, same as Hub Programs' hard-coded white.
     Order is intentionally NOT reverted to match this Figma frame's
     current-first/Home-last sequence: Niri explicitly corrected that to
     Home-first/current-last live on desktop, and that correction applies
     here too. */
  @media screen and (max-width: 767px) {
      .nd-hero-v2-breadcrumbs { top: 16px; right: 16px; font-size: 12px; gap: 6px; }
      .nd-hero-v2-breadcrumbs a, .nd-hero-v2-breadcrumbs .nd-breadcrumb-current { color: #ffffff; }
      .nd-hero-v2-breadcrumbs .nd-breadcrumb-sep { width: 5px; height: 5px; border-right-color: #ffffff; border-bottom-color: #ffffff; }
  }

  @keyframes ndFadeInUpV2 {
      0% { opacity: 0; transform: translateY(30px); }
      100% { opacity: 1; transform: translateY(0); }
  }

  /* Fixed content-box position, derived directly from Figma node 360:2541 /
     405:12587 (both place the 491px text block at global (1256,~228) inside
     the 1920x770 slide -> 173px from the right edge, 228px from the top).
     This is the SAME point for every box_position value: box_position only
     changes which corner the small accent tab is attached to (see the
     .nd-hero-v2-svg-container rules below) — it does not move the card. */
  .nd-hero-v2-box { position: absolute; top: 228px; right: 173px; width: 491px; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 20px; pointer-events: auto; opacity: 0; transform: translateY(30px); }

  /* Triggers once, when our own JS (not Slick) marks a slide .is-active. */
  .nd-hero-v2-slide-wrapper.is-active .nd-hero-v2-box { animation: ndFadeInUpV2 1s 0.3s ease-out forwards; }

  .nd-hero-v2-svg-container { position: absolute; z-index: -1; }
  .nd-hero-v2-svg-container svg { display: block; width: 100%; height: 100%; }
  /* Hidden except inside the 767px query further down, which turns it
     back on with a mobile-specific position/size. */
  .nd-hero-v2-svg-container-mobile { display: none; }

  /* box_position = which corner of the card the small accent tab is attached
     to. The card itself is a single SVG union of a "big box" (the solid
     card) + a smaller box overlapping one of its corners (confirmed via
     Figma metadata: node 405:12588 is literally a boolean union of two
     rounded rectangles). Both the blue asset (360:2541, native tab =
     top-left) and the white asset (405:12588, native tab = bottom-right)
     are Figma-confirmed starting points; the other 3 corners for each color
     are produced by mirroring that same asset (scaleX/scaleY) and
     recomputing its offset so the "big box" portion still lines up behind
     the text exactly as in the native case — not a redraw, so the card's
     proportions stay pixel-accurate in every orientation. */
  .variant-blue.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container { top: -237px; left: -246px; width: 827px; height: 618px; }
  .variant-blue.nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container { top: -237px; left: -90px; width: 827px; height: 618px; transform: scaleX(-1); }
  .variant-blue.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container { top: -148px; left: -246px; width: 827px; height: 618px; transform: scaleY(-1); }
  .variant-blue.nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container { top: -148px; left: -90px; width: 827px; height: 618px; transform: scaleX(-1) scaleY(-1); }

  .variant-white.nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container { top: -73px; left: -74px; width: 774px; height: 488px; }
  .variant-white.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container { top: -73px; left: -209px; width: 774px; height: 488px; transform: scaleX(-1); }
  .variant-white.nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container { top: -182px; left: -74px; width: 774px; height: 488px; transform: scaleY(-1); }
  .variant-white.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container { top: -182px; left: -209px; width: 774px; height: 488px; transform: scaleX(-1) scaleY(-1); }


  .nd-hero-v2-box h1 { font-family: 'Assistant', sans-serif; font-size: 60px; font-weight: 400; line-height: 1.2; margin: 0; }
  .nd-hero-v2-box p { font-family: 'Assistant', sans-serif; font-size: 24px; font-weight: 400; line-height: 1.4; margin: 0; }
  
  .variant-blue h1, .variant-blue p { color: #ffffff; }
  .variant-white h1, .variant-white p { color: #000000; }
  
  .nd-hero-v2-buttons { display: flex; gap: 12px; margin-top: 10px; }
  
  /* Mobile Responsiveness for V2 */
  @media screen and (max-width: 991px) {
      /* 600px is only the BASE/minimum -- nd-shared.js's syncHeight() (see
         the Hero V2 crossfade code) grows both of these with an inline
         style whenever the active slide's own text needs more room than
         this, so a longer slide's card (and mobile's union-shape tab
         below it) isn't clipped by .nd-hero-v2-container's overflow:hidden.
         Client-requested (2026-07-29): the card must actually extend for
         longer text instead of clipping it. */
      .nd-hero-v2-container, .nd-hero-v2-slider { height: 600px; }
      .nd-hero-v2-content-layer { display: flex; align-items: center; justify-content: center; }
      .nd-hero-v2-svg-container { display: none !important; }
      .nd-hero-v2-box { position: relative; top: auto; right: auto; width: 90%; padding: 40px 20px; border-radius: 16px; margin: 0 auto; }
      .variant-blue .nd-hero-v2-box { background: rgba(0, 42, 186, 0.9); }
      .variant-white .nd-hero-v2-box { background: rgba(255, 255, 255, 0.95); }
      .nd-hero-v2-box h1 { font-size: 40px; }
      .nd-hero-v2-box p { font-size: 18px; }
  }
  /* Real mobile union shapes (not the 991px tablet fallback's plain
     rounded-rect substitute): Figma component set 409:12720 "holds all
     states for white and blue boxes" -- Niri flagged the previous plain-
     box simplification as not matching this at all. Downloaded each
     variant's actual SVG asset directly (Frame300 = blue/visible,
     Variant4 = white/visible) rather than re-deriving the path by eye,
     same as how the desktop shapes were sourced. Rendered as a SECOND,
     mobile-only SVG container (.nd-hero-v2-svg-container-mobile, see
     class-nd-hero-carousel-v2.php) since it's a genuinely different
     asset, not a scaled-down version of the desktop one -- swapped in via
     display toggling, same idiom as the desktop/mobile background-media
     split above.
     .nd-hero-v2-box itself switches to absolute positioning (top:59px,
     full width) to match Figma's own coordinates exactly, rather than the
     991px block's centered-flex-child treatment; box_position is still
     ignored on phone (it only ever chose which corner the desktop SVG's
     notch attached to, and every phone slide uses the same single
     bottom-ish placement regardless).

     Text padding-top is 96px, not the box's own 59px top offset -- that
     59px is the box's position WITHIN THE SLIDE, a different frame of
     reference than the text block's 96px offset FROM THE BOX'S OWN TOP
     EDGE (both are direct children of the box in Figma, so both are
     already box-relative; subtracting 59 from 96 in an earlier pass was
     double-counting the box's own offset and produced 37px, well short
     of where Figma actually places the text -- this is what let text
     render high enough to spill above the union shape's visible top edge,
     particularly on the white variant whose shape starts at top:-23.

     The union shape's own "big box" portion (where the text sits) only
     spans to x=370 of the SVG's 392.923-wide viewBox -- the remaining
     ~23px is the small corner-notch tab, same union-of-two-rects idiom as
     desktop. Scaling the whole graphic up by 392.923/370 (~106.2%) while
     keeping left:0 makes the big box span the FULL container width
     instead of stopping short of the right edge, pushing the now-
     unneeded-at-full-width tab off past the viewport -- .nd-hero-v2-
     -container already has overflow:hidden, so it's simply clipped there
     rather than needing to be hidden explicitly. */
  @media screen and (max-width: 767px) {
      .nd-hero-v2-box {
          position: absolute; top: 59px; left: 0; width: 100%; max-width: none;
          padding: 96px 40px 40px; border-radius: 0; background: transparent; gap: 12px;
      }
      /* Overrides the 991px tablet block's own .variant-blue/.variant-white
         background above -- those selectors carry two classes' worth of
         specificity, so the plain ".nd-hero-v2-box { background:
         transparent }" a few lines up can't beat them on its own despite
         coming later; without this, the old solid/semi-transparent tablet
         box rendered underneath the real union SVG as a visible second
         box. */
      .variant-blue .nd-hero-v2-box,
      .variant-white .nd-hero-v2-box { background: transparent; }
      .nd-hero-v2-svg-container { display: none !important; }
      /* z-index:0, not -1: avoids a known WebKit/Safari compositing bug
         where a negative z-index on a descendant of an overflow:hidden
         ancestor that also has an animated/transformed sibling (.nd-hero-
         v2-box runs a transform-based keyframe animation) can get promoted
         onto its own composited layer that renders outside the ancestor's
         clip region instead of being clipped in place. The SVG sits at
         z-index:0, and the actual text content gets an explicit z-index:1
         below, so "behind the text" is real stacking order, not a negative
         value, on every engine. */
      /* left:10px / width:100% -- REVERTED (2026-07-29) back to this from a
         width:calc(100% - 10px) attempt: Niri confirmed the 10px overscan
         past the viewport edge is fine/intentional (matches Figma's own
         edge-bleed) and NOT the actual problem -- the real complaints are
         the mobile shape/position rebuild below (box_position support). */
      .nd-hero-v2-svg-container-mobile { display: block; position: absolute; z-index: 0; width: 100%; left: 10px; }
      .nd-hero-v2-svg-container-mobile svg { display: block; width: 100%; height: 100%; }
      .nd-hero-v2-box h1, .nd-hero-v2-box p, .nd-hero-v2-buttons { position: relative; z-index: 1; }
      /* bottom:0 (no `height` at all), not height:calc(100% - offset):
         client re-confirmed on a real device that the button was STILL
         rendering outside the card after the width/left fix above, even
         though this sandbox's Chromium browser measured everything
         matching correctly. `.nd-hero-v2-box` has no explicit height
         (auto, driven by its own title+desc+buttons content, which varies
         per slide) -- resolving a CHILD's percentage `height` against an
         ancestor whose own height is "auto" is a genuinely ambiguous case
         in the CSS spec, and it's plausible engines don't all agree on how
         to resolve it for a position:absolute box nested in another
         position:absolute auto-height box, even though Chromium happens to
         resolve it the intended way here. Setting `top` AND `bottom` as
         plain pixel offsets (zero percentages involved) sidesteps the
         ambiguity entirely: `bottom:0` pins this element's bottom edge to
         the box's actual rendered bottom edge directly, however tall the
         box's content turns out to be, without ever computing a
         percentage -- this is unambiguous and consistent across engines,
         unlike `height: calc(100% ± Npx)`. */
      /* box_position on mobile: client-corrected (2026-07-29) -- the
         earlier "should be curved" complaint wasn't actually about the
         10px overscan (that's fine, reverted separately), it was that
         box_position is ignored entirely on phone, same as the WPBakery/
         desktop setting already correctly handles. Real live slides use
         top-right, top-left AND bottom-right today -- every one of those
         was rendering with the wrong (always-bottom-right-shaped) tab
         before this, a decorative shape bulging out somewhere totally
         disconnected from the actual card content, which is what read as
         "the shape itself is not good at all".

         Two techniques per corner, same reasoning as blue's original
         bottom-right fix below: whichever corner puts the tab BELOW the
         main body is unsafe to draw as a single non-uniformly-stretched
         real SVG (stretching shrinks the main body's own share of the
         total) -- reproduced as 2 independent CSS shapes (::before body +
         ::after fixed tab), mirrored per corner. Whichever corner puts the
         tab ABOVE the main body is safe to stretch (matches white's own
         native top-right asset, already client-confirmed correct) -- uses
         the real fused SVG directly, mirrored via CSS transform (scaleX
         for left vs right; blue's own native asset is bottom-oriented, so
         its top-* rules also need scaleY to flip the tab upward).

         Blue's top-* vertical offset (top:-40px) is an extrapolated value
         -- Figma only exports blue's native bottom-right orientation, no
         top-facing asset exists to measure directly. White's two new
         bottom-* rules reuse blue's already-tuned 74px/77px/23px
         constants (only the tab WIDTH changes, to 115px, matching white's
         own real tab rectangle instead of blue's 162px). Both were
         live-verified: injected this exact CSS onto the deployed page and
         visually confirmed a clean, seamless rounded union with no gap or
         seam, for blue top-left/top-right and white bottom-right (the 3
         combinations actually in use on the live homepage today). */
      /* -left positions (both top and bottom): mirror the wrapper's own
         asymmetric overscan, not just the SVG's content. The base rule
         above (left:10px, width:100%) is calibrated for a RIGHT-side tab:
         a small intentional gap on the left, overscanning 10px past the
         box's own right edge on the right (client-confirmed fine, matches
         Figma's own edge-bleed). Client-reported (2026-07-29) on the
         mirrored (-left) slide: the tab wasn't poking outside the card the
         way it does on -right slides, AND the right-side gutter looked
         wrong -- root cause was that only the SVG's own content was being
         mirrored (via transform), while this wrapper box itself stayed
         anchored to the same (now wrong) side, leaving no overscan room
         on the left for the now-left-facing tab, and an unwanted/mismatched
         inset on the right where the overscan used to be. Swapping which
         edge gets the 10px offset mirrors the whole thing correctly. */
      .nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile,
      .nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile { left: auto; right: 10px; }

      /* Mobile-only simplification (2026-07-29): after several rounds
         trying to get the bottom-tab mobile card to look/behave as good as
         the top-tab one (real-SVG combine, then equal-size 2-piece rects,
         neither fully satisfying), client decided mobile should only ever
         show the "tab on top" treatment -- box_position's vertical
         component (top vs bottom) is now DESKTOP-ONLY. Only the left/right
         half of that setting still matters on phone: every bottom-*
         selector below is merged into its matching top-* selector
         (bottom-right behaves exactly like top-right, bottom-left exactly
         like top-left) instead of getting its own separate rendering
         technique, which is removed entirely. Desktop's own box_position
         CSS (the earlier .nd-hero-v2-svg-container rules, not -mobile) is
         untouched and still supports all 4 corners as before. */
      .nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container-mobile,
      .nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container-mobile,
      .nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile,
      .nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile { bottom: 0; }
      .variant-white.nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container-mobile,
      .variant-white.nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container-mobile,
      .variant-white.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile,
      .variant-white.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile { top: -23px; }
      .variant-blue.nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container-mobile,
      .variant-blue.nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container-mobile,
      .variant-blue.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile,
      .variant-blue.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile { top: -40px; }
      .variant-white.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile svg,
      .variant-white.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile svg { transform: scaleX(-1); }
      .variant-blue.nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container-mobile svg,
      .variant-blue.nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container-mobile svg { transform: scaleY(-1); }
      .variant-blue.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile svg,
      .variant-blue.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile svg { transform: scaleY(-1) scaleX(-1); }
      /* Client-reported (2026-07-29): white's top-* title sits too close to
         the card's own visible top edge. This ISN'T about the -23px offset
         above (that's Figma-accurate, measured directly from the exported
         asset) -- it's that white's real path has its main body's own flat
         top edge at viewBox y=97 (not y=0 -- y=0..~97 belongs to the TAB,
         which sits above main body natively), so the shared 96px
         padding-top (measured from the BOX's own top, not from where the
         card visually reads as "starting" in the non-tab width) leaves
         only ~10px of real breathing room once the -23px offset and the
         stretch scale are accounted for. Blue's own top-* rendering
         doesn't have this problem (confirmed client-perfect at the shared
         96px): its native asset has main body's flat top AT y=0, since
         blue's tab sits BELOW main body, not above -- no hidden offset to
         compensate for there. Fixed by adding extra top padding scoped to
         white's top-* only, live-tuned (not guessed) by measuring blue's
         own confirmed-good gap (~47px from the tab/main-body junction to
         the title) via its exported path's junction coordinates, and
         adding padding to white until it matched. Bottom-* positions were
         re-checked too and do NOT have this problem -- their filler is a
         plain rect with no internal shape offset, so the plain 96px
         padding already lands exactly where intended. */
      /* 145px -> 118px (2026-07-29): client felt 145px read as too much
         dead space above the title/buttons ("reduce this space maybe"),
         re-confirmed live that buttons/text were never actually clipped
         at either value -- this was purely a visual-balance call, not a
         containment bug. 118px keeps a real gap (~25px from the
         main-body/tab junction to the title, vs. the original bug's
         ~10px) without matching blue's full ~47px, which read as too
         much once seen in context. */
      .variant-white.nd-hero-v2-box-position-top-right .nd-hero-v2-box,
      .variant-white.nd-hero-v2-box-position-bottom-right .nd-hero-v2-box,
      .variant-white.nd-hero-v2-box-position-top-left .nd-hero-v2-box,
      .variant-white.nd-hero-v2-box-position-bottom-left .nd-hero-v2-box { padding-top: 118px; }

      /* 'Text Position (Mobile Only)' field (class-nd-hero-carousel-v2.php,
         .nd-hero-v2-mobile-text-top/-bottom on .nd-hero-v2-content-layer).
         Everything above this point is the TOP case (now the explicit
         default, unchanged) -- these rules only fire for -bottom, and are
         a straight mirror of the top rules rather than new numbers:
         the box moves from top:59px to the same 59px gap off the bottom,
         its 96px/118px(white) tab-clearance padding moves from the top
         edge to the bottom edge, and the mobile SVG union asset's own
         per-variant overshoot (23px white / 40px blue, each asset's own
         intrinsic empty viewBox margin around its tab) moves from
         overshooting past the container's top to overshooting past its
         bottom instead.
         The scaleY per variant is the other half of that same mirror:
         blue's native asset already has its tab at the BOTTOM (its top-*
         rules elsewhere in this file flip it up with scaleY), so for
         bottom text position blue needs NO scaleY at all; white's native
         asset has its tab at the TOP (needs no flip for top text
         position), so for bottom text position white needs scaleY added.
         scaleX(-1) for the left/right corner tab is untouched either way --
         that half of box_position stays orthogonal to this field.
         Every selector here stacks 4 classes on the ancestor
         (.nd-hero-v2-content-layer + variant + mobile-text-bottom +
         box-position) specifically so it outguns the existing 2-3 class
         top-case rules on specificity alone, not just source order --
         safe to reshuffle either block later without silently losing this
         override. */
      .nd-hero-v2-content-layer.nd-hero-v2-mobile-text-bottom .nd-hero-v2-box {
          top: auto; bottom: 59px;
          padding: 40px 40px 96px;
      }
      .nd-hero-v2-content-layer.variant-white.nd-hero-v2-mobile-text-bottom .nd-hero-v2-box {
          padding-top: 40px; padding-bottom: 118px;
      }
      .nd-hero-v2-content-layer.nd-hero-v2-mobile-text-bottom .nd-hero-v2-svg-container-mobile { top: 0; bottom: auto; }
      .nd-hero-v2-content-layer.variant-white.nd-hero-v2-mobile-text-bottom .nd-hero-v2-svg-container-mobile { bottom: -23px; }
      .nd-hero-v2-content-layer.variant-blue.nd-hero-v2-mobile-text-bottom .nd-hero-v2-svg-container-mobile { bottom: -40px; }
      .nd-hero-v2-content-layer.variant-blue.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container-mobile svg,
      .nd-hero-v2-content-layer.variant-blue.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container-mobile svg { transform: none; }
      .nd-hero-v2-content-layer.variant-blue.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile svg,
      .nd-hero-v2-content-layer.variant-blue.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile svg { transform: scaleX(-1); }
      .nd-hero-v2-content-layer.variant-white.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-top-right .nd-hero-v2-svg-container-mobile svg,
      .nd-hero-v2-content-layer.variant-white.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-bottom-right .nd-hero-v2-svg-container-mobile svg { transform: scaleY(-1); }
      .nd-hero-v2-content-layer.variant-white.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-top-left .nd-hero-v2-svg-container-mobile svg,
      .nd-hero-v2-content-layer.variant-white.nd-hero-v2-mobile-text-bottom.nd-hero-v2-box-position-bottom-left .nd-hero-v2-svg-container-mobile svg { transform: scaleY(-1) scaleX(-1); }

      .nd-hero-v2-box h1 { font-size: 28px; font-weight: 400; }
      .nd-hero-v2-box p { font-size: 16px; }
      .nd-hero-v2-buttons { margin-top: 0; }
      /* Client-reported (2026-07-29): "sizes of buttons" wrong on any slide
         with 2 CTAs (primary + secondary) -- confirmed live: .nd-btn-solid-
         white/.nd-btn-solid-blue/.nd-btn-outline-blue (Hero's own button
         classes, not the shared .nd-dr-cta other components use) have no
         mobile override at all, so both buttons keep their desktop 32px
         horizontal padding while splitting a ~295px-wide row two ways --
         not enough room for the label text, so it wraps to 2 lines and the
         button balloons to 86px tall instead of a clean 60px single-line
         pill. Scoped with :has() (already an established pattern in this
         file, see .nd-pc-grid/.content_top_margin above) to ONLY the
         2-button case -- a single full-width CTA (e.g. slide 1's
         "הפעילויות שלנו") already renders correctly at the base padding/
         font-size and must stay untouched. 146px result matches Figma's
         own node 409:12689's button width (145-147px) closely. */
      .nd-hero-v2-buttons:has(> a:nth-of-type(2)) .nd-btn-solid-white,
      .nd-hero-v2-buttons:has(> a:nth-of-type(2)) .nd-btn-solid-blue,
      .nd-hero-v2-buttons:has(> a:nth-of-type(2)) .nd-btn-outline-blue {
          font-size: 15px; padding: 14px 16px; white-space: nowrap;
      }
  }


@keyframes ndFadeIn { 0% { opacity: 0; } 100% { opacity: 1; } }


  /* --- ND Hero V2 Dots (hand-rolled, built by nd-shared.js — not Slick's) ---
     Client-reported (2026-07-27) these look different from Figma. Confirmed
     via Figma node 405:12598 (get_metadata + pixel-sampling the rendered
     screenshot): 5 plain filled circles, 11px diameter, 30px pitch (19px
     gap), solid white (#ffffff) at rest, solid #002ABA when active -- NO
     border anywhere, on either state. The previous hollow-outline-circle
     treatment (2px white border, transparent fill, solid-fill only when
     active) was a guess that had the same rest/active logic backwards from
     the real spec, exactly the same bug category the testimonial carousel's
     own dots had (see .nd-testimonial-dots below) and were already fixed
     for -- same solid-fill idiom applied here now too, just with hero's
     own white/blue colors instead of testimonial's gray/blue (contextually
     correct: these dots sit over a photo, not a plain white card). */
  .nd-hero-v2-slider .nd-hero-v2-dots {
      position: absolute;
      bottom: 30px;
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 19px;
      list-style: none;
      padding: 0;
      margin: 0;
      z-index: 10;
  }
  .nd-hero-v2-slider .nd-hero-v2-dots li {
      margin: 0;
      padding: 0;
      width: 11px;
      height: 11px;
  }
  .nd-hero-v2-slider .nd-hero-v2-dots li button {
      font-size: 0;
      line-height: 0;
      display: block;
      width: 11px;
      height: 11px;
      padding: 0;
      cursor: pointer;
      color: transparent;
      border: none;
      border-radius: 50%;
      background: #ffffff;
      outline: none;
      transition: background 0.3s ease;
  }
  .nd-hero-v2-slider .nd-hero-v2-dots li button.is-active {
      background: #002aba;
  }

/* --- ND 5 Content Promo ---
   Cloned/redesigned from the legacy 4XCROSS module (ac_addons/4XCROSS.zip
   -- exact title match "4XCROSS Module"; 4XCROSSSmall.zip is a near-
   identical derivative with a different image aspect ratio and extra
   display-mode variants this component doesn't need). Neither legacy zip
   supported more than 4 items or a video option; both are extended here to
   5 items (via nd_component_media_params()'s image/video toggle).

   Card 351.2x458px, radius 32px, shadow 0 0 20px rgba(161,161,161,.25) —
   pulled from Figma node 810:4283 (the row as it sits on the real
   homepage: 5 cards, 26px gaps, 5*351.2 + 4*26 = 1860px total, confirmed
   the live example runs all 5 items, not 4) cross-checked against all 5
   individual card nodes (810:4475/4455/4435/4415/4336). Those 5 nodes are
   NOT 5 different card designs -- each is the SAME card's "Property
   1=Default"/"Property 1=hover" pair for a different real program's
   photo+title (confirmed identical 351.2x458 structure across all 5).
   Idle state (Default) shows only image+title; hovering (810:4484)
   reveals a description paragraph and a "לפרטים נוספים" read-more line
   with an arrow -- that interaction is what drives the CSS hover reveal
   below, not a guess.

   810:4283 itself contains only the 5-card row, no title node -- Figma
   didn't give an exact title spec for this section, so .nd-5cp-title
   reuses the site's existing generic .nd-title-h2 look. The card-to-row
   gap-on-hover (20px -> 8px) and gradient stops came directly from the
   Default vs hover variants' own generated code. */
.nd-5cp-title { font-size: var(--nd-h2-desktop); font-weight: 700; color: var(--nd-color-primary-blue); text-align: center; margin: 0 0 40px 0; }

/* flex-wrap: nowrap, not wrap: Niri confirmed all 5 cards must always stay
   on one row, shrinking together if the viewport is too narrow rather than
   ever wrapping the 5th card to its own line -- confirmed live that plain
   flex-wrap:wrap dropped the 5th card to a second line on any viewport
   under ~1900px, not just small screens, since flexbox decides line breaks
   from the items' natural widths *before* shrinking is applied within a
   line. */
.nd-5cp-row { display: flex; flex-wrap: nowrap; justify-content: center; align-items: flex-start; gap: 26px; max-width: 1860px; margin: 0 auto; padding: 0 20px; }

.nd-5cp-card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    /* flex: 0 1 351.2px (grow:0, shrink:1, basis:351.2px), not a plain
       width -- lets all 5 shrink together to fit the row instead of
       wrapping. min-width:0 is required alongside it: flex items default
       to min-width:auto, which floors shrinking at the item's own content
       size regardless of flex-shrink, and this card's absolutely-positioned
       media layer (below) has no intrinsic width of its own to floor
       against -- without min-width:0 here, confirmed live the cards still
       refused to shrink past a much wider point and forced horizontal
       overflow on the row instead of the intended smooth shrink. */
    flex: 0 1 351.2px;
    min-width: 0;
    height: 458px;
    padding: 20px 20px 40px;
    border-radius: 32px;
    box-shadow: 0 0 20px rgba(161, 161, 161, 0.25);
    overflow: hidden;
    text-decoration: none;
    color: #ffffff;
    background: #ffffff;
}
.nd-5cp-card-media { position: absolute; inset: 0; z-index: 0; }
.nd-5cp-card-media .nd-media,
.nd-5cp-card-media .nd-media-img,
.nd-5cp-card-media video,
.nd-5cp-card-media .nd-media-embed,
.nd-5cp-card-media .nd-media-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border: 0; }

/* Idle: transparent -> rgba(0,42,186,.87). Hover: rgba(0,42,186,.47) ->
   solid #002aba -- both gradient stop sets are exactly what Figma's
   Default/hover variants generated, not interpolated.
   1s, ease-in-out, not 0.3s linear -- matches the other hover-triggered
   transitions on this card (gap, desc/cta reveal) below, all bumped to
   the same duration/easing together. Was 0.5s, then 0.7s; client asked
   again (2026-07-27) for it smoother and a bit longer still, so both the
   duration (0.7s -> 1s) and the easing (plain "ease" -> "ease-in-out",
   which softens the start of the motion too, not just the end) moved
   together this round. Confirmed live that mismatched durations across
   several simultaneously-triggered transitions is exactly what read as
   "bounces really fast": each one finishing at a different moment looks
   like a jerky multi-step snap rather than one smooth motion, even
   though no single transition was itself using a bounce/elastic easing
   curve -- so all of them still move together. */
.nd-5cp-card-overlay {
    position: absolute;
    inset: 0;
    z-index: 1;
    background: linear-gradient(to bottom, rgba(0, 42, 186, 0) 53.275%, rgba(0, 42, 186, 0.87) 100%);
    transition: background 1s ease-in-out;
    border-radius: inherit;
}
.nd-5cp-card:hover .nd-5cp-card-overlay,
.nd-5cp-card:focus-within .nd-5cp-card-overlay {
    background: linear-gradient(to bottom, rgba(0, 42, 186, 0.47) 51.201%, #002aba 100%);
}

/* gap: 0 at rest, not 20px -- flexbox gap reserves space BETWEEN items
   even while they're collapsed (desc/cta sit at max-height:0 but are still
   real flex children), so the old 20px-at-rest value pushed the title up
   off the card's bottom edge by roughly two gaps' worth of empty space
   even with nothing visible below it. Per Figma, the title sits flush at
   the bottom in the idle state -- gap:0 here is what actually achieves
   that instead of leaving accidental reserved whitespace under it. */
.nd-5cp-card-content { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; width: 100%; gap: 0; transition: gap 1s ease-in-out; }
.nd-5cp-card:hover .nd-5cp-card-content,
.nd-5cp-card:focus-within .nd-5cp-card-content { gap: 8px; }

/* Negative horizontal margin, not a smaller font: two of the five real
   (Figma-sourced) titles measured just 1.7px and 4.6px over the card's
   available content width at its full (unshrunk) 351.2px size — enough to
   wrap an entire trailing word to its own line over a few stray pixels.
   Extending the title's own box by 20px total (10px/side) rather than
   shrinking font-size site-wide comfortably fits both with margin to
   spare, confirmed live, without touching how any other card element is
   sized. Still centered: a symmetric negative margin widens the box
   without moving its visual center. */
.nd-5cp-card-title { font-family: 'Assistant', sans-serif; font-size: 26px; font-weight: 700; color: #ffffff; text-align: center; margin: 0 -10px; }
/* Mobile-only tap-to-expand control (see the 767px query further down) --
   desktop's card has no "+"/"x" icon at all, only the title. */
.nd-5cp-card-icon { display: none; }

/* Present in the DOM whenever there's real description/link content
   (needed for the hover reveal to have something to reveal), but
   collapsed to invisible at rest -- only :hover/:focus-within (keyboard
   access to the whole-card link) expands them. This is a CSS
   approximation of Figma's two discrete static states, not something
   Figma specifies a transition curve for.
   max-height ceiling raised from 120px to 140px: the description field's
   own char limit is now separately capped at 140 characters (client-
   requested, see nd_component_char_limit()'s '5cp_description' entry) --
   140 real characters at the new 16px/400 size (below; was 20px/600)
   comfortably needs a slightly taller ceiling than the 120px tuned for
   the old, larger/bolder text. Duration/easing bumped 0.7s ease -> 1s
   ease-in-out, alongside the other transitions on this card above, per
   the client's follow-up "smoother and a bit longer" request. */
.nd-5cp-card-desc,
.nd-5cp-card-cta {
    /* min-height:0 -- same reason .nd-5cp-card already needs min-width:0
       elsewhere in this file: .nd-5cp-card-cta is itself a flex container
       (for its icon+text row) AND a flex item of .nd-5cp-card-content's
       column -- flex items default to min-height:auto, which floors
       shrinking at the item's own content size (here, the 20px svg icon)
       regardless of max-height. */
    min-height: 0;
    opacity: 0;
    max-height: 0;
    overflow: hidden;
    transition: opacity 1s ease-in-out, max-height 1s ease-in-out, padding 1s ease-in-out;
}
.nd-5cp-card:hover .nd-5cp-card-desc,
.nd-5cp-card:hover .nd-5cp-card-cta,
.nd-5cp-card:focus-within .nd-5cp-card-desc,
.nd-5cp-card:focus-within .nd-5cp-card-cta {
    opacity: 1;
    max-height: 140px;
}
/* 16px/400, not 20px/600 -- client-requested (2026-07-27), a deliberate
   move away from Figma's own bolder/larger measured value for both the
   description and the CTA line below. */
.nd-5cp-card-desc { font-family: 'Assistant', sans-serif; font-size: 16px; font-weight: 400; color: #ffffff; text-align: center; margin: 0; }
/* padding:0 at rest, restored to 10px 0 only on hover (below) -- box-
   sizing:border-box alone does NOT make max-height:0 collapse this to
   true 0: border-box means padding is subtracted from the specified
   height to get content height, but content height can't go negative, so
   whenever padding exceeds max-height (20px of padding vs a 0 max-height),
   the rendered box floors at the padding amount regardless of box-sizing.
   Confirmed live -- border-box + min-height:0 + max-height:0 alone still
   rendered a real 20px (exactly the padding sum) until padding itself was
   also brought to 0 at rest. */
.nd-5cp-card-cta { display: flex; align-items: center; justify-content: center; gap: 11px; font-family: 'Assistant', sans-serif; font-size: 16px; font-weight: 400; color: #ffffff; padding: 0; }
.nd-5cp-card:hover .nd-5cp-card-cta,
.nd-5cp-card:focus-within .nd-5cp-card-cta {
    padding: 10px 0;
}
/* Client-requested: Figma has the arrow shift on hover, missing here --
   the icon+text pair is otherwise static once revealed. RTL context, and
   the arrow's own path already points left (the reading-forward
   direction here) -- translateX(-4px) nudges it further that same way,
   a conventional "invitation to click" micro-motion, not a direction
   flip. Transitions on its own (not inherited from a parent rule) so it
   animates independently of the reveal above.
   margin-left:4px reserves the exact room the hover shift needs: the svg
   sits flush against the row's own edge with no natural slack (flex
   child, no margin), and .nd-5cp-card itself has overflow:hidden for its
   rounded corners -- without this reserved space, client-reported the
   shifted arrow's tip gets clipped by that overflow instead of just
   sliding into empty space. */
.nd-5cp-card-cta svg { flex-shrink: 0; margin-left: 4px; transition: transform 0.3s ease; }
.nd-5cp-card:hover .nd-5cp-card-cta svg,
.nd-5cp-card:focus-within .nd-5cp-card-cta svg {
    transform: translateX(-4px);
}

/* Tablet range (768-991px): this breakpoint and the horizontal-scroll
   treatment were a judgment call at the time (Figma's homepage frame,
   810:4283, is a single 1860px-wide reference with no mobile variant) --
   kept here for tablet, but superseded below at the 767px phone
   breakpoint now that the real mobile frame (node 829:2899) turned up
   during this pass: cards stop shrinking (fixed 280px via flex-shrink:0)
   and the row scrolls horizontally instead. justify-content switches to
   flex-start specifically (not kept at center): centering an overflowing
   flex container is a known cross-browser quirk where only one direction
   becomes scrollable and the other side's content gets clipped. */
@media screen and (max-width: 991px) {
    .nd-5cp-title { font-size: var(--nd-h2-mobile); }
    .nd-5cp-card-title { font-size: 22px; }
    /* desc/cta font-size override removed here -- both are 16px at the
       base rule now (client-requested, see above), so this would have
       been a no-op duplicate. */
    .nd-5cp-row {
        justify-content: flex-start;
        overflow-x: auto;
        padding-bottom: 12px;
        -webkit-overflow-scrolling: touch;
    }
    .nd-5cp-card { flex: 0 0 280px; }
}

/* Phone (node 829:2899, "program3", plus the per-card open states at
   829:2153/2170/2187/2204/2221): not a smaller/scrolling version of the
   tablet row above -- Figma shows 5 cards stacked in a single column.
   Each is tap-to-expand: closed is a compact 106px row (just "+" icon +
   title), tapping the icon reveals description/CTA and grows the card
   (the "hover" variant in each of those 5 Figma nodes -- there's no real
   hover on touch, so nd-shared.js toggles a real .is-expanded class
   instead, reusing the exact same :hover/:focus-within reveal rules
   already built for desktop below rather than duplicating them). */
@media screen and (max-width: 767px) {
    .nd-5cp-row { flex-direction: column; flex-wrap: nowrap; overflow-x: visible; gap: 13px; padding: 0 20px; }
    /* justify-content:center (not flex-end): closed state is just the
       header row and should sit vertically centered in the compact
       106px card, not pinned to the bottom -- flex-end only makes sense
       once expanded content pushes the card taller, so it's re-set to
       flex-start specifically for .is-expanded below. */
    .nd-5cp-card { flex: 1 1 auto; width: 100%; height: auto; min-height: 106px; padding: 20px; justify-content: center; }
    .nd-5cp-card.is-expanded { justify-content: flex-start; }
    /* touch-action:manipulation -- disables the mobile browser's default
       double-tap-to-zoom gesture recognition (and the ~300ms tap delay
       that comes with it) on this control specifically. A rapid second
       tap in roughly the same spot -- exactly what closing this toggle
       requires -- is what that gesture recognizer is designed to catch,
       and it can swallow/delay the second tap's click event while it
       decides whether the pair of taps means "zoom" instead. */
    .nd-5cp-card-header { display: flex; align-items: center; width: 100%; gap: 10px; cursor: pointer; touch-action: manipulation; }
    /* DOM order is [title, icon] -- in this RTL wrapper a plain (non-
       reversed) row already puts the first child (title) on the right
       and the second (icon) on the left, same idiom as .nd-hub-card-cta's
       own "text then arrow, arrow ends up on the left" comment elsewhere
       in this file. That's what actually pins the icon to the card's far
       left edge: title has flex:1 (fills all remaining width itself, so
       there's no leftover space for the pair to merely cluster together
       off-center). */
    .nd-5cp-card-title { font-size: 20px; flex: 1 1 auto; margin: 0; text-align: right; }
    /* Plain 24x24 -- no longer needs its own enlarged tap target now that
       the whole .nd-5cp-card-header row is the click/tap target (see
       nd-shared.js), not just this icon. */
    .nd-5cp-card-icon {
        flex-shrink: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;
        transition: transform 0.3s ease;
    }
    .nd-5cp-card-icon svg { display: block; width: 100%; height: 100%; }
    /* "+" -> "x" is a 45deg rotation of the same plus glyph, not a second
       icon asset -- same trick as the header hamburger's own open/close
       transform elsewhere in this file. */
    .nd-5cp-card.is-expanded .nd-5cp-card-icon { transform: rotate(45deg); }
    .nd-5cp-card-content { align-items: stretch; width: 100%; }
    /* Only opacity here (not max-height) -- nd-shared.js sets max-height
       as an exact inline value (scrollHeight) on open/close instead, same
       proven mechanism as ND Accordion's own toggle. A max-height set via
       this class would still work in principle, but real inline styles
       set by JS on every toggle are simpler to reason about and match
       the one interactive pattern in this file already confirmed
       reliable, rather than debugging a second, subtly different
       fixed-ceiling version further. */
    .nd-5cp-card.is-expanded .nd-5cp-card-content { gap: 8px; }
    .nd-5cp-card.is-expanded .nd-5cp-card-desc,
    .nd-5cp-card.is-expanded .nd-5cp-card-cta { opacity: 1; }
    .nd-5cp-card.is-expanded .nd-5cp-card-cta { padding: 10px 0; }
    /* Client-requested: the open card should be 252px tall on mobile.
       min-height, not height -- guarantees this minimum while still
       letting a genuinely long real description (up to the field's own
       140-char cap) grow the card taller instead of clipping it. */
    .nd-5cp-card.is-expanded { min-height: 252px; }
    /* desc/cta font-size overrides removed here -- both are 16px at the
       base rule now (client-requested, see above), so these would have
       been no-op duplicates. */
    .nd-5cp-card-desc { text-align: right; }
    .nd-5cp-card-cta { justify-content: flex-end; }
    /* Gradient starts at 8.3% (Figma's own measured 28.302% minus a
       further 20-percentage-point extension Niri asked for on top of it,
       darkening still more of the card) -- desktop's unrelated rule is
       53.275%. .is-expanded reuses the desktop :hover gradient verbatim,
       same reasoning as the reveal rules above. */
    .nd-5cp-card-overlay { background: linear-gradient(to bottom, rgba(0, 42, 186, 0) 8.3%, rgba(0, 42, 186, 0.87) 100%); }
    .nd-5cp-card.is-expanded .nd-5cp-card-overlay { background: linear-gradient(to bottom, rgba(0, 42, 186, 0.47) 51.201%, #002aba 100%); }
}

/* --- ND Iframe ---
   Only max-width, deliberately not width:100%: html_content is raw,
   editor-pasted embed markup (YouTube/Vimeo iframe, third-party form
   snippet) that isn't ours to restyle beyond preventing horizontal
   overflow on narrow screens — forcing width would fight an embed that's
   deliberately a fixed small size (e.g. a badge widget).
   padding:0 20px on the OUTER wrapper (not .nd-iframe-embed itself, which
   stays untouched per the reasoning above) -- client-reported this
   section ran edge-to-edge with no side breathing room on wide screens.
   Matches the same 20px side padding every other full-width ND section
   already uses (Content Marker, 5 Content Promo, Power Counter, etc.). */
.nd-iframe { margin-top: 70px; padding: 0 20px; }
.nd-iframe-title { font-size: 60px; font-weight: 700; line-height: 1; letter-spacing: -2px; text-transform: uppercase; color: #002ABA; margin: 0 0 16px 0; text-align: center; }
/* text-align:center, not just max-width -- client-reported (2026-07-28)
   the gutter around a real embed (Visme) was inconsistent left vs right.
   Confirmed live: Visme's own script sizes its injected iframe to a
   specific computed width narrower than this container (~1501px in a
   ~1541px-wide container), and an iframe with no explicit centering is
   just a normal inline-replaced element -- in this RTL wrapper, that
   means it sits flush against its own "start" edge (the right side),
   dumping the entire leftover gap onto the left only. text-align:center
   centers it regardless of direction or Visme's exact computed width, so
   this works for any embed that ends up narrower than its container, not
   just this one. */
.nd-iframe-embed { max-width: 100%; overflow-x: auto; text-align: center; }
.nd-iframe-embed iframe { max-width: 100%; }
@media screen and (max-width: 991px) {
    .nd-iframe-title { font-size: var(--nd-h3-mobile); letter-spacing: -1px; }
}
/* Phone (node 927:2081): 28px, not the 18px --nd-h3-mobile value. */
@media screen and (max-width: 767px) {
    .nd-iframe-title { font-size: 28px; letter-spacing: normal; }
}

/* --- ND Hub Programs ---
   Redesigned against Figma per Niri's explicit review: title/media overlay
   (node 398:11675), tab bar (node 383:6556), and cards (node 366:4324) —
   the original build session's own docblock already flagged the tab/card
   styling as "a judgment call, not Figma-confirmed", so this replaces that
   guess with the real spec rather than patching around it.
   .nd-hub-top is still the position:relative anchor for the breadcrumb
   overlay (reuses .nd-hero-v2-breadcrumbs/.nd-breadcrumb-* as-is — same
   absolute top-right positioning, no new breadcrumb CSS needed). */
/* margin-top:70px -- explicit gap from whatever section precedes this one
   (e.g. ND 5 Content Promo on the homepage) per Niri's request; neither
   component had any top/bottom spacing of its own before, so the gap
   between them was whatever WPBakery's row/section wrapper happened to
   add by default, not a deliberate value. This only makes sense when
   something actually precedes the component, though — on Hub Page (ND)
   this is the FIRST component on the page, so the 70px (plus the legacy
   theme wrapper's own 100px, see the :has() rule below) showed up as an
   unwanted gray gap directly under the header. Both are zeroed together
   below whenever that's the case, the same technique Hero Carousel V2
   already uses for itself. */
.nd-hub-top { position: relative; max-width: 1860px; margin: 70px auto 0; padding: 0 20px; }
.content.content_top_margin:has(.nd-hub-programs) { padding-top: 0 !important; margin-top: 0 !important; }
.content.content_top_margin:has(.nd-hub-programs) .nd-hub-top { margin-top: 0; }
/* Homepage (ND, page-id-33231) has ND 5 Content Promo directly above Hub
   Programs, unlike Hub Page (ND) where Hub Programs is the page's FIRST
   component -- the :has() rule above only checks whether .nd-hub-programs
   exists ANYWHERE inside the content wrapper, not whether anything
   precedes it, so it was zeroing this gap on Homepage too and leaving no
   visible space under the promo tiles. !important + the page-id selector
   is scoped to this one page only, restoring the 70px gap without
   touching Hub Page (ND)'s own correct zero-gap behavior. */
/* No longer desktop-only: the previous pass scoped this to 992px+ since
   only desktop had been flagged at the time. Client has now asked for
   "space above the title, only if a preceding component exists" without
   a desktop/mobile qualifier, and this exact page (Homepage (ND)) is the
   one real case where that condition applies -- removing the media query
   restores the same 70px on every screen size, not just desktop. */
body.page-id-33231 .nd-hub-top { margin-top: 70px !important; }

/* Title+description overlaid ON the media (not plain text in a separate
   area above it like before) — confirmed against Figma's actual Hub Page
   frame (398:11675), not just the tab/card sub-nodes.
   Second pass: get_design_context on the actual title node (398:11778)
   shows white text (not #002ABA blue), regular/400 weight (not 700), and
   the whole title+description block sitting flush against the media's
   BOTTOM edge (not vertically centered) — the first pass's blue/bold/
   centered treatment was built from an inspector export that didn't
   actually match this node. Overlay gradient switched from light-at-
   bottom (which the old blue-text version needed) to dark-at-bottom,
   which is what white text needs for contrast against an arbitrary
   editor-uploaded photo; Figma's own frame relies on the photo's natural
   darker area here rather than a separate gradient layer, so this is
   still a judgment call for arbitrary photos, just now oriented the
   right direction for white text instead of blue. */
.nd-hub-media-wrap { position: relative; width: 100%; max-height: 600px; overflow: hidden; border-radius: 32px; margin-top: 24px; }
.nd-hub-media-wrap .nd-media-img,
.nd-hub-media-wrap video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* padding-bottom: 100px, not the 40px every other side uses -- the tab
   pill (.nd-hub-tabs-has-media .nd-hub-tab-bar) overlaps 60px up into
   the bottom of this same image, so a plain 40px clearance let the
   pill cover the last ~20px of text (a wrapped description's second
   line, confirmed live). 100px clears the 60px overlap with a 40px
   buffer to match the other sides' breathing room. */
.nd-hub-media-overlay {
    position: absolute; inset: 0; z-index: 2;
    display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
    text-align: center; padding: 40px 40px 100px;
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.55) 100%);
}
/* Figma node 398:11778/398:11780 (get_design_context, verbatim): color
   white, font-size 60px, weight 400 (regular -- NOT 700), line-height
   100%, letter-spacing -2px, uppercase (a no-op on Hebrew text, kept
   anyway for any future English use), center-aligned.
   font-size:60px is the FINAL/settled size here -- the transform:scale
   reveal below is kept from the previous pass (its ~0.883->1 ratio came
   from Figma's own two-state size reference, node 723:3714, and Niri
   didn't say to drop the animation, only that the settled size/color/
   weight/position were wrong), just recalculated against the correct
   60px base instead of the wrong 67.81px this replaced. */
.nd-hub-media-overlay .nd-hub-title {
    color: #ffffff;
    margin: 0 0 12px 0;
    font-family: 'Assistant', sans-serif;
    font-size: 60px;
    font-weight: 400;
    line-height: 1;
    letter-spacing: -2px;
    text-transform: uppercase;
    text-align: center;
    /* opacity:1 forced -- this title also carries the generic .nd-fade-in
       class (to hook into the shared IntersectionObserver with no JS
       changes needed), whose default is a fade-from-invisible; Figma's
       two-state reference is scale-only, so opacity is pinned here to
       keep this to ONE effect instead of a fade+scale combination that
       isn't what was shown. */
    opacity: 1;
    transform: scale(0.883);
    transform-origin: center;
    transition: transform 0.8s ease-out;
}
.nd-hub-media-overlay .nd-hub-title.is-visible { transform: scale(1); }
/* font-size was never actually set here before -- it silently inherited
   the theme's global 14px base font-size instead of Figma's 24px
   (confirmed live via getComputedStyle up the whole ancestor chain, all
   14px), not a regression from any earlier pass. */
.nd-hub-media-overlay .nd-hub-desc { color: #ffffff; font-family: 'Assistant', sans-serif; font-size: 24px; font-weight: 400; line-height: normal; margin: 0; max-width: 800px; }

/* Fallback for when media is off (e.g. the real homepage placement --
   confirmed live that's exactly the path homepage-nd actually takes, no
   media configured there) -- nothing to overlay onto, so no dark/light
   scrim, but the title itself uses the SAME exact spec as the overlay
   version above (this was the actual bug behind "title still shows as
   24px": the fix only ever touched .nd-hub-media-overlay .nd-hub-title,
   which this standalone path never matches, so it kept whatever
   font-size it happened to inherit instead of the real 60px spec). Same
   scale-reveal animation too, for visual consistency between whichever
   path a given placement takes. */
.nd-hub-title-standalone {
    color: #002aba;
    margin: 0 0 16px 0;
    font-family: 'Assistant', sans-serif;
    font-size: 60px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -2px;
    text-transform: uppercase;
    text-align: center;
    opacity: 1;
    transform: scale(0.883);
    transform-origin: center;
    transition: transform 0.8s ease-out;
}
.nd-hub-title-standalone.is-visible { transform: scale(1); }
.nd-hub-desc-standalone { text-align: center; max-width: 800px; margin: 0 auto 24px auto; }

.nd-hub-tabs { max-width: 1860px; margin: 40px auto 0; padding: 0 20px; }
/* When a hero media image is showing above, the tab pill should overlap
   its bottom edge by half the pill's own height (per Figma node
   398:11675 / 863:1960: the tabs+cards frame starts ~62px above the
   561px-tall hero image's bottom edge, and the pill's rendered height is
   120px, so half is 60px). Negative margin-top on the pill itself (not
   transform: translateY), so the pill actually participates in flow at
   its new position -- the cards below close up to meet it instead of
   leaving a leftover gap the way a transform (paint-only, flow-
   unaffected) would.
   .nd-hub-tabs-has-media's own margin-top is zeroed rather than left at
   the base rule's 40px: confirmed live that a block's margin-top
   collapses with its first child's margin-top when nothing (border/
   padding/content) separates them, so the intended -60px on the pill was
   actually combining with the parent's +40px into a net -20px (per the
   "positive max + negative min" collapse rule) -- only a ~17% overlap
   instead of the intended 50%. Zeroing the parent's contribution here
   removes that collapse arithmetic entirely rather than compensating for
   it with a bigger negative number that would break again if the
   parent's own margin ever changes. Only applied via the
   nd-hub-tabs-has-media modifier PHP adds when media_html is non-empty;
   the media-off/standalone layout keeps the base rule's 40px untouched. */
.nd-hub-tabs-has-media { margin-top: 0; }
.nd-hub-tabs-has-media .nd-hub-tab-bar { position: relative; z-index: 3; margin-top: -60px; }
/* Single white pill container (not separate bordered buttons per tab) --
   node 383:6556: one rounded-32 card with shadow holds all tabs; plain
   tabs have no background/border of their own, only the active one gets
   its own smaller solid-blue rounded-18 pill inside the container. */
/* width:fit-content + margin:0 auto, not a plain block flex row: a
   display:flex container with no explicit width stretches to fill its
   parent (block-level layout default) even though its own content only
   needs ~1208px (4 tabs x 280px + 3 gaps x 16px + 2 x 20px padding) --
   confirmed live this was exactly why the white pill read as "loose",
   the background itself was spanning the full 1820px row width with
   justify-content:center just centering the tabs INSIDE that oversized
   box, rather than the pill hugging the tabs the way Figma shows.
   max-width:100% + overflow-x:auto keeps the same horizontal-scroll
   fallback on narrow screens where fit-content's ~1208px doesn't fit. */
.nd-hub-tab-bar {
    display: flex; align-items: center; justify-content: center; gap: 16px;
    background: var(--nd-color-white); border-radius: 32px; box-shadow: 0 0 20px rgba(161, 161, 161, 0.25);
    padding: 20px; margin: 0 auto 32px; width: fit-content; max-width: 100%; overflow-x: auto;
}
.nd-hub-tab-btn {
    background: transparent; border: none; color: var(--nd-color-primary-blue);
    font-size: 24px; font-weight: 400; padding: 24px 0; width: 280px; flex-shrink: 0;
    border-radius: 18px; cursor: pointer; transition: background 0.3s ease, color 0.3s ease;
    font-family: 'Assistant', sans-serif;
}
.nd-hub-tab-btn:hover { background: rgba(0, 42, 186, 0.05); }
.nd-hub-tab-btn.is-active { background: var(--nd-color-primary-blue); color: var(--nd-color-white); }

.nd-hub-tab-panel { display: none; }
.nd-hub-tab-panel.is-active { display: block; }

/* Cards stretch to fill the full row width with a fixed 20px gap between
   them, regardless of how many are showing in a given tab (a tab with
   fewer than 5 programs previously left the fixed-351px cards centered
   with large empty margins on both sides instead of using the space) --
   direct client override of Figma's own static 5-card reference (which
   only ever shows exactly 5 cards at 351.2px + 26px gaps filling 1860px
   edge to edge, so it never had to define this fewer-than-5 case).
   Second pass: capped at 4 cards per row (up to 12 items can now show per
   tab off the homepage, and an uncapped stretchy row of 12 across would
   be absurd) -- still flexbox, not CSS Grid, specifically so the
   fewer-than-4 stretch behavior above survives: .nd-hub-card's
   flex-basis of exactly 1/4 the row (minus its share of the 3 gaps) is
   what forces a 5th card to wrap instead of the row growing past 4, while
   flex-grow:1 still lets 1-3 leftover cards on a partial last row expand
   to fill it, exactly like before. A plain `display:grid;
   grid-template-columns:repeat(4,1fr)` would cap the row correctly too,
   but leaves a partial row's leftover columns empty instead of
   stretching the cards that ARE there -- confirmed this matters here
   since the homepage's own default cap is 4 (a single, possibly partial,
   row) even more often than the 12-item off-homepage case. */
.nd-hub-grid { display: flex; flex-wrap: wrap; gap: 20px; }
/* Fixed card height (like Figma's own 552px reference) with the image as
   the one flexible piece -- keeps every card in a row the same height
   regardless of how long a real post's excerpt runs, matching Figma's
   fixed-card/flexible-image structure rather than a fixed image + variable
   card. min-width prevents cards from collapsing to nothing if many are
   ever shown in one row. */
/* flex-grow:0, not 1 (2026-08-03, client-reported): a full row of 4 cards
   already sums to exactly 100% via flex-basis alone (4 x calc(25% - 15px)
   + 3 x 20px gap = 100%), so grow was never actually needed to fill a
   complete row -- it only ever did anything on an INCOMPLETE last row,
   where a lone orphaned card (nothing else to share flex-grow with)
   stretched to fill the entire row width instead of staying card-sized.
   flex-shrink stays 1 (min-width:240px still guards against collapsing
   too far). */
.nd-hub-card {
    display: flex; flex-direction: column; gap: 20px; flex: 0 1 calc(25% - 15px); min-width: 240px; max-width: 100%; height: 480px;
    padding: 20px; border-radius: 32px;
    background: var(--nd-color-white); box-shadow: 0 0 20px rgba(161, 161, 161, 0.25); text-decoration: none; color: inherit;
    transition: transform 0.3s ease;
}
.nd-hub-card:hover { transform: translateY(-4px); }
/* flex:1 + min-height:0, not a fixed height: same "flex item won't shrink
   below its own content's automatic minimum" gotcha documented elsewhere
   in this file (ND 5 Content Promo's cta, ND Puzzle etc.) -- without
   min-height:0 here the image refuses to shrink to fit the fixed card
   height whenever a real post excerpt runs long. Image is padded inside
   the white card (not edge-to-edge) with its own 28px radius, per Figma --
   not the previous full-bleed-to-the-card-edge treatment. */
/* position:relative anchor for the img below -- percentage height on a
   flexed (flex-grow, not an explicit height) item is exactly the kind of
   case some browsers don't reliably treat as a "definite" size for a
   descendant's height:100% to resolve against; confirmed live the image
   was actually falling back to its own intrinsic aspect-ratio height
   (leaving the grey placeholder background showing above/below it)
   instead of stretching to fill this container, even though the
   container's OWN flex-grown height was measured as correct. Same
   position:absolute/inset:0 fill technique already used everywhere else
   in this codebase for exactly this reason (.nd-5cp-card-media,
   .nd-hero-v2-media-desktop, etc.) rather than percentage height. */
.nd-hub-card-img { position: relative; flex: 1 1 0; min-height: 0; width: 100%; border-radius: 28px; overflow: hidden; background: #d9d9d9; transition: flex-grow 0.5s ease; }
.nd-hub-card-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* gap:0 at rest (not 8px) -- same reason ND 5 Content Promo's content
   block needs gap:0 at rest: flexbox gap reserves space between items
   even while collapsed, which would leave the title sitting slightly
   above the card's true bottom instead of flush against it. */
.nd-hub-card-content { display: flex; flex-direction: column; gap: 0; flex-shrink: 0; transition: gap 0.5s ease; }
.nd-hub-card:hover .nd-hub-card-content,
.nd-hub-card:focus-within .nd-hub-card-content { gap: 8px; }
/* 26px, not 28px -- client-requested (2026-07-27); weight was already 700. */
.nd-hub-card-title { font-family: 'Assistant', sans-serif; font-size: 26px; font-weight: 700; color: #000000; margin: 0; text-align: right; }
/* Idle (366:4322): image + title only. Hover (366:4323): description +
   CTA arrow reveal underneath, image shrinking to compensate so the
   card's own height never changes -- same idle/hover split as ND 5
   Content Promo, and the same three-part fix that reveal needed here:
   min-height:0 (flex-item-won't-shrink-below-content gotcha), and
   padding animated to/from 0 alongside max-height (border-box alone
   doesn't collapse a box below its own padding sum -- both confirmed
   live during that component's own fix, applied directly here instead
   of re-discovering them from scratch). */
.nd-hub-card-desc,
.nd-hub-card-cta {
    min-height: 0;
    opacity: 0;
    max-height: 0;
    overflow: hidden;
    transition: opacity 0.5s ease, max-height 0.5s ease, padding 0.5s ease;
}
.nd-hub-card:hover .nd-hub-card-desc,
.nd-hub-card:hover .nd-hub-card-cta,
.nd-hub-card:focus-within .nd-hub-card-desc,
.nd-hub-card:focus-within .nd-hub-card-cta {
    opacity: 1;
    max-height: 160px;
}
.nd-hub-card-desc { font-family: 'Assistant', sans-serif; font-size: 20px; font-weight: 400; color: #000000; margin: 0; text-align: right; }
/* Same text-then-arrow CTA language as ND 5 Content Promo's card link
   (arrow after the text, left side in RTL) -- a visual affordance only,
   the whole card is already the link. padding:0 at rest, restored only on
   hover, for the same border-box-can't-shrink-below-padding reason as
   that component's cta. */
.nd-hub-card-cta { display: flex; align-items: center; justify-content: flex-end; gap: 11px; font-family: 'Assistant', sans-serif; font-size: 20px; font-weight: 700; color: var(--nd-color-primary-blue); padding: 0; }
.nd-hub-card:hover .nd-hub-card-cta,
.nd-hub-card:focus-within .nd-hub-card-cta {
    padding: 10px 0;
}
.nd-hub-card-cta svg { flex-shrink: 0; }

.nd-hub-cta { display: block; width: fit-content; margin: 32px auto 0; }
/* max-width:600px here used to constrain a BARE CF7 form (a plain
   single-column stacked box); tab 3 now renders the FULL
   nd_join_us_extended component (photo + 2-column form, own max-width
   1578px, own responsive breakpoints) via render_tab_form(), which needs
   the tab panel's real available width, not an artificial 600px cap. */
.nd-hub-form-box { width: 100%; }
/* nd_join_us_extended's own wrapper carries a 70px top margin + 20px side
   padding meant for it sitting as its own standalone page section --
   redundant/wrong once it's nested inside a Hub Programs tab panel, which
   already provides its own spacing context. */
.nd-hub-form-box .nd-component-wrapper.nd-join-us-extended { margin-top: 0; padding: 0; }

/* Empty/hidden except at the phone breakpoint below, where nd-shared.js's
   Slick instance appends its arrows/dots into it -- see .nd-hub-grid-nav
   inside that query for why (desktop never turns .nd-hub-grid into a
   Slick carousel at all). */
.nd-hub-grid-nav { display: none; }

@media screen and (max-width: 991px) {
    .nd-hub-media-overlay { padding: 24px; }
    .nd-hub-tab-btn { font-size: 18px; width: 200px; padding: 16px 0; }
    .nd-hub-tab-bar { padding: 12px; gap: 8px; }
    .nd-hub-card { flex: 1 1 100%; width: 100%; min-width: 0; height: auto; min-height: 420px; }
    .nd-hub-card-title { font-size: 22px; }
    .nd-hub-card-desc { font-size: 16px; }
}

/* Phone (node 835:3420): not a smaller wrapping grid -- Figma shows a real
   horizontal carousel (arrows + dots), same Slick-driven idiom as
   ND Testimonial's own carousel (nd-shared.js toggles this on/off across
   the 767px boundary via matchMedia). Cards also drop the hover-reveal
   behavior entirely (desc/cta always visible -- there's no hover on
   touch, and Figma's mobile card shows full content at rest). */
@media screen and (max-width: 767px) {
    /* Homepage's own standalone (no-media) title block (node 835:3337):
       zeroed entirely by the ".content_top_margin:has()" override above so
       the legacy theme's 100px push doesn't apply, but Figma's own mobile
       frame still wants a real 36px gap above "בואו לקחת חלק" itself --
       scoped via :has(.nd-hub-title-standalone) so this only restores that
       gap for the standalone/homepage case, not the with-media Hub Page
       layout (which has its own, different, top-spacing rules).
       Full selector prefix repeated (not just ".nd-hub-top:has(...)"): the
       zero rule above is 3 classes deep (.content.content_top_margin
       .nd-hub-top), so a shorter 2-class version here lost the specificity
       fight regardless of media-query nesting or source order -- confirmed
       live this was exactly why the first attempt had no effect. */
    .content.content_top_margin:has(.nd-hub-programs) .nd-hub-top:has(.nd-hub-title-standalone) { margin-top: 36px; }

    /* Compact equal-width pill (node 835:3089): fits all 4 tabs in one row
       at 393px with no scroll, unlike the 991px tablet rule above which
       still relies on the horizontal-scroll fallback at a reduced (but
       still fixed) 200px tab width. width:100% overrides the desktop
       base rule's width:fit-content -- fit-content sizing fought with the
       tabs' own flex:1 1 0 (each computes its "fill available space" share
       against an ambiguous fit-content container), collapsing the pill to
       a tight, too-narrow box instead of Figma's near-edge-to-edge 357/393
       width. */
    .nd-hub-tab-bar { width: 100%; padding: 8px; gap: 0; border-radius: 20px; overflow-x: visible; }
    .nd-hub-tab-btn { flex: 1 1 0; width: auto; font-size: 16px; padding: 12px 4px; border-radius: 12px; }

    /* Cards bleed to the viewport edge (node 835:3098's static reference
       renders the row centered with the outer cards cut off by its own
       752px-tall frame's overflow, not neatly inset) -- the negative
       margin cancels .nd-hub-tabs' own 20px side padding (shared with the
       tab pill above, which DOES want that inset) specifically for this
       carousel row, so the first/last card can peek off-screen like
       Figma shows instead of sitting flush inside a 20px gutter. */
    .nd-hub-grid { flex-wrap: nowrap; gap: 0; margin: 0 -20px; }
    /* Slick (this carousel's own JS-driven library) wraps .nd-hub-grid's
       children in its own .slick-list, which carries Slick's default
       overflow:hidden -- needed to actually clip the track horizontally to
       one "page" of cards, but it was also clipping each card's own
       vertical box-shadow (client-reported: "shadow gets cut off"), since
       the shadow paints outside the card's own box, and slick-list's box
       stopped exactly at the card's edge. Standard fix for this exact
       Slick behavior: give slick-list real padding so the shadow has room
       to paint inside its (still horizontally-clipping) box, then cancel
       the resulting layout push with a matching negative margin so
       nothing shifts. 20px matches the card's own shadow blur radius
       (box-shadow: 0 0 20px). */
    .nd-hub-grid .slick-list { padding: 20px 0 !important; margin: -20px 0; }
    /* height:auto -> 455px, fixed (client-reported, 2026-07-29): cards were
       visibly uneven sizes on mobile whenever a post had no description
       (a shorter card, empty <p>) or a title long enough to wrap to 2 lines
       (a taller card) -- confirmed live: 373px/399px/425px all present in
       the same grid. 455px covers the tallest real combination measured
       (1-line title + 2-line desc + cta = 425px) plus headroom for a
       2-line title too (adds one more title line-height, ~26px).
       flex:0 0 auto is REQUIRED alongside the explicit height, not
       redundant -- .nd-hub-card's own base/desktop rule sets
       `flex: 0 1 calc(25% - 15px)` (a percentage flex-basis, for the
       desktop ROW grid) and this mobile block never resets it. On
       .nd-hub-grid-stacked (Hub Page's own full-width column layout,
       flex-direction:column), that leftover flex-basis becomes the MAIN
       axis (height) basis and overrides a plain `height` property outright
       -- confirmed live this silently made `height:455px` render as ~25%
       of the grid's own height instead, with zero visible effect otherwise.
       flex:0 0 auto forces sizing back onto the explicit height/width
       properties on every layout mode this card is used in.
       Scoped to .nd-hub-grid (not bare .nd-hub-card), and display:flex
       forced with !important: in the carousel layout this card is also a
       Slick .slick-slide, and Slick's own CSS (loaded after this
       stylesheet, same pattern as the .slick-list override just above)
       fights this rule on two properties --
       `.slick-slide { height: 100% }` (1 class, beaten cleanly by this
       rule's 2-class selector) and `.slick-initialized .slick-slide
       { display: block }` (2 classes, an exact specificity TIE that Slick
       then won on source order alone). Losing either one silently broke
       this fix: height:100% collapsed cards back to content-based auto
       height (uneven again), and display:block killed the flex column
       entirely, so gap:25px (the image/title spacing, also client-reported)
       stopped applying too even though the property itself was correct.
       width:305px (not 265px) -- confirmed against Figma node 835:3116
       via get_design_context (client-reported the card "doesn't look like
       Figma", 2026-08-03): 265px is the IMAGE's own width there, not the
       card's. .nd-hub-card-img is `width:100%` (base rule, fills its
       parent's content box), so a 265px-wide CARD with this same 20px
       padding was rendering a 225px-wide image -- visibly narrower/more
       cropped than Figma's real 265px, and the card itself 40px too
       narrow overall. 305 = 265 (image) + 20+20 (padding), border-box, so
       the image resolves back to the full 265px Figma actually specifies.
       gap 25px and border-radius 20px (card) are also straight off that
       same node -- both were previously unverified guesses (32px/32px). */
    .nd-hub-grid .nd-hub-card { display: flex !important; width: 305px; height: 455px; flex: 0 0 auto; padding: 20px; margin: 0 10px; gap: 25px; border-radius: 20px; }
    /* border-radius:20px, not the desktop/base 28px -- also Figma node
       835:3116 (get_design_context), confirmed 2026-08-03. */
    .nd-hub-card-img { flex: 0 0 231px; height: 231px; border-radius: 20px; }
    /* Clamped to a fixed line count (not a fixed card height) so every
       card comes out the same size regardless of how long its real
       title/excerpt runs -- Figma's own static reference happens to use
       similar-length placeholder text per card, which hid this; real CMS
       content varies enough that height:auto made cards visibly uneven.
       2 lines, not 1: client-requested (2026-07-27), so a real title long
       enough to wrap isn't cut down to a single truncated line. */
    .nd-hub-card-title {
        font-size: 20px;
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    }
    /* flex:1 (client-reported, 2026-08-03): title/desc length varies per
       post, so .nd-hub-card-content's own natural height varies too --
       without growing to fill the card's fixed 455px, the CTA just sat
       right after the desc, landing at a different height on every card
       (a short/no-desc card left the CTA floating near the top with a big
       empty gap below it). Growing this box to fill the leftover space
       (card height minus the fixed image) lets .nd-hub-card-cta's own
       margin-top:auto below pin it flush to the same bottom edge on every
       card, regardless of how much title/desc it's sitting above. */
    .nd-hub-card-content,
    .nd-hub-card:hover .nd-hub-card-content,
    .nd-hub-card:focus-within .nd-hub-card-content { gap: 8px; flex: 1; }
    .nd-hub-card-desc,
    .nd-hub-card-cta,
    .nd-hub-card:hover .nd-hub-card-desc,
    .nd-hub-card:hover .nd-hub-card-cta,
    .nd-hub-card:focus-within .nd-hub-card-desc,
    .nd-hub-card:focus-within .nd-hub-card-cta {
        opacity: 1; max-height: none; overflow: visible;
    }
    .nd-hub-card-desc {
        font-size: 16px;
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    }
    .nd-hub-card-cta { font-size: 16px; padding: 10px 0 0 0; margin-top: auto; }

    /* Real spec (node 835:3098, via get_design_context): a 28x28 white
       square with barely-rounded corners (2.435px, not a pill) and NO
       shadow, a small 11x8.5 arrow glyph, and dot indicators that are
       small SOLID filled circles (not hollow rings) -- 20%-opacity blue at
       rest, full-opacity blue when active. The previous pass guessed a
       46px pill-shaped shadowed button and outlined-ring dots; both were
       wrong in every dimension once checked against the actual node. */
    .nd-hub-grid-nav { display: flex; align-items: center; justify-content: center; gap: 19px; margin-top: 20px; }
    .nd-hub-grid-arrow { order: 2; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; background: #ffffff; border: none; border-radius: 3px; box-shadow: none; cursor: pointer; padding: 0; }
    .nd-hub-grid-arrow svg { width: 11px; height: 8.5px; }
    .nd-hub-grid-arrow-prev { order: 1; transform: rotate(180deg); }
    .nd-hub-grid-arrow-next { order: 3; }
    .nd-hub-grid-nav .nd-hub-grid-dots { order: 2; display: flex; align-items: center; gap: 12px; list-style: none; margin: 0; padding: 0; }
    .nd-hub-grid-nav .nd-hub-grid-dots li { margin: 0; padding: 0; width: 7px; height: 7px; }
    .nd-hub-grid-nav .nd-hub-grid-dots li button { font-size: 0; line-height: 0; display: block; width: 7px; height: 7px; padding: 0; cursor: pointer; color: transparent; border: none; border-radius: 50%; background: var(--nd-color-primary-blue); opacity: 0.2; outline: none; }
    .nd-hub-grid-nav .nd-hub-grid-dots li.slick-active button { opacity: 1; }

    /* Standalone (no-media) title on phone -- Figma node 723:5250, the
       homepage's own "בואו לקחת חלק" heading above this carousel. */
    /* letter-spacing -1px, not the base rule's -2px: -2px was measured
       against Figma's 60px desktop size (roughly -3.3% of font-size) --
       left unchanged at this much smaller 28px, the same -2px is a much
       tighter -7.1% of font-size, cramped/overlapping on Hebrew glyphs.
       -1px keeps roughly the same proportion client-reported (2026-07-27)
       as "spacing between letters" needing a fix. */
    .nd-hub-title-standalone { font-size: 28px; letter-spacing: -1px; }

    /* Hub Page (ND) itself (node 872:2904): full-width cards in a plain
       scrollable column, not the carousel above -- same card typography
       (inherited from the rules above), just a different container layout
       and no nav/arrows/dots at all. margin:0 cancels the carousel-only
       ".nd-hub-grid { margin: 0 -20px }" bleed above -- that rule and this
       one both match the same element (a stacked grid is still
       ".nd-hub-grid.nd-hub-grid-stacked"), and the carousel's edge-to-edge
       peek was leaking through, flattening this layout's own 20px side
       inset against the viewport -- per direct feedback the stacked list
       is supposed to keep real padding here, unlike the carousel. */
    .nd-hub-grid-stacked { flex-direction: column; flex-wrap: wrap; gap: 35px; margin: 0; }
    .nd-hub-grid-stacked .nd-hub-card { width: 100%; margin: 0; }
    .nd-hub-grid-stacked ~ .nd-hub-grid-nav { display: none; }

    /* Media-overlay title/description (node 619:4794 and equivalents):
       28px/16px, not the desktop 60px/24px -- padding-bottom trimmed from
       the desktop 100px to match the smaller ~41px tab-pill overlap at
       this scale (vs. desktop's 60px). */
    .nd-hub-media-overlay { padding: 24px 20px 80px; }
    /* letter-spacing -1px, not the base rule's -2px -- client-requested
       (2026-08-03), same fix already applied to .nd-hub-title-standalone
       (this component's OTHER title path, when there's no hero media) but
       missed here: -2px was tuned against the 60px desktop size, left
       unchanged at this much smaller 28px it's disproportionately tight
       on Hebrew glyphs. */
    .nd-hub-media-overlay .nd-hub-title { font-size: 28px; letter-spacing: -1px; }
    .nd-hub-media-overlay .nd-hub-desc { font-size: 16px; }

    /* Hero media itself (node 619:4794: 392x296, in a 393px-wide frame --
       genuinely edge-to-edge, not a 32px-rounded card inset like desktop)
       -- bleeds past .nd-hub-top's own 20px side padding (shared with the
       breadcrumbs/title above it, which DO want that inset) the same way
       the homepage carousel's cards already bleed past .nd-hub-tabs'
       matching padding. Explicit height:296px per Niri's own measurement
       -- the max-height:600px on the shared base rule doesn't constrain
       anything at this width, the actual displayed height was just
       whatever the video/image's own intrinsic aspect ratio happened to
       produce. */
    .nd-hub-media-wrap { width: calc(100% + 40px); height: 296px; margin: 24px -20px 0; border-radius: 0; }
    /* .nd-media is the plain wrapper div nd_component_render_media()/
       render_override_video() always render around the actual <img>/
       <video> -- same gotcha documented on ND Puzzle's own .nd-media rule:
       it has no explicit size of its own, so with no height set here it
       stays auto (sized to content), and the img/video's own height:100%
       below has no definite parent height to resolve against, silently
       falling back to its intrinsic aspect ratio instead of actually
       filling the now-fixed 296px frame. Scoped to phone only, not the
       shared desktop rule above -- desktop's wrapper has no fixed height
       of its own for this to safely resolve against. */
    .nd-hub-media-wrap .nd-media { position: absolute; inset: 0; }

    /* Tab pill's 50% overlap onto the hero media, mirrored from desktop's
       own -60px (half of ITS 120px pill height, per the desktop docblock
       above) -- but the mobile pill is much shorter: 8px pill padding +
       12px button padding top/bottom + a ~19px text line at 16px, so
       roughly 8+12+19+12+8=59px tall, half of that ~30px. Desktop's -60px
       was otherwise still cascading down to phone width unchanged, nearly
       overlapping the whole pill behind the media instead of half of it. */
    .nd-hub-tabs-has-media .nd-hub-tab-bar { margin-top: -30px; }

    /* Hidden on phone per direct feedback -- scoped to Hub Programs' own
       reuse of this markup (not the bare .nd-hero-v2-breadcrumbs selector),
       so Hero Carousel V2's own mobile breadcrumbs (node 1027:5614, shown
       deliberately in white, smaller, closer to the corner) are untouched. */
    .nd-hub-top .nd-hero-v2-breadcrumbs { display: none; }
}

/* --- ND Photo / Video ---
   Desktop/mobile swap reuses class-nd-hero-carousel-v2.php's exact
   approach: both media blocks are always rendered, only one is
   display:block at a time, toggled by this same 767px breakpoint (see
   .nd-hero-v2-media-desktop/.nd-hero-v2-media-mobile above) rather than a
   second method for the same problem. */
/* Compound selector (not bare .nd-single-media): render_html()'s own
   top-level div reuses "nd-single-media" as its class too, so it's nested
   directly inside the auto-generated .nd-component-wrapper.nd-single-media
   outer div -- same collision as Damka Radio/Puzzle/Testimonial/Form
   Section. A bare selector would double-apply the gutter to both.
   The aspect-ratio/overflow rule below was ONLY ever half-fixed for this --
   the padding line above got the compound-selector treatment, but
   aspect-ratio/overflow/position stayed on the bare ".nd-single-media"
   selector, which ALSO matches the outer wrapper (same class collision).
   That gave the WRAPPER its own aspect-ratio-computed height AND
   overflow:hidden, independent of its actual child content -- invisible
   while the wrapper had no vertical padding of its own, but adding the
   60px top padding above pushed the real (descendant) media box down
   inside a wrapper whose OWN height was still locked to the un-padded
   aspect-ratio, clipping the bottom of the video/image. Scoped to the
   true descendant now, the same way the padding line already was. */
.nd-component-wrapper.nd-single-media { padding: 0 20px; }
.nd-component-wrapper.nd-single-media .nd-single-media { position: relative; width: 100%; aspect-ratio: 1860 / 749; border-radius: 32px; overflow: hidden; }
.nd-sm-media-layer { position: absolute; inset: 0; z-index: 1; }
.nd-sm-media-desktop, .nd-sm-media-mobile { position: absolute; inset: 0; width: 100%; height: 100%; }
.nd-sm-media-mobile { display: none; }
@media screen and (max-width: 767px) {
    .nd-sm-media-desktop { display: none; }
    .nd-sm-media-mobile { display: block; }
}
.nd-sm-media-layer .nd-media-img, .nd-sm-media-layer video { width: 100%; height: 100%; object-fit: cover; display: block; }
.nd-sm-media-layer .nd-media-embed, .nd-sm-media-layer .nd-media-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.nd-sm-overlay { position: absolute; z-index: 2; bottom: 40px; left: 40px; right: 40px; pointer-events: none; }
.nd-sm-overlay .nd-title-h2 { margin: 0; }

/* On top of both the media and the title overlay — the title text stays
   visually readable underneath since this anchor has no fill/content of
   its own, it only captures clicks. Video interaction here means the
   configured autoplay/loop/sound still plays correctly underneath, same
   as every other decorative background video in this codebase (none of
   them expose end-user-clickable native player controls to begin with —
   nd_component_render_media()'s <video> output has no `controls`
   attribute) — not literally reaching through to a YouTube/Vimeo iframe's
   own on-screen play/pause chrome, which a full-bleed overlay link
   necessarily sits above. */
.nd-sm-overlay-link { position: absolute; inset: 0; z-index: 3; }

@media screen and (max-width: 767px) {
    .nd-sm-overlay { bottom: 24px; left: 24px; right: 24px; }
    /* node 839:10798 (Product Page's own instance): 386x217, a ~1.78:1
       box, not the desktop 1860:749 (~2.48:1) ratio still applying
       unchanged here -- there was no mobile override for this at all, so
       the dedicated mobile-cropped image/video was being forced into the
       wrong-shaped box and object-fit:cover was cropping it incorrectly. */
    .nd-component-wrapper.nd-single-media .nd-single-media { aspect-ratio: 386 / 217; }
    /* Top spacing from the section above: the shared wrapper rule only
       ever had a horizontal 20px gutter (padding: 0 20px), no top
       margin/padding at any breakpoint at all -- every other component on
       this page has some section-to-section spacing mechanism, this one
       was simply missed. */
    .nd-component-wrapper.nd-single-media { padding-top: 60px; }
}

/* --- ND Promotions Highlight ---
   Figma node 1073:1880. Container bg #002aba hardcoded (not
   var(--nd-color-primary-blue), which is #1935CA) — same call already made
   for Hero Carousel V2's accent shape: the Figma instance itself uses this
   exact hex, distinct from the shared token, and prior components already
   established that the literal Figma value wins over the generic var. */
.nd-promotions-highlight { margin-top: 70px; padding: 0 20px; }
.nd-promo-highlight { max-width: 1860px; margin: 0 auto; background: #002aba; border-radius: 32px; padding: 80px 174px; display: flex; flex-direction: column; align-items: center; gap: 40px; }
.nd-promo-highlight-head { display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; }
.nd-promo-highlight-title { color: #ffffff; font-size: 60px; font-weight: 700; margin: 0; font-family: 'Assistant', sans-serif; }
.nd-promo-highlight-subtitle { color: #ffffff; font-size: 24px; font-weight: 400; margin: 0; font-family: 'Assistant', sans-serif; max-width: 1010px; }

.nd-promo-highlight-row { display: flex; gap: 36px; width: 100%; align-items: flex-start; justify-content: center; }
.nd-promo-highlight-item { flex: 1 1 0; min-width: 0; max-width: 762px; display: flex; flex-direction: column; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
.nd-promo-highlight-media { position: relative; width: 100%; height: 411px; border-radius: 32px; overflow: hidden; box-shadow: 0 0 16.5px rgba(109, 109, 109, 0.25); }
/* transition lives on the media itself, not a wildcard -- only the
   image/video should scale, the tile's own border-radius/shadow stay put.
   overflow:hidden on the parent above is what keeps the zoomed image
   cropped to the tile's rounded corners instead of spilling past them.
   position:absolute/inset:0 (not width/height:100%) -- client-reported
   (2026-08-03) images weren't filling the card when the upload wasn't
   already close to the card's own aspect ratio. Root cause: this img is
   nested inside nd_component_render_media()'s own `.nd-media` wrapper div
   (added by the shared helper, between this rule's own .nd-promo-highlight-
   media and the real <img>), and that wrapper has no height of its own --
   height:100% on the img had nothing definite to resolve against, so it
   silently fell back to `auto`, and Chrome's own default img aspect-ratio
   (from the width/height attributes wp_get_attachment_image() always
   outputs) took over instead, sizing the image by its own upload
   dimensions rather than the card. Same fix already used everywhere else
   in this file for exactly this gotcha (.nd-hub-card-img, .nd-5cp-card-
   media, etc.): position the img/video absolutely against the outer
   (sized, position:relative) container instead, skipping the unsized
   wrapper entirely. */
.nd-promo-highlight-media .nd-media-img, .nd-promo-highlight-media video, .nd-promo-highlight-media .nd-media-embed, .nd-promo-highlight-media .nd-media-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; border: 0; transition: transform 0.4s ease; }
/* Client-requested hover animation (Figma has it, site didn't): image
   grows slightly, title goes bold. Scoped to .nd-promo-highlight-item
   (the whole clickable tile, an <a> when linked) so hovering anywhere on
   the tile triggers both together, not just the media itself. */
.nd-promo-highlight-item:hover .nd-promo-highlight-media .nd-media-img,
.nd-promo-highlight-item:hover .nd-promo-highlight-media video,
.nd-promo-highlight-item:focus-within .nd-promo-highlight-media .nd-media-img,
.nd-promo-highlight-item:focus-within .nd-promo-highlight-media video {
    transform: scale(1.08);
}
.nd-promo-highlight-item-title { color: #ffffff; font-size: 24px; font-weight: 400; text-align: center; margin: 0; font-family: 'Assistant', sans-serif; transition: font-weight 0.2s ease; }
.nd-promo-highlight-item:hover .nd-promo-highlight-item-title,
.nd-promo-highlight-item:focus-within .nd-promo-highlight-item-title {
    font-weight: 700;
}

@media screen and (max-width: 991px) {
    .nd-promo-highlight { padding: 48px 24px; gap: 28px; }
    .nd-promo-highlight-title { font-size: 36px; }
    .nd-promo-highlight-subtitle { font-size: 18px; }
    .nd-promo-highlight-row { flex-direction: column; align-items: stretch; }
    .nd-promo-highlight-item { max-width: none; }
    .nd-promo-highlight-media { height: 260px; }
}
/* Phone (node 829:2549): tighter padding/media height than the 991px
   tablet block, matching the measured card exactly. */
@media screen and (max-width: 767px) {
    .nd-promo-highlight { padding: 40px 20px; }
    .nd-promo-highlight-title { font-size: 28px; }
    .nd-promo-highlight-subtitle { font-size: 16px; }
    .nd-promo-highlight-media { height: 171px; }
    .nd-promo-highlight-item-title { font-size: 16px; }
}

/* --- ND Puzzle ---
   Figma node 1073:6627. Confirmed via metadata (5 rounded rectangles, no
   two the same size) that the 5 media slots are FIXED, differently-shaped
   mosaic positions, not a uniform repeatable grid — every slot below is
   hardcoded to its own Figma position/size (percentages of a 1860x724
   reference canvas, kept fluid via aspect-ratio on the container) rather
   than driven by a generic loop.
   The CTA area's outline is a Figma boolean union of two rounded
   rectangles (520x441 title bubble + 461x348 description/CTA bubble),
   fetched as its own exported node (1073:6633) via get_design_context —
   an actual traced SVG path, not a filled/shadowed card: it's a hollow
   ~2px stroke outline (built with Figma's inside-stroke mask technique)
   with NO fill and NO drop-shadow, matching what Hero Carousel V2 does
   with its own accent shape. Two earlier passes wrongly assumed a solid
   white filled card because the mosaic tiles right next to it do have a
   fill+shadow — this shape doesn't. The exported path is authored in the
   node's own natural orientation; in place on the real page, Figma wraps
   it in "-scaleY(-100%) rotate(180deg)", which composes to a pure
   horizontal mirror (rotate180 flips both axes, scaleY(-1) flips y back,
   net: x only) — reproduced here with transform: scaleX(-1) on the SVG
   itself rather than hand-mirroring the path's coordinates. Title/desc
   text still sit in their own plain (unstyled, no fill/shadow) containers
   on top, sized to the same regions established in the prior pass. */
.nd-puzzle { max-width: 1860px; margin: 0 auto; }
/* Compound selector: render_html()'s own top-level div also reuses
   "nd-puzzle" as its class, nested directly inside the auto-generated
   .nd-component-wrapper.nd-puzzle outer div -- same collision as Damka
   Radio above. Padding must land only on the true outer wrapper (this
   compound match), not the inner div too, or the mosaic would get double
   side-inset instead of a single 20px gutter from the viewport edge. */
.nd-component-wrapper.nd-puzzle { padding: 0 20px; }
.nd-puzzle-mosaic { position: relative; width: 100%; aspect-ratio: 1860 / 724; margin-top: 70px; }

.nd-puzzle-slot { position: absolute; display: block; border-radius: 32px; overflow: hidden; box-shadow: 0 0 16.5px rgba(195, 195, 195, 0.25); background: #d9d9d9; }
/* .nd-media is the plain wrapper div nd_component_render_media() always
   renders around the actual <img>/<video>/embed — it has no explicit
   size of its own anywhere else, so without this rule its height stays
   auto (sized to its content) and the img's own height:100% below has no
   definite containing-block height to resolve against, silently falling
   back to the img's intrinsic width/height-attribute aspect ratio instead
   of actually filling the slot. Confirmed live: this is exactly why
   images looked "wrong size" only in the slots where the fallback ratio
   came out SHORTER than the slot (a visible gray gap at the bottom) and
   not in the slots where it came out TALLER (silently clipped by this
   rule's own overflow: hidden, so no visible symptom). */
.nd-puzzle-slot .nd-media { display: block; width: 100%; height: 100%; }
.nd-puzzle-slot .nd-media-img, .nd-puzzle-slot video, .nd-puzzle-slot .nd-media-embed { width: 100%; height: 100%; object-fit: cover; display: block; border: 0; }
/* iframe (YouTube/Vimeo) client-reported (2026-08-03) not filling the
   frame -- object-fit does nothing on an iframe (only a real replaced
   element like img/video respects it), so the 100%/100% rule above just
   sizes the iframe's own box correctly while the ACTUAL video track
   inside it gets letterboxed by the provider's own player whenever its
   native ratio (assumed standard 16:9 -- there's no way to know the
   real uploaded ratio from here) doesn't match this slot's own ratio.
   These 5 slots range from 244x349 (portrait, ~0.7:1) to 759x348
   (~2.18:1) -- all a long way from 16:9 -- so a real mismatch is the
   common case, not an edge case.
   Standard fix for this exact embed limitation: oversize the iframe
   itself and center it, so the letterboxed video (bars and all) scales
   up until the real picture -- not the bars -- covers the slot, then
   .nd-puzzle-slot's own overflow:hidden crops the rest same as
   object-fit:cover would for a native element.
   Per-slot, not one blanket value (client-reported, 2026-08-03, after
   an earlier uniform 260%/150% pass looked "huge" and cropped top/bottom
   that didn't need cropping): each slot's own oversize is calculated
   against its own real ratio vs. an assumed 16:9 source, oversizing ONLY
   the axis that actually needs it and leaving the other at 100% -- e.g.
   slot 1 (358x260, narrower than 16:9) only needs extra width, so its
   height stays exactly 100% and nothing gets cropped top/bottom there;
   slot 3 (759x348, wider than 16:9) is the mirror case and only needs
   extra height. */
.nd-puzzle-slot .nd-media-embed { position: relative; }
.nd-puzzle-slot .nd-media-embed iframe {
    position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    border: 0;
}
.nd-puzzle-slot-1 .nd-media-embed iframe { width: 130%; height: 100%; }
.nd-puzzle-slot-2 .nd-media-embed iframe { width: 118%; height: 100%; }
.nd-puzzle-slot-3 .nd-media-embed iframe { width: 100%;  height: 123%; }
.nd-puzzle-slot-4 .nd-media-embed iframe { width: 100%;  height: 117%; }
.nd-puzzle-slot-5 .nd-media-embed iframe { width: 255%; height: 100%; }

.nd-puzzle-slot-1 { left: 0%;      top: 0.03%;  width: 28.387%; height: 48.06%; }
.nd-puzzle-slot-2 { left: 29.85%; top: 0%;      width: 40.81%;  height: 48.06%; }
.nd-puzzle-slot-3 { left: 0.01%;  top: 51.79%;  width: 38.98%;  height: 48.20%; }
.nd-puzzle-slot-4 { left: 40.33%; top: 51.83%;  width: 13.12%;  height: 48.20%; }
.nd-puzzle-slot-5 { left: 80.75%; top: 64.09%;  width: 19.25%;  height: 35.91%; }

.nd-puzzle-cta-wrap { position: absolute; left: 54.82%; top: 0; width: 45.2%; height: 100%; }
.nd-puzzle-cta-outline { position: absolute; inset: 0; width: 100%; height: 100%; transform: scaleX(-1); pointer-events: none; }
/* Separate mobile trace (own path, not a rescale -- see class-nd-puzzle.php's
   docblock addition), swapped in via plain display:none/block across the
   767px boundary, same idiom as Hero Carousel V2's own desktop/mobile SVG
   containers. Hidden here so both aren't rendered at once above phone width. */
.nd-puzzle-cta-outline-mobile { display: none; }
.nd-puzzle-title-card, .nd-puzzle-desc-card {
    position: absolute; display: flex; flex-direction: column;
    align-items: center; justify-content: center; text-align: center; gap: 24px; padding: 32px 48px;
}
.nd-puzzle-title-card { left: 38.15%; top: 0; width: 61.85%; height: 60.91%; }
.nd-puzzle-desc-card { left: 0; top: 51.93%; width: 54.83%; height: 48.07%; }
/* line-height 1.05, not 1.15 -- client-requested (2026-07-27) slightly
   tighter spacing between wrapped lines. */
.nd-puzzle-cta-title { color: #002aba; font-size: 60px; font-weight: 700; line-height: 1.05; margin: 0; font-family: 'Assistant', sans-serif; }
.nd-puzzle-cta-desc { color: #000000; font-size: 20px; font-weight: 400; margin: 0; font-family: 'Assistant', sans-serif; }
.nd-puzzle-cta-btn { display: inline-flex; align-items: center; gap: 4px; background: #e7342b; color: #ffffff; font-size: 20px; font-weight: 700; padding: 16px 32px; border-radius: 12px; text-decoration: none; font-family: 'Assistant', sans-serif; }
.nd-puzzle-cta-btn:hover { opacity: 0.9; }
.nd-puzzle-cta-heart { width: 20px; height: 20px; display: block; }

@media screen and (max-width: 991px) {
    .nd-puzzle-mosaic { aspect-ratio: auto; height: auto; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
    .nd-puzzle-slot { position: static; width: auto; height: 200px; }
    .nd-puzzle-cta-wrap { position: static; width: auto; height: auto; display: contents; }
    .nd-puzzle-cta-outline { display: none; }
    .nd-puzzle-title-card, .nd-puzzle-desc-card { position: static; width: auto; height: auto; grid-column: 1 / -1; padding: 32px 24px; }
    .nd-puzzle-cta-title { font-size: 32px; }
    /* One shared value here, not per-slot: this grid's own cell WIDTH is
       dynamic (1fr of whatever the viewport leaves after the 12px gap),
       not a fixed number like the desktop/phone mosaics, so there's no
       exact ratio to calculate against. Across this breakpoint's real
       width range (768-991px) a 2-column cell against the fixed 200px
       height always comes out wider than 16:9 (roughly 1.8:1 to 2.4:1),
       so every slot needs extra HEIGHT, never extra width -- same
       oversize-the-one-axis-that-needs-it approach as the desktop rules
       above, just one value covering the whole range instead of five. */
    .nd-puzzle-slot .nd-media-embed iframe { width: 100%; height: 135%; }
}
/* Phone (node 829:2519): per direct feedback the 991px tablet fallback's
   plain 2-column grid doesn't read as the real design at all, so this
   rebuilds the actual asymmetric mobile mosaic -- a 358.984x812 reference
   canvas (own aspect-ratio, distinct from desktop's 1860x724) with 5
   absolutely-positioned tiles plus the hollow union outline (traced from
   node 829:2520 in isolation, same inside-stroke-mask technique as the
   desktop trace, but its own genuinely different path -- not a rescale of
   the desktop one) sitting behind the title/description/CTA text. No
   transform: scaleX(-1) here (unlike the desktop outline): this trace was
   pulled directly from the mobile frame itself, already in the right
   orientation.
   Slot-to-position mapping (1-5, matching the existing PHP loop order,
   HTML unchanged): 1=full-width top banner, 2=small square tile beside
   the title, 3=the tall tile beside the description, 4=bottom-left,
   5=bottom-right -- the most natural top-to-bottom reading of Figma's
   layout. Swap the percentages below (not the PHP) if real content ever
   shows a slot landing in the wrong spot. */
@media screen and (max-width: 767px) {
    .nd-puzzle-mosaic { position: relative; width: 100%; height: auto; aspect-ratio: 358.984 / 812; display: block; }
    .nd-puzzle-slot { position: absolute; display: block; width: auto; height: auto; border-radius: 20px; }
    .nd-puzzle-slot-1 { left: 0%;       top: 0%;        width: 100%;     height: 23.645%; }
    .nd-puzzle-slot-2 { left: 0%;       top: 24.507%;   width: 36.491%;  height: 16.503%; }
    .nd-puzzle-slot-3 { left: 71.027%;  top: 43.842%;   width: 28.971%;  height: 31.034%; }
    .nd-puzzle-slot-4 { left: 0%;       top: 76.355%;   width: 48.748%;  height: 23.645%; }
    .nd-puzzle-slot-5 { left: 51.816%;  top: 76.355%;   width: 48.192%;  height: 23.645%; }
    /* Own values, not the desktop rule's -- these 5 tiles are a completely
       different mosaic (own positions/ratios above), not a rescale of the
       desktop one, so the axis that needs oversizing against an assumed
       16:9 source is worked out fresh per tile here too. Slot 3 in
       particular is a real outlier (a tall, narrow ~0.41:1 tile), needing
       a much bigger width oversize than any desktop slot to still cover
       without cropping its own top/bottom. */
    .nd-puzzle-slot-1 .nd-media-embed iframe { width: 100%; height: 106%; }
    .nd-puzzle-slot-2 .nd-media-embed iframe { width: 182%; height: 100%; }
    .nd-puzzle-slot-3 .nd-media-embed iframe { width: 431%; height: 100%; }
    .nd-puzzle-slot-4 .nd-media-embed iframe { width: 196%; height: 100%; }
    .nd-puzzle-slot-5 .nd-media-embed iframe { width: 198%; height: 100%; }

    .nd-puzzle-cta-wrap { position: absolute; display: block; left: 0; top: 24.507%; width: 100%; height: 50.370%; }
    .nd-puzzle-cta-outline { display: none; }
    .nd-puzzle-cta-outline-mobile { display: block; position: absolute; inset: 0; width: 100%; height: 100%; transform: scaleX(-1); pointer-events: none; }
    .nd-puzzle-title-card, .nd-puzzle-desc-card { position: absolute; display: flex; flex-direction: column; align-items: center; text-align: center; padding: 0; }
    .nd-puzzle-title-card { left: 70.345%; top: 8.802%; width: 42.619%; transform: translateX(-50%); justify-content: flex-start; }
    .nd-puzzle-desc-card { left: 34.818%; top: 44.743%; width: 62.954%; gap: 16px; transform: translateX(-50%); justify-content: flex-start; }
    .nd-puzzle-cta-title { font-size: 20px; }
    .nd-puzzle-cta-desc { font-size: 16px; }
    .nd-puzzle-cta-btn { font-size: 16px; }
}

/* --- ND Testimonial / Promo (rebuild) ---
   Figma node 398:12022. Real Slick (SLIDE mode, not fade:true) — see class
   docblock in class-nd-testimonial.php for why this component uses Slick
   at all when Hero Carousel V2 deliberately doesn't (Figma's own mockup
   shows adjacent slides peeking in at both edges, which centerMode+real
   sliding produces natively and a plain crossfade can't).
   Second pass: media/text side order was swapped from Figma (confirmed
   live via getBoundingClientRect — text was rendering on the right,
   media on the left; Figma's own reference has media on the right, text
   on the left). Fixed with `order` rather than reordering the markup —
   .nd-testimonial-media is second in the DOM (after .nd-testimonial-text)
   for a sane reading/tab order, so order is the only way to flip the
   VISUAL side without also flipping that. */
/* Compound selector (not bare .nd-testimonial): render_html()'s own
   top-level div reuses "nd-testimonial" as its class too, nested directly
   inside the auto-generated .nd-component-wrapper.nd-testimonial outer
   div -- same collision as Damka Radio/Puzzle elsewhere in this file. A
   bare .nd-testimonial rule would apply the margin to both. */
.nd-component-wrapper.nd-testimonial { margin-top: 70px; }
.nd-testimonial { max-width: 1920px; margin: 0 auto; padding: 40px 0 0; }
.nd-testimonial-slider { direction: rtl; }
.nd-testimonial-slider .slick-track { display: flex; }
.nd-testimonial-slider .slick-slide { height: auto; }
/* Client-reported (2026-07-27, still cut off as of 2026-08-03 -- see below)
   the card's shadow gets cut off -- same root cause and same fix as ND Hub
   Programs' own mobile carousel (see that component's own .nd-hub-grid
   .slick-list rule): Slick's default overflow:hidden on its own
   .slick-list is needed for centerMode's horizontal clipping, but it also
   clips this card's box-shadow, which paints outside the card's own box.
   Real padding gives the shadow room inside slick-list's (still-clipping)
   box; the matching negative margin cancels the resulting layout push so
   nothing shifts. 20px matches the card's own shadow blur radius.
   All 4 sides, not just top/bottom (2026-08-03): the original pass only
   padded vertically, so the shadow was still visibly cut hard on the
   LEFT/RIGHT of a peeking (non-active, centerMode) neighbor card --
   exactly the same clipping, just the other axis, and never fixed there. */
.nd-testimonial-slider .slick-list { padding: 20px !important; margin: -20px; }
.nd-testimonial-slide { padding: 0 32px; }
/* width:fit-content + max-width (not justify-content:space-between
   stretched across the whole slide) -- client-reported (2026-08-03) a big
   empty gap between the text and the photo. Root cause: this card had no
   width of its own, so it stretched to match .nd-testimonial-slide's full
   width (up to ~1856px on a wide desktop, since the slide itself has to
   stay that wide for centerMode's peek math) and space-between then threw
   ALL of that leftover width into the one gap between the two children --
   confirmed live on a slide with no media set at all, where the card
   stretched so wide the (empty) media slot's space pushed nothing
   visible off past the right edge of the viewport, reading as "the image
   is just gone".
   max-width:1860px, not Figma's own literal 1664px frame width (node
   398:12022's Component 30) -- client-reported (2026-08-03) the first pass
   read as too narrow: 1860 is this site's actual standing content-width
   convention (ND Hub Programs/Puzzle/Promotions Highlight/Content Editor
   etc. all use the same 1860 cap), which this static Figma frame wasn't
   drawn to match.
   width:100%, not fit-content (2026-08-03, second pass): still read as
   "not like Figma" even at the 1860 cap, and the real cause turned out to
   be one level down -- .nd-testimonial-media's own width:44% was a
   percentage flex-basis inside a fit-content (shrink-to-fit) parent, a
   circular sizing reference (the parent's size depends on the child's
   resolved width, which depends on a percentage OF the parent) that
   browsers resolve unpredictably. Confirmed live: the media was actually
   rendering ~524px wide -- barely more than half its own max-width:795px
   cap -- while the text sat at its normal ~522px, an obviously-too-small
   photo next to the text rather than Figma's substantially-bigger media
   panel. width:100% gives the browser a definite width to resolve that
   percentage against; see .nd-testimonial-media below for the other half
   of this fix (flex-grow instead of a hard 795px cap, so it actually
   fills the extra room this now provides). */
/* Horizontal spacing corrected against Figma's real node metadata
   (2026-08-04, client-reported the text-to-edge/image-to-edge/text-to-image
   gaps read uneven): pulling Component 23's actual child x/width values
   (not just eyeballing the static export) gives text frame x:74 in a
   1664-wide card (left padding 74, unchanged) but the photo union sits at
   x:835 width:795, i.e. only 1664-(835+795)=34px from the card's RIGHT
   edge, with 835-(74+653)=108px between the text frame's own right edge
   and the photo -- so the real spec is an asymmetric 74/108/34, not the
   even 74/40/74 this was first built with. padding-left/-right (not the
   shorthand) since these are genuinely different values now; -left/-right
   stay physical-side (unaffected by dir="rtl") same as everywhere else in
   this file, and text is DOM-order-2/physically-left while photo is
   DOM-order-1/physically-right (see this rule's own history above), so
   -left targets the text side and -right the photo side correctly. */
.nd-testimonial-card { background: #ffffff; border-radius: 32px; box-shadow: 0 0 20px rgba(161, 161, 161, 0.25); display: flex; align-items: center; gap: 108px; padding: 40px 34px 40px 74px; min-height: 605px; width: 100%; max-width: 1860px; margin: 0 auto; }

/* Two-tier vertical gap (2026-08-04, from Figma node 839:7251's own child
   metadata, not a guess): the text column is really two groups, not one
   flat stack -- an inner quote+description group (own internal gap 25px,
   see .nd-testimonial-content below) and the meta line, separated from
   that group by a much bigger 68px gap. This outer rule now carries only
   that outer 68px value; the quote-to-description 25px lives on the new
   .nd-testimonial-content wrapper class-nd-testimonial.php now renders. */
/* align-items:flex-start, not flex-end (2026-08-04, client-reported the
   name/program row sitting on the wrong side) -- for a column-direction
   flex container, align-items' cross-axis start/end follow the INLINE
   axis, which flips with `dir`: under this component's dir="rtl", inline-
   start is the physical RIGHT and inline-end is the physical LEFT (the
   opposite of the intuitive reading of the keyword names), so flex-end
   here was actually pinning children to the left. .nd-testimonial-meta
   isn't full-width like the quote/description block, so its own
   misalignment was the one actually visible; text-align:right (a
   property of the text content itself, not the flex box) had been
   masking the same underlying issue on the quote/description. */
.nd-testimonial-text { order: 2; display: flex; flex-direction: column; gap: 68px; align-items: flex-start; text-align: right; max-width: 653px; }
.nd-testimonial-content { display: flex; flex-direction: column; gap: 25px; align-items: flex-start; width: 100%; }
.nd-testimonial-quote { font-size: 32px; font-weight: 700; color: #000000; margin: 0; font-family: 'Assistant', sans-serif; }
.nd-testimonial-desc { font-size: 22px; font-weight: 400; color: #000000; margin: 0; font-family: 'Assistant', sans-serif; }
/* gap 28px (was 20) / separator height 37px (was 28) -- both corrected
   against the same Figma node metadata (Frame 149's own gap-[28px], and
   Line 3's 37px length before its rotate-90). */
.nd-testimonial-meta { display: flex; align-items: center; gap: 28px; font-size: 22px; color: #000000; font-family: 'Assistant', sans-serif; }
.nd-testimonial-meta-sep { display: inline-block; width: 1px; height: 37px; background: #000000; opacity: 0.25; }

/* Card/photo union (Figma boolean op: a main panel + a smaller tab
   notched into its left edge) is a real CSS mask now, not a traced/baked
   shape — a genuine reusable-CSS need since this mask has to fit whatever
   photo an editor uploads, not one fixed pre-cut image.
   Rect sizing corrected (2026-08-04, client-reported "not like Figma")
   against node 839:7251's own Component 23 -- the one actually used as
   the live reference photo -- pulling its REAL exported union PNG (not
   the placeholder-color ones the other two demo cards use, which have a
   different, non-representative hand-adjusted shape) and reading its
   alpha channel directly: tab flush-BOTTOM (not vertically centered)
   spanning 178/795 (22.4%) of the width and 225-548 of the 548-tall box
   (41.1%-100%), main panel flush to the right/top/bottom taking the
   remaining 617/795 width.
   ONE fused <path>, not two independent <rect>s (2026-08-04, second
   round -- client-reported the two boxes still didn't read as one
   connected shape even with the corrected sizing): two plain unioned
   rects leaves the reflex corner where the tab's top edge meets the
   main panel's left edge as a sharp, unrounded 90° notch (each rect's
   OWN rx:32 only rounds its convex/outer corners, never a corner newly
   created by the union) -- exactly the "not connected" look. Fixed the
   same way Hero Carousel V2's own accent shape is built (see that
   component's own -mobile fused-SVG comments elsewhere in this file):
   one hand-authored path with an explicit fillet arc (same 32px radius
   as every other corner here) at that one reflex corner, so the two
   boxes blend into a single continuous silhouette instead of just
   touching.
   Third round (2026-08-04, client-reported the fillet itself now reads
   as a wrong-looking "indented"/hooked corner, pointed at node
   398:12245/398:12303/398:12333 -- Figma's own 3 real example cards --
   as the exact reference): the first fillet attempt had the right
   RADIUS but the wrong SVG arc sweep-flag for that one corner (`0`
   instead of `1`), which picks the OTHER of the two circles of that
   radius touching both endpoints -- the one centered inside the solid
   panel instead of inside the empty notch, producing a small backwards
   hook into the photo instead of a clean rounded corner. Confirmed by
   overlaying this exact path on Figma's own node 398:12245 export
   (get_screenshot, not the flattened photo+shadow PNG used for the
   original sizing pass) at 1:1 scale -- the corrected sweep traces that
   real image's own corner boundary pixel-for-pixel.
   Fourth round (2026-08-05, client-reported still not matching, this
   time attaching the 3 real exported reference PNGs directly --
   Union1/2/3.png, real photos already cropped to the true mask with
   alpha transparency intact): reading Union1.png's alpha channel
   directly (numpy, exact boundary per row/column, not eyeballed) proved
   the THIRD round's premise wrong, not just its sweep-flag -- 398:12245
   is NOT a simple one-notch L-shape with the tab flush to the bottom.
   The main panel's own bottom edge sits at y:455 (not 544, i.e. not the
   card's full height) and the tab is only 340px wide (not 178) -- so
   there's a SECOND rounded notch cut from the bottom-right, and the
   "tab" is really a wider block whose own right edge becomes visible
   below where the main panel ends. Verified by rasterizing exactly this
   two-rect model (main panel 178,0 to 795,455; tab 0,223 to 340,544;
   both radius 32) and diffing it pixel-for-pixel against Union1.png's
   real alpha mask: 99.7% IoU, the only mismatched pixels a 1-2px
   antialiasing halo right at the two fillet corners -- as close to a
   direct trace as this technique gets. Same fillet technique as before
   (hand-authored arc, sweep:1, at each of the two reflex corners this
   two-rect union creates), just applied to both corners now instead of
   one. Union2.png/Union3.png confirm the earlier finding that the other
   two reference nodes really are separate hand-placed crops with their
   own different notch geometry (a single top-right notch and a single
   top-left notch respectively, neither with this second bottom-right
   cut) -- 398:12245/Union1's own placement stays the one reusable
   default, per the same reasoning as before, now built from real
   measurements instead of a flattened-PNG guess.
   Fifth round (2026-08-05, same day, client-annotated a screenshot with
   arrows pointing at EVERY plain rounded corner -- not just the two
   fillet corners -- reading as cut inward instead of rounded outward):
   this was a real, much older bug than round four's, present on every
   plain corner since the very first version, and my round-four fillet2
   inherited it by copying the existing (wrong) convention. Root cause,
   found by actually computing the SVG arc endpoint-to-center formula
   (SVG 1.1 spec F.6.5) instead of assuming a flag by analogy: for a
   path wound counter-clockwise like this one, a plain convex corner
   needs sweep-flag:0, but every convex corner here was written with
   sweep:1 -- which, for a quarter-circle arc between two points 32px
   apart on each axis, resolves to a circle CENTERED ON THE SHARP CORNER
   ITSELF rather than inset into the filled area, so the "rounding"
   arcs away from the corner into the photo, carving a matching quarter-
   circle bite out of it instead of smoothing it. This coincidentally
   read as correct for the ONE existing reflex fillet, because a
   concave fillet needs the opposite flag (sweep:1) from a convex corner
   in the same winding direction -- so the one corner that got a real
   verification pass in round three happened to be the one flag value
   that was already right, and nothing forced a check of the other six.
   Recomputed every corner's actual arc center from its flag with the
   real formula (not eyeballed) and confirmed: all 6 plain corners need
   sweep:0, both reflex fillets keep sweep:1. Re-verified the corrected
   path the same way as round four -- rasterized with the real SVG arc
   math this time, not an assumed center -- against Union1.png: 99.94%
   IoU, matching visually corner-for-corner.
   mask lives on .nd-media (the inner content wrapper), NOT on this outer
   element -- see that rule below for why (filter/mask-on-same-element
   fix, 2026-08-04, client-reported the media's edges bleeding past the
   union shape). */
.nd-testimonial-media {
    /* flex:1 1 795px, not flex:0 0 auto/width:44% (2026-08-03) -- see
       .nd-testimonial-card's own comment above for the percentage/
       fit-content bug this was part of. 795px is Figma's real fixed media
       width (node 398:12022), used here as the flex-basis so this starts
       at that size and only grows (flex-grow:1) to fill whatever extra
       room .nd-testimonial-text's own fixed max-width:653px leaves once
       the card is filling the full 1860px cap -- text stays at its
       Figma-intended size instead of stretching illegibly wide, media
       (whose mask-size:100% 100% already scales to fit any box) absorbs
       the difference instead of leaving it as dead space. */
    /* align-self:stretch, not height:100% (2026-08-03, third pass --
       client-reported the video still isn't the same size as images):
       .nd-testimonial-card is align-items:center, which sizes each flex
       child to its OWN natural height and centers it -- height:100% here
       had no definite card height to resolve against (the card has only
       min-height, not height), so it fell back to auto.
       align-self:stretch alone turned out NOT to be enough on its own
       (fourth pass, same report persisted): a flex item's OWN natural/
       hypothetical size still feeds into the row's cross-size calculation
       before stretching happens, and that natural size came from
       whatever was inside -- an <img> with no resolvable height falls
       back to its own intrinsic aspect ratio, so a real photo kept
       pulling the row tall (confirmed live via offsetHeight, immune to
       this browser session's own zoom-vs-getBoundingClientRect
       discrepancy: 792px), while an iframe has no intrinsic size at all
       and never pulled it past the 605px min-height floor -- two
       different media types landing on two genuinely different row
       heights, driven by upload content the client can't control.
       Real fix: give the actual media content NO presence in normal flow
       at all (position:absolute below, same technique already used for
       ND Puzzle/ND Hub Programs' card image), so nothing about the photo
       or the iframe can influence this row's height in the first place --
       only .nd-testimonial-text's own natural height and the 605px floor
       do, and align-self:stretch then fills whatever that resolves to. */
    position: relative; order: 1; flex: 1 1 795px; align-self: stretch; min-height: 460px;
    filter: drop-shadow(0 0 16.5px rgba(109, 109, 109, 0.25));
}
/* position:absolute/inset:0 on .nd-media itself (the plain wrapper div
   nd_component_render_media() always renders around the actual
   img/video/embed) -- takes it completely out of normal flow, so it has
   NO natural/hypothetical size of its own to feed into
   .nd-testimonial-media's row-height calculation above (see that rule's
   own comment: this is what actually fixes image vs. video landing at
   different heights, not align-self alone). .nd-testimonial-media itself
   is this element's positioning context (position:relative, set above).
   The union mask lives HERE, not on the outer .nd-testimonial-media, and
   the outer keeps the filter/drop-shadow instead of also carrying the
   mask -- client-reported (2026-08-04) the media's rendered edges bleeding
   past the union shape as a rectangular border. filter and mask on the
   SAME element is a known Chromium/WebKit trouble spot (the filter's own
   effect region calculation doesn't always run after the mask clips the
   box, so it can paint past the clipped silhouette); splitting them onto
   parent (filter) and child (mask) -- the child is already a plain
   position:absolute/inset:0 box with no filter of its own -- means the
   filter is computed from this element's own already-shape-clipped
   rendered output, never from an unclipped rectangle. */
.nd-testimonial-media .nd-media {
    position: absolute; inset: 0; display: block;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 795 544'%3E%3Cpath d='M210,0 A32,32 0 0,0 178,32 L178,191 A32,32 0 0,1 146,223 L32,223 A32,32 0 0,0 0,255 L0,512 A32,32 0 0,0 32,544 L308,544 A32,32 0 0,0 340,512 L340,487 A32,32 0 0,1 372,455 L763,455 A32,32 0 0,0 795,423 L795,32 A32,32 0 0,0 763,0 Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 795 544'%3E%3Cpath d='M210,0 A32,32 0 0,0 178,32 L178,191 A32,32 0 0,1 146,223 L32,223 A32,32 0 0,0 0,255 L0,512 A32,32 0 0,0 32,544 L308,544 A32,32 0 0,0 340,512 L340,487 A32,32 0 0,1 372,455 L763,455 A32,32 0 0,0 795,423 L795,32 A32,32 0 0,0 763,0 Z'/%3E%3C/svg%3E");
    -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.nd-testimonial-media .nd-media-img, .nd-testimonial-media video { width: 100%; height: 100%; object-fit: cover; display: block; border: 0; }
/* iframe (YouTube/Vimeo) client-reported (2026-08-03) not filling the
   frame, then not rendering AT ALL under that first fix -- object-fit
   does nothing on an iframe, only a real replaced element (img/video)
   respects it, so sizing the iframe itself to 100%/100% just left the
   actual video track letterboxed by the provider's player whenever its
   native ratio (assumed 16:9) doesn't match this box's own ratio. The
   oversize-and-center fix for that is the same idea as ND Puzzle/ND
   Promotions Highlight, but positioned/transformed on THIS WRAPPER DIV
   (which is the same element as .nd-media above, just also matching this
   more specific selector) instead of directly on the iframe element --
   putting position:absolute+transform straight on an <iframe> nested
   under an ancestor with mask-image is exactly the kind of cross-
   process-compositing combination that made the video vanish entirely
   rather than just letterbox (iframes paint in their own layer;
   transforming that layer under a masked ancestor is unreliable). The
   div wrapper is a normal element, not an iframe, so it composites with
   the mask fine; the iframe inside it just stays a plain 100%/100%,
   never transformed itself. Modest 125%/100% oversize (not Puzzle's more
   aggressive per-slot values): this box's own ratio (795x544 nominal,
   ~1.46:1, growing wider as flex-grow gives it more room) never strays
   far from 16:9 in either direction. This rule's own top/left/width/
   transform override .nd-media's inset:0 above for this specific
   (embed-only) case -- later in source, same specificity, wins the tie. */
.nd-testimonial-media .nd-media-embed {
    top: 50%; left: 50%; right: auto; bottom: auto;
    width: 125%; height: 100%;
    transform: translate(-50%, -50%);
}
.nd-testimonial-media .nd-media-embed iframe { width: 100%; height: 100%; display: block; border: 0; }

.nd-testimonial-nav { display: flex; align-items: center; justify-content: center; gap: 19px; margin-top: 30px; }
/* No box-shadow -- client-reported (2026-08-03) these buttons have a
   shadow Figma doesn't (node 398:12058: plain white bg,rounded-[4px],
   no shadow class at all). The mobile override below already dropped it
   (box-shadow:none) at some point; this base/desktop rule was the one
   spot it never got removed from. */
.nd-testimonial-arrow { order: 2; width: 46px; height: 46px; display: flex; align-items: center; justify-content: center; background: #ffffff; border: none; border-radius: 4px; cursor: pointer; padding: 0; }
.nd-testimonial-arrow svg { width: 18px; height: 14px; }
/* order swapped (2026-08-03, client-reported "pointing the opposite
   direction" vs Figma): .nd-testimonial-nav inherits dir="rtl" from its
   ancestor, so order:1 is the RIGHT-most slot and order:3 the LEFT-most --
   prev (order:1, rotated 180 from the shared arrow SVG) was landing on
   the right pointing left, next (order:3, unrotated) on the left pointing
   right. Figma (node 398:12058) has it the other way: the rotated arrow
   sits on the LEFT (pointing left) and the unrotated one on the RIGHT
   (pointing right) -- confirmed via that node's own screenshot. This only
   swaps visual position; each button's actual prev/next click behavior
   is still bound by class in nd-shared.js regardless of `order`. */
.nd-testimonial-arrow-prev { order: 3; transform: rotate(180deg); }
.nd-testimonial-arrow-next { order: 1; }
/* Solid-fill dots (gray idle, blue active), no border -- confirmed via
   Figma's own design context for node 398:12508 ("Frame 151": 5 plain
   filled circles, r:5.5 i.e. 11px diameter, fill #D9D9D9, the last one
   #002ABA, spaced on a 30px pitch = 19px edge-to-edge gap). The previous
   outline-circle treatment (2px blue border, transparent fill, solid-
   fill only when active) was a guess from an earlier pass and is what
   QA flagged as "not like figma" -- mobile's own dots already use a
   solid-fill idiom (opacity-based) and weren't part of this complaint. */
.nd-testimonial-nav .nd-testimonial-dots { order: 2; display: flex; align-items: center; gap: 19px; list-style: none; margin: 0; padding: 0; }
.nd-testimonial-nav .nd-testimonial-dots li { margin: 0; padding: 0; width: 11px; height: 11px; }
.nd-testimonial-nav .nd-testimonial-dots li button { font-size: 0; line-height: 0; display: block; width: 11px; height: 11px; padding: 0; cursor: pointer; color: transparent; border: none; border-radius: 50%; background: #D9D9D9; outline: none; }
.nd-testimonial-nav .nd-testimonial-dots li.slick-active button { background: var(--nd-color-primary-blue); }

@media screen and (max-width: 991px) {
    /* width:100%/margin:0 reset -- the desktop rule's width:fit-content/
       max-width:1664px/margin:0 auto (2026-08-03 fix) is meant to stop the
       card over-stretching in the desktop SIDE-BY-SIDE layout; this stacked
       column layout never had that problem (media is already width:100%
       of the card below) and fit-content here would just make sizing
       depend on its children's own auto/100% widths circularly. */
    .nd-testimonial-card { flex-direction: column-reverse; padding: 24px; min-height: 0; gap: 24px; width: 100%; max-width: none; margin: 0; }
    /* order reset to 0 on both: flex-direction:column-reverse still
       respects `order` (it only flips the visual direction of the
       already order-sorted sequence), so the desktop side-swap above
       would otherwise silently flip this stacking too -- media was
       always on top/text below here, unreviewed by Niri this pass, so
       left exactly as it was rather than changed as a side effect. */
    .nd-testimonial-text { order: 0; max-width: none; align-items: flex-start; }
    .nd-testimonial-quote { font-size: 24px; }
    .nd-testimonial-desc { font-size: 16px; }
    /* flex:0 0 auto reset (2026-08-03): the desktop rule's flex-basis is
       now a fixed 795px (was `auto`) -- harmless on that row layout, but
       flex-basis applies to whichever axis is the main one, and this
       breakpoint's flex-direction:column-reverse makes that the VERTICAL
       axis, so an un-reset 795px basis would fight the height:220px right
       after it. */
    .nd-testimonial-media { order: 0; flex: 0 0 auto; width: 100%; max-width: none; height: 220px; min-height: 0; filter: none; }
    .nd-testimonial-media .nd-media { mask-image: none; -webkit-mask-image: none; }
    .nd-testimonial-slide { padding: 0 12px; }
}

/* Phone (node 872:3001, Hub Page's own testimonial reference): per direct
   feedback, several pieces of the 991px tablet fallback above don't match
   the real mobile design. */
@media screen and (max-width: 767px) {
    /* 70px top margin (the shared desktop/tablet spacing rhythm) removed
       entirely on phone. */
    .nd-component-wrapper.nd-testimonial { margin-top: 0; }

    /* Name/program line (node 872:3015): a plain 16px line, not desktop's
       22px treatment -- there was no phone override for this at all
       before, so it was inheriting the desktop font-size/gap unchanged. */
    .nd-testimonial-meta { font-size: 16px; gap: 8px; }
    .nd-testimonial-meta-sep { height: 18px; }

    /* Card/photo union mask restored: the 991px tablet block above strips
       it entirely (mask-image:none/filter:none) as an unreviewed
       tablet-only simplification, but Figma's own mobile card (node
       872:3005) keeps the exact same union shape, just smaller -- masks
       aren't baked to one fixed size, they need to fit whatever photo/
       video an editor loads. Reusing the identical mask data URI
       unmodified works here: mask-size:100% 100% stretches it to fit
       whatever box it's given, and this mobile media box's own ~319:220
       aspect ratio is already within 1% of the desktop mask's 795:544
       reference, so no new coordinates are needed. Mask on .nd-media
       (not this outer element) and filter kept on the outer -- same
       filter/mask-split fix as the desktop rule above. */
    .nd-testimonial-media { filter: drop-shadow(0 0 16.5px rgba(109, 109, 109, 0.25)); }
    .nd-testimonial-media .nd-media {
        -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 795 544'%3E%3Cpath d='M210,0 A32,32 0 0,0 178,32 L178,191 A32,32 0 0,1 146,223 L32,223 A32,32 0 0,0 0,255 L0,512 A32,32 0 0,0 32,544 L308,544 A32,32 0 0,0 340,512 L340,487 A32,32 0 0,1 372,455 L763,455 A32,32 0 0,0 795,423 L795,32 A32,32 0 0,0 763,0 Z'/%3E%3C/svg%3E");
        mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 795 544'%3E%3Cpath d='M210,0 A32,32 0 0,0 178,32 L178,191 A32,32 0 0,1 146,223 L32,223 A32,32 0 0,0 0,255 L0,512 A32,32 0 0,0 32,544 L308,544 A32,32 0 0,0 340,512 L340,487 A32,32 0 0,1 372,455 L763,455 A32,32 0 0,0 795,423 L795,32 A32,32 0 0,0 763,0 Z'/%3E%3C/svg%3E");
        -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
        -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    }

    /* Nav (node 872:3029): the exact same 28x28 no-shadow button / small
       filled-dot design already built for ND Hub Programs' own mobile nav
       (node 835:3098) -- same component, not a fresh guess. */
    .nd-testimonial-arrow { width: 28px; height: 28px; border-radius: 3px; }
    .nd-testimonial-arrow svg { width: 11px; height: 8.5px; }
    .nd-testimonial-nav .nd-testimonial-dots li { width: 7px; height: 7px; }
    .nd-testimonial-nav .nd-testimonial-dots li button { width: 7px; height: 7px; border: none; background: var(--nd-color-primary-blue); opacity: 0.2; }
    .nd-testimonial-nav .nd-testimonial-dots li.slick-active button { opacity: 1; }
}

/* --- ND Content Editor ---
   Figma node 839:6742: a centered column (title, body paragraph, CTA).
   Body is still real rich HTML (client brief requires the editor) --
   .nd-ce-body's own rule below is the base paragraph look Figma shows,
   with nested h1-h4/ul/ol/li/a rules underneath it for whatever additional
   formatting an editor actually adds (Figma has no spec for those since
   this particular design only uses plain paragraphs). Reuses .nd-dr-cta
   (Damka / Damka Radio's shared CTA button) rather than inventing a
   fourth button look, per the brief. */
/* Compound selector (not bare .nd-content-editor): render_html()'s own
   top-level div reuses "nd-content-editor" as its class too, nested
   directly inside the auto-generated .nd-component-wrapper.nd-content-editor
   outer div -- same collision as Damka Radio/Puzzle/Testimonial/Single
   Media/Form Section elsewhere in this file. A bare rule doubled this
   padding onto both, confirmed as the "double padding" Niri reported. */
.nd-component-wrapper.nd-content-editor { padding: 60px 20px; }
.nd-content-editor { display: flex; flex-direction: column; align-items: center; gap: 20px; max-width: 1860px; margin: 0 auto; text-align: center; }
.nd-ce-title { font-family: 'Assistant', sans-serif; font-size: 60px; font-weight: 700; color: #002ABA; letter-spacing: -2px; text-transform: uppercase; text-align: center; max-width: 874px; margin: 0; }
.nd-ce-body { font-family: 'Assistant', sans-serif; font-size: 20px; font-weight: 400; color: #000000; line-height: normal; text-align: center; max-width: 1100px; margin: 0 auto; }
.nd-ce-body > *:last-child { margin-bottom: 0; }
/* Repeats font-family/size/weight/color/line-height/text-align directly on
   the nested <p> (not just the wrapping .nd-ce-body div): the actual text
   sits inside a <p> the rich text editor generates, and the parent theme
   has its own rule targeting bare <p> tags directly (Trade Gothic) that
   wins over an inherited value from an ancestor regardless of that
   ancestor's specificity -- same reason Damka's body text needed this. */
.nd-ce-body p { font-family: 'Assistant', sans-serif; font-size: 20px; font-weight: 400; color: #000000; line-height: normal; text-align: center; margin: 0 0 16px 0; }
.nd-ce-body h1, .nd-ce-body h2, .nd-ce-body h3, .nd-ce-body h4 { color: #002ABA; font-weight: 700; margin: 24px 0 12px 0; }
.nd-ce-body h1:first-child, .nd-ce-body h2:first-child, .nd-ce-body h3:first-child, .nd-ce-body h4:first-child { margin-top: 0; }
.nd-ce-body ul, .nd-ce-body ol { margin: 0 0 16px 0; padding-inline-start: 24px; text-align: right; }
.nd-ce-body li { margin-bottom: 8px; }
.nd-ce-body a { color: #002ABA; text-decoration: underline; }
/* Client-requested (2026-07-27): a second CTA button. .nd-content-editor
   is a flex COLUMN, so the two buttons (both plain .nd-dr-cta children)
   need their own row wrapper to sit side by side instead of stacking. */
.nd-ce-buttons { display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap; }

@media screen and (max-width: 991px) {
    .nd-ce-title { font-size: var(--nd-h2-mobile); }
    .nd-ce-body { font-size: 18px; }
}
/* Phone (node 1027:5441): 28px/16px, not the 991px tablet block's
   24px/18px. .nd-ce-body p repeated for the same reason it's set
   elsewhere in this file (the actual text sits in a nested <p>).
   letter-spacing -1px, not the base rule's -2px: same fix, same reasoning
   as ND Hub Programs' own standalone title -- -2px was tuned against the
   60px desktop size (~-3.3% of font-size); left unchanged at this much
   smaller 28px it's a disproportionately tight -7.1%, cramped on Hebrew
   glyphs. Client-reported (2026-07-27). Nothing here forces a single
   line -- max-width:874px on the base rule already just wraps normally,
   so the "allow 2 lines" half of this request needed no separate fix. */
@media screen and (max-width: 767px) {
    .nd-ce-title { font-size: 28px; letter-spacing: -1px; }
    .nd-ce-body, .nd-ce-body p { font-size: 16px; }
    /* Figma's own mobile frame has only 13px of side inset and no
       top/bottom padding of its own at this level (node 1027:5441) -- the
       desktop 60px/20px was still applying here unreduced even before the
       double-padding fix above. 36px vertical matches the same real
       section-to-section gap already confirmed elsewhere on this page
       (ND Hub Programs' standalone title, ND Testimonial, ND Form
       Section), not the desktop rhythm. */
    .nd-component-wrapper.nd-content-editor { padding: 36px 13px; }
}

/* --- ND Table (utility CSS class, not a component) ---
   Client-requested (2026-08-03): a plain CSS class for styling any table
   an editor pastes/inserts on a page, for use across a variety of pages'
   own content -- not a new shortcode/component, just a class to apply.
   No Figma spec exists for this (it wasn't a designed component), so the
   look below is a new, reasonable-default design matching this site's
   existing visual language (brand blue header, 20px radius + the same
   0 0 20px rgba(161,161,161,0.25) shadow used everywhere else in this
   file, Assistant font) -- flag any color/spacing changes wanted after
   seeing it live, nothing here is pinned to an approved design.
   Usage: wrap the table in a div and add BOTH classes --
     <div class="nd-table-wrap"><table class="nd-table">...</table></div>
   -- .nd-table-wrap is optional but recommended: it's what makes a table
   wider than its column scroll horizontally on narrow screens instead of
   overflowing the page, since a real <table> can't do that on its own
   without breaking out of table layout. WPBakery's Text/Raw HTML element
   and the block editor's own Table block both expose an "Extra class" /
   "Additional CSS Class(es)" field for the class="nd-table" part; the
   wrap div needs a plain Raw HTML/Custom HTML block around it. */
.nd-table-wrap { width: 100%; overflow-x: auto; }
.nd-table { width: 100%; border-collapse: collapse; font-family: 'Assistant', sans-serif; font-size: 18px; color: #000000; direction: rtl; border-radius: 20px; overflow: hidden; box-shadow: 0 0 20px rgba(161, 161, 161, 0.25); }
.nd-table th, .nd-table td { padding: 16px 24px; text-align: right; border-bottom: 1px solid rgba(0, 0, 0, 0.08); }
.nd-table thead th { background: #002aba; color: #ffffff; font-weight: 700; }
.nd-table tbody tr:last-child td { border-bottom: 0; }
.nd-table tbody tr:nth-child(even) { background: #f7f8fc; }
@media screen and (max-width: 767px) {
    .nd-table { font-size: 14px; }
    .nd-table th, .nd-table td { padding: 10px 12px; }
}

/* --- ND Inside Page Menu ---
   New component, client-requested (2026-08-03), no Figma spec -- see
   class-nd-inside-page-menu.php's own docblock for the referenced
   behavior (shinshin.jewishagency.org/community's top pill nav).
   .nd-ipm-item layers compact nav-strip sizing on top of the shared
   .nd-btn-solid-blue/.nd-btn-outline-blue color/border logic (both
   already used elsewhere, e.g. Hero Carousel V2's own CTAs) rather than
   duplicating those colors/states here -- this override only touches
   size/spacing, not color, so it stays in sync with those classes if
   the brand color ever changes.
   Top spacing (2026-08-03, client-reported this was missing -- "same
   like other components we created"): every other ND component wrapper
   in this file carries its own top margin/padding at this exact 70px/0
   desktop/mobile split (e.g. ND Testimonial, ND Text Box just above),
   this one simply never got it added when it was first built. */
.nd-component-wrapper.nd-inside-page-menu { margin-top: 70px; }
.nd-ipm { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; }
.nd-ipm-item { font-size: 16px; padding: 10px 24px; border-radius: 8px; }
@media screen and (max-width: 767px) {
    .nd-component-wrapper.nd-inside-page-menu { margin-top: 0; }
    .nd-ipm { gap: 8px; }
    .nd-ipm-item { font-size: 14px; padding: 8px 16px; }
}

/* --- ND Text Box ---
   New component, client-requested (2026-08-03), no Figma spec -- see
   class-nd-text-box.php's own docblock. .nd-tb-buttons mirrors
   .nd-damka-buttons/.nd-ce-buttons exactly (same shared 2-CTA-row idiom);
   the card itself (radius/shadow/padding/max-width) matches this file's
   own standing convention for a centered content card (same numbers as
   ND Newsletter's .nd-nl-card, minus that one's own fixed blue -- this
   card's background is the client-configurable bg_color inline style
   instead). */
.nd-component-wrapper.nd-text-box { margin-top: 70px; }
.nd-tb { width: 100%; max-width: 1860px; margin: 0 auto; padding: 80px 174px; border-radius: 32px; display: flex; flex-direction: column; align-items: center; gap: 24px; text-align: center; }
/* margin:0, overriding the shared .nd-title-h2 default (0 0 16px 0) --
   this card already spaces its children via its own flex gap above;
   without this the title's own margin would compound with that gap,
   same fix already applied on ND Content Editor's title for the exact
   same reason. */
.nd-tb .nd-title-h2 { margin: 0; }
.nd-tb-text { font-family: 'Assistant', sans-serif; font-size: 20px; font-weight: 400; color: #000000; margin: 0; max-width: 900px; }
.nd-tb-buttons { display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap; }
.nd-tb-buttons .nd-dr-cta { margin-top: 0; }
@media screen and (max-width: 991px) {
    .nd-tb { padding: 48px 24px; gap: 20px; }
    .nd-tb-text { font-size: 18px; }
}
@media screen and (max-width: 767px) {
    .nd-component-wrapper.nd-text-box { margin-top: 0; }
    .nd-tb { padding: 40px 20px; }
    .nd-tb-text { font-size: 16px; }
}
