.elementor-kit-5{--e-global-color-primary:#051934;--e-global-color-secondary:#C09D4F;--e-global-color-text:#051934;--e-global-color-accent:#3C75CB;--e-global-color-nswhite:#FFFFFF;--e-global-color-nsbg:#F3EFEA;--e-global-color-nssurface:#F8F6F3;--e-global-color-nscream:#F8F4EF;--e-global-color-nslavender:#F4F0F9;--e-global-color-nsarrowfill:#F1EFEE;--e-global-color-nsarrowline:#F1ECE9;--e-global-color-nseyebrow:#936A0F;--e-global-typography-primary-font-family:"DM Sans";--e-global-typography-primary-font-size:64px;--e-global-typography-primary-font-weight:400;--e-global-typography-primary-line-height:1.08em;--e-global-typography-primary-letter-spacing:-0.64px;--e-global-typography-secondary-font-family:"DM Sans";--e-global-typography-secondary-font-size:52px;--e-global-typography-secondary-font-weight:400;--e-global-typography-secondary-line-height:1em;--e-global-typography-secondary-letter-spacing:-0.52px;--e-global-typography-text-font-family:"Manrope";--e-global-typography-text-font-size:20px;--e-global-typography-text-font-weight:400;--e-global-typography-text-line-height:1.5em;--e-global-typography-accent-font-family:"Manrope";--e-global-typography-accent-font-size:16px;--e-global-typography-accent-font-weight:600;--e-global-typography-accent-line-height:1.1em;--e-global-typography-nsh3-font-family:"DM Sans";--e-global-typography-nsh3-font-size:32px;--e-global-typography-nsh3-font-weight:500;--e-global-typography-nsh3-line-height:1.2em;--e-global-typography-nsh4-font-family:"DM Sans";--e-global-typography-nsh4-font-size:26px;--e-global-typography-nsh4-font-weight:500;--e-global-typography-nsh4-line-height:1.1em;--e-global-typography-nseyebrow-font-family:"Manrope";--e-global-typography-nseyebrow-font-size:13px;--e-global-typography-nseyebrow-font-weight:500;--e-global-typography-nseyebrow-text-transform:uppercase;--e-global-typography-nseyebrow-line-height:1.5em;--e-global-typography-nseyebrow-letter-spacing:1.3px;--e-global-typography-nsbodytight-font-family:"Manrope";--e-global-typography-nsbodytight-font-size:20px;--e-global-typography-nsbodytight-font-weight:400;--e-global-typography-nsbodytight-line-height:1.2em;--e-global-typography-nsbodys-font-family:"Manrope";--e-global-typography-nsbodys-font-size:16px;--e-global-typography-nsbodys-font-weight:400;--e-global-typography-nsbodys-line-height:1.5em;--e-global-typography-nslabel-font-family:"Manrope";--e-global-typography-nslabel-font-size:16px;--e-global-typography-nslabel-font-weight:400;--e-global-typography-nslabel-line-height:1.1em;--e-global-typography-nsstatvalue-font-family:"Manrope";--e-global-typography-nsstatvalue-font-size:20px;--e-global-typography-nsstatvalue-font-weight:600;--e-global-typography-nsstatvalue-line-height:1.5em;--e-global-typography-nsstatlabel-font-family:"Manrope";--e-global-typography-nsstatlabel-font-size:16px;--e-global-typography-nsstatlabel-font-weight:400;--e-global-typography-nsstatlabel-line-height:1em;--e-global-typography-nsname-font-family:"Manrope";--e-global-typography-nsname-font-size:20px;--e-global-typography-nsname-font-weight:600;--e-global-typography-nsname-line-height:1.2em;--e-global-typography-nsavatar-font-family:"Manrope";--e-global-typography-nsavatar-font-size:19px;--e-global-typography-nsavatar-font-weight:600;--e-global-typography-nsavatar-line-height:1.2em;background-color:#FFFFFF;color:#051934;font-family:"Manrope", Sans-serif;font-size:20px;font-weight:400;line-height:1.5em;}.elementor-kit-5 button,.elementor-kit-5 input[type="button"],.elementor-kit-5 input[type="submit"],.elementor-kit-5 .elementor-button{background-color:#051934;font-family:"Manrope", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;color:#FFFFFF;border-radius:32px 32px 32px 32px;padding:12px 24px 12px 24px;}.elementor-kit-5 e-page-transition{background-color:#FFBC7D;}.elementor-kit-5 a{color:#051934;}.elementor-kit-5 a:hover{color:#3C75CB;}.elementor-kit-5 h1{color:#051934;font-family:"DM Sans", Sans-serif;font-size:64px;font-weight:400;line-height:1.08em;letter-spacing:-0.64px;}.elementor-kit-5 h2{color:#051934;font-family:"DM Sans", Sans-serif;font-size:52px;font-weight:400;line-height:1em;letter-spacing:-0.52px;}.elementor-kit-5 h3{color:#051934;font-family:"DM Sans", Sans-serif;font-size:32px;font-weight:500;line-height:1.2em;}.elementor-kit-5 h4{color:#051934;font-family:"DM Sans", Sans-serif;font-size:26px;font-weight:500;line-height:1.1em;}.elementor-kit-5 label{color:#051934;}.elementor-kit-5 input:not([type="button"]):not([type="submit"]),.elementor-kit-5 textarea,.elementor-kit-5 .elementor-field-textual{font-family:"Manrope", Sans-serif;font-size:16px;font-weight:400;line-height:1.1em;color:#051934;background-color:rgba(255,255,255,0.6);border-radius:15px 15px 15px 15px;}.elementor-section.elementor-section-boxed > .elementor-container{max-width:1360px;}.e-con{--container-max-width:1360px;--container-default-padding-top:0px;--container-default-padding-right:0px;--container-default-padding-bottom:0px;--container-default-padding-left:0px;}.elementor-widget:not(:last-child){--kit-widget-spacing:0px;}.elementor-element{--widgets-spacing:0px 0px;--widgets-spacing-row:0px;--widgets-spacing-column:0px;}{}h1.entry-title{display:var(--page-title-display);}@media(max-width:1024px){.elementor-kit-5{--e-global-typography-primary-font-size:52px;}.elementor-section.elementor-section-boxed > .elementor-container{max-width:1024px;}.e-con{--container-max-width:1024px;}}@media(max-width:767px){.elementor-kit-5{--e-global-typography-primary-font-size:38px;--e-global-typography-secondary-font-size:34px;}.elementor-section.elementor-section-boxed > .elementor-container{max-width:767px;}.e-con{--container-max-width:767px;}}
/* Start custom CSS *//* ---- optical size ------------------------------------------------------ */
/* DM Sans Variable carries an `opsz` axis (9-40). CSS `font-optical-sizing`
   defaults to `auto`, which feeds the FONT SIZE into that axis — so a 52px H2
   renders the display cut at opsz 40, roughly 12% narrower than the design, and
   headings wrap a word early. Figma composed with the STATIC DM Sans, i.e. the
   opsz 14 cut, so pin the axis there.

   `font-optical-sizing: none` does NOT do this: it falls back to the axis
   DEFAULT, which in this build measures as opsz 9 (wider than 14, still wrong).
   Only an explicit font-variation-settings hits 14.

   Measured on the testimonials H2 ("us to help their children understand"):
     opsz 40 (auto) 800.95px | opsz 9 904.83px | opsz 14 891.53px
     Figma inked width 895.63px  -> 14 is the match.

   The property inherits, so declaring it once on <body> covers every widget.
   Manrope has no opsz axis and simply ignores the unknown tag. */
body { font-variation-settings: "opsz" 14; }

/* ---- responsive type scale --------------------------------------------- */
/* The step-down now lives on the Kit typography globals themselves
   (apply-northstar-kit.php -> ns_type 'tablet'/'mobile'), so every widget bound
   to a global inherits it. Only the two headings that do NOT follow their
   global's own scale need saying here: the CTA and contact H2s override
   line-height/tracking locally, so their responsive size has to come with them. */
@media (max-width: 767px) {
  .ns-cta-title .elementor-heading-title,
  .ns-contact-title .elementor-heading-title { font-size: 34px !important; }
}

/* ---- heading emphasis runs -------------------------------------------- */
/* Mixed upright/italic headings are one heading widget with <em>/<strong>
   runs. DM Sans italic + bold are registered as real webfont styles, so the
   browser picks the correct face rather than synthesising one. */
.elementor-heading-title em            { font-style: italic; }
.elementor-heading-title strong        { font-weight: 700; }

/* Only the hero's emphasis run carries the accent blue. */
.ns-hero-title .elementor-heading-title strong em {
  color: var(--e-global-color-accent);
}

/* ---- page frame ------------------------------------------------------- */
/* Section BACKGROUNDS run edge to edge; only the CONTENT is held to the 1440
   artboard. Elementor's boxed inner already caps at the 1360 Kit width, so the
   1440 frame is that plus the 40px gutter. Nothing is capped on <body> — doing
   that stopped the white bands reaching the screen edge on wide monitors. */
html { background-color: var(--e-global-color-nswhite); }

/* Anything positioned against the 1440 frame anchors to the boxed inner. */
#hero > .e-con-inner { position: relative; }

/* The testimonial band is a CARD in Figma (1440 wide, 40px radius, white
   showing at its corners), not a full-bleed band — so it caps at the artboard
   width instead of running to the screen edge. One rule, no extra wrapper. */
#reviews { max-width: 1440px; margin-inline: auto; }

/* ---- header ----------------------------------------------------------- */
/* In Figma the header sits INSIDE the hero frame at y=0, overlaying it. Left in
   normal flow it would push the hero down by its own 79px and every section
   offset would drift. */
.elementor-location-header {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 100;
}

/* Logged in, WordPress pins a 32px admin bar to the top and pushes the document
   down with `html { margin-top: 32px }`. That margin moves the FLOW — the hero
   starts at y=32 — but not the initial containing block, which is what an
   absolutely positioned element with no positioned ancestor resolves against.
   So the header stayed at y=0: tucked under the bar, and 32px out of step with
   the hero it overlays. Offset it by the same amount.

   Only meaningful at >=1025 where the header is absolute; below that it is in
   normal flow and already sits under the bar, and `top` on a static element is
   ignored — so this needs no media query. WordPress's taller 46px bar only
   appears at <=782, which is always in that static range. */
body.admin-bar .elementor-location-header { top: 32px; }

/* A typography group bound to a Kit global ignores local sub-controls, so these
   one-off values cannot be set on the widget.

   Figma: DM Sans 400 / 64px / line-height 110% (70.4px) / letter-spacing 0.
   This heading previously carried a -3.2px tracking hack, invented to explain a
   render that measured ~8% narrower than the build. That width gap was the
   unpinned `opsz` axis (see the optical-size block at the top of this file), not
   tracking — the API reports letterSpacing 0.0 and no run overrides. With opsz
   pinned the hack over-tightens, so it is gone. Verify against Figma's inked
   width for "Ready to learn more?": 560.57px. */
.ns-cta-title .elementor-heading-title {
  line-height: 1.1 !important;          /* 110% -> 70.4px */
  letter-spacing: normal !important;    /* Figma: 0, not the H2 global's -0.01em */
}

/* "Let's Chat" is 52px like the four section H2s but does NOT share their metrics:
   Figma gives it 110% line-height (57.2px) and zero tracking, where the H2 global
   is 100% and -0.52px. Bound to the global, both came through wrong. */
.ns-contact-title .elementor-heading-title {
  line-height: 1.1 !important;
  letter-spacing: normal !important;
}

/* ---- hero ------------------------------------------------------------- */
/* The exported composite is pre-clipped to the hero's visible right half and
   sits behind the copy column. */
.ns-hero-art  { z-index: 0; pointer-events: none; }
.ns-hero-art img { display: block; width: 100%; height: auto; }

/* The art returns to normal flow under the copy once the overlap composition
   stops fitting. That threshold is NOT Elementor's 1024 tablet breakpoint: the
   artwork is 972px wide and anchored right, so by ~1300 its left edge has run
   past the copy column and the constellation's baked-in subject labels sit on
   top of the headline. Measured at 1100: art x=128..1100 against copy x=64..550
   — the whole column covered. Stack from 1299 down.

   Elementor emits no rule when a responsive position is reset to "Default", so
   the reset has to live here. */
/* Measured at 1440: the artwork is 972px wide, anchored to the viewport's right
   edge, and its first opaque pixel is 13.5% in (source x=194 of 1439) — so its
   ink starts at 100vw - 841px. The copy column is a FIXED 486px at x=64, i.e.
   it always ends at 550. Those cross below 1391px, and because the artwork has
   the subject labels baked into it, the headline and the constellation's own
   text then print on top of each other.

   The constraint is on the INK, not the box: with the art anchored right,
     ink = 100vw - w + 0.1348w  >=  574   (550 copy edge + 24 clear)
   solves to  w <= (100vw - 574px) / 0.8652, i.e. x 1.156. At 1440 that allows
   1001px, so the 972px design width is untouched and the verified 1440
   composition still matches Figma; below 1440 the art shrinks just enough to
   keep its labels off the headline. Constraining the left edge directly instead
   would have shrunk the art to 866px at 1440 and broken the match. */
@media (min-width: 1200px) {
  .ns-hero-art { width: min(972px, calc((100vw - 574px) * 1.156)) !important; }
}

/* Narrower than that the art would be smaller than the copy beside it, so it
   goes back into normal flow underneath. */
@media (max-width: 1199px) {
  .ns-hero-art { position: static !important; width: 100% !important; margin-top: 32px; }
}

/* The header only leaves its overlay once the hero is a plain stack. */
@media (max-width: 1024px) {
  .elementor-location-header { position: static; }
}

/* ---- buttons ---------------------------------------------------------- */
/* Figma's "Button primary 3" is a dark pill plus a light circular arrow with a
   gap between them. It is ONE control, so it is ONE anchor: the <a> is
   transparent and only lays its two spans out in a row. .elementor-button-text
   becomes the pill, .elementor-button-icon becomes the circle. The whole shape
   is the link, so there is a single tab stop, a single focus ring and a single
   hit area — a second icon widget alongside gave the page two of each.

   The gap comes from the widget's own icon_indent control, not from here. */
.ns-btn .elementor-button { display: inline-flex; align-items: center; }
.ns-btn .elementor-button-content-wrapper {
  display: inline-flex;
  align-items: center;
  flex-direction: row;
}
.ns-btn .elementor-button-text {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-grow: 0;
  background-color: var(--e-global-color-primary);
  color: var(--e-global-color-nswhite);
  border-radius: 32px;
  padding: 12px 24px;
}
.ns-btn .elementor-button-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border-radius: 50%;
  color: var(--e-global-color-primary);
}
.ns-btn .elementor-button-icon svg { width: 24px; height: 24px; display: block; }

