/* ==========================================================================
   JKM Portal — Responsive stylesheet (all device sizes)
   Target: https://jkm.aidan.work  (Joomla + Helix Ultimate + Novaly
           + SP Page Builder + DJ-MegaMenu + Smart Slider 3)

   HOW THIS IS LOADED (already wired — see templates/novaly/index.php):
   a literal <link> is emitted immediately before </head>. It must be done
   that way, NOT via $theme->add_css(): SP Page Builder queues
   sppagebuilder.css AFTER the theme's own CSS, so a stylesheet added the
   normal way would lose on source order.

   Verified load order on portal-jkm-dev.test — this file is index 43 of 46
   stylesheets, after sppagebuilder.css (17) and custom.css (26).
   smartslider.min.css still loads after it (44), so the Smart Slider rules
   in section 6 carry a `body` prefix to win on specificity instead.

   To deploy to the live site, copy this file plus the index.php <link> block.

   ---------------------------------------------------------------------------
   BREAKPOINT LANDSCAPE OF THIS SITE (six systems, three different scales)
   ---------------------------------------------------------------------------
   Bootstrap 5 (Joomla/Helix)  576 / 768 / 992 / 1200 / 1400
   SPPB grid  — NAMES ARE SHIFTED UP ONE STEP vs Bootstrap:
        .sppb-col-sm-*  applies at >= 768px
        .sppb-col-md-*  applies at >= 992px
        .sppb-col-lg-*  applies at >= 1200px
        below 768px every column is full width
   SPPB per-addon CSS   max-width 575.98 / 767.98 / 991.98 / 1199.98
   DJ-MegaMenu          desktop menu >= 980px, offcanvas <= 979px
   Smart Slider 3       desktop >= 1200; tablet 701-1199 portrait /
                        901-1199 landscape; mobile <= 700 portrait /
                        <= 900 landscape
   Helix JS             tablet 991, mobile 480

   This file standardises on the SPPB/Bootstrap scale (575.98 / 767.98 /
   991.98 / 1199.98 / 1400) so it never introduces a seventh scale.
   ========================================================================== */


/* ==========================================================================
   1. TOKENS
   Fluid spacing/type scale. clamp() = no breakpoint needed for these.
   ========================================================================== */
:root {
	--jkm-gutter:        clamp(1rem, 4vw, 2rem);     /* page side padding   */
	--jkm-section-y:     clamp(2rem, 6vw, 5rem);     /* section top/bottom  */
	--jkm-stack-gap:     clamp(1rem, 3vw, 2rem);     /* gap between cards   */
	--jkm-tap:           44px;                       /* WCAG 2.5.5 target   */
	--jkm-radius:        8px;
}


/* ==========================================================================
   2. HORIZONTAL OVERFLOW GUARD
   The single most common mobile defect on SPPB/Smart Slider pages: one wide
   child (slider layer, negative margin row, wide image) drags a horizontal
   scrollbar across the whole document.

   NOTE: `overflow-x: clip` — NOT `hidden`. `hidden` turns the element into a
   scroll container, which silently kills `position: sticky` on the header and
   breaks the sticky accessibility widget. `clip` does not.
   ========================================================================== */
html,
body {
	overflow-x: clip;
	max-width: 100%;
}

body .body-wrapper,
body .body-innerwrapper,
body #sp-main,
body #sp-page-builder,
body .mod-sppagebuilder {
	max-width: 100%;
	overflow-x: clip;
}

/* Media never exceeds its column. `height: auto` stops the squash that a bare
   width/height attribute pair causes once the width is constrained. */
#sp-main img,
#sp-main video,
#sp-main svg,
#sp-main iframe,
#sp-main embed,
#sp-main object {
	max-width: 100%;
	height: auto;
}

/* Long unbroken strings (URLs, emails, reference numbers, IC numbers) are a
   frequent overflow source on gov content pages. */
#sp-component p,
#sp-component li,
#sp-component td,
#sp-component dd,
#sp-component h1,
#sp-component h2,
#sp-component h3,
#sp-component h4,
.sppb-addon-content p,
.sppb-addon-content li {
	overflow-wrap: break-word;
	word-wrap: break-word;
}


/* ==========================================================================
   3. SPPB GRID CORRECTIONS
   These fix three layouts that are actually wrong today — verified against
   the markup of /ms/utama, not guessed.

   SPPB columns are flex-based: `flex: 0 0 X%; max-width: X%`.
   Our selectors are (0,3,0) vs SPPB's (0,1,0), so they win on specificity
   regardless of source order.
   ========================================================================== */

/* -- 3a. THE SQUEEZED SERVICES ROW, 768–1199.98px ------------------------
   MEASURED, not inferred. Rendered in headless Chrome at 768x1024, the
   "PERKHIDMATAN KAMI" cards occupy only the left half of the page with
   ~400px of dead space beside them.

   Cause: SP Page Builder writes a PER-COLUMN ID rule for this column —
       #column-wrap-id-1658365734894
         base            -> flex-basis: 100%
         <=1199.98px     -> flex-basis:  50%   <-- squeeze starts here
         <= 991.98px     -> flex-basis:  50%
         <= 767.98px     -> flex-basis: 100%
   i.e. full width on desktop, full width on phones, half width for the
   432px band in between. That is a width set in the builder, NOT the
   grid-class behaviour an earlier draft of this file assumed — and an ID
   selector (1,0,0) outranks any class selector, so the previous
   `.sppb-row > .sppb-col-md-12.sppb-col-sm-6` rule here never applied.

   PROPER FIX: open the page in SP Page Builder and set that column's
   Tablet/Small-Desktop width to 100%. This rule is the stopgap; it is
   pinned to the one column id that is actually wrong (a site-wide
   `!important` on the class would risk overriding intentional layouts).
   Only one column across the 10 pages audited has this pattern. */
@media (min-width: 768px) and (max-width: 1199.98px) {
	#column-wrap-id-1658365734894 {
		flex-basis: 100% !important;
		max-width: 100% !important;
	}
}

/* -- 3a-2. TABLET 768–991.98px -------------------------------------------- */
@media (min-width: 768px) and (max-width: 991.98px) {

	/* `sppb-col-md-2 sppb-col-sm-6` — the site's dominant pattern (30x).
	   Six-across icon/quicklink strip on desktop drops straight to 2-across
	   on tablet: very tall, lots of whitespace. 3-across reads better.
	   VERIFIED APPLYING: measured 220px (33.3%) at 768px viewport. */
	.sppb-row > .sppb-col-md-2.sppb-col-sm-6 {
		-ms-flex: 0 0 33.333333%;
		flex: 0 0 33.333333%;
		max-width: 33.333333%;
		width: 33.333333%;
	}
}

