/* ####################  SHARED BASE  #################### */

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

.jpes-21 {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #2B2B2B;
  background: #FDFCFA;
  -webkit-font-smoothing: antialiased;
  text-align: left; /* Odoo's wrapper inherits text-align:center — reset it */
  /* full-bleed break-out of Odoo's .container (delete if you set the section to Full width) */
  position: relative;
  width: 100vw;
  left: 50%;
  right: 50%;
  margin-left: -50vw;
  margin-right: -50vw;
}
.jpes-21 :where(a) { text-decoration: none; color: #2B5492; }
.jpes-21 :where(a:hover) { color: #1E3E6E; }
.jpes-21 h1, .jpes-21 h2, .jpes-21 blockquote {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500;
  margin: 0;
}

/* --------------------------------
Font Helper for Odoo Blocks

These two are deliberately UNSCOPED so they can be applied inside Odoo blocks that sit
outside a .jpes-21 wrapper. Both names must therefore be ones nothing else can own:
.display-3-fs and .jpes-21-lead are invented, so they cannot collide.

`.lead` USED TO BE ONE OF THEM AND MUST NOT BE AGAIN. .lead is Bootstrap's, and Odoo's
editor applies it whenever an author picks a lead-paragraph text style — so styling it as
the 12px mono eyebrow silently turned real body copy into a label. It did exactly that to
a paragraph of the Technical Director job description, and to the contact form's spacer
paragraph before that. Any future helper here needs a namespaced name.
-------------------------------- */

.display-3-fs {
  font-family: Georgia, 'Times New Roman', serif;
  font-weight: 500;
  margin: 0;
}

/* Bootstrap's own lead-paragraph class, given a size and rhythm that suit the house body
   text. Colour is deliberately NOT set: .lead is unscoped, so it must stay legible on the
   dark bands as well as the light ones — it inherits whatever context it lands in. */
.lead { font-size: 18px; line-height: 1.7; font-weight: 400; }

.jpes-21 p { margin: 0; }

/* shared eyebrow label */
.jpes-21-eyebrow, .jpes-21-lead { font: 500 12px ui-monospace, Menlo, monospace; letter-spacing: .18em; color: #2B5492; }
.jpes-21-eyebrow--light { color: #9DB4DA; }

/* shared buttons — colour change on hover */
.jpes-21-btn {
  display: inline-block;
  padding: 14px 28px;
  font-size: 15px;
  font-weight: 500;
  color: #fff;
  transition: background-color .2s ease, border-color .2s ease, color .2s ease;
}
.jpes-21-btn--primary { background: #2B5492; }
.jpes-21-btn--primary:hover { background: #3866AC; color: #fff; }
.jpes-21-btn--outline { background: transparent; border: 1px solid rgba(255,255,255,.45); }
.jpes-21-btn--outline:hover { border-color: #fff; color: #fff; }
/* Inverted primary, for use ON the blue band (the services hero). --primary is blue on
   white and disappears against #2B5492, so a blue ground needs its own primary rather
   than an outline button doing double duty as the main call to action. Lives here with
   the other variants, not in the services stylesheet: any future blue band needs it too. */
.jpes-21-btn--onblue { background: #fff; color: #2B5492; }
.jpes-21-btn--onblue:hover { background: #E8EFF9; color: #1E3E6E; }

/* technical-drawing corner ticks (the sparing 1.3 nod) */
.jpes-21-tick { position: absolute; width: 14px; height: 14px; }
.jpes-21-tick--tl { top: -1px; left: -1px; border-top: 1.5px solid #2B5492; border-left: 1.5px solid #2B5492; }
.jpes-21-tick--tr { top: -1px; right: -1px; border-top: 1.5px solid #2B5492; border-right: 1.5px solid #2B5492; }
.jpes-21-tick--bl { bottom: -1px; left: -1px; border-bottom: 1.5px solid #2B5492; border-left: 1.5px solid #2B5492; }
.jpes-21-tick--br { bottom: -1px; right: -1px; border-bottom: 1.5px solid #2B5492; border-right: 1.5px solid #2B5492; }

/* figure caption with flanking rules */
.jpes-21-figcap {
  display: flex;
  align-items: center;
  gap: 10px;
  font: 10.5px ui-monospace, Menlo, monospace;
  color: #2B5492;
}
.jpes-21-figcap::before, .jpes-21-figcap::after { content: ""; flex: 1; height: 1px; background: #2B5492; }

/* faint blueprint grid — the hero's own 40px module, echoed on the dark bands so the
   drawing-sheet reference carries past the fold. Far below the hero's .08: white on
   flat dark reads much louder than the same alpha over the hero's photography.
   Shared with the closing CTA's overlay, which draws the same grid on a real element. */
.jpes-21 { --jpes21-grid: rgba(255,255,255,.028); }

/* shared metric — Georgia value in blue over a small muted label.
   Consumers: front featured-case block, case-studies cards. */
.jpes-21-metric { display: flex; flex-direction: column; gap: 4px; }
.jpes-21-metric__value { font-family: Georgia, "Times New Roman", serif; font-size: 26px; font-weight: 500; color: #2B5492; }
.jpes-21-metric__label { font-size: 12.5px; color: #6B6B66; }

/* ============ CASE STUDY PAGE ============
   Shared by case-study-template/ and every case-study-<id>/ instance, which link
   base.css only. Namespace .jpes-21-cs-*. Reuses eyebrow / tick / figcap / btn / grid. */

/* ---- hero (light "drawing-sheet": the case-studies section opens LIGHT where the front
   page opens dark — inverting the value is the differentiator, not a darker dark. The grid
   is faint blue (ink-on-white, like an engineering drawing). Mono eyebrow, serif title,
   corner ticks and blue accent all carry the theme through. ---- */
.jpes-21-cs-hero { background: #FDFCFA; position: relative; border-bottom: 1px solid #E8E5DF; --jpes21-grid: rgba(43,84,146,.07); }
.jpes-21-cs-hero__inner {
  position: relative;                        /* lift content above the grid */
  max-width: 1168px; margin: 0 auto; padding: 88px 56px;
  display: grid; grid-template-columns: 1.1fr .9fr; gap: 56px; align-items: center;
}
.jpes-21-cs-hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
.jpes-21-cs-hero__copy { display: flex; flex-direction: column; gap: 20px; }
.jpes-21-cs-hero__title { font-size: 44px; line-height: 1.15; color: #2B2B2B; }
.jpes-21-cs-hero__title em { font-style: italic; }
.jpes-21-cs-hero__sub { font-size: 16px; line-height: 1.7; color: #4A4A46; max-width: 520px; text-wrap: pretty; }
.jpes-21-cs-hero__fig { display: flex; flex-direction: column; }
.jpes-21-cs-hero__image { position: relative; height: 320px; background-color: #E6E3DC; background-size: cover; background-position: center; }
/* image + caption now sit on a light ground — default blue ticks & figcap (as FIG 2/3) */
.jpes-21-cs-hero__fig .jpes-21-figcap { padding-top: 10px; }

/* ---- shared section label row (SEC 0X · TITLE  ......  note) ----
   Now used beyond case studies (contact page), so .jpes-21-sec is the canonical
   name; .jpes-21-cs-sec stays as-is so existing case-study markup is untouched. */
.jpes-21-cs-sec, .jpes-21-sec { display: flex; justify-content: space-between; align-items: baseline; gap: 24px; margin-bottom: 30px; }
.jpes-21-cs-sec__note, .jpes-21-sec__note { font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: #9A9A94; text-align: right; }

/* ---- SEC 01 narrative ---- */
.jpes-21-cs-narrative { background: #FDFCFA; }
.jpes-21-cs-narrative__inner { max-width: 1168px; margin: 0 auto; padding: 76px 56px; }
.jpes-21-cs-narrative__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; }
.jpes-21-cs-narrative__cols p { font-size: 16px; line-height: 1.7; color: #4A4A46; text-wrap: pretty; }

/* ---- SEC 02 test log ---- */
.jpes-21-cs-log { background: #F4F2ED; border-top: 1px solid #E8E5DF; }
.jpes-21-cs-log__inner { max-width: 1168px; margin: 0 auto; padding: 76px 56px; }
.jpes-21-cs-log__table { width: 100%; border-collapse: collapse; }
.jpes-21-cs-log__table th {
  text-align: left; font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .12em;
  color: #9A9A94; padding: 0 0 12px; border-bottom: 1px solid #D8D4CC;
}
.jpes-21-cs-log__table th.is-after { color: #2B5492; }
.jpes-21-cs-log__table th:last-child { text-align: right; }
.jpes-21-cs-log__row td { padding: 18px 16px 18px 0; border-bottom: 1px solid #E2DED6; vertical-align: baseline; }
.jpes-21-cs-log__param { font-family: Georgia, "Times New Roman", serif; font-size: 19px; color: #2B2B2B; }
.jpes-21-cs-log__before { font: 13px ui-monospace, Menlo, monospace; letter-spacing: .04em; color: #6B6B66; }
.jpes-21-cs-log__after { font: 500 13px ui-monospace, Menlo, monospace; letter-spacing: .04em; color: #2B5492; }
.jpes-21-cs-log__status { font: 11px ui-monospace, Menlo, monospace; letter-spacing: .1em; color: #8A8A84; text-align: right; padding-right: 0; }

/* ---- SEC 02 figures ---- */
.jpes-21-cs-figs { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; margin-top: 44px; }
.jpes-21-cs-fig { display: flex; flex-direction: column; }
.jpes-21-cs-fig__image { position: relative; height: 260px; background-color: #E6E3DC; background-size: cover; background-position: center; }
.jpes-21-cs-fig .jpes-21-figcap { padding-top: 10px; }

/* ---- SEC 03 what we designed out ---- */
.jpes-21-cs-designed-out { background: #FDFCFA; }
.jpes-21-cs-designed-out__inner {
  max-width: 1168px; margin: 0 auto; padding: 76px 56px;
  display: grid; grid-template-columns: 1fr 300px; gap: 56px; align-items: start;
}
.jpes-21-cs-reasons { list-style: none; counter-reset: cs-reason; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 22px; }
.jpes-21-cs-reason { counter-increment: cs-reason; position: relative; padding-left: 52px; font-size: 16px; line-height: 1.65; color: #2B2B2B; text-wrap: pretty; }
.jpes-21-cs-reason::before {
  content: counter(cs-reason, decimal-leading-zero); position: absolute; left: 0; top: 3px;
  font: 500 13px ui-monospace, Menlo, monospace; color: #2B5492;
}

/* ---- IN SERVICE stamp ---- */
.jpes-21-cs-stamp { justify-self: center; align-self: start; border: 2px solid #2B5492; padding: 14px 22px; text-align: center; color: #2B5492; transform: rotate(-4deg); }
.jpes-21-cs-stamp__code { font: 500 12px ui-monospace, Menlo, monospace; letter-spacing: .14em; }
.jpes-21-cs-stamp__status { font: 700 20px Georgia, "Times New Roman", serif; letter-spacing: .04em; margin: 5px 0; padding: 5px 0; border-top: 1px solid rgba(43,84,146,.4); border-bottom: 1px solid rgba(43,84,146,.4); }
.jpes-21-cs-stamp__loc { font: 10.5px ui-monospace, Menlo, monospace; letter-spacing: .1em; color: rgba(43,84,146,.75); }

/* ---- footer (dark anchor at the page foot; the section signature is the light hero, so
   a dark footer here grounds the page without reintroducing a dark band "on top") ---- */
.jpes-21-cs-foot { background: #23252B; }
.jpes-21-cs-foot__inner {
  max-width: 1168px; margin: 0 auto; padding: 56px;
  display: grid; grid-template-columns: 1.5fr 1fr auto; gap: 40px; align-items: center;
}
.jpes-21-cs-foot__client { font-size: 14px; line-height: 1.6; color: rgba(255,255,255,.75); max-width: 440px; }
.jpes-21-cs-foot__contact { font: 12.5px ui-monospace, Menlo, monospace; line-height: 1.9; color: rgba(255,255,255,.6); }
.jpes-21-cs-foot__cta { display: inline-block; background: #2B5492; color: #fff; padding: 16px 26px; font-weight: 500; font-size: 15px; white-space: nowrap; transition: background-color .2s ease; }
.jpes-21-cs-foot__cta:hover { background: #3866AC; color: #fff; }
.jpes-21-cs-foot__docref { grid-column: 1 / -1; margin-top: 8px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,.1); font: 10.5px ui-monospace, Menlo, monospace; letter-spacing: .1em; color: rgba(255,255,255,.32); }

/* ---- responsive ---- */
@media (max-width: 960px) {
  .jpes-21-cs-hero__inner, .jpes-21-cs-designed-out__inner, .jpes-21-cs-foot__inner { grid-template-columns: 1fr; }
  .jpes-21-cs-hero__inner { padding: 56px 24px; gap: 36px; }
  .jpes-21-cs-narrative__inner, .jpes-21-cs-log__inner, .jpes-21-cs-designed-out__inner { padding-left: 24px; padding-right: 24px; }
  .jpes-21-cs-narrative__cols, .jpes-21-cs-figs { grid-template-columns: 1fr; gap: 28px; }
  .jpes-21-cs-hero__title { font-size: 34px; }
  .jpes-21-cs-stamp { justify-self: start; }
}
@media (max-width: 600px) {
  .jpes-21-cs-log__table thead { display: none; }
  .jpes-21-cs-log__row { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px; padding: 16px 0; }
  .jpes-21-cs-log__row td { padding: 0; border: 0; }
  .jpes-21-cs-log__param { grid-column: 1 / -1; margin-bottom: 6px; }
  .jpes-21-cs-log__status { text-align: left; }
}


/* ####################  SITE HEADER  #################### */

/* ============ SITE HEADER (Odoo native, restyled to .jpes-21) ============
   Restyles Odoo's native website header to the light "drawing-sheet" look. The menu
   structure, links, logo and Account/login are all managed in Odoo — this is styling only.

   Everything is scoped to #top (desktop) and .o_navbar_mobile / .o_header_mobile (mobile)
   so it never touches other navbars, dropdowns or .nav-links elsewhere on the site.
   Targets Odoo 17/18 markup: #top_menu, .o_mega_menu / .s_mega_menu_odoo_menu,
   .o_header_social_links, .js_usermenu, .o_navbar_mobile.

   !important is used where Odoo's colour-combination system (o_cc*) or theme rules would
   otherwise win. Run `python3 build.py` from pages/ after editing to refresh core.css. */

/* ---- the bar (desktop + mobile navbars) ---- */
#top .navbar {
  background-color: #FDFCFA !important;   /* override Odoo's o_cc colour combo */
  box-shadow: none !important;            /* replace shadow-sm with a hairline */
  border-bottom: 1px solid #E8E5DF;
}
#top .navbar-brand img { max-height: 40px; width: auto; }

/* ---- top-level nav links ---- */
#top_menu > .nav-item > .nav-link {
  position: relative;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .01em;
  color: #2B2B2B !important;
  padding: 8px 2px;
  margin: 0 14px;
}
#top_menu > .nav-item > .nav-link:hover,
#top_menu > .nav-item.show > .nav-link { color: #2B5492 !important; }
/* blue underline drawn in from the left on hover (the site's link motif).
   ::before is used so it never collides with Bootstrap's dropdown caret on ::after. */
#top_menu > .nav-item > .nav-link::before {
  content: "";
  position: absolute;
  left: 2px; right: 2px; bottom: 0;
  height: 1px;
  background: #2B5492;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s cubic-bezier(.22, .61, .36, 1);
}
#top_menu > .nav-item > .nav-link:hover::before,
#top_menu > .nav-item.show > .nav-link::before { transform: none; }

/* ---- Services mega-menu panel ---- */
#top .o_mega_menu.dropdown-menu {
  margin-top: 0 !important;
  padding: 0;
  background: #FDFCFA;
  border: 1px solid #E8E5DF;
  border-radius: 0;
  box-shadow: 0 16px 40px rgba(43, 84, 146, .10);
}
#top .s_mega_menu_odoo_menu {
  position: relative;
  background: #FDFCFA !important;   /* override o_cc1 */
  padding: 28px 0;
}
/* faint blue blueprint grid — the drawing-sheet touch, ink-on-white */
#top .s_mega_menu_odoo_menu::before {
  content: "";
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(rgba(43, 84, 146, .05) 1px, transparent 1px) 0 0 / 32px 32px,
    linear-gradient(90deg, rgba(43, 84, 146, .05) 1px, transparent 1px) 0 0 / 32px 32px;
}
#top .s_mega_menu_odoo_menu .container { position: relative; }   /* lift above the grid */
/* group headings (Automation / Research & Development) as blue mono eyebrows */
#top .s_mega_menu_odoo_menu h4 { margin: 0; }
#top .s_mega_menu_odoo_menu h4 a {
  font: 500 12px ui-monospace, Menlo, monospace;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #2B5492 !important;
  text-decoration: none;
}
#top .s_mega_menu_odoo_menu h4 a:hover { color: #1E3E6E !important; }
/* the 2px --primary separator -> a thin blue rule */
#top .s_mega_menu_odoo_menu .s_hr hr {
  border-top-width: 1px !important;
  border-top-color: #2B5492 !important;
  opacity: .3;
  margin: 12px 0 14px;
}
/* sub-service links */
#top .s_mega_menu_odoo_menu .nav-link {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: #4A4A46 !important;
  padding: 6px 0 !important;
}
#top .s_mega_menu_odoo_menu .nav-link:hover { color: #2B5492 !important; }

/* ---- right cluster: LinkedIn + Account ---- */
#top .o_header_social_links .fa-linkedin {
  color: #2B2B2B;
  font-size: 17px;
  transition: color .2s ease;
}
#top .o_header_social_links a:hover .fa-linkedin { color: #2B5492; }

#top .o_no_autohide_item > .nav-link {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  color: #2B2B2B !important;
}
#top .o_no_autohide_item > .nav-link:hover { color: #2B5492 !important; }
#top .js_usermenu {
  background: #FDFCFA;
  border: 1px solid #E8E5DF;
  border-radius: 0;
  box-shadow: 0 12px 28px rgba(43, 84, 146, .12);
}
#top .js_usermenu .dropdown-item { font-size: 14px; color: #2B2B2B; }
#top .js_usermenu .dropdown-item:hover { background: #EEF3FB; color: #2B5492; }

/* ---- mobile (offcanvas panel) ---- */
.o_navbar_mobile { background: #FDFCFA; }
.o_navbar_mobile .nav-link {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #2B2B2B !important;
}
.o_navbar_mobile .nav-link:hover,
.o_navbar_mobile .nav-link.active { color: #2B5492 !important; }
.o_navbar_mobile .nav-item { border-color: #E8E5DF !important; }


/* ####################  FRONT  #################### */

.jpes-21-standard { position: relative; }
.jpes-21-standard::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
/* the grid is a positioned box, so it would otherwise paint over the section's
   in-flow content — positioning the inner wrapper lifts the content back on top */
.jpes-21-standard__inner { position: relative; }

/* ============ HERO ============ */
.jpes-21-hero {
  position: relative;
  background-color: #232B38; /* fallback while photography is not yet uploaded */
  background-size: cover;
  background-position: center;
}
.jpes-21-hero__overlay {
  position: absolute;
  inset: 0;
  overflow: hidden;                  /* clip click ripples to the hero's edges */
  /* layers paint top-to-bottom: the white grid sits ABOVE the darkening gradient */
  background:
    linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px) 0 0 / 40px 40px,        /* horizontal lines */
    linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px) 0 0 / 40px 40px,  /* vertical lines   */
    linear-gradient(90deg, rgba(20,24,32,.9) 0%, rgba(20,24,32,.6) 55%, rgba(20,24,32,.3) 100%);
}
/* cursor "torch" — a brighter copy of the grid, revealed in a soft pool that follows the pointer.
   JS writes --mx / --my (see the <script> in the HTML block); with no JS this stays invisible.
   Shared with the closing CTA so the page opens and closes on the same note. */
.jpes-21-hero__overlay::after,
.jpes-21-contact__overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;              /* never intercept hover/clicks meant for the content */
  background:
    linear-gradient(rgba(255,255,255,.45) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, rgba(255,255,255,.45) 1px, transparent 1px) 0 0 / 40px 40px;
  /* the mask clips this bright grid down to a circle at the cursor; default is off-screen */
  -webkit-mask: radial-gradient(circle 200px at var(--mx, -200px) var(--my, -200px), #000 0%, transparent 65%);
          mask: radial-gradient(circle 200px at var(--mx, -200px) var(--my, -200px), #000 0%, transparent 65%);
  opacity: 0;
  transition: opacity .35s ease;     /* the pool fades in on enter, out on leave */
}
/* The torch tracks a cursor, so it is meaningless on touch — and iOS synthesises a
   :hover on tap that sticks until you tap elsewhere, which would strand the glow
   mid-section. Gate it to pointers that genuinely hover: a mouse, never a finger.
   Taps still get the ripple; that is handled on pointerdown in JS. */
@media (hover: hover) and (pointer: fine) {
  .jpes-21-hero:hover .jpes-21-hero__overlay::after,
  .jpes-21-contact:hover .jpes-21-contact__overlay::after { opacity: 1; }
}

/* click ripple — a pulse of brightened grid that sweeps outward from the click, like a
   shockwave through the blueprint. JS spawns one full-hero <span> per click; its bright grid
   is revealed only inside a ring-shaped mask whose radius (--jpes21-r) animates outward.
   @property makes --jpes21-r an animatable <length> so @keyframes can tween the ring. */
@property --jpes21-r {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@keyframes jpes21-ripple {
  0%   { --jpes21-r: 0px; opacity: 1; }
  70%  { opacity: 1; }
  100% { --jpes21-r: 700px; opacity: 0; }
}
.jpes-21-ripple {
  position: absolute;
  inset: 0;                          /* cover the hero so this grid lines up with the base grid */
  pointer-events: none;
  /* the same 40px grid as the torch, a touch brighter */
  background:
    linear-gradient(rgba(255,255,255,.5) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, rgba(255,255,255,.5) 1px, transparent 1px) 0 0 / 40px 40px;
  /* reveal only a ~110px-wide ring band centred on the click (--rx,--ry), sweeping outward */
  -webkit-mask: radial-gradient(circle at var(--rx, 50%) var(--ry, 50%),
      transparent calc(var(--jpes21-r) - 55px), #000 var(--jpes21-r), transparent calc(var(--jpes21-r) + 55px));
          mask: radial-gradient(circle at var(--rx, 50%) var(--ry, 50%),
      transparent calc(var(--jpes21-r) - 55px), #000 var(--jpes21-r), transparent calc(var(--jpes21-r) + 55px));
  animation: jpes21-ripple 2s ease-out forwards;
}
.jpes-21-hero__inner {
  position: relative;
  max-width: 1168px;
  margin: 0 auto;
  padding: 80px 56px;
  display: grid;
  grid-template-columns: 1fr 380px;
  gap: 72px;
  align-items: center;
}
.jpes-21-hero__copy { display: flex; flex-direction: column; gap: 26px; }
.jpes-21-hero__title { font-size: 54px; line-height: 1.12; color: #fff; max-width: 620px; text-wrap: pretty; }
.jpes-21-hero__subtitle { font-size: 18px; line-height: 1.6; color: rgba(255,255,255,.82); max-width: 520px; text-wrap: pretty; }
.jpes-21-hero__actions { display: flex; gap: 14px; margin-top: 6px; }

/* pinned figure card — makes the machine unmistakable on load */
.jpes-21-figcard { background: transparent; padding: 0; }
.jpes-21-figcard .jpes-21-tick { border-color: #fff; }
.jpes-21-figcard .jpes-21-figcap { color: #fff; }
.jpes-21-figcard .jpes-21-figcap::before, .jpes-21-figcard .jpes-21-figcap::after { background: #fff; }
.jpes-21-figcard__image {
  position: relative;
  height: 280px;
  background-color: #DCE4F0;
  background-size: cover;
  background-position: center;
}
.jpes-21-figcard .jpes-21-figcap { padding: 10px 2px 8px; }

/* ============ STATS BAND ============ */
.jpes-21-stats { background: #2B5492; color: #fff; }
.jpes-21-stats__inner {
  max-width: 1168px;
  margin: 0 auto;
  padding: 44px 56px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 28px 48px; /* row gap is the stats -> sources step; columns keep the original 48 */
}
.jpes-21-stat { position: relative; display: flex; flex-direction: column; gap: 8px; border-left: 1px solid rgba(255,255,255,.3); padding-left: 24px; }
/* terminator ticks turn each divider into a dimension line */
.jpes-21-stat::before, .jpes-21-stat::after { content: ""; position: absolute; left: -4px; width: 8px; height: 1px; background: rgba(255,255,255,.45); }
.jpes-21-stat::before { top: 0; }
.jpes-21-stat::after { bottom: 0; }
.jpes-21-stat__number { font-family: Georgia, "Times New Roman", serif; font-size: 44px; font-weight: 500; line-height: 1; }
.jpes-21-stat__label { font-size: 13.5px; line-height: 1.5; color: rgba(255,255,255,.8); }

/* source references — numbered markers in the labels, keyed to the list below the
   band, the way a drawing carries numbered notes. Two markers share [1] because two
   of the three figures come from the same study. */
.jpes-21-stat__ref {
  font: 500 10px ui-monospace, Menlo, monospace;
  vertical-align: super;
  line-height: 0; /* keep the raised marker from opening up the label's line box */
  margin-left: 3px;
  color: rgba(255,255,255,.6);
}
.jpes-21-stats__sources {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 20px;
  padding-top: 16px;
  border-top: 1px solid rgba(255,255,255,.18);
  font: 10.5px ui-monospace, Menlo, monospace;
}
.jpes-21-stats__sources-label { letter-spacing: .14em; color: rgba(255,255,255,.42); }
/* the global :where(a) blue would vanish on this band — these need their own colour */
.jpes-21-stats__source { color: rgba(255,255,255,.62); }
.jpes-21-stats__source:hover { color: #fff; text-decoration: underline; }

/* ============ THE PROBLEM ============ */
.jpes-21-problem { background: #FDFCFA; }
.jpes-21-problem__inner {
  max-width: 1168px;
  margin: 0 auto;
  padding: 88px 56px;
  display: grid;
  grid-template-columns: 380px 1fr;
  gap: 64px;
}
.jpes-21-problem__head { display: flex; flex-direction: column; gap: 16px; }
.jpes-21-problem__title { font-size: 34px; line-height: 1.2; text-wrap: pretty; }
.jpes-21-problem__body { display: flex; flex-direction: column; gap: 18px; font-size: 16.5px; line-height: 1.7; color: #4A4A46; max-width: 620px; }

/* proof cards — two jobs that answer the problem stated above.
   Span the whole 380px + 1fr text grid rather than sitting in one of its columns. */
.jpes-21-problem__proofs {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin-top: 16px;
}
.jpes-21-proof {
  position: relative;
  background: #fff;
  border: 1px solid #E8E5DF;
  border-top: 3px solid #2B5492;
  padding: 26px 26px 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  transition: transform .25s ease, box-shadow .25s ease;
}
.jpes-21-proof:hover { transform: translateY(-3px); box-shadow: 0 14px 30px rgba(43,84,146,.13); }
/* status reads as metadata, not as a claim — hence grey, not the eyebrow blue */
.jpes-21-proof__status {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 500 10.5px ui-monospace, Menlo, monospace;
  letter-spacing: .14em;
  color: #9A9A94;
}
.jpes-21-proof__status::before { content: ""; flex: none; width: 6px; height: 6px; background: #2B5492; }
.jpes-21-proof__quote { font-style: italic; font-size: 21px; line-height: 1.35; color: #2B2B2B; text-wrap: pretty; }
.jpes-21-proof__ref { font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .1em; line-height: 1.55; color: #2B5492; }
.jpes-21-proof__body { font-size: 15px; line-height: 1.65; color: #4A4A46; text-wrap: pretty; }
/* metrics as a spec-sheet readout: the figure, then what it measures.
   margin-top:auto pushes the readout to the card floor, so the rules — and the
   links below them — line up across cards whose quote/body run to different lengths. */
.jpes-21-proof__metrics { border-top: 1px solid #E8E5DF; margin-top: auto; padding-top: 2px; }
.jpes-21-proof__metric {
  display: grid;
  grid-template-columns: 132px 1fr;
  gap: 16px;
  align-items: baseline;
  padding: 11px 0;
  border-bottom: 1px solid #E8E5DF;
}
.jpes-21-proof__value { font-family: Georgia, "Times New Roman", serif; font-size: 19px; font-weight: 500; color: #2B5492; }
.jpes-21-proof__label { font-size: 12.5px; line-height: 1.5; color: #6B6B66; }
.jpes-21-proof__link { font-size: 14.5px; font-weight: 500; padding-top: 2px; }

/* ============ CAPABILITIES ============ */
.jpes-21-capabilities { background: #F4F2ED; border-top: 1px solid #E8E5DF; }
.jpes-21-capabilities__inner {
  max-width: 1168px;
  margin: 0 auto;
  padding: 88px 56px;
  display: flex;
  flex-direction: column;
  gap: 44px;
}
.jpes-21-capabilities__head { display: flex; justify-content: space-between; align-items: flex-end; gap: 32px; }
/* space the eyebrow off the heading with flex gap, never a margin: the `.jpes-21 h2`
   reset is (0,1,1) and silently outweighs any margin set on the (0,1,0) title class */
.jpes-21-capabilities__heading { display: flex; flex-direction: column; gap: 16px; }
.jpes-21-capabilities__title { font-size: 34px; line-height: 1.2; }
.jpes-21-capabilities__note { font-size: 15px; line-height: 1.6; color: #6B6B66; max-width: 380px; text-wrap: pretty; }
.jpes-21-capabilities__grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; }

/* capability card — grows slightly + changes colour on hover */
.jpes-21-capability {
  background: #FDFCFA;
  border-top: 3px solid #2B5492;
  padding: 26px 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 150px;
  transition: transform .25s ease-in-out, background-color .25s ease-in-out, box-shadow .25s ease-in-out;
}
.jpes-21-capability:hover {
  transform: scale(1.03);
  background: #EEF3FB;
  box-shadow: 0 12px 28px rgba(43,84,146,.14);
}
.jpes-21-capability__num { font: 500 11px ui-monospace, Menlo, monospace; color: #9A9A94; }
.jpes-21-capability__title { font-size: 17px; font-weight: 600; color: #2B2B2B; }
.jpes-21-capability__desc { font-size: 13.5px; line-height: 1.55; color: #6B6B66; }

/* ============ STANDARD / PULL QUOTE ============ */
.jpes-21-standard { background: #23252B; }
.jpes-21-standard__inner {
  max-width: 1168px;
  margin: 0 auto;
  padding: 96px 56px;
  display: grid;
  grid-template-columns: 1fr 380px;
  gap: 64px;
  align-items: center;
}
.jpes-21-standard__text { display: flex; flex-direction: column; gap: 24px; }
.jpes-21-standard__quote { font-style: italic; font-size: 40px; line-height: 1.25; color: #fff; text-wrap: pretty; }
.jpes-21-standard__body { font-size: 16px; line-height: 1.7; color: rgba(255,255,255,.72); max-width: 560px; text-wrap: pretty; }
/* the photo is a CAD assembly on screen, so it earns the same figure treatment as
   the hero and case images — ticks and a numbered caption, muted for a dark ground */
.jpes-21-standard__fig { display: flex; flex-direction: column; }
.jpes-21-standard__image { position: relative; height: 300px; background-color: #31343C; background-size: cover; background-position: center; }
.jpes-21-standard__fig .jpes-21-tick { border-color: rgba(255,255,255,.7); }
.jpes-21-standard__fig .jpes-21-figcap { padding-top: 10px; color: rgba(255,255,255,.5); }
.jpes-21-standard__fig .jpes-21-figcap::before, .jpes-21-standard__fig .jpes-21-figcap::after { background: rgba(255,255,255,.28); }

/* ============ FEATURED CASE STUDY ============ */
.jpes-21-case { background: #FDFCFA; }
.jpes-21-case__inner {
  max-width: 1168px;
  margin: 0 auto;
  padding: 88px 56px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 64px;
  align-items: center;
}
.jpes-21-case__fig { display: flex; flex-direction: column; }
.jpes-21-case__image {
  position: relative;
  height: 400px;
  background-color: #E6E3DC;
  background-size: cover;
  background-position: center;
}
.jpes-21-case__fig .jpes-21-figcap { padding-top: 10px; }
.jpes-21-case__content { display: flex; flex-direction: column; gap: 18px; }
.jpes-21-case__title { font-size: 32px; line-height: 1.25; text-wrap: pretty; }
.jpes-21-case__body { font-size: 16px; line-height: 1.7; color: #4A4A46; text-wrap: pretty; }
.jpes-21-case__metrics { display: flex; gap: 28px; padding: 18px 0; border-top: 1px solid #E8E5DF; border-bottom: 1px solid #E8E5DF; }
/* .jpes-21-metric{,__value,__label} promoted to base.css — shared with case-studies cards */
.jpes-21-case__link { font-size: 15px; font-weight: 500; }

/* ============ PEOPLE ============ */
.jpes-21-people { background: #F4F2ED; border-top: 1px solid #E8E5DF; }
.jpes-21-people__inner {
  max-width: 1168px;
  margin: 0 auto;
  padding: 88px 56px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}
.jpes-21-people__head { display: flex; flex-direction: column; gap: 14px; max-width: 620px; }
.jpes-21-people__title, .display-3-fs { font-size: 34px; line-height: 1.2; }
.jpes-21-people__body { font-size: 16px; line-height: 1.7; color: #4A4A46; text-wrap: pretty; }
.jpes-21-people__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.jpes-21-person { display: flex; flex-direction: column; gap: 12px; }
.jpes-21-person__image { position: relative; height: 280px; background-color: #E6E3DC; background-size: cover; background-position: center; }
.jpes-21-person__name { font-size: 16px; font-weight: 600; color: #2B2B2B; }
.jpes-21-person__role { font-size: 13.5px; color: #6B6B66; margin-top: -8px; }

/* ============ ACCREDITATIONS ============ */
.jpes-21-lower { background: #FDFCFA; }
.jpes-21-lower__inner { max-width: 1168px; margin: 0 auto; padding: 56px 56px 64px; }
.jpes-21-accreditations {
  position: relative;
  display: flex;
  align-items: center;
  gap: 40px;
  padding: 28px 0;
  border-top: 1px solid #E8E5DF;
  border-bottom: 1px solid #E8E5DF;
}
/* graduated scale along the top rule — this band is effectively the sheet's title
   block, so it gets the ruler edge. Minor ticks every 12px, major every 60px. */
.jpes-21-accreditations::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 9px;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(90deg, rgba(43,84,146,.26) 0 1px, transparent 1px 60px),
    repeating-linear-gradient(90deg, rgba(43,84,146,.14) 0 1px, transparent 1px 12px);
  background-size: 100% 9px, 100% 5px;
  background-repeat: no-repeat;
}
.jpes-21-accreditations__label { font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .14em; color: #9A9A94; flex: none; }
.jpes-21-accreditations__logos { display: flex; gap: 20px; flex: 1; justify-content: space-between; flex-wrap: wrap; }
.jpes-21-accreditation-logo {
  width: 130px;
  height: 44px;
  background: #EFEDE8;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 10.5px ui-monospace, Menlo, monospace;
  color: #9A9A94;
  text-align: center;
}

/* ============ CONTACT + FOOTER ============ */
.jpes-21-contact { position: relative; background: #2B2B2B; }
/* the closing CTA carries the hero's blueprint treatment — same 40px grid, same cursor
   torch and click ripple. A real element rather than a ::before, because the ripple
   spans are appended into it and need something to be clipped by. */
.jpes-21-contact__overlay {
  position: absolute;
  inset: 0;
  overflow: hidden;      /* clip click ripples to the section's edges */
  pointer-events: none;  /* never intercept clicks meant for the CTA button */
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
.jpes-21-contact__inner {
  position: relative; /* lift the content above the grid/torch overlay */
  max-width: 720px;
  margin: 0 auto;
  padding: 80px 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
  text-align: center;
}
.jpes-21-contact__text { display: flex; flex-direction: column; align-items: center; gap: 16px; }
.jpes-21-contact__title { font-size: 36px; line-height: 1.2; color: #fff; text-wrap: pretty; }
.jpes-21-contact__body { font-size: 16px; line-height: 1.7; color: rgba(255,255,255,.72); max-width: 560px; text-wrap: pretty; }
.jpes-21-contact .jpes-21-btn { padding: 15px 32px; }

/* ============ RESPONSIVE ============ */
@media (max-width: 960px) {
  .jpes-21-hero__inner { padding: 56px 24px; grid-template-columns: 1fr; gap: 40px; }
  .jpes-21-hero__title { font-size: 36px; }
  .jpes-21-figcard { max-width: 440px; }
  .jpes-21-figcard__image { height: 200px; }
  .jpes-21-stats__inner,
  .jpes-21-problem__inner,
  .jpes-21-capabilities__inner,
  .jpes-21-standard__inner,
  .jpes-21-case__inner,
  .jpes-21-people__inner,
  .jpes-21-lower__inner,
  .jpes-21-contact__inner {
    padding-left: 24px;
    padding-right: 24px;
    grid-template-columns: 1fr;
  }
  /* stacked stats need their original breathing room back; the base 28px row gap
     exists only to set the stats -> sources step on the 3-column desktop layout */
  .jpes-21-stats__inner { row-gap: 40px; }
  .jpes-21-problem__proofs { grid-template-columns: 1fr; }
  .jpes-21-capabilities__head { flex-direction: column; align-items: flex-start; }
  .jpes-21-capabilities__grid, .jpes-21-people__grid { grid-template-columns: repeat(2, 1fr); }
  .jpes-21-case__image { height: 240px; }
  .jpes-21-accreditations { flex-direction: column; align-items: flex-start; }
  .jpes-21-accreditations__logos { justify-content: flex-start; }
}
@media (max-width: 600px) {
  .jpes-21-capabilities__grid, .jpes-21-people__grid { grid-template-columns: 1fr; }
  .jpes-21-proof__metric { grid-template-columns: 1fr; gap: 2px; }
  .jpes-21-hero__title { font-size: 31px; }
  .jpes-21-standard__quote { font-size: 27px; }
}

/* ============ MOTION ============
   The page plots itself like a drawing: copy rises into place, rules and the ruler
   scale draw outward from their origins, and the corner ticks strike in last.

   Every initial state below is scoped to html.jpes-21-anim — a class JS adds only
   when the visitor has NOT asked for reduced motion. Without JS, or with reduced
   motion on, none of these rules match and the page renders visible and static.
   Nothing here is required for the layout to be readable. */

.jpes-21-anim .jpes-21-reveal { opacity: 0; transform: translateY(18px); }
.jpes-21-anim .jpes-21-reveal.is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity .75s cubic-bezier(.22, .61, .36, 1) var(--d, 0ms),
    transform .75s cubic-bezier(.22, .61, .36, 1) var(--d, 0ms);
}

/* figures settle in from fractionally small, like a print being laid on the sheet */
.jpes-21-anim .jpes-21-figcard.jpes-21-reveal,
.jpes-21-anim .jpes-21-case__fig.jpes-21-reveal,
.jpes-21-anim .jpes-21-standard__fig.jpes-21-reveal { transform: translateY(18px) scale(.985); }
.jpes-21-anim .jpes-21-figcard.is-in,
.jpes-21-anim .jpes-21-case__fig.is-in,
.jpes-21-anim .jpes-21-standard__fig.is-in { transform: none; }

/* corner ticks strike in from their own corner, once the figure has landed */
.jpes-21-anim .jpes-21-tick { opacity: 0; transform: scale(.25); }
.jpes-21-anim .jpes-21-tick--tl { transform-origin: top left; }
.jpes-21-anim .jpes-21-tick--tr { transform-origin: top right; }
.jpes-21-anim .jpes-21-tick--bl { transform-origin: bottom left; }
.jpes-21-anim .jpes-21-tick--br { transform-origin: bottom right; }
.jpes-21-anim .jpes-21-tick:nth-of-type(2) { --dt: 500ms; }
.jpes-21-anim .jpes-21-tick:nth-of-type(3) { --dt: 570ms; }
.jpes-21-anim .jpes-21-tick:nth-of-type(4) { --dt: 640ms; }
.jpes-21-anim .is-in .jpes-21-tick {
  opacity: 1;
  transform: none;
  transition:
    opacity .45s ease var(--dt, 430ms),
    transform .5s cubic-bezier(.22, .61, .36, 1) var(--dt, 430ms);
}

/* caption rules extend outward from the label, the way a dimension line is drawn */
.jpes-21-anim .jpes-21-figcap::before,
.jpes-21-anim .jpes-21-figcap::after { transform: scaleX(0); }
.jpes-21-anim .jpes-21-figcap::before { transform-origin: right; }
.jpes-21-anim .jpes-21-figcap::after { transform-origin: left; }
.jpes-21-anim .is-in .jpes-21-figcap::before,
.jpes-21-anim .is-in .jpes-21-figcap::after {
  transform: none;
  transition: transform .85s cubic-bezier(.22, .61, .36, 1) .3s;
}

/* the graduated scale sweeps out left to right, like a rule being laid down */
.jpes-21-anim .jpes-21-accreditations::before { transform: scaleX(0); transform-origin: left; }
.jpes-21-anim .jpes-21-accreditations.is-in::before {
  transform: none;
  transition: transform 1.2s cubic-bezier(.22, .61, .36, 1) .15s;
}

/* dimension terminators strike in after their figure has risen */
.jpes-21-anim .jpes-21-stat::before,
.jpes-21-anim .jpes-21-stat::after { opacity: 0; transform: scaleX(.2); }
.jpes-21-anim .jpes-21-stat.is-in::before,
.jpes-21-anim .jpes-21-stat.is-in::after {
  opacity: 1;
  transform: none;
  transition:
    opacity .5s ease calc(var(--d, 0ms) + 300ms),
    transform .55s cubic-bezier(.22, .61, .36, 1) calc(var(--d, 0ms) + 300ms);
}

/* proof metric rows deal out in sequence once the card is in */
.jpes-21-anim .jpes-21-proof__metric { opacity: 0; transform: translateX(-8px); }
.jpes-21-anim .jpes-21-proof__metric:nth-of-type(2) { --dm: 460ms; }
.jpes-21-anim .jpes-21-proof__metric:nth-of-type(3) { --dm: 550ms; }
.jpes-21-anim .is-in .jpes-21-proof__metric {
  opacity: 1;
  transform: none;
  transition:
    opacity .5s ease var(--dm, 370ms),
    transform .5s cubic-bezier(.22, .61, .36, 1) var(--dm, 370ms);
}

/* links draw their underline out from the left on hover (CSS-only, no JS needed) */
.jpes-21-case__link, .jpes-21-proof__link, .jpes-21-stats__source { position: relative; }
.jpes-21-case__link::after,
.jpes-21-proof__link::after,
.jpes-21-stats__source::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .4s cubic-bezier(.22, .61, .36, 1);
}
.jpes-21-case__link:hover::after,
.jpes-21-proof__link:hover::after,
.jpes-21-stats__source:hover::after { transform: none; }
.jpes-21-stats__source:hover { text-decoration: none; } /* the drawn rule replaces it */

/* Safety net: honour reduced motion even for the CSS-only hovers above. */
@media (prefers-reduced-motion: reduce) {
  .jpes-21 *,
  .jpes-21 *::before,
  .jpes-21 *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}


/* ####################  CONTACT  #################### */

/* ============ CONTACT PAGE ============
   Shared primitives (.jpes-21 base, -eyebrow, -tick, -sec, -btn) live in ../../base.css
   and must not be repeated here.
   Run `python3 build.py` from pages/ after editing to refresh core.css.

   Namespace .jpes-21-ct-* — .jpes-21-contact* is already the FRONT page's closing
   CTA band, so it is deliberately not reused here.

   TWO SECTIONS ARE ODOO SNIPPETS, NOT OUR MARKUP, and each needs a hook class
   appended to its EXISTING class list:
       form snippet ->  class="s_text_block o_colored_level pt0 pb0 jpes-21-ct-form"
       map snippet  ->  class="...whatever it already has... jpes-21-ct-map"

   HOW: Odoo's Customize panel has no free-text class field. Use the page code view
   (the same one that exposes the snippet's raw HTML) and append the class by hand.
   Get this wrong and NOTHING below applies — every rule is scoped under one of these
   two classes, so a missing hook fails silently and completely rather than partially.

   The scoping is not optional: core.css is injected site-wide, so an unscoped
   `input {}` here would restyle every form on the site, Odoo's own included. */

/* ---- hero: light "drawing-sheet" opening, matching the case-studies index ---- */
.jpes-21-ct-hero { position: relative; background: #FDFCFA; border-bottom: 1px solid #E8E5DF; --jpes21-grid: rgba(43,84,146,.07); }
.jpes-21-ct-hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
.jpes-21-ct-hero__inner {
  position: relative; max-width: 1168px; margin: 0 auto; padding: 96px 56px;
  display: flex; flex-direction: column; gap: 16px;
}
.jpes-21-ct-hero__title { font-family: Georgia, "Times New Roman", serif; font-weight: 500; font-size: 40px; line-height: 1.15; color: #2B2B2B; max-width: 640px; text-wrap: pretty; }
.jpes-21-ct-hero__intro { font-size: 16px; line-height: 1.7; color: #4A4A46; max-width: 600px; text-wrap: pretty; }

/* ---- SEC 01 details: the drawing's title block. Darker ground than the hero so the
   three cards read as inset panels, the same figure/ground step as the test-log band. ---- */
.jpes-21-ct-details { background: #F4F2ED; }
.jpes-21-ct-details__inner { max-width: 1168px; margin: 0 auto; padding: 76px 56px; }
.jpes-21-ct-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 24px; }
.jpes-21-ct-card { position: relative; background: #FDFCFA; padding: 26px 24px; display: flex; flex-direction: column; gap: 12px; }
.jpes-21-ct-card__label { font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: #9A9A94; }
/* <address> defaults to italic; the block is data, not prose */
.jpes-21-ct-card__value { font-style: normal; font-size: 15.5px; line-height: 1.7; color: #2B2B2B; }
.jpes-21-ct-card__value a { color: #2B5492; }
.jpes-21-ct-card__value a:hover { color: #1E3E6E; text-decoration: underline; }

/* ---- SEC 02 head: pays out no bottom padding, because the Odoo form section
   immediately below supplies it. The two must share a background to read as one band. ---- */
.jpes-21-ct-formhead { background: #FDFCFA; }
.jpes-21-ct-formhead__inner { max-width: 1168px; margin: 0 auto; padding: 76px 56px 0; }
.jpes-21-ct-steps { list-style: none; counter-reset: ct-step; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; }
.jpes-21-ct-step { counter-increment: ct-step; position: relative; padding-top: 18px; border-top: 1px solid #E2DED6; }
.jpes-21-ct-step::before {
  content: counter(ct-step, decimal-leading-zero); position: absolute; top: -8px; left: 0;
  background: #FDFCFA; padding-right: 10px;
  font: 500 13px ui-monospace, Menlo, monospace; color: #2B5492;
}
.jpes-21-ct-step__label { margin: 10px 0 8px; font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: #9A9A94; }
.jpes-21-ct-step__body { font-size: 15px; line-height: 1.65; color: #4A4A46; text-wrap: pretty; }

/* ---- ODOO FORM SNIPPET ----
   Add jpes-21-ct-form to the OUTER section (the s_text_block you select in the editor),
   not to the inner section.s_website_form. Written against the live snippet markup:
       section.s_text_block.jpes-21-ct-form
         > div.s_allow_columns.container
           > div.row
             > div.offset-lg-2.col-lg-9
               > section.s_website_form > div.container > form#contactus_form
                 > div.s_website_form_rows.row
                   > div.mb-3.col-lg-6.s_website_form_field
                       label.s_website_form_label[style="width:200px"]
                         span.s_website_form_label_content + span.s_website_form_mark
                       input.form-control.s_website_form_input                     */
/* The snippet is a sibling of our .jpes-21 wrappers, not a child, so it inherits NOTHING
   from base.css — the house font has to be restated here or the form renders in Odoo's
   theme face while the band above it renders in ours. */
.jpes-21-ct-form {
  background: #FDFCFA;
  padding: 36px 0 76px;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #2B2B2B;
  -webkit-font-smoothing: antialiased;
}

/* Two nested .container elements — the outer one carries the page column, the inner one
   must contribute nothing, or the form is inset by 56px twice. */
.jpes-21-ct-form > .container { max-width: 1168px; padding-left: 56px; padding-right: 56px; }
.jpes-21-ct-form .s_website_form > .container { max-width: none; padding-left: 0; padding-right: 0; }

/* The snippet ships indented (offset-lg-2 col-lg-9), which would leave the form hanging
   inside the left edge that every band above it aligns to. Only the OUTER wrapper column
   is neutralised — the `> .container > .row >` chain keeps the form's own col-lg-6 field
   columns intact. Delete this rule if you'd rather set the column width in the editor. */
.jpes-21-ct-form > .container > .row > [class*="col-"] { flex: 0 0 100%; max-width: 100%; margin-left: 0; }

/* The snippet arrives with a spacer paragraph — <p class="lead"><br></p> — above the form.
   It is pure whitespace, so it goes. (It used to be worse: base.css once styled .lead as
   the mono eyebrow, and this rendered as a stray blue line. That rule is now
   .jpes-21-lead — see the Font Helper note in base.css.) :has() removes ONLY a paragraph
   whose entire content is that <br>; real intro copy survives. */
.jpes-21-ct-form .lead:has(> br:only-child) { display: none; }

.jpes-21-ct-form label {
  display: block; margin-bottom: 7px;
  font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: #9A9A94;
  text-transform: uppercase;
}
/* the snippet writes width:200px as an inline style on every label, which no selector
   can outrank — !important is the only lever, and this is what it is for */
.jpes-21-ct-form .s_website_form_label { width: auto !important; }
.jpes-21-ct-form input[type="text"],
.jpes-21-ct-form input[type="email"],
.jpes-21-ct-form input[type="tel"],
.jpes-21-ct-form input[type="url"],
.jpes-21-ct-form input[type="number"],
.jpes-21-ct-form input[type="date"],
.jpes-21-ct-form select,
.jpes-21-ct-form textarea {
  width: 100%;
  padding: 13px 14px;
  font-family: inherit;
  font-size: 15px;
  color: #2B2B2B;
  background: #fff;
  border: 1px solid #D8D4CC;
  border-radius: 0;                    /* square corners; Bootstrap rounds these */
  transition: border-color .18s ease, box-shadow .18s ease;
  appearance: none;
}
.jpes-21-ct-form textarea { min-height: 150px; resize: vertical; }
.jpes-21-ct-form input:focus,
.jpes-21-ct-form select:focus,
.jpes-21-ct-form textarea:focus {
  outline: none;                        /* replaced, not removed — see the ring below */
  border-color: #2B5492;
  box-shadow: 0 0 0 3px rgba(43,84,146,.16);
}
.jpes-21-ct-form ::placeholder { color: #A9A9A2; }
/* the required marker is a real span (form[data-mark="*"] writes it), not a ::after */
.jpes-21-ct-form .s_website_form_mark { color: #2B5492; }

/* The submit row ships with an empty 200px-wide .s_website_form_label spacer div and a
   .text-center utility. Bootstrap utilities are !important, so aligning left needs the
   same weight. The spacer is a div, not a <label>, hence the class selector. */
.jpes-21-ct-form .s_website_form_submit { text-align: left !important; }
.jpes-21-ct-form .s_website_form_submit > .s_website_form_label { display: none; }

.jpes-21-ct-form button[type="submit"],
.jpes-21-ct-form a.btn,
.jpes-21-ct-form .s_website_form_send {
  display: inline-block;
  padding: 15px 32px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  color: #fff;
  background: #2B5492;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  transition: background-color .2s ease;
}
.jpes-21-ct-form button[type="submit"]:hover,
.jpes-21-ct-form a.btn:hover,
.jpes-21-ct-form .s_website_form_send:hover { background: #3866AC; color: #fff; }

/* ---- ODOO MAP SNIPPET (section class: jpes-21-ct-map) ----
   Set the section to Full width in Odoo. Corner ticks are drawn with pseudo-elements
   because we cannot insert <i> elements into a snippet we do not own. */
.jpes-21-ct-map { position: relative; background: #E6E3DC; line-height: 0; }
.jpes-21-ct-map iframe, .jpes-21-ct-map .s_map_embedded, .jpes-21-ct-map .s_map_color_filter {
  display: block; width: 100%; min-height: 420px; border: 0;
}
/* desaturated by default so the map sits inside the palette rather than shouting
   Google's; full colour on hover, when the visitor is actually reading it */
.jpes-21-ct-map iframe { filter: grayscale(1) contrast(.96); transition: filter .35s ease; }
.jpes-21-ct-map:hover iframe { filter: none; }
.jpes-21-ct-map::before, .jpes-21-ct-map::after {
  content: ""; position: absolute; width: 18px; height: 18px; pointer-events: none; z-index: 1;
}
.jpes-21-ct-map::before { top: 0; left: 0; border-top: 1.5px solid #2B5492; border-left: 1.5px solid #2B5492; }
.jpes-21-ct-map::after { bottom: 0; right: 0; border-bottom: 1.5px solid #2B5492; border-right: 1.5px solid #2B5492; }

/* No closing strip: the map is the last band, and Odoo's own site footer closes the
   page — it already publishes contact details, company number and VAT, so a strip of
   ours would duplicate them. The .jpes-21-ct-foot* rules were removed with the
   04-foot.html fragment; see ../README-removed-foot.txt. */

/* ---- responsive: same breakpoints as the case-study page ---- */
@media (max-width: 960px) {
  .jpes-21-ct-hero__inner { padding: 64px 24px; }
  .jpes-21-ct-hero__title { font-size: 32px; }
  .jpes-21-ct-details__inner, .jpes-21-ct-formhead__inner { padding-left: 24px; padding-right: 24px; }
  /* direct child only — a descendant selector here would re-pad the inner
     .s_website_form > .container and double-inset the form on mobile */
  .jpes-21-ct-form > .container { padding-left: 24px; padding-right: 24px; }
  .jpes-21-ct-grid, .jpes-21-ct-steps { grid-template-columns: 1fr; }
  .jpes-21-ct-map iframe, .jpes-21-ct-map .s_map_embedded { min-height: 320px; }
}
/* the hover-to-colour map is meaningless without a hovering pointer, and iOS keeps a
   synthesised :hover stuck after a tap — show the map in colour outright instead */
@media (hover: none) {
  .jpes-21-ct-map iframe { filter: none; }
}


/* ####################  BLOG  #################### */

/* ============ BLOG / INSIGHTS ============
   Shared primitives (.jpes-21 base, -eyebrow, -tick, -sec, -metric) live in ../../base.css
   and must not be repeated here.
   Run `python3 build.py` from pages/ after editing to refresh core.css.

   TWO PARTS, TWO MECHANISMS:

   1. HEADER (../01-hero.html) — our markup, pasted into Odoo's own editable drop zone
      inside #o_wblog_blog_top. Styled by .jpes-21-bl-* below.

   2. INDEX — Odoo's website_blog templates, which we do NOT own. Styled by scoping to
      Odoo's own stable IDs (#o_wblog_index_content, #o_wblog_posts_loop_container).
      Unlike the contact form, this needs NO hand-added class: the IDs are rendered by
      website_blog.blog_post_short itself, so there is nothing to forget. Those IDs only
      exist on blog index pages, so scoping to them is safe in a site-wide stylesheet.

   Namespace .jpes-21-bl-*. NOTE: this hero duplicates .jpes-21-cases-hero and
   .jpes-21-ct-hero. Three copies of one component is worth consolidating into a shared
   .jpes-21-index-hero in base.css — deliberately deferred, not overlooked. */

/* ---- header: light "drawing-sheet" band, matching the case-studies index ---- */
.jpes-21-bl-hero { position: relative; background: #FDFCFA; border-bottom: 1px solid #E8E5DF; --jpes21-grid: rgba(43,84,146,.07); }
.jpes-21-bl-hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
.jpes-21-bl-hero__inner {
  position: relative; max-width: 1168px; margin: 0 auto; padding: 96px 56px;
  display: flex; flex-direction: column; gap: 16px;
}
.jpes-21-bl-hero__title { font-size: 40px; line-height: 1.15; color: #2B2B2B; max-width: 640px; text-wrap: pretty; }
.jpes-21-bl-hero__intro { font-size: 16px; line-height: 1.7; color: #4A4A46; max-width: 600px; text-wrap: pretty; }

@media (max-width: 960px) {
  .jpes-21-bl-hero__inner { padding: 64px 24px; }
  .jpes-21-bl-hero__title { font-size: 32px; }
}

/* ---- INDEX: post cards (Odoo website_blog.posts_loop, CARDS DESIGN) ----
   Written against the rendered markup of a live post in cards mode:

     div.pb-4.col-12.col-md-6.col-xl-4                    grid column
       article.o_wblog_post.position-relative.card.h-100
         > a.card-img-top.d-block.mb-2                    cover link
             > .o_record_cover_container.d-flex.flex-column.h-100.o_cc3.o_list_cover
                 > .o_record_cover_image                  background-image, inline style
                 > .o_wblog_post_list_author              avatar + author, white, over the image
         > .card-body.px-2.py-0.mb-2
             > a.o_blog_post_title.text-reset.h5
             > div > a > div[data-oe-field="teaser"]
             > .o_wblog_post_short_tag_section > a.badge.o_tag.o_color_N   (x6)
         > .o_wblog_normalize_font.card-footer.px-2.pb-2
             > … > time.fw-bold.px-2

   BOOTSTRAP UTILITIES ARE !important BY DEFINITION (px-2, py-0, mb-2, pb-2, fw-bold,
   text-reset, h-100 …). Every !important below beats one of those, never a fight of our
   own making. Both sides !important means specificity decides, and every selector here
   carries #o_wblog_index_content, so ours wins.

   Bootstrap CARD classes (.card, .card-body, .card-footer, .card-img-top) are components,
   NOT utilities — no !important on them, so plain ID-scoped rules beat them. */

/* the section itself — .o_wblog_page_cards_bg is added by the template when cards design
   is on, and paints its own background; bring it onto the house ground */
#o_wblog_index_content, #o_wblog_index_content.o_wblog_page_cards_bg { background: #FDFCFA; }
#o_wblog_index_content > .container { max-width: 1168px; padding: 72px 56px !important; }

/* Odoo ships col-12 / col-md-6 / col-xl-4, so three-up only arrives at 1200px. The
   case-studies grid goes three-up from 960px — pull the third column in at lg to match,
   otherwise the two indexes disagree at exactly the widths most desktops sit at. */
@media (min-width: 992px) {
  #o_wblog_posts_loop_container [class*="col-xl-4"] { flex: 0 0 auto; width: 33.3333%; }
}

/* card root */
#o_wblog_index_content .o_wblog_post.card {
  background: #FDFCFA;
  border: 0;
  border-radius: 0;
  transition: transform .25s ease, box-shadow .25s ease;
}
#o_wblog_index_content .o_wblog_post.card:hover { transform: translateY(-4px); box-shadow: 0 12px 28px rgba(43,84,146,.14); }

/* cover — square edge plus the house corner ticks, drawn as pseudo-elements because the
   markup is Odoo's and we cannot insert <i> tick elements into it */
#o_wblog_index_content .o_wblog_post > .card-img-top { margin-bottom: 0 !important; border-radius: 0; }

/* THE COVER IMAGE IS ABSOLUTELY POSITIONED — this is the important thing to know here.
   .o_record_cover_image is taken out of flow by Odoo, so it contributes NOTHING to the
   container's height; the container is sized by its in-flow children only. The one
   in-flow child was .o_wblog_post_list_author, which we hide below.

   So the height MUST live on the container. Putting min-height on the image instead
   collapses the container to zero and the image then overflows downward, painting over
   the post title — visible only on hover, when Odoo fades the cover.

   Tell-tale in the markup: the author block carries `position-relative`, which is what
   you add to lift in-flow content above an absolutely-positioned background. Unpublished
   posts render correctly because Odoo adds a ribbon element — a second in-flow child —
   which keeps the container from collapsing. */
#o_wblog_index_content .o_record_cover_container {
  position: relative;
  min-height: 200px;
  background-color: #E6E3DC;
  border-radius: 0;
}
#o_wblog_index_content .o_record_cover_image { background-size: cover; background-position: center; }
#o_wblog_index_content .o_record_cover_container::before,
#o_wblog_index_content .o_record_cover_container::after {
  content: ""; position: absolute; width: 14px; height: 14px; z-index: 2; pointer-events: none;
}
#o_wblog_index_content .o_record_cover_container::before { top: 0; left: 0; border-top: 1.5px solid #2B5492; border-left: 1.5px solid #2B5492; }
#o_wblog_index_content .o_record_cover_container::after { bottom: 0; right: 0; border-bottom: 1.5px solid #2B5492; border-right: 1.5px solid #2B5492; }

/* author overlay hidden on the index — it prints "J P Engineering Services Ltd, Ross
   Parker" in white over the cover. Authorship still shows on the article page itself,
   which is where it counts for attribution. */
#o_wblog_index_content .o_wblog_post_list_author { display: none !important; }

/* body — .card-body keeps Bootstrap's flex:1 1 auto, which is what pushes the footer to
   the card floor and makes card feet line up across different teaser lengths.

   The bottom padding is REAL padding, not leftover slack. With `0` here, short cards
   still looked fine because flex:1 1 auto stretched the body and the spare room read as
   spacing — but a card whose tags wrap to two rows has no spare room, and the last chip
   row ends up touching the footer's hairline rule. The spacing has to be declared. */
#o_wblog_index_content .card-body { padding: 18px 22px !important; margin-bottom: 0 !important; }

#o_wblog_index_content .o_blog_post_title {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500;
  font-size: 20px;
  line-height: 1.25;
  color: #2B2B2B !important;        /* .text-reset is `color: inherit !important` */
  text-wrap: pretty;
}
#o_wblog_index_content .o_wblog_post:hover .o_blog_post_title { color: #2B5492 !important; }

#o_wblog_index_content [data-oe-field="teaser"] { font-size: 14px; line-height: 1.6; color: #6B6B66; text-wrap: pretty; }

/* tags — Odoo colour-codes these (o_color_0/1/4/6), which fights a two-colour palette.
   Neutralised to quiet mono chips. Only the first three show: every tag still works on
   the post page and in the filter nav, this trims the index card only. */
#o_wblog_index_content .o_wblog_post_short_tag_section { gap: 6px; padding-top: 14px !important; }
#o_wblog_index_content .o_wblog_post_short_tag_section .o_tag {
  margin: 0 !important;             /* .mb-2 .me-1 */
  padding: 4px 7px;
  background: none !important;      /* beats .o_color_N's background-color */
  color: #2B5492 !important;
  /* .o_color_N sets border-color with !important too — killing the background alone
     leaves the chips outlined in Odoo's red/green/purple/orange, which is louder than
     the filled badges were. The border needs the same weight. */
  border: 1px solid #D8D4CC !important;
  border-radius: 0;
  font: 500 10px ui-monospace, Menlo, monospace;
  letter-spacing: .08em;
  text-transform: uppercase;
}
#o_wblog_index_content .o_wblog_post_short_tag_section .o_tag:hover { border-color: #2B5492 !important; background: #EEF3FB !important; }
#o_wblog_index_content .o_wblog_post_short_tag_section .o_tag:nth-of-type(n+4) { display: none; }

/* footer — the hairline rule the case-study cards use. `mx-n2` on the inner div and
   `px-2` on the <time> are Odoo cancelling its own gutter; both are left alone
   deliberately, because zeroing one without the other misaligns the date. */
#o_wblog_index_content .o_wblog_post > .card-footer {
  padding: 12px 22px 18px !important;   /* .px-2 .pb-2 */
  background: none;
  border-top: 1px solid #E8E5DF;
  border-radius: 0;
}
#o_wblog_index_content .o_wblog_post time {
  font: 500 10.5px ui-monospace, Menlo, monospace !important;   /* beats .fw-bold */
  letter-spacing: .1em;
  color: #9A9A94;
}

@media (max-width: 960px) {
  #o_wblog_index_content > .container { padding: 56px 24px !important; }
}

/* ---- STILL TO WRITE ----
   website_blog.blogs_nav (the tag filter + search bar above the grid) and
   website.pager (pagination below it). Neither has been seen; guessing Odoo markup is
   what cost a debugging round on the contact form. */


/* ####################  CAREERS  #################### */

/* ============ CAREERS (Odoo website_hr_recruitment) ============
   Shared primitives (.jpes-21 base, -eyebrow, -tick, -sec) live in ../../base.css and
   must not be repeated here.
   Run `python3 build.py` from pages/ after editing to refresh core.css.

   STYLING ONLY — no fragments, no pasted HTML. All three pages are rendered by Odoo's
   recruitment module, so this file is nothing but overrides. There is no folder of
   NN-*.html blocks as the other pages have.

   THE MARKUP THIS IS WRITTEN AGAINST IS CAPTURED IN ../ODOO-TEMPLATES.md — including the
   traps. Re-capture and diff it after any Odoo upgrade before trusting these rules.

   SCOPING. `#wrap` alone is EVERY Odoo page, so it is never used on its own:
       jobs index   #wrap.o_website_hr_recruitment_jobs_list
       job detail   #wrap.js_hr_recruitment
       apply page   #wrap:has(#hr_recruitment_form)
       back-nav     nav.navbar:has(+ #wrap.js_hr_recruitment)
   All four are Odoo's own hooks — nothing has to be added by hand in the editor, so the
   contact-form failure mode (a forgotten class silently killing every rule) cannot recur.

   Bootstrap utilities are !important by definition; card/btn COMPONENT classes are not.
   Every !important below beats one of those, never a fight of our own making. */

/* ==================================================================
   1. JOBS INDEX
   ================================================================== */

.o_website_hr_recruitment_jobs_list { background: #FDFCFA; }
.o_website_hr_recruitment_jobs_list .oe_website_jobs { max-width: 1168px; padding-left: 56px; padding-right: 56px; }

/* ---- page header row: "Our Job Offers" + the filter dropdowns ----
   No hero is possible here (styling only, no new elements), so this row has to carry the
   page opening on its own: serif title, hairline beneath, filters kept to the right. */
.o_website_hr_recruitment_jobs_list .o_wevent_index_topbar_filters {
  align-items: baseline !important;
  margin: 72px 0 0 !important;          /* .mt-4 .mb-3 */
  padding-bottom: 24px;
  border-bottom: 1px solid #E8E5DF;
}
.o_website_hr_recruitment_jobs_list .o_wevent_index_topbar_filters h1 {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500;
  font-size: 40px;
  line-height: 1.15;
  color: #2B2B2B;
}

/* ---- job rows: spec-sheet, not cards ----
   Odoo already stacks them (#jobs_grid > .row.flex-column), so the row reading comes
   almost free — the card chrome just has to come off. */
.o_website_hr_recruitment_jobs_list #jobs_grid > .row > .col-lg { margin-bottom: 0 !important; }  /* .mb32 */
.o_website_hr_recruitment_jobs_list .card {
  position: relative;
  border: 0;
  border-bottom: 1px solid #E2DED6;
  border-radius: 0;
  /* Bootstrap/Odoo card styling clips overflow to keep images inside the radius. There is
     no radius and no image here, and the hover arrow travels outward — leave it unclipped
     or the arrow is cut off at the card edge mid-animation. */
  overflow: visible;
}

/* Odoo's theme gives .card a filled background and a lift-and-shadow on hover. These rows
   are a spec sheet, not cards: the only hover signal should be the title turning blue and
   the arrow sliding. Resting and hover states are reset together so the theme cannot
   reintroduce either through a rule we have not seen — !important because the theme's own
   hover rule carries it. */
.o_website_hr_recruitment_jobs_list .card,
.o_website_hr_recruitment_jobs_list .card:hover,
.o_website_hr_recruitment_jobs_list .card:focus-within {
  background: transparent !important;
  background-color: transparent !important;
  box-shadow: none !important;
  transform: none !important;
}
.o_website_hr_recruitment_jobs_list .card-body { position: relative; padding: 30px 52px 30px 0 !important; }  /* .p-4 */

/* The arrow is the row's affordance, in place of a button. It is inset from the card edge
   rather than sitting on it: `right: 0` put it flush against the boundary with nowhere to
   travel, so the 5px hover nudge pushed it past the edge. 16px at rest + 5px travel keeps
   it comfortably inside the 52px right padding at every point of the transition. */
.o_website_hr_recruitment_jobs_list .card-body::after {
  content: "→";
  position: absolute; top: 30px; right: 16px;
  font-size: 18px; line-height: 1.3; color: #C9C6BF;
  transition: color .2s ease, transform .25s cubic-bezier(.22,.61,.36,1);
}
.o_website_hr_recruitment_jobs_list .card:hover .card-body::after { color: #2B5492; transform: translateX(5px); }

.o_website_hr_recruitment_jobs_list .card-body h3 {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500; font-size: 24px; line-height: 1.25; color: #2B2B2B;
  margin: 0; text-wrap: pretty;
  transition: color .2s ease;
}
.o_website_hr_recruitment_jobs_list .card:hover .card-body h3 { color: #2B5492; }

/* "N open positions" is metadata, not a heading — .text-reset forces the !important */
.o_website_hr_recruitment_jobs_list .card-body h5 {
  margin: 8px 0 0;
  font: 500 11px ui-monospace, Menlo, monospace;
  letter-spacing: .12em; text-transform: uppercase;
  color: #9A9A94 !important;            /* .text-reset is `color: inherit !important` */
}

/* The index renders the WHOLE of job.description — which on this site is the full
   role write-up, headings and bullet lists included, so one vacancy filled the page.
   Clamped to a three-line teaser: structural children are dropped rather than
   line-clamped, because -webkit-line-clamp behaves unpredictably once a container holds
   headings and lists. Nothing is lost — the whole description is on the job page. */
.o_website_hr_recruitment_jobs_list .card-body .oe_empty {
  position: relative;
  margin: 14px 0 16px !important;       /* .mb16 */
  max-width: 760px;
  max-height: 4.95em;                   /* 3 lines at line-height 1.65 */
  overflow: hidden;
  font-size: 15px; line-height: 1.65;
  color: #6B6B66 !important;            /* .text-muted */
  text-wrap: pretty;
}
.o_website_hr_recruitment_jobs_list .card-body .oe_empty :is(h1, h2, h3, h4, h5, h6, ul, ol, table, img, blockquote) { display: none; }
.o_website_hr_recruitment_jobs_list .card-body .oe_empty p { margin: 0 0 8px; }
/* fade the cut edge so it reads as a teaser rather than a rendering fault */
.o_website_hr_recruitment_jobs_list .card-body .oe_empty::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1.8em;
  background: linear-gradient(rgba(253,252,250,0), #FDFCFA);
  pointer-events: none;
}

/* ---- location / department / contract type as one mono metadata line ---- */
.o_website_hr_recruitment_jobs_list .o_job_infos {
  flex-direction: row !important;       /* .flex-column */
  flex-wrap: wrap;
  gap: 4px 24px;
  font: 500 11px ui-monospace, Menlo, monospace;
  letter-spacing: .1em; text-transform: uppercase; color: #2B5492;
}
.o_website_hr_recruitment_jobs_list .o_job_infos .fw-light { font-weight: 500 !important; }
/* This site's vocabulary is mono labels and hairlines — it carries no iconography
   anywhere else, and these three FontAwesome marks would be the only icons on it. The
   values (a city, a department, a contract type) already say what they are. */
.o_website_hr_recruitment_jobs_list .o_job_infos .fa { display: none; }

/* admin-only "unpublished" flag — kept red so it stays a warning, squared to match */
.o_website_hr_recruitment_jobs_list .o_jobs_unpublished .badge {
  border-radius: 0;
  font: 500 10px ui-monospace, Menlo, monospace;
  letter-spacing: .1em; text-transform: uppercase;
}

/* ---- empty state: a speculative-application prompt ----
   NOTE: "contact us" in this copy is bold text, NOT a link — CSS cannot make it one.
   See ../ODOO-TEMPLATES.md; it needs an anchor adding in Odoo. It is deliberately NOT
   styled to look clickable, because a fake affordance is worse than a plain one. */
.o_website_hr_recruitment_jobs_list .oe_website_jobs p.h5 {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500 !important;          /* .fw-light */
  font-size: 26px; line-height: 1.35;
  color: #2B2B2B !important;            /* .text-muted */
  max-width: 640px; margin: 0 auto !important; padding: 0 !important;
  text-wrap: pretty;
}
.o_website_hr_recruitment_jobs_list .oe_website_jobs p.h5 .fw-bold { color: #2B5492; }
/* Odoo's job_not_found cartoon is off-brand on a technical site */
.o_website_hr_recruitment_jobs_list img[src*="job_not_found"] { display: none; }
.o_website_hr_recruitment_jobs_list .text-center.pt24 { padding: 72px 0 96px !important; }

/* alerts (search misses, and the manager-only "create a job" notice) */
.o_website_hr_recruitment_jobs_list .alert {
  border-radius: 0; border-color: #E2DED6;
  font: 12px ui-monospace, Menlo, monospace; letter-spacing: .04em;
}

/* ---- pager ----
   Bootstrap's standard .pagination > .page-item > .page-link, as emitted by
   website.pager. NOT captured from the live page — verify against a paginated index. */
.o_website_hr_recruitment_jobs_list .navbar { padding: 0; }
.o_website_hr_recruitment_jobs_list .page-link {
  border: 0; border-radius: 0; background: none;
  font: 500 12px ui-monospace, Menlo, monospace; letter-spacing: .1em;
  color: #2B5492; padding: 8px 12px;
}
.o_website_hr_recruitment_jobs_list .page-item.active .page-link { color: #2B2B2B; text-decoration: underline; }

/* ==================================================================
   2. JOB DETAIL
   ================================================================== */

/* The back-nav sits OUTSIDE #wrap, so :has(+ …) is the only way to reach it without
   also matching the site header's own .navbar. */
nav.navbar:has(+ #wrap.js_hr_recruitment) {
  background: #FDFCFA;
  border-top: 0 !important;             /* .border-top */
  border-bottom: 1px solid #E8E5DF;
  box-shadow: none !important;          /* .shadow-sm */
  padding: 0;
}
nav.navbar:has(+ #wrap.js_hr_recruitment) .navbar-brand {
  font: 500 11px ui-monospace, Menlo, monospace;
  letter-spacing: .12em; text-transform: uppercase;
  padding: 18px 0;
}
nav.navbar:has(+ #wrap.js_hr_recruitment) .navbar-brand a { color: #2B5492; }
nav.navbar:has(+ #wrap.js_hr_recruitment) .navbar-brand a:hover { color: #1E3E6E; }
nav.navbar:has(+ #wrap.js_hr_recruitment) .fa-long-arrow-left { color: #2B5492 !important; }

.js_hr_recruitment { background: #FDFCFA; }
.js_hr_recruitment section.pb32 { border-bottom: 1px solid #E8E5DF; padding-bottom: 44px !important; }
.js_hr_recruitment section.pb32 .container { max-width: 1168px; padding-left: 56px; padding-right: 56px; }
.js_hr_recruitment section.pb32 .mt32 { margin-top: 64px !important; }
.js_hr_recruitment .row { align-items: center; }

.js_hr_recruitment h1 {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500; font-size: 42px; line-height: 1.15; color: #2B2B2B;
  text-wrap: pretty;
}
.js_hr_recruitment h5.fw-light {
  margin-top: 12px;
  font: 500 11px ui-monospace, Menlo, monospace !important;   /* .fw-light */
  letter-spacing: .12em; text-transform: uppercase; color: #2B5492;
}

/* ---- the job description body ----
   Rendered from job.website_description into a div with NO class, so it is reached
   structurally. If a description is ever built with full-width website snippets, this
   max-width will fight them — relax it then. */
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) {
  max-width: 1168px; margin: 0 auto; padding: 52px 56px;
  font-size: 16px; line-height: 1.7; color: #4A4A46;
}
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) :is(h1, h2, h3, h4) {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500; color: #2B2B2B; line-height: 1.25;
  margin: 34px 0 12px;
}
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) :is(h2, h3) { font-size: 24px; }
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) :is(h4, h5) { font-size: 19px; }
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) p { margin: 0 0 16px; max-width: 720px; text-wrap: pretty; }
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) :is(ul, ol) { max-width: 720px; padding-left: 20px; margin: 0 0 20px; }
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) li { margin-bottom: 8px; }

/* ---- HANDS OFF SNIPPET INTERNALS ----
   The description is authored in Odoo's editor, so it can contain website snippets that
   bring their own colour, layout and spacing. The prose rules above must not reach inside
   them, and one of them did real damage:

   s_comparisons builds each column as .card.text-bg-primary — Bootstrap paints that in the
   theme's primary, which on this site is near-black. The `:is(h1,h2,h3,h4) { color:#2B2B2B }`
   rule then wrote black text onto a black card, so "Responsibilities" / "Must Have" /
   "Nice to have" rendered invisible and left an empty dark strip at the top of each column.
   The 8px li margin was separately breaking .list-group-flush, whose items are meant to
   butt together.

   Colour and spacing are handed back wherever an element declares it owns them. */
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class])
  :is(.card, .list-group, [class*="text-bg-"], [class*="o_cc"]) :is(h1, h2, h3, h4, h5, h6) {
  color: inherit;
  margin: 0;
}
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) .list-group-item { margin: 0; }
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class])
  :is(.card, .list-group) :is(ul, ol) { margin: 0; padding-left: 0; max-width: none; }

/* ---- WYSIWYG artefacts ----
   Authors press Enter for spacing, and Odoo's editor leaves the empty element behind:
   this description contains both <p><br></p> and <h3><br></h3>. The empty HEADING is the
   worse of the two — a screen reader announces an empty heading level, and it collects
   the 34px heading margin, which is part of why the spacing looked uneven. display:none
   removes it from the accessibility tree as well as the page.
   `br:only-child` is the safe test: a paragraph that merely ENDS in <br> keeps its text
   and is untouched. */
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class])
  :is(p, h1, h2, h3, h4, h5, h6):has(> br:only-child) { display: none; }

/* bullets are authored as <li><p>…</p></li>; the paragraph's own bottom margin would
   open a 16px gap inside every bullet */
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) li > p { margin: 0; }
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) li > p + p { margin-top: 8px; }

/* .lead paragraphs here are additionally wrapped in <strong>. Two consecutive paragraphs
   of full-weight bold at 18px shout; medium reads as emphasis. (These are the paragraphs
   that used to render as a mono eyebrow, before .lead was renamed — see base.css.) */
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) .lead { max-width: 720px; }
.js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) .lead strong { font-weight: 500; }

/* ---- s_rating: stars become a segmented gauge ----
   Five filled stars on your own job advert read as a review score of the company. As a
   statement of what the ROLE demands, a filled gauge says the same thing without the
   connotation, and matches the measured vocabulary the rest of the site uses.
   The FontAwesome glyph is suppressed (`content: none` on ::before) and each <i> becomes
   a segment; `display: contents` on the active/inactive spans drops their children into
   one flex row, so filled and empty segments share a single even track. */
.js_hr_recruitment .s_rating { display: flex; flex-direction: column; gap: 7px; padding-bottom: 20px !important; }
.js_hr_recruitment .s_rating_title {
  font: 500 11px ui-monospace, Menlo, monospace;
  letter-spacing: .12em; text-transform: uppercase; color: #9A9A94;
}
.js_hr_recruitment .s_rating_icons { display: flex; gap: 4px; max-width: 210px; }
.js_hr_recruitment .s_rating_active_icons,
.js_hr_recruitment .s_rating_inactive_icons { display: contents; }
.js_hr_recruitment .s_rating_icons .fa::before { content: none; }
.js_hr_recruitment .s_rating_icons .fa { flex: 1; height: 6px; font-size: 0; }
.js_hr_recruitment .s_rating_active_icons .fa { background: #2B5492; }
.js_hr_recruitment .s_rating_inactive_icons .fa { background: #E2DED6; }

/* ---- Apply buttons ----
   MISSED IN THE FIRST PASS: only the apply PAGE's buttons were styled, so both "Apply
   Now!" buttons on the detail page kept the Odoo theme's black pill. Odoo drives button
   colour through --bs-btn-* custom properties on .btn-primary and adds .btn-lg on top,
   so the overrides are set directly rather than via those variables. */
.js_hr_recruitment .btn-primary,
.js_hr_recruitment .btn.btn-primary.btn-lg {
  display: inline-block;
  padding: 16px 34px !important;        /* .btn-lg */
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 15px !important;
  font-weight: 500;
  color: #fff !important;
  background: #2B5492 !important;
  border: 0 !important;
  border-radius: 0 !important;
  transition: background-color .2s ease;
}
.js_hr_recruitment .btn-primary:hover,
.js_hr_recruitment .btn.btn-primary.btn-lg:hover { background: #3866AC !important; }

/* ---- closing Apply band ----
   Light, not dark: Odoo's own site footer follows immediately, and two dark bands in a
   row is the mistake the contact page's closing strip was removed for. */
.js_hr_recruitment .o_job_bottom_bar {
  background: #F4F2ED;
  border-top: 1px solid #E8E5DF;
  margin: 0 !important;                 /* .mt24 .mb48 */
  padding: 56px 24px !important;
}

/* ==================================================================
   3. APPLY FORM
   ================================================================== */

/* #wrap on this page carries only .container, so the form's ID is the reliable hook */
#wrap:has(#hr_recruitment_form) { max-width: 1168px; padding-left: 56px; padding-right: 56px; }
#wrap:has(#hr_recruitment_form) .breadcrumb {
  font: 500 11px ui-monospace, Menlo, monospace;
  letter-spacing: .12em; text-transform: uppercase;
}
#wrap:has(#hr_recruitment_form) .breadcrumb-item.active a,
#wrap:has(#hr_recruitment_form) .breadcrumb-item.active { color: #9A9A94; }
#wrap:has(#hr_recruitment_form) > h1 {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500; font-size: 40px; line-height: 1.15; color: #2B2B2B;
  margin: 10px 0 40px !important;       /* .mb-4 */
}
#wrap:has(#hr_recruitment_form) #jobs_section { max-width: none; padding: 0; }
#hr_recruitment_form .container, #forms > .container { max-width: none; padding: 0; }

/* ---- fields ----
   Unlike the contact form, this markup puts label and input SIDE BY SIDE in a nested
   .row. Setting that row to block stacks them, matching the contact page. */
#hr_recruitment_form .s_website_form_field > .row { display: block; }
#hr_recruitment_form .s_website_form_field { padding: 0 0 18px !important; }   /* .py-2 */
#hr_recruitment_form .s_website_form_label {
  display: block;
  width: auto !important;               /* inline style="width: 200px" — only !important beats it */
  margin-bottom: 7px;
  font: 500 11px ui-monospace, Menlo, monospace;
  letter-spacing: .12em; text-transform: uppercase; color: #9A9A94;
}
#hr_recruitment_form .s_website_form_mark { color: #2B5492; }

#hr_recruitment_form input[type="text"],
#hr_recruitment_form input[type="email"],
#hr_recruitment_form input[type="tel"],
#hr_recruitment_form input[type="file"],
#hr_recruitment_form textarea {
  width: 100%;
  padding: 13px 14px;
  font-family: inherit; font-size: 15px; color: #2B2B2B;
  background: #fff;
  border: 1px solid #D8D4CC;
  border-radius: 0;
  transition: border-color .18s ease, box-shadow .18s ease;
}
#hr_recruitment_form textarea { min-height: 150px; resize: vertical; }
#hr_recruitment_form :is(input, textarea):focus {
  outline: none;                        /* replaced, not removed — ring below */
  border-color: #2B5492;
  box-shadow: 0 0 0 3px rgba(43,84,146,.16);
}
#hr_recruitment_form ::placeholder { color: #A9A9A2; }

/* ---- CV upload ----
   ::file-selector-button is the supported way to style the native picker. No hiding the
   input behind a proxy label: a broken upload control on a careers page costs real
   candidates, and the native control is the one guaranteed to work. */
#hr_recruitment_form input[type="file"] { padding: 9px 12px; }
#hr_recruitment_form input[type="file"]::file-selector-button {
  margin-right: 14px; padding: 8px 16px;
  border: 0; border-radius: 0;
  background: #2B5492; color: #fff;
  font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .1em; text-transform: uppercase;
  cursor: pointer; transition: background-color .2s ease;
}
#hr_recruitment_form input[type="file"]:hover::file-selector-button { background: #3866AC; }
#hr_recruitment_form .text-muted.small { display: block; margin-top: 7px; font-size: 12.5px; color: #9A9A94 !important; }

#hr_recruitment_form .o_linkedin_icon { color: #2B5492; }

/* ---- submit ----
   The row ships an empty 200px .s_website_form_label spacer, same as the contact form. */
#hr_recruitment_form .s_website_form_submit > .s_website_form_label { display: none; }
#hr_recruitment_form .s_website_form_submit { margin-bottom: 72px !important; }   /* .mb64 */
#hr_recruitment_form .s_website_form_send,
#wrap:has(#hr_recruitment_form) .btn-primary {
  display: inline-block;
  padding: 15px 32px;
  font-family: inherit; font-size: 15px; font-weight: 500;
  color: #fff; background: #2B5492;
  border: 0; border-radius: 0; cursor: pointer;
  transition: background-color .2s ease;
}
#hr_recruitment_form .s_website_form_send:hover,
#wrap:has(#hr_recruitment_form) .btn-primary:hover { background: #3866AC; color: #fff; }

/* ---- sidebar: job summary ---- */
#jobs_section .col-md-3 {
  padding: 26px 24px !important;        /* .ps-5 */
  background: #F4F2ED;
  align-self: start;
}
#jobs_section .col-md-3 .text-muted.small {
  font: 500 11px ui-monospace, Menlo, monospace;
  letter-spacing: .12em; text-transform: uppercase;
  color: #9A9A94 !important;
}
#jobs_section .col-md-3 h6 {
  margin: 4px 0 18px;
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500; font-size: 17px; line-height: 1.3; color: #2B2B2B;
}
#jobs_section .col-md-3 hr { width: 100% !important; border-color: #D8D4CC; margin: 4px 0 18px !important; }
#jobs_section .o_apply_description_link {
  display: inline-block; width: 100%; text-align: center;
  margin-bottom: 26px !important;       /* .mb16 */
  padding: 13px 20px;
  font-size: 13.5px; font-weight: 500;
  color: #2B5492; background: none;
  border: 1px solid #2B5492; border-radius: 0;
  transition: background-color .2s ease, color .2s ease;
}
#jobs_section .o_apply_description_link:hover { background: #2B5492; color: #fff; }

/* Odoo wraps a zero-width space in <font class="bg-o-color-2"> inside the Apply button
   label, which paints a coloured block mid-text. */
.js_hr_recruitment .btn font, #wrap:has(#hr_recruitment_form) .btn font { background: none !important; color: inherit !important; }

/* ==================================================================
   RESPONSIVE
   ================================================================== */

@media (max-width: 960px) {
  .o_website_hr_recruitment_jobs_list .oe_website_jobs,
  .js_hr_recruitment section.pb32 .container,
  #wrap:has(#hr_recruitment_form) { padding-left: 24px; padding-right: 24px; }
  .o_website_hr_recruitment_jobs_list .o_wevent_index_topbar_filters { margin-top: 48px !important; }
  .o_website_hr_recruitment_jobs_list .o_wevent_index_topbar_filters h1,
  #wrap:has(#hr_recruitment_form) > h1 { font-size: 31px; }
  .js_hr_recruitment h1 { font-size: 32px; }
  .js_hr_recruitment [itemtype="https://schema.org/JobPosting"] > div:not([class]) { padding: 40px 24px; }
  /* the detail page's Apply button is in a 3-col beside the title — let it go full width */
  .js_hr_recruitment section.pb32 .col-md-3 .text-center { text-align: left !important; }
  #jobs_section .col-md-3 { margin-top: 32px; }
}


/* ####################  CASE STUDIES INDEX  #################### */

/* ============ CASE STUDIES INDEX ============
   Shared primitives (.jpes-21 base, -eyebrow, -btn, -tick, -figcap, -metric) live in
   ../../base.css and must not be repeated here.
   Run `python3 build.py` from pages/ after editing to refresh core.css. */

/* section entrance — a light "drawing-sheet" hero (matching the single case study page):
   the case-studies area opens LIGHT where the front page opens dark, which is the
   differentiator. Faint blue grid = ink-on-white, like an engineering drawing. */
.jpes-21-cases-hero { position: relative; background: #FDFCFA; border-bottom: 1px solid #E8E5DF; --jpes21-grid: rgba(43,84,146,.07); }
.jpes-21-cases-hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
.jpes-21-cases-hero__inner {
  position: relative; max-width: 1168px; margin: 0 auto; padding: 96px 56px;
  display: flex; flex-direction: column; gap: 16px;
}
.jpes-21-cases-hero__title { font-size: 40px; line-height: 1.15; color: #2B2B2B; max-width: 640px; text-wrap: pretty; }
.jpes-21-cases-hero__intro { font-size: 16px; line-height: 1.7; color: #4A4A46; max-width: 600px; text-wrap: pretty; }

/* card grid on the light ground below the hero */
.jpes-21-cases { background: #FDFCFA; }
.jpes-21-cases__inner { max-width: 1168px; margin: 0 auto; padding: 72px 56px; }
.jpes-21-cases__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }

/* case study card — the modular unit appended by n8n. The whole card is one link;
   inner text colours are set explicitly so they don't inherit the anchor's blue. */
.jpes-21-case-card {
  background: #FDFCFA;
  display: flex;
  flex-direction: column;
  transition: transform .25s ease, box-shadow .25s ease;
}
.jpes-21-case-card:hover { transform: translateY(-4px); box-shadow: 0 12px 28px rgba(43,84,146,.14); }
/* flex:1 so the link fills the grid-stretched card height; without it the card
   stretches but its single child does not, and the footer's margin-top:auto
   has no slack to consume — the even-bottom effect silently fails. */
.jpes-21-case-card__link { display: flex; flex-direction: column; flex: 1; color: inherit; }
.jpes-21-case-card__image {
  position: relative;
  height: 200px;
  background-color: #E6E3DC;
  background-size: cover;
  background-position: center;
}
.jpes-21-case-card__body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 22px;
  flex: 1;                       /* stretch so the footer can pin to the bottom */
}
.jpes-21-case-card__title {
  font-family: Georgia, "Times New Roman", serif;  /* h3 isn't seri-fied by base */
  font-weight: 500;
  font-size: 20px;
  line-height: 1.25;
  color: #2B2B2B;
  text-wrap: pretty;
}
.jpes-21-case-card__summary { font-size: 14px; line-height: 1.6; color: #6B6B66; text-wrap: pretty; }
.jpes-21-case-card__foot {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-top: auto;              /* even-bottom cards despite variable summary length */
  padding-top: 14px;
  border-top: 1px solid #E8E5DF;
}
.jpes-21-case-card__cta { font-size: 13.5px; font-weight: 500; color: #2B5492; }
.jpes-21-case-card:hover .jpes-21-case-card__cta { color: #3866AC; }

/* ---- responsive: mirror the People grid's breakpoints ---- */
@media (max-width: 960px) {
  .jpes-21-cases-hero__inner { padding: 64px 24px; }
  .jpes-21-cases-hero__title { font-size: 32px; }
  .jpes-21-cases__inner { padding-left: 24px; padding-right: 24px; }
  .jpes-21-cases__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
  .jpes-21-cases__grid { grid-template-columns: 1fr; }
}


/* ####################  CASE STUDY TEMPLATE  #################### */

/* Case study template — per-study overrides only (usually none).
   All case-study page styling lives in ../../base.css (.jpes-21-cs-*).
   Any rule added here must be self-scoped: core.css is injected site-wide. */


/* ####################  CASE STUDY: JPES-1  #################### */

/* JPES-1 case study — per-study overrides only (usually none).
   All case-study page styling lives in ../../base.css (.jpes-21-cs-*).
   Any rule added here must be self-scoped: core.css is injected site-wide. */


/* ####################  CASE STUDY: JPES-3  #################### */

/* JPES-3 case study — per-study overrides only (usually none).
   All case-study page styling lives in ../../base.css (.jpes-21-cs-*).
   Any rule added here must be self-scoped: core.css is injected site-wide. */


/* ####################  SERVICES  #################### */

/* ============ SERVICES ============
   Shared by the two service-hub folders and every service-<name> instance, which
   link base.css only. Namespace is .jpes-21-svc- prefixed.
   Reuses eyebrow / tick / figcap / btn / sec / grid.
   Shared primitives (.jpes-21 base, -eyebrow, -btn, -tick, -figcap, -sec) live in
   ../../base.css and must not be repeated here.

   NOTE: never write a glob like service-hub-<star><slash> in a comment here. CSS
   comments do not nest, so that sequence closes the comment early and the parser
   then eats the next rule whole. It silently deleted the hero's background once.

   Run `python3 build.py` from pages/ after editing to refresh core.css. */

/* ---- hero (BLUE) ------------------------------------------------------------
   The third value in the site's system, and the reason it exists: the front page
   opens DARK, case studies open LIGHT, services open BLUE. A visitor arriving from
   the nav should know they have crossed into a new section before reading a word.

   Gradient rather than flat #2B5492 because a 100vw flood of one hex reads as a
   web banner; the 115deg sweep from #1E3E6E to #31609F gives the band depth and
   keeps the figure card's edge legible where it overlaps the lighter end. */
.jpes-21-svc-hero {
  position: relative;
  background: linear-gradient(115deg, #1E3E6E 0%, #2B5492 62%, #31609F 100%);
  --jpes21-grid: rgba(255,255,255,.055);
}
/* Same 40px module as every other band. The grid is re-pointed via --jpes21-grid
   rather than re-declared, so the blueprint reference is one variable, not a copy. */
.jpes-21-svc-hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
.jpes-21-svc-hero__inner {
  position: relative;                        /* lift content above the grid */
  max-width: 1168px; margin: 0 auto; padding: 88px 56px;
  display: grid; grid-template-columns: 1.1fr .9fr; gap: 56px; align-items: center;
}
.jpes-21-svc-hero__copy { display: flex; flex-direction: column; gap: 20px; }
.jpes-21-svc-hero__title { font-size: 44px; line-height: 1.15; color: #fff; text-wrap: pretty; }
.jpes-21-svc-hero__title em { font-style: italic; }
.jpes-21-svc-hero__sub { font-size: 16px; line-height: 1.7; color: rgba(255,255,255,.74); max-width: 520px; text-wrap: pretty; }
.jpes-21-svc-hero__actions { display: flex; flex-wrap: wrap; gap: 12px; }

/* figure card — same construction as the front hero's .jpes-21-figcard, but the
   ticks and caption must invert: base.css draws both in #2B5492, which is invisible
   against this band. Scoped overrides rather than a new modifier class, so the
   markup stays identical to every other figure on the site. */
.jpes-21-svc-hero__fig { display: flex; flex-direction: column; }
.jpes-21-svc-hero__image {
  position: relative; height: 320px;
  background-color: rgba(255,255,255,.08);
  background-size: cover; background-position: center;
}
.jpes-21-svc-hero .jpes-21-tick { border-color: rgba(255,255,255,.85); }
.jpes-21-svc-hero .jpes-21-figcap { padding-top: 10px; color: rgba(255,255,255,.62); }
.jpes-21-svc-hero .jpes-21-figcap::before,
.jpes-21-svc-hero .jpes-21-figcap::after { background: rgba(255,255,255,.28); }

/* ---- SEC 01 outcomes --------------------------------------------------------
   Two columns, not three: these paragraphs run 20-40 words and a 3-across grid
   sets them at a measure too narrow to read comfortably. Two also divides cleanly
   into the 4, 5 and 6 item counts the twelve pages actually carry — a 3-across
   grid would leave a visible dead cell on every odd-numbered page. */
.jpes-21-svc-outcomes { background: #FDFCFA; }
.jpes-21-svc-outcomes__inner { max-width: 1168px; margin: 0 auto; padding: 76px 56px; }
/* Second outcomes band on one page, sat on the tint so the page keeps alternating.
   Needed because not every service has a delivery process: Control Systems, System
   Integration and Process Optimisation state CAPABILITIES instead, and FMEA carries a
   "why partner with us" band after its advantages. Rendering those as numbered process
   stages would assert a sequence the business has never claimed, so they reuse this
   band and only the ground colour changes. */
.jpes-21-svc-outcomes--tint { background: #F4F2ED; border-top: 1px solid #E8E5DF; }
.jpes-21-svc-outcomes__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 36px 48px; }
/* the rule above each item is the "drawing sheet" tell — it reads as a spec entry
   rather than a marketing card, and needs no box, shadow or icon to do it */
.jpes-21-svc-outcome { padding-top: 16px; border-top: 1px solid #E2DED6; }
.jpes-21-svc-outcome__n { font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: #2B5492; }
.jpes-21-svc-outcome__title {
  font-family: Georgia, "Times New Roman", serif;   /* h3 isn't seri-fied by base */
  font-weight: 500; font-size: 20px; line-height: 1.25; color: #2B2B2B;
  margin: 8px 0 8px; text-wrap: pretty;
}
.jpes-21-svc-outcome__text { font-size: 15px; line-height: 1.7; color: #4A4A46; text-wrap: pretty; }

/* ---- SEC 02 process ---------------------------------------------------------
   On the tinted band so the page alternates paper / tint / paper down its length —
   twelve pages of unbroken #FDFCFA would read as one undifferentiated column.

   Vertical rather than a horizontal stepper: step counts vary 4-6 across the
   twelve pages and the copy varies more, so a fixed-column track would either
   crop the long ones or strand whitespace on the short ones. Vertical absorbs both. */
.jpes-21-svc-process { background: #F4F2ED; border-top: 1px solid #E8E5DF; }
/* Paper variant, for pages whose band order would otherwise put two tinted bands
   together (FMEA: outcomes / why / process; the hubs: outcomes / children / process).
   The rule down the page is alternation, not "process is always tinted" — grounds are
   assigned per page from the sequence, and every page must read paper / tint / paper. */
.jpes-21-svc-process--paper { background: #FDFCFA; }
.jpes-21-svc-process__inner { max-width: 1168px; margin: 0 auto; padding: 76px 56px; }
.jpes-21-svc-process__steps {
  list-style: none; counter-reset: svc-step; margin: 0; padding: 0;
  position: relative; max-width: 780px;
}
/* the spine, inset to the centre of the 28px markers. Stops short at both ends so
   it reads as a connector between stages, not a rule running off into the padding. */
.jpes-21-svc-process__steps::before {
  content: ""; position: absolute; left: 13.5px; top: 10px; bottom: 10px;
  width: 1px; background: #D8D4CC;
}
.jpes-21-svc-step { counter-increment: svc-step; position: relative; padding: 0 0 30px 56px; }
.jpes-21-svc-step:last-child { padding-bottom: 0; }
/* decimal-leading-zero to match .jpes-21-cs-reason — the house numeral grammar.
   Opaque background so the marker punches a hole in the spine behind it. */
.jpes-21-svc-step::before {
  content: counter(svc-step, decimal-leading-zero);
  position: absolute; left: 0; top: 0;
  width: 28px; height: 28px; border-radius: 50%;
  background: #F4F2ED; border: 1px solid #2B5492; color: #2B5492;
  font: 500 11px ui-monospace, Menlo, monospace;
  display: flex; align-items: center; justify-content: center;
}
.jpes-21-svc-step__title {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500; font-size: 19px; line-height: 1.3; color: #2B2B2B;
  margin: 3px 0 7px; text-wrap: pretty;
}
.jpes-21-svc-step__text { font-size: 15px; line-height: 1.7; color: #4A4A46; text-wrap: pretty; }

/* ---- SEC 03 technology partners (optional band) -----------------------------
   Only the pages that genuinely name partners carry this (Machine Vision: Cognex,
   Keyence; Robotics; Control Systems). Pages without one simply omit the fragment —
   there is no empty state to style. */
.jpes-21-svc-tech { background: #FDFCFA; border-top: 1px solid #E8E5DF; }
.jpes-21-svc-tech__inner { max-width: 1168px; margin: 0 auto; padding: 64px 56px; }
.jpes-21-svc-tech__text { font-size: 15px; line-height: 1.7; color: #4A4A46; max-width: 720px; text-wrap: pretty; }
.jpes-21-svc-tech__list { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 26px; padding: 0; list-style: none; }
.jpes-21-svc-tech__item {
  font: 500 12px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: #2B5492;
  border: 1px solid #C9D3E4; padding: 9px 16px;
}

/* ---- child services grid (hub pages only) -----------------------------------
   Text cards, deliberately without images. Six more placeholder frames on the two
   hubs would be six more assets to source before the section stops looking unfinished;
   the blue top rule and mono index carry the theme without needing photography. */
.jpes-21-svc-children { background: #FDFCFA; border-top: 1px solid #E8E5DF; }
.jpes-21-svc-children--tint { background: #F4F2ED; }
.jpes-21-svc-children__inner { max-width: 1168px; margin: 0 auto; padding: 76px 56px; }
.jpes-21-svc-children__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.jpes-21-svc-child {
  display: flex; flex-direction: column; gap: 10px;
  padding: 24px 22px 22px;
  background: #FDFCFA; border: 1px solid #E8E5DF; border-top: 2px solid #2B5492;
  color: inherit;                                  /* don't inherit the anchor blue */
  transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.jpes-21-svc-child:hover { transform: translateY(-4px); box-shadow: 0 12px 28px rgba(43,84,146,.14); color: inherit; }
.jpes-21-svc-child__title {
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 500; font-size: 19px; line-height: 1.25; color: #2B2B2B; text-wrap: pretty;
}
.jpes-21-svc-child__text { font-size: 14px; line-height: 1.6; color: #6B6B66; text-wrap: pretty; }
/* margin-top:auto gives even-bottom cards despite variable summary length —
   same mechanism as .jpes-21-case-card__foot */
.jpes-21-svc-child__cta { margin-top: auto; padding-top: 14px; font-size: 13.5px; font-weight: 500; color: #2B5492; }
.jpes-21-svc-child:hover .jpes-21-svc-child__cta { color: #3866AC; }

/* ---- closing CTA ------------------------------------------------------------
   Dark, matching .jpes-21-cs-foot: the section signature is the blue hero, so a
   second blue band at the foot would flatten the page. Dark grounds it instead. */
.jpes-21-svc-cta { background: #23252B; }
.jpes-21-svc-cta__inner {
  max-width: 1168px; margin: 0 auto; padding: 64px 56px;
  display: grid; grid-template-columns: 1fr auto; gap: 40px; align-items: center;
}
.jpes-21-svc-cta__title { font-size: 30px; line-height: 1.2; color: #fff; max-width: 620px; text-wrap: pretty; }
.jpes-21-svc-cta__text { font-size: 15px; line-height: 1.7; color: rgba(255,255,255,.62); max-width: 560px; margin-top: 14px; text-wrap: pretty; }
.jpes-21-svc-cta__docref {
  grid-column: 1 / -1; margin-top: 8px; padding-top: 20px;
  border-top: 1px solid rgba(255,255,255,.1);
  font: 10.5px ui-monospace, Menlo, monospace; letter-spacing: .1em; color: rgba(255,255,255,.32);
}

/* ---- responsive: mirror the case-study page's breakpoints ---- */
@media (max-width: 960px) {
  .jpes-21-svc-hero__inner { grid-template-columns: 1fr; padding: 56px 24px; gap: 36px; }
  .jpes-21-svc-hero__title { font-size: 34px; }
  .jpes-21-svc-outcomes__inner,
  .jpes-21-svc-process__inner,
  .jpes-21-svc-tech__inner,
  .jpes-21-svc-children__inner { padding-left: 24px; padding-right: 24px; }
  .jpes-21-svc-outcomes__grid { grid-template-columns: 1fr; gap: 28px; }
  .jpes-21-svc-children__grid { grid-template-columns: repeat(2, 1fr); }
  .jpes-21-svc-cta__inner { grid-template-columns: 1fr; padding-left: 24px; padding-right: 24px; }
  .jpes-21-svc-cta__title { font-size: 26px; }
}
@media (max-width: 600px) {
  .jpes-21-svc-children__grid { grid-template-columns: 1fr; }
  /* pull the spine and markers in so the copy keeps a usable measure on a phone */
  .jpes-21-svc-step { padding-left: 44px; }
}


/* ####################  ROI CALCULATOR PROMO  #################### */

/* ============ ROI CALCULATOR PROMO ============
   A reusable promotional band that links out to the Automation ROI Calculator. Unlike the
   page folders, this is not a page — it is ONE block pasted onto whichever pages it suits.
   Namespace .jpes-21-roi-*. Reuses eyebrow / tick / btn from base.css.
   Shared primitives (.jpes-21 base, -eyebrow, -btn, -tick) live in ../../base.css and must
   not be repeated here.
   Run `python3 build.py` from pages/ after editing to refresh core.css / core.link.css. */

/* ---- band ----
   A light "instrument sheet": paper ground with the same faint-blue blueprint grid the
   case-study hero uses (ink-on-white). Deliberately distinct from the services blue heroes
   and the dark CTA, so the block reads as its own thing wherever it is dropped and never
   competes with the band above or below it. Hairlines top and bottom delineate it when it
   lands between two same-coloured bands. */
.jpes-21-roi { position: relative; background: #FDFCFA; border-top: 1px solid #E8E5DF; border-bottom: 1px solid #E8E5DF; --jpes21-grid: rgba(43,84,146,.06); }
.jpes-21-roi::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px,
    linear-gradient(90deg, var(--jpes21-grid) 1px, transparent 1px) 0 0 / 40px 40px;
}
.jpes-21-roi__inner {
  position: relative;                          /* lift content above the grid */
  max-width: 1168px; margin: 0 auto; padding: 72px 56px;
  display: grid; grid-template-columns: 1.05fr .95fr; gap: 56px; align-items: center;
}

/* ---- copy ---- */
.jpes-21-roi__copy { display: flex; flex-direction: column; gap: 20px; align-items: flex-start; }
.jpes-21-roi__title {
  font-family: Georgia, "Times New Roman", serif; font-weight: 500;
  font-size: 30px; line-height: 1.2; color: #2B2B2B; text-wrap: pretty;
}
.jpes-21-roi__text { font-size: 16px; line-height: 1.7; color: #4A4A46; max-width: 460px; text-wrap: pretty; }
/* button reuses .jpes-21-btn.jpes-21-btn--primary — no new button CSS. The small top
   margin lets the CTA breathe from the paragraph without a wrapper element. */
.jpes-21-roi__cta { margin-top: 4px; }

/* ---- readout (the instrument card) ----
   A white panel with three stacked rows: mono label left, Georgia value right — the same
   metric grammar as the case studies, arranged as a small before/after/so-what readout.
   Corner ticks mark it as a figure, like the case-card and hero images. */
.jpes-21-roi__readout { position: relative; background: #fff; border: 1px solid #E8E5DF; padding: 6px 26px; }
.jpes-21-roi__row {
  display: flex; justify-content: space-between; align-items: baseline; gap: 20px;
  padding: 18px 0; border-bottom: 1px solid #EEEBE4;
}
.jpes-21-roi__row-label { font: 500 11px ui-monospace, Menlo, monospace; letter-spacing: .12em; color: #9A9A94; }
.jpes-21-roi__row-value { font-family: Georgia, "Times New Roman", serif; font-weight: 500; font-size: 22px; color: #2B2B2B; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* payback is the punchline — the "so what". Blue value (the house conclusion colour,
   same as .jpes-21-metric__value), a touch larger, and no bottom rule since it closes
   the readout. */
.jpes-21-roi__row--result { border-bottom: 0; }
.jpes-21-roi__row--result .jpes-21-roi__row-value { font-size: 26px; color: #2B5492; }
/* honesty: these figures are a teaser, not a claim. Say so. */
.jpes-21-roi__note { margin-top: 14px; font: 10.5px ui-monospace, Menlo, monospace; letter-spacing: .04em; color: #9A9A94; text-align: right; }

/* ---- variants ----------------------------------------------------------------
   Two orthogonal modifiers give the four placements. Ground: light (default) or --dark.
   Grid: on (default) or removed with --flat. Combine freely:
     (base)                    light + grid
     --flat                    light, no grid
     --dark --flat             dark, no grid
     --dark                    dark + grid
   The dark variants also swap the eyebrow to .jpes-21-eyebrow--light in the markup, the
   same way the front hero does — a #2B5492 eyebrow is unreadable on charcoal. */

/* drop the blueprint grid */
.jpes-21-roi--flat::before { display: none; }

/* dark ground — the same charcoal as the closing CTA and case-study foot, so a dark ROI
   block reads as part of the system rather than a one-off. Grid alpha follows base.css's
   dark-band convention (white on flat dark reads far louder than the same alpha on paper). */
.jpes-21-roi--dark { background: #23252B; border-top-color: rgba(255,255,255,.10); border-bottom-color: rgba(255,255,255,.10); --jpes21-grid: rgba(255,255,255,.03); }
.jpes-21-roi--dark .jpes-21-roi__title { color: #fff; }
.jpes-21-roi--dark .jpes-21-roi__text { color: rgba(255,255,255,.72); }
/* dark readout card */
.jpes-21-roi--dark .jpes-21-roi__readout { background: rgba(255,255,255,.04); border-color: rgba(255,255,255,.12); }
.jpes-21-roi--dark .jpes-21-roi__row { border-bottom-color: rgba(255,255,255,.08); }
.jpes-21-roi--dark .jpes-21-roi__row-label { color: rgba(255,255,255,.5); }
.jpes-21-roi--dark .jpes-21-roi__row-value { color: #fff; }
/* payback stays the conclusion, lifted to a readable blue on dark (as the process steps
   do on the dark services band — #2B5492 is too dark against charcoal) */
.jpes-21-roi--dark .jpes-21-roi__row--result .jpes-21-roi__row-value { color: #6D93D0; }
.jpes-21-roi--dark .jpes-21-roi__note { color: rgba(255,255,255,.4); }
/* base.css draws the ticks in #2B5492; invert them on the dark card */
.jpes-21-roi--dark .jpes-21-tick { border-color: rgba(255,255,255,.85); }

/* ---- responsive: mirror the services / case-study breakpoints ---- */
@media (max-width: 960px) {
  .jpes-21-roi__inner { grid-template-columns: 1fr; padding: 56px 24px; gap: 36px; }
  .jpes-21-roi__title { font-size: 26px; }
}
@media (max-width: 600px) {
  .jpes-21-roi__readout { padding: 4px 20px; }
  .jpes-21-roi__row-value { font-size: 20px; }
  .jpes-21-roi__row--result .jpes-21-roi__row-value { font-size: 23px; }
}


/* ####################  MAINTENANCE  #################### */

/* ============ MAINTENANCE PAGE ============
   Full-screen holding block for Odoo: paste 01-maintenance.html as an embedded HTML block;
   this CSS ships site-wide in core.css. Reuses .jpes-21 base, .jpes-21-eyebrow--light and
   .jpes-21-tick from base.css — only the maintenance-specific layout is defined here.
   Run `python3 build.py` from pages/ after editing to refresh core.css. */

.jpes-21-maint {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background: #1B2029;
  overflow: hidden;
}
/* faint blueprint grid, fading toward the edges */
.jpes-21-maint::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px) 0 0 / 44px 44px,
    linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px) 0 0 / 44px 44px;
  -webkit-mask: radial-gradient(circle at 50% 45%, #000 0%, transparent 78%);
          mask: radial-gradient(circle at 50% 45%, #000 0%, transparent 78%);
}
.jpes-21-maint__sheet { position: relative; width: 100%; max-width: 560px; padding: 56px 48px; }

/* Spacing selectors are scoped under .jpes-21-maint (specificity 0,2,0) so they outrank
   base's `.jpes-21 p` / `.jpes-21 h1` margin resets (both 0,1,1). */
.jpes-21-maint .jpes-21-maint__wordmark { font-family: Georgia, "Times New Roman", serif; font-size: 18px; color: #fff; margin: 0 0 40px; }
.jpes-21-maint .jpes-21-eyebrow { display: block; margin: 0 0 18px; }
.jpes-21-maint .jpes-21-maint__title { font-family: Georgia, "Times New Roman", serif; font-weight: 500; font-size: 40px; line-height: 1.15; color: #fff; margin: 0 0 18px; }
.jpes-21-maint .jpes-21-maint__msg { font-size: 16px; line-height: 1.7; color: rgba(255, 255, 255, 0.72); max-width: 460px; margin: 0 0 32px; }
.jpes-21-maint .jpes-21-maint__contact { font: 12.5px ui-monospace, Menlo, monospace; letter-spacing: 0.04em; line-height: 1.9; color: rgba(255, 255, 255, 0.55); border-top: 1px solid rgba(255, 255, 255, 0.12); padding-top: 20px; margin: 0; }
.jpes-21-maint__contact a { color: #9DB4DA; text-decoration: none; }
.jpes-21-maint__contact a:hover { color: #fff; }

@media (max-width: 520px) {
  .jpes-21-maint__sheet { padding: 44px 28px; }
  .jpes-21-maint .jpes-21-maint__title { font-size: 31px; }
}