/* Figma pins the composite's pill and circle to 48px. */
.ns-btn.ns-btn-48 .elementor-button-text { min-height: 48px; }
.ns-btn.ns-btn-48 .elementor-button-icon { width: 48px; height: 48px; }

/* Header and footer use the OTHER Figma button: a single dark pill with the
   arrow INSIDE it (150x44 = 20 + label 80 + 6 + icon 24 + 20). No circle, so it
   must not pick up the composite rules — its padding and fill come from the
   widget's own controls and only the height is pinned here. */
.ns-pill .elementor-button { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
/* The content wrapper is a flex row holding a 24px icon and the label. At the
   default `align-items: stretch` the label span was pulled to the icon's 24px
   height while its 17.6px line box stayed at the top — putting the text 4px
   above the pill's optical centre. Centre the row instead of stretching it. */
.ns-pill .elementor-button-content-wrapper { align-items: center; }
.ns-pill .elementor-button-icon svg { width: 24px; height: 24px; display: block; }

/* Circle fill per placement. */
.ns-arrow-solid   .elementor-button-icon { background-color: var(--e-global-color-nsarrowfill); }
.ns-arrow-outline .elementor-button-icon {
  background-color: var(--e-global-color-nswhite);
  border: 1px solid var(--e-global-color-nsarrowline);
}

/* ---- icons ------------------------------------------------------------ */
/* Elementor renders an icon SVG at 1em SQUARE, which squashes any non-square
   glyph: the sparkle's viewBox is 63.17x70, so a 37px star drew 37x37 where
   Figma has 37x41. Let the height follow the intrinsic ratio.
   (The bare Icon-widget rules that used to sit here died when the cards
   collapsed into icon-boxes — there is no .elementor-widget-icon left.) */
.elementor-widget-icon-box .elementor-icon svg { width: 1em !important; height: auto !important; }
.elementor-widget-icon-box .elementor-icon { line-height: 0; }



/* icon_space/title_bottom_space DO emit (the wrapper gets flex gap:61 and the
   title margin-bottom:16) even though they are not listed by get_controls() —
   Elementor registers those style controls lazily. So add NOTHING for the icon
   gap here; an extra margin-bottom stacked on the flex gap and ran the card
   +62px. Only the title's default top margin needs clearing. */
.ns-teach-box .elementor-icon-box-title       { margin-top: 0 !important; }
.ns-teach-box .elementor-icon-box-description { margin: 0 !important; }

/* star-rating's line-box renders 30px for a 24px icon; Figma's row is 24. */
.ns-rating .elementor-star-rating { line-height: 1; }
/* Marked stars carry .elementor-star-full. The widget's stars_color control did
   not emit, and e_font_icon_svg strips the eicons glyph entirely — hence the
   unicode star style plus explicit colours here. */
/* icon_size (24) already sets the font-size and the rule above zeroes the line
   box; only the explicit height is still doing work. */
/* Figma's star is a specific rounded five-point shape (2037-335 > Stars >
   icon star). Elementor can only draw its own unicode glyph or a Font Awesome
   star, and e_font_icon_svg strips the eicons one entirely — so the widget's
   glyph is hidden and the real vector is painted through a CSS mask. One
   native star-rating widget still renders the row; five icon widgets per card
   would undo the icon-box consolidation. */
.ns-rating .elementor-star-rating i {
  width: 24px;
  height: 24px;
  color: transparent !important;
  -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M11.4956%202.7143C11.7021%202.29575%2012.299%202.29575%2012.5055%202.7143L15.1795%208.13242C15.2616%208.29863%2015.4201%208.41383%2015.6036%208.44048L21.5828%209.30932C22.0447%209.37643%2022.2291%209.94406%2021.8949%2010.2698L17.5683%2014.4873C17.4355%2014.6166%2017.375%2014.803%2017.4063%2014.9857L18.4277%2020.9408C18.5066%2021.4008%2018.0237%2021.7516%2017.6106%2021.5345L12.2626%2018.7228C12.0986%2018.6366%2011.9026%2018.6366%2011.7385%2018.7228L6.3905%2021.5345C5.97738%2021.7516%205.49453%2021.4008%205.57343%2020.9408L6.59481%2014.9857C6.62614%2014.803%206.56557%2014.6166%206.43285%2014.4873L2.10622%2010.2698C1.77199%209.94406%201.95642%209.37643%202.41832%209.30932L8.39757%208.44048C8.58099%208.41383%208.73955%208.29863%208.82158%208.13242L11.4956%202.7143Z%22%20fill%3D%22%23000%22%20stroke%3D%22%23000%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E") center / 24px 24px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M11.4956%202.7143C11.7021%202.29575%2012.299%202.29575%2012.5055%202.7143L15.1795%208.13242C15.2616%208.29863%2015.4201%208.41383%2015.6036%208.44048L21.5828%209.30932C22.0447%209.37643%2022.2291%209.94406%2021.8949%2010.2698L17.5683%2014.4873C17.4355%2014.6166%2017.375%2014.803%2017.4063%2014.9857L18.4277%2020.9408C18.5066%2021.4008%2018.0237%2021.7516%2017.6106%2021.5345L12.2626%2018.7228C12.0986%2018.6366%2011.9026%2018.6366%2011.7385%2018.7228L6.3905%2021.5345C5.97738%2021.7516%205.49453%2021.4008%205.57343%2020.9408L6.59481%2014.9857C6.62614%2014.803%206.56557%2014.6166%206.43285%2014.4873L2.10622%2010.2698C1.77199%209.94406%201.95642%209.37643%202.41832%209.30932L8.39757%208.44048C8.58099%208.41383%208.73955%208.29863%208.82158%208.13242L11.4956%202.7143Z%22%20fill%3D%22%23000%22%20stroke%3D%22%23000%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E") center / 24px 24px no-repeat;
}
.ns-rating .elementor-star-full  { background-color: var(--e-global-color-primary); }
.ns-rating .elementor-star-empty { background-color: rgba(5, 25, 52, .18); }