/* -- 3b. PHONE <= 767.98px ------------------------------------------------ */
@media (max-width: 767.98px) {

	/* Below 768px SPPB stacks EVERYTHING to 100%. For narrow icon tiles
	   (the md-2 quicklinks) that produces an absurdly long single column.
	   Keep them 2-up. */
	.sppb-row > [class*="sppb-col-md-2"],
	.sppb-row > [class*="sppb-col-md-3"] {
		-ms-flex: 0 0 50%;
		flex: 0 0 50%;
		max-width: 50%;
		width: 50%;
	}

	/* Give the now-stacked columns consistent vertical rhythm. */
	.sppb-row > [class*="sppb-col-"] {
		margin-bottom: var(--jkm-stack-gap);
	}
	.sppb-row > [class*="sppb-col-"]:last-child {
		margin-bottom: 0;
	}
}

/* -- 3c. VERY SMALL PHONES <= 359px --------------------------------------- */
@media (max-width: 359px) {
	/* 2-up tiles get too cramped below ~360px (Galaxy Fold cover screen). */
	.sppb-row > [class*="sppb-col-md-2"],
	.sppb-row > [class*="sppb-col-md-3"] {
		-ms-flex: 0 0 100%;
		flex: 0 0 100%;
		max-width: 100%;
		width: 100%;
	}
}

/* -- 3d. Section + container padding at every size ------------------------ */
#sp-page-builder .sppb-section,
.mod-sppagebuilder .sppb-section {
	padding-left: var(--jkm-gutter);
	padding-right: var(--jkm-gutter);
}

/* -- 3e. DESKTOP SIDE INSETS LEAKING ONTO PHONES --------------------------
   MEASURED on /bm/utama at 425px: three sections on the homepage carry a
   FIXED 120px horizontal inset, written by the page builder as base CSS
   with NO media query, so it applies at every width:

       #section-id-1658365734870   margin-right/left: 120px
       #section-id-1658365734909   margin-right/left: 120px
       #section-id-1658365734906   padding-right/left: 120px

   240px of inset on a 425px screen leaves the content 185px — and because
   the value is fixed rather than proportional it gets worse as the screen
   narrows: at 390px the content is down to 150px, 38% of the viewport.
   That is the wide white margin either side of the service cards.

   The builder writes these as `.sp-page-builder .page-content #section-id-N`
   (specificity 1,2,0), which is why rule 3d above — a plain class rule —
   never won against them. Hence the !important here.

   Below 1200px every section gets the same modest gutter instead, and nested
   sections get none so the inset is not applied twice.

   Cut-off is 1199.98px. The 240px inset costs a share of the screen that
   only becomes reasonable on a real desktop:
        425px -> 56%      768px -> 31%
        992px -> 24%     1200px -> 20%      1440px -> 17%
   Holding the tight gutter to 1200 also removes the visible step that a
   992 cut-off produced when resizing. From 1200 up the inset is left
   exactly as designed.

   PROPER FIX is in the builder: set those three sections' Mobile, Tablet
   and Small-Desktop padding / margin to 0. This is the stopgap. */
@media (max-width: 1199.98px) {
	body .sp-page-builder .page-content .sppb-section,
	body .mod-sppagebuilder .sppb-section {
		margin-left: 0 !important;
		margin-right: 0 !important;
		padding-left: var(--jkm-gutter) !important;
		padding-right: var(--jkm-gutter) !important;
	}

	/* A section nested inside another must not add a second gutter. */
	body .sp-page-builder .page-content .sppb-section .sppb-section,
	body .mod-sppagebuilder .sppb-section .sppb-section {
		padding-left: 0 !important;
		padding-right: 0 !important;
	}
}

#sp-page-builder .sppb-row-container,
#sp-page-builder .sppb-container-inner {
	width: 100%;
}

/* Nested rows must not add a second gutter on top of the section's. */
#sp-page-builder .sppb-section .sppb-section {
	padding-left: 0;
	padding-right: 0;
}


/* ==========================================================================
   4. HEADER (inner pages — the /ms/ landing page has no #sp-header)
   The Helix column classes are:
       #sp-logo   col-7  col-sm-5  col-md-4  col-lg-4
       #sp-menu2  col-3  col-sm-6  col-md-6  col-lg-8
   Those sum to 10 / 11 / 10 / 12 — so below 1200px the burger button stops
   short of the right edge instead of aligning with it. Replace the fixed
   column widths with a flex row below 992px.
   ========================================================================== */
@media (max-width: 991.98px) {

	#sp-header .container-inner > .row,
	#sp-header .row {
		-ms-flex-wrap: nowrap;
		flex-wrap: nowrap;
		-ms-flex-align: center;
		align-items: center;
	}

	#sp-header #sp-logo {
		-ms-flex: 1 1 auto;
		flex: 1 1 auto;
		width: auto;
		max-width: none;
		min-width: 0;      /* lets a long logo shrink instead of pushing out */
	}

	#sp-header #sp-menu2 {
		-ms-flex: 0 0 auto;
		flex: 0 0 auto;
		width: auto;
		max-width: none;
		margin-left: auto; /* pins the burger to the right edge */
	}

	#sp-header #sp-menu2 .sp-column {
		display: -ms-flexbox;
		display: flex;
		-ms-flex-pack: end;
		justify-content: flex-end;
		-ms-flex-align: center;
		align-items: center;
	}
}

/* -- 4a. LOGO — one per language ------------------------------------------
   Helix's `logo_image` is a TEMPLATE STYLE param, not a per-language one,
   and this site runs Falang with a single menu tree, so there are no en-GB
   menu items to attach a second template style to. Setting logo_image alone
   therefore put the Malay logo on English pages.

   Joomla filters MODULES by language natively, so the header logo is two
   language-assigned mod_custom modules in the `logo` position:
       134 Logo [MS] -> Logo-JKM-MS.png (654x72)
       216 Logo [EN] -> Logo-JKM-EN.png (664x72, "THE SOCIAL WELFARE DEPARTMENT")
   and Helix's own `.logo` element is hidden here — the same thing the
   LANDING PAGE template style already does via its custom code.

   The template's `logo_image` param is deliberately LEFT SET even though it
   is never shown: it makes the failure mode, if this stylesheet ever fails
   to load, the real Malay logo rather than the HELIX ULTIMATE placeholder. */
#sp-header .logo {
	display: none;
}

/* The module's own image carries the responsive height. Both logos are
   ~9:1, so 30px on a phone is ~270px wide and 52px on desktop ~475px. */
#sp-header #sp-logo .mod-custom img,
#sp-header #sp-logo .sp-module img {
	height: clamp(30px, 4.5vw, 52px);
	width: auto;
	max-width: 100%;
}

/* Kept for the landing page and any layout still rendering Helix's logo. */
#sp-header .logo-image,
#sp-header .logo-image-phone {
	height: clamp(30px, 4.5vw, 52px) !important;
	width: auto;
	max-width: 100%;
}