/* ---- header geometry (Figma 2037:251) --------------------------------- */
/* Nav pill 189x38: pad 10/24, two links 45+64 with a 32 gap. Elementor's own
   item padding pushed it to 215, so the item box is zeroed and the gap owned
   by the list. */
/* Elementor's anchor is .elementor-item, not a bare `a` — targeting `li > a`
   left the widget's own horizontal padding in place and the pill ran wide.

   Scoped to --main: unqualified, `.elementor-nav-menu` also matches the BURGER
   DROPDOWN's list, and the !important beat the widget's own
   padding_vertical_dropdown_item — leaving 18px-tall rows in the mobile menu
   however that control was set. */
.elementor-location-header .elementor-nav-menu--main .elementor-item {
  padding: 0 !important;
}
/* menu_space_between already emits +/-16px margins on each item (=32 between),
   so an extra flex gap here would double it. */
/* Button widgets render their icon at 1em (16px at our 16px label). Figma's
   button icon is 24x24 in every instance. */
.elementor-button .elementor-button-icon svg,
.elementor-button svg {
  width: 24px;
  height: 24px;
}

/* Figma constrains only the FIRST teach card's title to 307px, which is what
   makes it break after "tutoring"; the second card runs full width. */
.ns-teach-narrow .elementor-icon-box-title { max-width: 307px; }