/* Keep the header from eating the viewport on landscape phones. */
@media (max-width: 991.98px) and (orientation: landscape) and (max-height: 500px) {
	#sp-header #sp-logo .mod-custom img,
	#sp-header #sp-logo .sp-module img,
	#sp-header .logo-image,
	#sp-header .logo-image-phone {
		height: 28px !important;
	}
	#sp-header {
		padding-top: 4px;
		padding-bottom: 4px;
	}
}


/* ==========================================================================
   5. DJ-MEGAMENU / OFFCANVAS
   The menu flips to offcanvas at 979px but the SPPB grid flips at 992px,
   so 980–991px renders the DESKTOP menu over a TABLET grid — the widest
   menu in the narrowest desktop space, which is where it overflows.

   The correct fix is a module parameter, not CSS:
       Modules → DJ-MegaMenu → Advanced → Mobile breakpoint: 979 → 991
   Do that, and 980–991px stops being a dead zone.

   Until then, these rules stop the desktop menu overflowing in that band.
   ========================================================================== */
@media (min-width: 980px) and (max-width: 1199.98px) {
	.dj-megamenu > li > a,
	.dj-megamenu li a.dj-up_a {
		padding-left: 10px;
		padding-right: 10px;
		font-size: 0.875rem;
		letter-spacing: 0;
	}
	/* Dropdowns carry inline width:200px/400px; keep them inside the viewport. */
	.dj-subwrap,
	.dj-subwrap-in {
		max-width: calc(100vw - 2rem) !important;
	}
}

/* Offcanvas panel: a fixed panel wider than a small phone is unusable. */
@media (max-width: 991.98px) {
	.dj-offcanvas,
	#sp-header .offcanvas-menu {
		width: min(320px, 88vw) !important;
		max-width: 88vw !important;
	}
	.dj-offcanvas-content {
		overflow-y: auto;
		-webkit-overflow-scrolling: touch;
		overscroll-behavior: contain; /* stops scroll chaining to the page */
	}
}


/* ==========================================================================
   6. SMART SLIDER 3 (the /ms/ landing page is essentially one slider)
   Smart Slider scales itself via its own media queries — do NOT fight its
   layer positioning here, that belongs in the slider's Tablet/Mobile tabs.
   These rules only stop it leaking outside the viewport and make its own
   controls usable on touch.
   ========================================================================== */
/* `body` prefix is deliberate: smartslider.min.css is queued AFTER this file
   in the head (verified: index 44 vs 43 of 46), so equal-specificity rules
   would lose on source order. */
body .n2-section-smartslider,
body .n2-ss-align,
body .n2-padding {
	max-width: 100%;
}

body .n2-section-smartslider {
	overflow-x: clip;
}

/* Slider arrows/bullets are often below the 44px touch minimum. */
@media (pointer: coarse) {
	.n2-ss-slider .nextend-arrow,
	.n2-ss-slider .n2-bullet,
	.n2-ss-slider .nextend-bullet-bar .n2-bullet {
		min-width: var(--jkm-tap);
		min-height: var(--jkm-tap);
	}
}

/* Landscape phones: an 800px-tall slider fills more than the screen. */
@media (orientation: landscape) and (max-height: 500px) {
	.n2-section-smartslider .n2-ss-slider {
		max-height: 100svh;
	}
}


/* ==========================================================================
   7. TYPOGRAPHY
   Scoped deliberately. SP Page Builder writes per-addon font sizes as
   `#sppb-addon-<uuid> .sppb-addon-title { font-size: 36px }` in an inline
   <style> — specificity (1,1,0) plus late source order. Nothing short of an
   ID-matched rule or !important overrides it, and doing that globally would
   throw away the sizes set in the builder.

   => Builder-set sizes are best changed in the builder's own Tablet / Mobile
      tabs. The rules below cover everything the builder did NOT set:
      Joomla article content, page titles, and default addon text.
   ========================================================================== */
#sp-component .com-content-article h1,
#sp-component .article-body h1,
#sp-title h1 {
	font-size: clamp(1.5rem, 1.1rem + 2vw, 2.5rem);
	line-height: 1.25;
}

/* -- 7a. PAGE TITLE BANNER -------------------------------------------------
   `templates/novaly/css/custom.css:578` sets

       .sp-page-title .sp-page-title-heading { font-size: 54px; ... }

   with no media queries, so the page title was a flat 54px at EVERY width —
   measured 54px from 360px right through to 1920px. At 360px that wrapped
   "Kalendar Aktiviti" onto two lines and pushed the banner to 235px tall.

   Note the element is an <h2 class="sp-page-title-heading">, not an h1, and
   it is not matched by `#sp-title .sp-page-title` (that is the wrapper) —
   which is why an earlier draft of the rule above never applied to it.

   `body` prefix gives (0,2,1) against custom.css's (0,2,0), so this wins on
   specificity alone and does not depend on which order the two stylesheets
   happen to load in — which currently differs between dev and production.

   Resulting sizes: 360px -> ~25px, 768px -> ~34px, 1200px and up -> 40px
   (the 2.5rem cap). Adjust the 2.5rem to taste; anything below 3.375rem is
   smaller than the original 54px. */
body .sp-page-title .sp-page-title-heading {
	font-size: clamp(1.5rem, 1.1rem + 2.2vw, 2.5rem);
	line-height: 1.2;
}

#sp-component .com-content-article h2,
#sp-component .article-body h2 {
	font-size: clamp(1.3rem, 1rem + 1.5vw, 2rem);
	line-height: 1.3;
}

#sp-component .com-content-article h3,
#sp-component .article-body h3 {
	font-size: clamp(1.15rem, 0.95rem + 1vw, 1.5rem);
	line-height: 1.35;
}

#sp-component .com-content-article,
#sp-component .article-body,
.sppb-addon-text-block .sppb-addon-content {
	font-size: clamp(0.95rem, 0.9rem + 0.25vw, 1.0625rem);
	line-height: 1.7;
}

/* Comfortable measure on wide screens — 100+ character lines are hard to read. */
@media (min-width: 1200px) {
	#sp-component .com-content-article > p,
	#sp-component .article-body > p,
	#sp-component .com-content-article > ul,
	#sp-component .com-content-article > ol {
		max-width: 72ch;
	}
}

/* ---- OPTIONAL: force-shrink builder headings on phones --------------------
   Uncomment ONLY if headings still overflow on small phones after checking
   the builder's Mobile tab. This overrides every per-addon size at <= 575.98px.

@media (max-width: 575.98px) {
	#sp-page-builder .sppb-addon-title,
	#sp-page-builder .sppb-addon-header .sppb-title-heading,
	#sp-page-builder .section-title .sppb-addon-title {
		font-size: clamp(1.15rem, 4.5vw, 1.5rem) !important;
		line-height: 1.3 !important;
	}
}
--------------------------------------------------------------------------- */