/* Difference card: the star is RIGHT-aligned above left-aligned text. icon-box
   has no control for that (text_align moves the whole block), so it is one
   rule rather than a reason to hand-build the card out of three widgets. */
.ns-diff-box .elementor-icon-box-icon { text-align: right; }

/* About card: Figma runs the description at 80% like the other body copy. The
   description lives inside the widget now, so the class goes on the widget. */
.ns-about-box .elementor-icon-box-description { opacity: .8; }
/* position:right sets the icon beside the WHOLE content column, so the 70px
   star no longer drives the title row's height the way it does in Figma — the
   card came out 170 instead of 202. Giving the title the star's height (and
   centring its text in it, as Figma does: title 38 centred in 70) restores
   both the row height and the star/title alignment. */
/* Only while the star actually sits BESIDE the title. Below the tablet
   breakpoint icon-box stacks the icon above the content, and the 70px title
   then just opens a hole between the heading and the copy. */
@media (min-width: 768px) {
  .ns-about-box .elementor-icon-box-title {
    min-height: 70px;
    display: flex;
    align-items: center;
  }
}

/* Cards are widgets now, so their margins come from the grid/flex parent. */
.ns-diff-box .elementor-icon-box-title,
.ns-about-box .elementor-icon-box-title { margin-top: 0; }
.ns-diff-box .elementor-icon-box-description,
.ns-about-box .elementor-icon-box-description { margin-bottom: 0; }