/* ==========================================================================
   8. TABLES
   Audited across 10 representative pages: 7 tables total, max 3 columns,
   and every one of them is an SP Page Builder table addon
   (`.sppb-addon-table-main` with `data-responsive="true"`) which already
   ships its own responsive behaviour.

   So this is deliberately NARROW — an earlier draft applied
   `display:block; overflow-x:auto` to every table below 991.98px, which
   would (a) override SPPB's own responsive tables and (b) needlessly turn
   comfortable 3-column tables into scrollers on tablets.

   What remains is a safety net only, for plain hand-written content tables
   on phones. It matters because section 2 sets `overflow-x: clip` on the
   page: without a scroller of its own, an over-wide table would be CLIPPED
   (content unreachable) rather than merely causing page scroll.
   ========================================================================== */
@media (max-width: 575.98px) {

	/* Plain content tables only. SPPB's own table addon is excluded so its
	   built-in responsive mode keeps working. */
	#sp-component table:not(.sppb-addon-table-main):not(.sppb-addon-table-main *),
	.sppb-addon-content table:not(.sppb-addon-table-main) {
		display: block;
		width: 100%;
		max-width: 100%;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
	}

	/* Inside the scroller, stop cells collapsing to one word per line. */
	#sp-component table:not(.sppb-addon-table-main) th,
	#sp-component table:not(.sppb-addon-table-main) td {
		white-space: normal;
		min-width: 7ch;
	}
}


/* ==========================================================================
   9. EMBEDS — YouTube, Google Maps, e-service iframes.
   A fixed width/height iframe is an overflow guarantee on mobile.
   ========================================================================== */
#sp-component iframe[src*="youtube"],
#sp-component iframe[src*="youtu.be"],
#sp-component iframe[src*="vimeo"],
#sp-component iframe[src*="google.com/maps"],
.sppb-addon-content iframe[src*="youtube"],
.sppb-addon-content iframe[src*="google.com/maps"] {
	width: 100%;
	aspect-ratio: 16 / 9;
	height: auto;
	max-width: 100%;
	border: 0;
	display: block;
}

@media (max-width: 575.98px) {
	#sp-component iframe[src*="google.com/maps"] {
		aspect-ratio: 4 / 3; /* maps are more legible taller on a phone */
	}
}


/* ==========================================================================
   10. TOUCH TARGETS & FORMS  (WCAG 2.1 AA — 2.5.5 Target Size)
   Applies only to touch/stylus pointers, so mouse users keep tight spacing.
   ========================================================================== */
@media (pointer: coarse) {

	#sp-main .sppb-btn,
	#sp-main .btn,
	#sp-main button,
	#sp-main input[type="submit"],
	#sp-main input[type="button"],
	.dj-offcanvas a,
	.offcanvas-menu a,
	#sp-footer a,
	#sp-footer1 a,
	.mod-languages a {
		min-height: var(--jkm-tap);
		display: inline-flex;
		align-items: center;
	}

	/* Burger / offcanvas toggle. */
	.dj-megamenu-offcanvas .dj-hamburger,
	#sp-header .burger-icon,
	.offcanvas-toggler {
		min-width: var(--jkm-tap);
		min-height: var(--jkm-tap);
	}

	/* 16px minimum stops iOS Safari zooming the page on focus. */
	#sp-main input[type="text"],
	#sp-main input[type="search"],
	#sp-main input[type="email"],
	#sp-main input[type="tel"],
	#sp-main input[type="password"],
	#sp-main input[type="number"],
	#sp-main input[type="date"],
	#sp-main select,
	#sp-main textarea {
		font-size: 16px;
		min-height: var(--jkm-tap);
	}
}

/* Search / filter forms stack rather than squeeze on phones. */
@media (max-width: 575.98px) {
	#sp-main .input-group,
	#sp-main .search .form-inline,
	#sp-component .filters .btn-toolbar {
		display: -ms-flexbox;
		display: flex;
		-ms-flex-wrap: wrap;
		flex-wrap: wrap;
		gap: 0.5rem;
	}
	#sp-main .input-group > * {
		-ms-flex: 1 1 100%;
		flex: 1 1 100%;
		width: 100%;
		border-radius: var(--jkm-radius) !important;
	}
}


/* ==========================================================================
   11. DJ ACCESSIBILITY WIDGET
   Sticky top-right, 40x40, 10px margin. On phones it sits over the header /
   first content block and is under the 44px touch minimum.
   ========================================================================== */
@media (max-width: 767.98px) {
	.djacc.djacc--sticky.djacc-popup {
		margin: 8px !important;
	}
	.djacc-popup .djacc__openbtn {
		width: var(--jkm-tap) !important;
		height: var(--jkm-tap) !important;
	}
	/* The panel itself must never be wider than the screen. */
	.djacc__panel {
		max-width: calc(100vw - 16px) !important;
		max-height: 80svh;
		overflow-y: auto;
	}
}


/* ==========================================================================
   12. FOOTER
   ========================================================================== */
@media (max-width: 991.98px) {
	#sp-footer .sppb-row > [class*="sppb-col-"],
	#sp-footer1 .sppb-row > [class*="sppb-col-"] {
		margin-bottom: var(--jkm-stack-gap);
	}
	#sp-footer .list-items,
	#sp-footer1 .list-items {
		padding-left: 0;
		list-style: none;
	}
	#sp-footer .list-items li,
	#sp-footer1 .list-items li {
		display: flex;
		align-items: flex-start;
		gap: 0.5rem;
		margin-bottom: 0.75rem;
		line-height: 1.6;
	}
}

@media (max-width: 575.98px) {
	#sp-footer,
	#sp-footer1 {
		text-align: center;
	}
	#sp-footer .social-list,
	#sp-footer1 .social-list {
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		gap: 0.5rem;
		padding-left: 0;
		list-style: none;
	}
	#sp-footer .social-list a,
	#sp-footer1 .social-list a {
		width: var(--jkm-tap);
		height: var(--jkm-tap);
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}
	#sp-footer .list-items li,
	#sp-footer1 .list-items li {
		justify-content: center;
		text-align: left;
	}
}


/* ==========================================================================
   13. LARGE / ULTRAWIDE DESKTOP
   The site already opens .sppb-row-container to 1920px at >= 1400px (set in
   Helix custom code). Guard against unreadably long lines and giant gaps.
   ========================================================================== */
@media (min-width: 1400px) {
	#sp-page-builder .sppb-row-container,
	#sp-page-builder .sppb-container-inner {
		padding-left: 2rem;
		padding-right: 2rem;
	}
}

@media (min-width: 1921px) {
	/* Beyond the 1920px cap, centre the content instead of stretching it. */
	#sp-page-builder .sppb-row-container {
		margin-left: auto;
		margin-right: auto;
	}
}


/* ==========================================================================
   14. LANDSCAPE PHONES / SHORT VIEWPORTS
   ========================================================================== */