/* ---- interaction: hover + focus --------------------------------------- */
/* The Figma file ships no hover variants, so states are derived here. Every
   hover colour is checked against the surface it actually sits on rather than
   against white (practice: button hovers, check contrast against the parent). */

/* One control, so ONE hover. The two halves previously carried the transition
   while the lift sat on the anchor, and the icon's border-color was only
   transitioned on the outline variant — three properties on two elements with
   nothing tying them together, which is what read as staggered timing. Declare
   ONE shared duration/easing on every property that moves, on both halves and
   the anchor, so the whole control resolves as a single gesture. */
.ns-btn .elementor-button,
.ns-btn .elementor-button-text,
.ns-btn .elementor-button-icon,
.elementor-form .elementor-button {
  transition-property: background-color, border-color, color, transform, box-shadow;
  transition-duration: .2s;
  transition-timing-function: cubic-bezier(.2, .6, .3, 1);
}
.ns-btn .elementor-button:hover,
.ns-btn .elementor-button:focus-visible { transform: translateY(-1px); }

/* Default (light grounds: hero, about) — the navy pill lifts one step and the
   cream chip inverts to navy, so the pair keeps reading as one object. */
.ns-btn .elementor-button:hover .elementor-button-text,
.ns-btn .elementor-button:focus-visible .elementor-button-text {
  background-color: #0C2E57;
  box-shadow: 0 6px 16px rgba(5, 25, 52, .18);
}
.ns-btn .elementor-button:hover .elementor-button-icon,
.ns-btn .elementor-button:focus-visible .elementor-button-icon {
  background-color: var(--e-global-color-primary);
  border-color: var(--e-global-color-primary);
  color: var(--e-global-color-nswhite);
}

/* On the CTA card the ground is a night-sky photo. Lifting navy to a lighter
   navy there is nearly invisible and the label loses contrast, and inverting
   the chip to navy makes it vanish into the sky. Invert the pair instead:
   white pill with navy label (15.9:1), navy chip with a white arrow. */
.ns-cta-card .elementor-button:hover .elementor-button-text,
.ns-cta-card .elementor-button:focus-visible .elementor-button-text {
  background-color: var(--e-global-color-nswhite);
  color: var(--e-global-color-primary);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .35);
}
.ns-cta-card .elementor-button:hover .elementor-button-icon,
.ns-cta-card .elementor-button:focus-visible .elementor-button-icon {
  background-color: var(--e-global-color-primary);
  color: var(--e-global-color-nswhite);
}

/* The plain header/footer pill is a different control from the composite, so it
   needs its own hover — splitting the two button systems dropped it.

   CASCADE TRAP: Elementor's per-widget rule is
     .elementor-23 .elementor-element.elementor-element-HeaderCta .elementor-button
   which is (0,4,0) — exactly the same weight as
     .elementor-location-header .ns-pill .elementor-button:hover
   and post-23.css loads AFTER the kit's post-5.css, so the widget rule won the
   tie and the hover silently did nothing. !important breaks the tie; adding
   another class would too, but the widget .elementor-kit-5 is generated and could grow. */
.ns-pill .elementor-button {
  transition: background-color .18s ease, color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.elementor-location-header .ns-pill .elementor-button:hover,
.elementor-location-header .ns-pill .elementor-button:focus-visible {
  background-color: #0C2E57 !important;
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(5, 25, 52, .18);
}
/* Footer pill is white-on-navy; gold keeps ~7:1 against the navy label. */
.elementor-location-footer .ns-pill .elementor-button:hover,
.elementor-location-footer .ns-pill .elementor-button:focus-visible {
  background-color: var(--e-global-color-secondary) !important;
  color: var(--e-global-color-primary) !important;
  transform: translateY(-1px);
}

/* The form's submit is a <button>, not this composite. Elementor's own form
   button rule carries element-id specificity, so a plain
   `.elementor-form .elementor-button:hover` never landed — hence the tag
   qualifier and !important. */
.elementor-form button.elementor-button {
  transition: background-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.elementor-form button.elementor-button:hover,
.elementor-form button.elementor-button:focus-visible {
  background-color: #0C2E57 !important;
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(5, 25, 52, .18);
}

/* Carousel controls invert the same way. */
#reviews .elementor-swiper-button:hover,
#reviews .elementor-swiper-button:focus-visible {
  background: var(--e-global-color-primary);
  color: var(--e-global-color-nswhite);
}

/* Nav links: underline rather than a colour shift, so contrast never drops. */
.elementor-nav-menu a { transition: opacity .15s ease; }
.elementor-nav-menu a:hover,
.elementor-nav-menu a:focus-visible { text-decoration: underline; text-underline-offset: 4px; }

/* Contact details and inline links. */
.ns-contact-value a { transition: color .15s ease; }
.ns-contact-value a:hover,
.ns-contact-value a:focus-visible { color: var(--e-global-color-accent); }

/* Cards lift subtly to signal the whole surface is one unit. */

/* One visible focus treatment everywhere, including keyboard-only. */
a:focus-visible,
button:focus-visible,
.elementor-button:focus-visible,
input:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--e-global-color-accent);
  outline-offset: 3px;
}
.elementor-field-textual:focus {
  outline: 2px solid var(--e-global-color-accent);
  outline-offset: 1px;
}