@media (orientation: landscape) and (max-height: 500px) {
	#sp-page-builder .sppb-section,
	.mod-sppagebuilder .sppb-section {
		padding-top: 1.5rem;
		padding-bottom: 1.5rem;
	}
	.dj-offcanvas-content {
		max-height: 100svh;
	}
}


/* ==========================================================================
   15. ACCESSIBILITY & PRINT
   ========================================================================== */

/* Visible keyboard focus — required for WCAG 2.4.7, and easy to lose when a
   template resets outlines. */
#sp-main a:focus-visible,
#sp-main button:focus-visible,
#sp-main input:focus-visible,
#sp-main select:focus-visible,
#sp-main textarea:focus-visible,
.dj-offcanvas a:focus-visible {
	outline: 3px solid #d4af37;   /* --sppb-link-color from the live preset */
	outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}
	.sppb-addon-sppb-flibox .sppb-flipbox-panel {
		transform: none !important;
	}
}

@media print {
	#sp-header,
	#sp-footer,
	#sp-footer1,
	.djacc,
	.dj-megamenu-offcanvas,
	.offcanvas-menu,
	.n2-section-smartslider,
	.sppb-btn {
		display: none !important;
	}
	#sp-main,
	#sp-component {
		width: 100% !important;
		max-width: 100% !important;
	}
	#sp-component a[href^="http"]::after {
		content: " (" attr(href) ")";
		font-size: 0.85em;
		word-break: break-all;
	}
	#sp-component table {
		display: table !important;
		overflow: visible !important;
	}
}


/* ==========================================================================
   16. THIRD-PARTY EXTENSIONS ON INNER PAGES
   Found by auditing the dev site's real pages — none of these appear on the
   two pages that were originally sampled, and each ships fixed-size or
   floating UI that behaves badly on small screens.
   ========================================================================== */

/* -- 16a. PWebBox floating panel -----------------------------------------
   Renders a fixed floating toggler + panel (`.pwebbox_toggler`,
   `.pwebbox-content`). On a phone it overlays the content it is supposed to
   sit beside, and the toggler is usually under the 44px touch minimum. */
@media (max-width: 767.98px) {
	.pwebbox-content,
	.pwebbox-container-inset,
	.pwebbox-container-outset {
		max-width: calc(100vw - 16px) !important;
		box-sizing: border-box;
	}

	.pwebbox_toggler {
		min-width: var(--jkm-tap);
		min-height: var(--jkm-tap);
	}

	/* Never let the panel exceed the screen height with no way to scroll. */
	.pwebbox-content {
		max-height: 85svh;
		overflow-y: auto;
		overscroll-behavior: contain;
	}
}

/* -- 16a-2. The fixed side rail — REMOVED on mobile ------------------------
   MEASURED at 390x844: `#pwebbox_toggler_static179` was a fixed rail at
   x=336, 54x170px, z-index 1030, sitting ON TOP of the page content and
   clipping the centred "PERKHIDMATAN KAMI" heading. No overflow metric
   catches it — `position: fixed` adds nothing to scrollWidth.

   An earlier draft only scaled it to 0.78 and padded headings around it.
   That was a mitigation, not a fix. The rail's six links now live inside
   the offcanvas instead, as two ordinary mod_custom modules in the
   template's `offcanvas-modules` position (see section 19), so below the
   offcanvas breakpoint the rail is simply removed.

   Kept at >= 992px, where the viewport is wide enough for it and it is
   still the desktop entry point to these links. */
@media (max-width: 991.98px) {
	.pwebbox_toggler,
	[id^="pwebbox_toggler"],
	[id^="pwebbox"][id$="_box"],
	.pwebbox-container-inset,
	.pwebbox-container-outset {
		display: none !important;
	}
}


/* ==========================================================================
   19. OFFCANVAS QUICK LINKS  (mobile replacement for the PWebBox rail)
   Rendered by two mod_custom modules (ms-MY id 214 / en-GB id 215):
       .jkm-oc-tools > ul.jkm-oc-tools__list > li > a > img + span
       .jkm-oc-tools__lang  (FaLang switcher via {loadmoduleid 133})

   POSITION: `dj-offcanvas-bottom`, NOT the template's `offcanvas-modules`.
   There are two offcanvas panels on this site and only one is reachable:
   the header burger is `button.dj-mobile-open-btn` (DJ-MegaMenu), which
   opens `aside.dj-offcanvas`. Helix's own `.offcanvas-menu` exists in the
   DOM but stays off-screen at x=390 with no visible trigger — modules put
   in `offcanvas-modules` render but are invisible to users. Verified by
   clicking the real burger and measuring both panels.
   DJ-MegaMenu exposes `dj-offcanvas-top` / `dj-offcanvas-bottom`
   (mod_djmegamenu.php:169-170); bottom places these under the menu.
   ========================================================================== */

/* The DJ offcanvas is dark themed and its menu items are white; the default
   link colour makes these quick links the odd ones out. Match the menu. */
.dj-offcanvas .jkm-oc-tools__list > li > a,
.dj-offcanvas .jkm-oc-tools__lang a {
	color: #fff;
}

.dj-offcanvas .jkm-oc-tools__list > li > a:hover,
.dj-offcanvas .jkm-oc-tools__list > li > a:focus-visible {
	color: #fff;
	text-decoration: underline;
}
.jkm-oc-tools {
	margin-top: 1.25rem;
	padding-top: 1rem;
	border-top: 1px solid rgba(255, 255, 255, 0.15);
}

.jkm-oc-tools__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.jkm-oc-tools__list > li {
	margin: 0;
}

.jkm-oc-tools__list > li > a {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	min-height: var(--jkm-tap);   /* WCAG 2.5.5 */
	padding: 0.5rem 0.25rem;
	line-height: 1.3;
	text-decoration: none;
	border-radius: var(--jkm-radius);
}

.jkm-oc-tools__list > li > a:hover,
.jkm-oc-tools__list > li > a:focus-visible {
	background: rgba(255, 255, 255, 0.08);
}

/* The source icons are 152x130 PNGs; lock the box so rows stay aligned. */
.jkm-oc-tools__list img {
	width: 28px;
	height: 28px;
	flex: 0 0 28px;
	object-fit: contain;
}

.jkm-oc-tools__lang {
	margin-top: 0.75rem;
	padding-top: 0.75rem;
	border-top: 1px solid rgba(255, 255, 255, 0.15);
}

/* mod_falang actually outputs `.mod-languages > ul.lang-inline` (verified in
   the rendered markup, not assumed from the module name). */
.jkm-oc-tools__lang .mod-languages,
.jkm-oc-tools__lang ul.lang-inline,
.jkm-oc-tools__lang ul {
	max-width: 100%;
	margin: 0;
	padding-left: 0;
	list-style: none;
}

.jkm-oc-tools__lang ul.lang-inline {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
}

@media (pointer: coarse) {
	.jkm-oc-tools__lang a {
		display: inline-flex;
		align-items: center;
		min-height: var(--jkm-tap);
	}
}

/* Desktop keeps the PWebBox rail, so this offcanvas copy would duplicate it.
   The DJ offcanvas is itself only reachable below its 979px breakpoint, but
   hide the block above 992px anyway so the two can never both appear. */
@media (min-width: 992px) {
	.jkm-oc-tools {
		display: none;
	}
}

/* -- 16b. DearFlip flipbook + PDF embeds ---------------------------------
   Both plugins are installed site-wide. Flipbooks size themselves from a
   fixed pixel height and PDF embeds from fixed width/height attributes;
   either will overflow a phone. */
#sp-component .df-element,
#sp-component ._df_book,
#sp-component ._df_thumb,
#sp-component .df-container {
	max-width: 100% !important;
	box-sizing: border-box;
}

@media (max-width: 767.98px) {
	#sp-component .df-element,
	#sp-component ._df_book {
		height: min(70svh, 520px) !important;
	}

	/* pdf_embed / pdfemb output — object and iframe variants. */
	#sp-component .pdfemb-viewer,
	#sp-component object[type="application/pdf"],
	#sp-component iframe[src*=".pdf"] {
		width: 100% !important;
		max-width: 100% !important;
		height: min(70svh, 520px) !important;
	}
}

/* -- 16b-2. Navigation links that are still under the WCAG minimum --------
   Measured in headless Chrome at 390x844 with touch emulation on:
     4x 20px  — links inside the PWebBox panel ("Peta Laman")
     3x 15px  — `ul.footer-menu` links ("Dasar Privasi")
   Both are `display: inline`, so the `min-height` in section 10 had no
   effect on them, and the footer-menu list lives inside an SPPB raw-html
   addon OUTSIDE `#sp-footer`, so that selector never matched either.

   These are navigation lists, not inline prose links, so they get the full
   44px target. Prose links are deliberately left alone — WCAG 2.5.8 exempts
   links inside a block of text, and padding them out would wreck line
   spacing in article copy. */
@media (pointer: coarse) {
	.pwebbox-content a,
	.pwebbox-customhtml-container a,
	ul.footer-menu a,
	.footer-menu li > a {
		display: inline-flex;
		align-items: center;
		min-height: var(--jkm-tap);
		padding-block: 2px;
	}
}

/* ==========================================================================
   17. LANDING PAGE (/ms/) — Smart Slider quick-link tiles
   The landing page is a single Smart Slider slide. Its five quick-link
   tiles (Portal Utama / Kanak-kanak / Orang Kurang Upaya / Warga Emas /
   Bantuan Kebajikan) sit in a `.n2-ss-layer-row-inner` that is
   `flex-wrap: nowrap`, so Smart Slider keeps all five on ONE line at every
   width and simply scales them down.

   MEASURED at 390x844: each tile renders 65x53px with 12.8px labels —
   far too small to read or tap, and well under the WCAG target minimum.
   At 1440 the same tiles are ~190x140px, which is the intended design.

   No metric caught this either: the row does not overflow, it just shrinks.

   ATTEMPTED AND REVERTED — do not retry this in CSS:
       .n2-ss-layer-row-inner { flex-wrap: wrap }
       .n2-ss-layer-row-inner > .n2-ss-layer-col { flex: 0 0 44% }
   Rendered at 390px this was WORSE than the original: four tiles stayed at
   75px while the fifth ("Bantuan Kebajikan") expanded to the full 324px and
   grew to 187px tall, overlapping the crest and the welcome heading. Smart
   Slider sizes these columns itself at a specificity this file cannot
   reach, and the neighbouring layers are absolutely positioned — so
   reflowing one layer does not move the others out of its way.

   FIXED 2026-09-14 — at the slider level, not here. Slide 3 of slider 2
   ("LANDING PAGE (BM)") has two near-duplicate tile rows; the one shown on
   mobile portrait is `n-uc-1e9ffe27e9ffb`, and its "Wrap after" for Mobile
   was 0 (= never wrap). Set to 3, giving a 3+2 layout with readable labels.
   Wrap after 2 was tried first and rejected: it needs three lines, which
   overflowed the row's mobile height and overlapped the welcome headings
   (they are absolutely positioned and do not move out of the way).

   GOTCHA: Smart Slider caches generated slider CSS in
   `#__nextend2_section_storage` under application='cache',
   section='notweb/n2-ss-<sliderId>'. Until that row is deleted the front end
   keeps serving the OLD flex-wrap rules and the change looks like it failed.
   Clearing Joomla's cache alone is not enough.

   This section intentionally emits NO rules; it records the finding.
   ========================================================================== */

/* -- 16c. Falang / language switcher -------------------------------------- */
@media (pointer: coarse) {
	.mod-falang a,
	.mod-falang .falang-item,
	.mod-languages__select select {
		min-height: var(--jkm-tap);
	}
}

/* -- 16d. Visitor counter -------------------------------------------------
   Digit sprites often carry fixed widths and push the footer sideways. */
#sp-footer .mod-visitorcounter,
#sp-footer1 .mod-visitorcounter,
.mod-visitorcounter {
	max-width: 100%;
	overflow: hidden;
}

/* -- 16e. Oversized content images ---------------------------------------
   The org-chart pages carry images up to 1831x2289. Section 2 already caps
   them to the column width; this stops a tall portrait scan from filling
   several screens of vertical scroll on a phone.
   NOTE: scaling a 1831px-wide org chart down to a 360px screen makes its
   text unreadable no matter what CSS does — those specific images want a
   zoomable viewer or a PDF link. Flagged rather than papered over. */
@media (max-width: 767.98px) {
	#sp-component .com-content-article img,
	#sp-component .article-body img {
		max-height: 75svh;
		width: auto;
		height: auto;
		object-fit: contain;
	}
}


/* ==========================================================================
   18. LISTING PAGES  (com_content category lists, com_jmap sitemap)
   Found by sweeping 14 pages at 360 / 768 / 1440.
   ========================================================================== */

/* -- 18a. Category list tables --------------------------------------------
   /ms/kalendar-aktiviti, /ms/media-laman/siaran-media, teks-ucapan etc. all
   render `table.com-content-category__table` with three columns:
   Tajuk | Tarikh Diterbitkan | Tontonan (hits).

   At 360px all three are crammed into the viewport: titles wrap to five or
   six lines and the "Tontonan" header is partly under the fixed side rail.
   The hits count is the least useful of the three on a phone, so it is the
   one that gives up its width. Both the header cell and the body cells must
   be hidden or the columns misalign. */