/* ---- anchors ---------------------------------------------------------- */
/* The header overlays the hero rather than being fixed, so only the WP admin
   bar needs an offset. Elementor owns smooth scrolling natively — do not set
   scroll-behavior here. */
.admin-bar #services,
.admin-bar #reviews,
.admin-bar #about,
.admin-bar #contact { scroll-margin-top: 46px; }

/* ---- media ------------------------------------------------------------ */

/* ---- text rhythm ------------------------------------------------------ */
/* Every body block goes through wpautop, and the trailing <p> keeps a ~14px
   bottom margin that Elementor does not reset. Left alone it inflates every
   card and column against the Figma geometry. */

/* ---- muted body copy -------------------------------------------------- */
/* Figma applies a real 80% opacity here, not a lightened hex. */
.ns-muted        { opacity: .8; }
/* Figma renders BOTH footer legal lines at 50% white (node opacity 0.5 on a
   #FFFFFF fill). Declared once, on the widget — a second .ns-footer-legal p
   rule used to sit in the footer block and the two multiplied to 25%. */
.ns-footer-legal { opacity: .5; transition: opacity .18s ease; }

/* ---- testimonials ----------------------------------------------------- */
/* Cards are a fixed 356px in the design with the quote clamped and a
   "Read more" affordance. Clamp the quote rather than truncating content. */
/* 356px card - 64px padding - stars/person/link rows and gaps leaves exactly
   five 30px lines for the quote. */
.ns-quote { opacity: .8; }
.ns-quote p {
  display: -webkit-box;
  -webkit-line-clamp: 5;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
}
/* Read more EXPANDS the quote in place — it must not scroll. The toggle lives
   in Elementor Pro Custom Code (behaviour); the clamp state lives here. */
.ns-review-open .ns-quote p { -webkit-line-clamp: unset; overflow: visible; }
.ns-review-open { min-height: 0 !important; }
.ns-avatar .elementor-heading-title {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 26px;
  background: rgba(5, 25, 52, .7);
}
.ns-readmore p { margin: 0; line-height: 1.1; }
.ns-readmore a { color: var(--e-global-color-primary); text-decoration: none; }
.ns-readmore a:hover { text-decoration: underline; }

/* ---- testimonial carousel --------------------------------------------- */
/* Real Elementor Pro nested carousel. Elementor renders its arrows overlaid on
   the track and its dots inside the swiper; Figma puts the arrows top-right
   beside the heading and the dots centred below. Reposition, don't fake. */
#reviews { position: relative; }
.ns-carousel .swiper { padding-bottom: 0 !important; }

.ns-carousel .swiper-pagination {
  position: static;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 56px;
  line-height: 0;
  /* Swiper centres the dot strip for an ABSOLUTE pagination: it writes an inline
     pixel width and a translate(-50%,-50%) transform to pair with left/top 50%.
     Taking it back into flow kills left/top but leaves those two behind, so the
     strip shrink-wrapped to 46px and sat 23px LEFT and 14px HIGH of centre.
     Drop both and let justify-content do the centring. */
  width: 100% !important;
  transform: none !important;
}
.ns-carousel .swiper-pagination-bullet {
  width: 8px !important; height: 8px !important; margin: 0 !important; opacity: 1;
  background: rgba(5, 25, 52, .25);
  transition: width .2s ease, height .2s ease, background .2s ease;
}
/* Swiper marks its own controls with *-lock when the track cannot move (all
   slides fit). Hiding on that class means the arrows and dots disappear while
   there are only two reviews and come back on their own once a third is added —
   no inert controls, and nothing hard-coded to today's review count. */
.ns-carousel .swiper-button-lock,
.ns-carousel .swiper-pagination-lock { display: none !important; }
.ns-carousel:has(.swiper-button-lock)::before { display: none; }

.ns-carousel .swiper-pagination-bullet-active {
  width: 14px !important; height: 14px !important;
  background: var(--e-global-color-primary);
}

/* The swiper buttons' offset parent is the CAROUSEL WIDGET (Elementor
   containers are position:relative), not the section — so offsets are measured
   from the widget's top-left, which sits 330px into the section. Figma puts the
   control group bottom-aligned with the heading block, i.e. 108px ABOVE the
   widget. */
@media (min-width: 1025px) {
  .ns-carousel { position: relative; }
  .ns-carousel::before {
    content: '';
    position: absolute;
    /* The ::before anchors to .ns-carousel, but the swiper buttons anchor to a
       wrapper 24px higher — hence the different offsets for the same visual row. */
    top: -112px; right: 0;
    width: 116px; height: 56px;
    border-radius: 35px;
    background: var(--e-global-color-nswhite);
    pointer-events: none;
  }
  #reviews .elementor-swiper-button { top: -84px; margin: 0; }
  #reviews .elementor-swiper-button-prev { right: 64px; left: auto; }
  #reviews .elementor-swiper-button-next { right: 4px;  left: auto; }
}