@media (max-width: 575.98px) {
	#sp-component #categorylist_header_hits,
	#sp-component .com-content-category__table td.list-hits {
		display: none;
	}

	/* Narrow columns + justified text produce rivers of whitespace between
	   words (very visible on the long Malay titles). */
	#sp-component .com-content-category__table th,
	#sp-component .com-content-category__table td,
	#sp-component .com-content-category__table a {
		text-align: left;
		hyphens: auto;
	}

	/* Give the date its own line rather than squeezing it to one word wide. */
	#sp-component .com-content-category__table td.list-date {
		white-space: nowrap;
	}
}

/* Bootstrap badges computed to 10.5px here (.75em of a 14px parent) — used
   for the hit counts. Readable floor wherever they survive. */
#sp-component .badge {
	font-size: max(0.75em, 12px);
}

/* The "Tontonan" count badges shipped as `.badge.bg-info`: white text on
   Bootstrap's cyan #0dcaf0, measured at 1.96:1 — a severe contrast failure
   (12px text needs 4.5:1). Bootstrap 5.3 itself pairs bg-info with DARK
   text for exactly this reason; the template kept white.

   Recoloured as a pale maroon pill instead of a solid one on purpose: a hit
   count is secondary information and should not outweigh the article title
   beside it. #870223 on #f4e6ea measures 8.41:1 (1.96 -> 8.41).

   Scoped to the listing's hits cell so other badges elsewhere on the site
   keep whatever treatment they were given. */
#sp-component td.list-hits .badge,
#sp-component .com-content-category .badge.bg-info {
	background-color: #f4e6ea !important;   /* .bg-info is !important */
	color: #870223 !important;
	font-weight: 600;
}

/* -- 18b. com_jmap sitemap ------------------------------------------------
   /ms/peta-laman. MEASURED at 360px: there is exactly ONE `.jmapcolumn`,
   already the full 330px — the two apparent columns are the tree's own
   node layout, not a column grid. An earlier draft forced `.jmapcolumn`
   to 100% width, which was a no-op; removed.

   The node links compute to 30px tall with `min-height: 20px` coming from
   com_jmap's own stylesheet, which outranks a plain `#jmap_sitemap a`
   rule — hence !important here. 30px clears the WCAG 2.5.8 AA minimum of
   24px, so this is a comfort improvement, not a conformance fix.

   Gated on pointer only, NOT width: capping this at max-width 767.98px left
   a gap at exactly 768px (a portrait tablet is a touch device and was still
   measuring 52 undersized links). */
@media (pointer: coarse) {
	#jmap_sitemap.sitemap a {
		display: flex;
		align-items: center;
		min-height: var(--jkm-tap) !important;
	}
}

/* -- 18c. AccordeonCK sidebar menu ----------------------------------------
   The "Perkhidmatan Utama" sidebar module (`ul.accordeonck`) renders its
   links ~36px tall. That clears WCAG 2.5.8 AA (24px) but is tight for
   touch, and these are the main service-navigation links on every section
   page — the same treatment already given to the offcanvas and footer menus.
   Section 10 missed them because it only targeted buttons and the offcanvas.

   Deliberately NOT applied on fine pointers: on a mouse this list would
   just get taller for no benefit. */
@media (pointer: coarse) {
	#sp-main .accordeonck a,
	#sp-main .accordeonck-wrap a {
		display: flex;
		align-items: center;
		min-height: var(--jkm-tap);
	}

	/* The +/- expand toggles sit at the far right, next to the fixed rail. */
	#sp-main .accordeonck .toggler,
	#sp-main .accordeonck span.oc,
	#sp-main .accordeonck-wrap .toggler {
		min-width: var(--jkm-tap);
		min-height: var(--jkm-tap);
	}
}

/* -- 18d. CATEGORY PAGINATION — counter overlapping the page links ---------
   Joomla renders:
       .com-content-category__navigation
         p.com-content-category__counter.float-end   "Halaman 1 daripada 12"
         .com-content-category__pagination > ul.pagination

   The counter is floated right. `.pagination` is a flex container, so it
   establishes an independent formatting context and DOES shrink away from
   that float — but its items default to `flex-wrap: nowrap`, so the 14 page
   circles overflow their own shrunken container and slide underneath the
   counter text.

   MEASURED on /ms/media-laman/siaran-media (12 pages) — page links sitting
   under the counter:
       1440px  ul 515..1213, counter 1213..1365  -> covers 8, 9, 10, >>
        768px  ul  39..577,  counter  577..729   -> covers 6, 7, 8
        360px  ul  15..193,  counter  193..345   -> covers 1, 2, 3
   i.e. broken at every width, not only on desktop.

   Fix: drop the float and lay the navigation out as a flex row — links
   left, counter right — and let the page links wrap instead of overflowing.
   Below ~576px the two simply stack.

   !important is needed only against Bootstrap's utility classes
   (`.float-end`, `.pt-3`, `.pe-2`, `.mb-4`), which are themselves
   !important. */
body .com-content-category__navigation {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem 1.5rem;
}

body .com-content-category__navigation .com-content-category__pagination {
	order: 1;
	min-width: 0;          /* allow shrinking inside the flex row */
	margin: 0;
}

body .com-content-category__navigation .com-content-category__counter {
	order: 2;
	float: none !important;
	margin: 0;
	padding-top: 0 !important;
	padding-right: 0 !important;
}

/* The wrap is the actual repair: 12+ pages can no longer overrun the row.
   `justify-content` must also be reset — the theme sets `space-between` on
   ul.pagination, which is invisible while the links fit on one line but
   spreads the LAST wrapped row across the full width once they don't
   (14 items x 69px = 966px into 790px usable at 1440px). */
body .com-content-category__navigation ul.pagination {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-start;
	row-gap: 0.5rem;
	margin-bottom: 0 !important;
}

/* The CURRENT page was invisible. Joomla marks it
   `<li class="active"><span class="page-link">1</span>`, and the theme gives
   that span `color: #fff` with `background-color: rgba(0,0,0,0)` — white text
   on the white page. Measured, not guessed: it renders as a blank 54x54 gap
   between the "previous" arrow and page 2, which is exactly the hole visible
   in the reported screenshot.

   #870223 is the site's own brand maroon, sampled from the page-title
   banner gradient (`linear-gradient(#870223, #390613)`), so this matches the
   existing palette rather than introducing a new colour. White on #870223 is
   roughly 10:1, comfortably past WCAG AA.

   ROOT CAUSE (found later, while fixing hover) — custom.css has:

       .page-item.active .page-link,
       .page-link:hover { background-color: var(--maincolor);
                          color: #fff !important; }

   `--maincolor` is defined only in the NUMBERED preset files. This site's
   dev instance loads `presets/default.css`, which has no `:root` block at
   all, so the variable is undefined, the background falls back to
   `transparent`, and the `!important` white text lands on the white page.
   That single broken variable is why BOTH the active page and the hover
   state disappeared.

   Deliberately NOT fixing this by defining `--maincolor` here: production
   loads `presets/preset1.css`, where it IS defined (#0345bf), and this
   stylesheet loads after it — so defining it would silently repaint all 27
   other `var(--maincolor)` usages in custom.css on the live site. Far
   beyond what was asked. The pagination gets explicit colours instead.

   Hover is a lighter maroon than active so the two states stay tellable
   apart: #a8324a gives 6.5:1 against white text, active #870223 gives
   10.2:1. Both clear WCAG AA. */