#reviews .elementor-swiper-button {
  position: absolute;
  width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--e-global-color-nsarrowfill);
  color: var(--e-global-color-primary);
  transition: background .18s ease, color .18s ease;
}
/* Elementor gives its swiper glyph an explicit near-white fill, which washes it
   out on the light arrow chip. The Figma glyph is a stroked long arrow, so the
   stroke must follow currentColor and the fill must stay none. */
/* Figma insets the glyph to 16x12 inside the 24px icon box (left/right 16.67%,
   top/bottom 25%); the raw export is an 18x14 viewBox with
   preserveAspectRatio="none", so a 24x24 box would stretch it. */
#reviews .elementor-swiper-button svg {
  width: 16px;
  height: auto;
  fill: none !important;
  stroke: currentColor;
}

/* Below desktop the controls sit under the track, centred. Taken out of their
   absolute placement they fell back into the carousel's own COLUMN flow and
   stacked on top of each other at the left edge (both at x=20), with the prev
   button half-hidden behind the card. Lay them out as a row of their own. */
@media (max-width: 1024px) {
  /* Wrap, don't stack: the track takes a full row, so the two buttons land side
     by side on the next one and the dots on a third. A plain column put each
     button on its own line. */
  .ns-carousel {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    column-gap: 8px;
    row-gap: 24px;
  }
  .ns-carousel .swiper { order: 1; flex: 0 0 100%; width: 100%; }
  #reviews .elementor-swiper-button { position: static; order: 2; margin: 0; }
  /* Dots are redundant next to a working pair of arrows on a phone, and with
     one card per view they multiply with the review count. Arrows only. */
  .ns-carousel .swiper-pagination { display: none; }
  .ns-carousel::before { display: none; }
}

/* The image widget's height:100% only reaches the <img>; the widget and its
   container have to carry the stretched row height down to it. */
/* The widget must STRETCH, and must not carry an explicit height while doing
   it: `height: 100%` on a flex item cancels `align-self: stretch`, so the
   widget fell back to the image's intrinsic ratio (456x342 in a 680 column) at
   every width where 903px was not the natural size. Stretch the widget, and let
   the img's own height:100% + object-fit:cover controls fill it.
   (Elementor 4.x drops .elementor-widget-container, so there is no inner
   wrapper to carry the height through.) */
.ns-teach-image { align-self: stretch; }
.ns-teach-image img { display: block; }

/* ---- contact ---------------------------------------------------------- */
/* The two columns are fixed at Figma's 600 + 671, which only add up inside the
   1360 desktop row. Between the tablet breakpoint and 1440 they were still
   fixed and still `space-between`, so the available width ran out and the
   columns sat edge to edge with no gutter at all. Let them share the row and
   keep a real gutter; no new breakpoint, just the range where the fixed widths
   stop fitting. */
@media (min-width: 1025px) and (max-width: 1439px) {
  /* The class sits on the boxed SECTION, and a boxed container puts its flex
     layout on .e-con-inner — so the gap has to go there, not on the .e-con. */
  .ns-contact-grid > .e-con-inner { gap: 40px; }
  .ns-contact-grid .ns-contact-col { width: auto; flex: 1 1 0; min-width: 0; }
}

/* font-weight is deliberately absent: the widget's own (0,3,0) rule from the
   `text` global outranks this (0,1,0) .elementor-kit-5, so declaring it here only
   looked like it worked. */
.ns-contact-label { color: rgba(5, 25, 52, .7); opacity: .8; }
.ns-contact-value a {
  color: var(--e-global-color-primary);
  text-decoration: underline;
  text-underline-position: from-font;
  /* `from-font` uses Manrope's own stroke, which at 20px draws 2px. Figma's is
     a single pixel 2px under the baseline (measured off the reference: one row,
     261px wide). At 2px it read as a heavy underscore. */
  text-decoration-thickness: 1px;
}
.ns-contact-label p, .ns-contact-value p { margin: 0; }

/* The 282px label column is set on the widget (Figma puts both values at x=322).
   It only makes sense while the row is 600px wide, so below tablet the label
   shrinks to its text and a real gap takes over. */
@media (max-width: 767px) {
  .ns-contact-row .ns-contact-label { width: auto !important; }
  .ns-contact-row { gap: 16px; }
}

/* ---- footer ----------------------------------------------------------- */
.ns-footer-legal p { margin: 0; }

/* The Weber credit always points at weberco.io, and at rest it must look exactly
   like the copyright beside it: Manrope 16/24, white at 50% (Figma 2037-427),
   no underline. On hover the whole line lifts to full white.
   TWO traps here, both previously live:
   - the dimming is on the WIDGET, so the hover has to lift the widget; raising
     opacity on the <a> alone is white-on-white, i.e. no visible hover;
   - the Kit's global `a:hover` (0,2,1) outranks `.ns-footer-legal a` (0,1,1)
     and repainted the link accent BLUE on navy, so the colour has to be pinned. */
.ns-footer-legal a { color: inherit; text-decoration: none; }
.ns-footer-legal a:hover,
.ns-footer-legal a:focus-visible { color: var(--e-global-color-nswhite) !important; }
.ns-footer-legal:has(a:hover),
.ns-footer-legal:has(a:focus-visible) { opacity: 1; }

/* Figma hangs a gold sparkle off the form panel's top-left and bottom-right
   corners (37x41, #C09D4F = the `secondary` global). Pure decoration, so it is
   the panel's own ::before/::after rather than two more widgets to position
   absolutely — the CSS-mask + global-colour route the project already uses for
   custom button icons. Offsets are Figma's: panel x729..1400 / y100..571 with
   the stars at (718,82) and (1376,547). */
.ns-contact-panel { position: relative; }
.ns-contact-panel::before,
.ns-contact-panel::after {
  content: "";
  position: absolute;
  width: 37px;
  height: 41px;
  background-color: var(--e-global-color-secondary);
  -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cpath%20d%3D%22M46.858%2016.707c.288-.211.647.148.436.436l-10.78%2014.726c-.08.11-.08.26%200%20.37l10.763%2014.605c.212.288-.148.648-.436.437l-14.71-10.767a.31.31%200%200%200-.369%200l-14.588%2010.75c-.288.211-.649-.15-.436-.437l10.749-14.588a.31.31%200%200%200%200-.37L16.72%2017.16c-.211-.287.15-.647.437-.435l14.605%2010.762c.11.08.26.081.37%200z%22%2F%3E%3Cpath%20d%3D%22M30.983%203.48c.094-.935%201.458-.935%201.553%200l2.389%2023.617a.78.78%200%200%200%20.656.692l20.125%203.147c.88.138.88%201.405%200%201.543l-20.125%203.147a.78.78%200%200%200-.656.692l-2.39%2023.616c-.094.936-1.458.936-1.552%200l-2.39-23.616a.78.78%200%200%200-.655-.692L7.813%2032.479c-.88-.138-.88-1.405%200-1.543l20.125-3.147a.78.78%200%200%200%20.656-.692z%22%2F%3E%3C%2Fsvg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cpath%20d%3D%22M46.858%2016.707c.288-.211.647.148.436.436l-10.78%2014.726c-.08.11-.08.26%200%20.37l10.763%2014.605c.212.288-.148.648-.436.437l-14.71-10.767a.31.31%200%200%200-.369%200l-14.588%2010.75c-.288.211-.649-.15-.436-.437l10.749-14.588a.31.31%200%200%200%200-.37L16.72%2017.16c-.211-.287.15-.647.437-.435l14.605%2010.762c.11.08.26.081.37%200z%22%2F%3E%3Cpath%20d%3D%22M30.983%203.48c.094-.935%201.458-.935%201.553%200l2.389%2023.617a.78.78%200%200%200%20.656.692l20.125%203.147c.88.138.88%201.405%200%201.543l-20.125%203.147a.78.78%200%200%200-.656.692l-2.39%2023.616c-.094.936-1.458.936-1.552%200l-2.39-23.616a.78.78%200%200%200-.655-.692L7.813%2032.479c-.88-.138-.88-1.405%200-1.543l20.125-3.147a.78.78%200%200%200%20.656-.692z%22%2F%3E%3C%2Fsvg%3E") center / contain no-repeat;
  pointer-events: none;
}
.ns-contact-panel::before { left: -11px;  top: -18px; }
.ns-contact-panel::after  { right: -13px; bottom: -17px; }

/* ---- form ------------------------------------------------------------- */
/* Figma: inputs 56px / 15px radius / NO border, fill rgba(255,255,255,.6);
   the textarea is 118px, padding 20 and its radius is deliberately 8px. */
.elementor-field-group .elementor-field-textual {
  height: 56px;
  padding: 0 20px;
  border-radius: 15px;
  border: 0 !important;
  box-shadow: none !important;
  background-color: rgba(255, 255, 255, .6) !important;
  color: var(--e-global-color-primary);
}
.elementor-field-group textarea.elementor-field-textual {
  height: 118px;
  padding: 20px;
  border-radius: 8px;
  resize: none;
}
/* Row rhythm comes from the form widget's own row_gap / label_spacing —
   adding margins here double-counted and grew the panel by 25px. */
.elementor-field-group > label {
  font-family: Manrope, sans-serif;
  font-size: 16px;
  line-height: 1.2;
  color: var(--e-global-color-primary);
}
.elementor-field-group .elementor-field-textual::placeholder { color: rgba(5, 25, 52, .7); opacity: 1; }

/* Focus state. The Elementor form widget exposes field_border_color / _width /
   _radius but NO focus variants, so this cannot come from a control. Figma has
   no focus spec either, so it is derived: the field brightens to solid white
   and takes a 2px navy ring drawn with box-shadow rather than a border, so the
   56px height and the text position do not shift when it is applied.
   `:focus-within` covers the label-click path; `:focus-visible` alone would
   miss it. The default UA outline is replaced, not removed. */
.elementor-field-group .elementor-field-textual {
  transition: background-color .18s ease, box-shadow .18s ease;
}
.elementor-field-group .elementor-field-textual:focus,
.elementor-field-group .elementor-field-textual:focus-visible {
  outline: none;
  background-color: var(--e-global-color-nswhite) !important;
  box-shadow: inset 0 0 0 2px var(--e-global-color-primary) !important;
}
/* Invalid fields after a submit attempt read as an error, not just "not focused". */
.elementor-field-group .elementor-field-textual:user-invalid {
  box-shadow: inset 0 0 0 2px #B3261E !important;
}
/* Figma leaves 32px between the field block and the submit; Elementor's own
   row gap only supplies 16. The button itself is pinned to the 48px pill. */
.elementor-form .elementor-field-group.elementor-field-type-submit {
  margin-top: 16px;
}
.elementor-form .elementor-field-group button.elementor-button {
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}/* End custom CSS */
/* Start Custom Fonts CSS */@font-face {
	font-family: 'DM Sans';
	font-display: swap;
	src: url('https://northstar-education.com/wp-content/uploads/2026/08/DMSans-Variable.ttf') format('truetype');
}
/* End Custom Fonts CSS */
/* Start Custom Fonts CSS */@font-face {
	font-family: 'Manrope';
	font-display: swap;
	src: url('https://northstar-education.com/wp-content/uploads/2026/08/Manrope-Variable.ttf') format('truetype');
}
/* End Custom Fonts CSS */