body .com-content-category__navigation ul.pagination li.active .page-link {
	background-color: #870223;
	border-color: #870223;
	color: #fff;
}

/* custom.css already forces `color: #fff !important` on :hover, so only the
   background and border need repairing — the text colour is fine as-is. */
body .com-content-category__navigation ul.pagination li .page-link:hover,
body .com-content-category__navigation ul.pagination li .page-link:focus-visible {
	background-color: #a8324a;
	border-color: #a8324a;
	color: #fff;
}

/* Hovering the page you are already on should not look like a different
   page: keep the active circle on its own, darker maroon. */
body .com-content-category__navigation ul.pagination li.active .page-link:hover,
body .com-content-category__navigation ul.pagination li.active .page-link:focus-visible {
	background-color: #870223;
	border-color: #870223;
}

/* Disabled arrows must not react at all. */
body .com-content-category__navigation ul.pagination li.disabled .page-link:hover,
body .com-content-category__navigation ul.pagination li.disabled .page-link:focus-visible {
	background-color: #fff;
	border-color: #dee2e6;
	color: #6c757d;
	cursor: default;
}

/* Stack them on phones so neither gets squeezed. */
@media (max-width: 575.98px) {
	body .com-content-category__navigation {
		justify-content: center;
		text-align: center;
	}

	body .com-content-category__navigation ul.pagination {
		justify-content: center;
	}
}


/* -- 18e. CONTENT LINK COLOUR ---------------------------------------------
   MEASURED contrast of every visible link on /ms/media-laman/siaran-media:

       category list   #049ad3 on #ffffff   3.20:1   <-- FAIL (needs 4.5:1)
       footer          #ffffff on #2f000c  18.65:1   pass
       header          #000000 on #f1f5f9  19.17:1   pass
       sidebar         #000000 on #f8f8f8  19.77:1   pass

   The pale blue used for article titles in listings was the only thing on
   the page failing WCAG 2.1 AA for normal-size text — and at 14px it needs
   4.5:1, not 3:1. It is also the odd one out against a site whose brand
   family is maroon (#870223 page-title banner, #2f000c footer).

   #870223 is that same brand maroon, giving 10.2:1 on white and ~9.7:1 on
   the striped rows. Hover adds an underline so the state is not signalled
   by colour alone (WCAG 1.4.1).

   Scoped to reader-facing content links and explicitly NOT to buttons or
   pagination, which carry their own colour treatment. */
:root {
	--jkm-link: #870223;
	--jkm-link-hover: #5c0118;
}

body #sp-component .com-content-category a:not(.btn):not(.sppb-btn):not(.page-link),
body #sp-component .com-content-article a:not(.btn):not(.sppb-btn):not(.page-link),
body #sp-component .article-body a:not(.btn):not(.sppb-btn):not(.page-link),
body #sp-component .com-content-category__table a {
	color: var(--jkm-link);
}

body #sp-component .com-content-category a:not(.btn):not(.sppb-btn):not(.page-link):hover,
body #sp-component .com-content-category a:not(.btn):not(.sppb-btn):not(.page-link):focus-visible,
body #sp-component .com-content-article a:not(.btn):not(.sppb-btn):not(.page-link):hover,
body #sp-component .com-content-article a:not(.btn):not(.sppb-btn):not(.page-link):focus-visible,
body #sp-component .article-body a:not(.btn):not(.sppb-btn):not(.page-link):hover,
body #sp-component .article-body a:not(.btn):not(.sppb-btn):not(.page-link):focus-visible {
	color: var(--jkm-link-hover);
	text-decoration: underline;
}


/* ==========================================================================
   20. TYPOGRAPHY — ONE FAMILY EVERYWHERE (Plus Jakarta Sans)
   Audited across /ms/utama, /ms/, /ms/kalendar-aktiviti and
   /ms/hubungi-kami/direktori-pegawai. Before this rule the rendered pages
   used four different text stacks:
        588x  "Plus Jakarta Sans", "sans-serif"
         88x  "Plus Jakarta Sans"
         43x  Poppins, sans-serif      (Helix typography, template style 16)
         18x  Poppins, serif           (PWebBox module font_family params)
          5x  Montserrat               (Smart Slider layer fonts, /ms/)

   The Helix typography params are now Plus Jakarta Sans on BOTH template
   styles (15 LANDING PAGE and 16 HOMEPAGE) — that is the source of truth and
   fixes the 43. This rule exists for the rest, which live in places a
   template setting cannot reach: per-addon SPPB styles, per-module PWebBox
   `font_family`, and per-layer Smart Slider fonts.

   The font is available on every page (Google Fonts link on all of them,
   plus a local copy under media/com_sppagebuilder for inner pages) —
   verified before switching, so nothing falls back to a system font.

   ICON FONTS ARE EXEMPT. Forcing the family onto an icon element replaces
   its glyphs with blank boxes, so elements carrying an icon-library class,
   and <i>, are excluded. Excluding <i> is safe both ways: an icon <i> keeps
   Font Awesome, while a genuine italic <i> simply inherits Plus Jakarta Sans
   from its parent.

   Smart Slider IS included. An earlier draft exempted it, on the belief that
   forcing the family broke the /ms/ landing page. That was wrong: the page
   was only ever screenshotted 1.8s after load, mid-way through the slider's
   per-character reveal animation. Given 9s to settle it renders perfectly,
   with or without this rule. Verified with the stylesheet removed entirely —
   the same "broken" frame appeared, so the cause was never CSS.

   !important is required here, not laziness: SPPB writes per-addon font
   rules with ID selectors and PWebBox writes per-module ones, both of which
   outrank a plain class rule.

   All exclusions are SIMPLE selectors on purpose. A complex selector inside
   :not() would drop the entire rule on any engine that cannot parse it,
   silently un-standardising the whole site.
   ========================================================================== */
:root {
	--jkm-font: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont,
		"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

body,
body :not(i):not([class*="fa-"]):not([class*="icofont"]):not([class*="flaticon"]):not([class*="webexicon"]) {
	font-family: var(--jkm-font) !important;
}

/* Form controls do not inherit font by default in every engine. */
button,
input,
select,
textarea,
optgroup,
option {
	font-family: var(--jkm-font) !important;
}

/* Keep real monospace where it is meaningful. */
code,
kbd,
pre,
samp,
tt,
code *,
pre * {
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas,
		"Liberation Mono", monospace !important;
}
