/* ==========================================================================
   TriRoda — Malaysia electric cargo tricycles
   Visual system transposed from jpddc.com (金彭): pure-white canvas, red
   primary, orange accent, hairline tables, centred section titles, small
   radii, almost no shadow.

   Prototype only — standalone folder, nothing here touches the live site.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Brand */
  --red: #d53434;
  --red-dark: #b72b2b;
  --orange: #de5310;
  --dark: #36353b;

  /* Text */
  --ink: #111111;
  --ink-2: #666666;
  --ink-3: #999999;
  --ink-soft: #6e6e6e;
  --ink-4: #bbbbbb;

  /* Surfaces */
  --white: #ffffff;
  --soft: #f4f4f4;
  --soft-2: #fafafa;
  --line: #e6e6e6;
  --line-soft: #ededed;
  --tint: #fdf3f3;
  --tint-2: #fdeded;
  --footer: #1a1a1a;
  --black: #000000;

  /* Type */
  --display: "Barlow Condensed", "Arial Narrow", "Haettenschweiler", Impact, sans-serif;
  --body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Layout */
  /* Height of the sticky header. Measured 83px at 1440 / 1024 / 720 / 390
     — it does not change with the viewport, so one value is enough.
     Used only by scroll-margin-top below. */
  --header-h: 83px;
  --pad-x: 80px;
  --shell: 1440px;
  --content: 1280px;
  --hero-copy: 620px;

  /* Shape */
  --r-btn: 4px;
  --r-card: 6px;
}

/* --------------------------------------------------------------------------
   Anchor jumps must land BELOW the sticky header, not under it.
   Without this, clicking #line-up / #compare / #service put the section
   title behind the header — measured 3px hidden on desktop, 27px on mobile,
   35px for #enquire. [id] is deliberately broad: every anchor target on the
   site needs it, and scroll-margin does nothing unless the element is the
   scroll target anyway.
   -------------------------------------------------------------------------- */
[id] { scroll-margin-top: calc(var(--header-h) + 12px); }

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--white);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--display);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0.01em;
}

p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
table { border-collapse: collapse; width: 100%; }

:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */
.shell {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 var(--pad-x);
}

.section { padding: 80px 0; }
.section--soft { background: var(--soft); }
.section--tight { padding: 74px 0; }

/* Centred section title with the red rule underneath — the jpddc signature. */
.sec-head {
  text-align: center;
  max-width: 760px;
  margin: 0 auto 48px;
}
/* An eyebrow inside a section head (the About page) reuses the hero's red
   letterspaced label, but needs breathing room above the title — in the hero
   it is the first thing in the column and has no top margin to fight.
   Scoped to .sec-head .hero__eyebrow so the hero's own copy is untouched. */
.sec-head .hero__eyebrow { margin-bottom: 14px; }
.sec-head h1,
.sec-head h2 {
  font-size: 44px;
  text-transform: uppercase;
  text-wrap: balance;
}
.sec-head .rule {
  width: 46px;
  height: 4px;
  background: var(--red);
  margin: 14px auto;
}
.sec-head p {
  font-size: 16px;
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 15px 30px;
  border: 1px solid transparent;
  border-radius: var(--r-btn);
  font-family: var(--body);
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}
.btn--red { background: var(--red); color: var(--white); }
.btn--red:hover { background: var(--dark); }
.btn--ghost-red { background: transparent; color: var(--red); border-color: var(--red); }
.btn--ghost-red:hover { background: var(--red); color: var(--white); }
.btn--white { background: var(--white); color: var(--red); }
.btn--white:hover { background: var(--dark); color: var(--white); }
.btn--ghost-white { background: transparent; color: var(--white); border-color: rgba(255, 255, 255, .9); }
.btn--ghost-white:hover { background: var(--white); color: var(--red); border-color: var(--white); }
.btn--sm { padding: 13px 22px; font-size: 14px; }
.btn--block { flex: 1 1 auto; }

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  color: var(--red);
  font-size: 14px;
  font-weight: 600;

  /* Was 22px tall. WCAG 2.5.8 wants 24 — the service-card disclosures are
     primary content, not decoration. */
  min-height: 24px;
  padding: 2px 0;}
.link-arrow:hover { color: var(--dark); }
.link-arrow svg { flex: none; }

/* --------------------------------------------------------------------------
   5. Header
   -------------------------------------------------------------------------- */
.site-header {
  border-bottom: 1px solid var(--line);
  background: var(--white);
  position: sticky;
  top: 0;
  z-index: 40;
}
.site-header__inner {
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Fluid, not fixed. Measured at 1440px: the row needs logo 119 + nav + right,
     and the nav itself is 474px in English but 552px in Malay ("Soalan Lazim"
     and "Bandingkan" are far longer than "FAQ" and "Compare"). With a fixed
     34px nav gap the extra 78px had nowhere to come from, so below ~1440px the
     Malay nav wrapped to two lines and the switch chips squeezed into a 2-row
     blob. Letting the gaps breathe fixes it at every width without shrinking
     the labels. */
  gap: clamp(12px, 1.6vw, 24px);
}

.logo { display: flex; align-items: center; gap: 10px; }
.logo__mark {
  width: 36px; height: 36px;
  display: grid; place-items: center;
  background: var(--red);
  border-radius: var(--r-btn);
  color: var(--white);
  font-family: var(--display);
  font-size: 24px;
  font-weight: 700;
  line-height: 1;
}
.logo__word {
  font-family: var(--display);
  font-size: 25px;
  font-weight: 700;
  letter-spacing: .02em;
}

/* Fluid gap between nav links. A fixed 34px is what broke the Malay header:
   that nav is 78px wider than English, and the width had to come from
   somewhere. Measured at 1100px the English nav still fits on one line, so the
   clamp keeps a generous gap on wide screens and only tightens when needed. */
.nav { display: flex; align-items: center; gap: clamp(14px, 2.1vw, 34px); white-space: nowrap; }
.nav a {
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.nav a:hover { color: var(--red); }
.nav a[aria-current="page"] { color: var(--red); }

.header-right { display: flex; align-items: center; gap: clamp(10px, 1.25vw, 18px); }

.lang {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 9px;
  background: var(--soft);
  border-radius: var(--r-btn);
  /* Never let the switch wrap or shrink — see the Malay header note above. */
  flex: none;
  white-space: nowrap;
}
.lang button {
  background: none;
  border: 0;
  /* Was padding: 0 with 13px text, which measured 18x21 — under the 24px
     WCAG 2.5.8 minimum. 4px of side padding gets EN past 24px wide. */
  padding: 0 4px;
  min-height: 24px;
  display: inline-flex;
  align-items: center;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  cursor: pointer;
  /* The three chips must stay on one row — a wrapped switch reads as broken. */
  white-space: nowrap;
}
.lang button.is-active { color: var(--red); font-weight: 700; }
.lang__sep { font-size: 13px; color: #c4c4c4; }

.header-phone {
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
}

.nav-toggle {
  display: none;
  width: 44px; height: 44px;
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   6. Hero — copy left, vehicle right (the studio shots are full-width
   compositions, so overlaying copy on them is not readable)
   -------------------------------------------------------------------------- */
.hero { background: var(--soft); }
/* Capped and centred. Without a cap the media column keeps growing with the
   window, and once the frame follows the photo's own ratio (see .hero__media) an
   uncapped hero would be 1090px tall on a 2560px screen.
   The cap does NOT move the copy: padding-left below self-aligns the text to a
   centred 1280px column, which at a 1440px box resolves to 80px — the same
   640px from the left edge it lands on today at 2560px. */
.hero__inner {
  display: flex;
  align-items: center;
  max-width: var(--shell);
  margin: 0 auto;
}

.hero__copy {
  flex: 0 0 var(--hero-copy);
  padding-left: max(var(--pad-x), calc((100% - var(--content)) / 2));
  padding-right: 40px;
}
.hero__eyebrow {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .16em;
  color: var(--red);
  text-transform: uppercase;
}
.hero__copy h1 {
  /* 56px, not 64: at 64 the English headline measured ~546px against a 540px
     column and broke as "HAUL MORE. PAY / LESS.", leaving an orphan word.
     text-wrap: balance keeps any future longer headline splitting evenly
     instead of stranding its last word. */
  font-size: 56px;
  line-height: 1.06;
  margin: 20px 0;
  text-wrap: balance;
  text-transform: uppercase;
}
.hero__copy p {
  font-size: 17px;
  line-height: 1.7;
  color: #555555;
  margin-bottom: 20px;
}
.hero__actions { display: flex; gap: 14px; flex-wrap: wrap; }

/* The frame follows the PHOTO, exactly the way .svc__media does further down.
   All three hero shots are 1440x810.

   A fixed height here was the bug. It held the frame at 500px while the width
   tracked the window, so the frame ratio swung from 1.12 to 3.39 as the page got
   wider and the photo stayed at 1.78. object-fit: cover then had to cut whatever
   was left over, and MEASURED crop was:

       1080px -> 37% off horizontally      1920px -> 32% off vertically
       1280px -> 26% off horizontally      2560px -> 47% off vertically
       1440px ->  8% off horizontally

   — i.e. the raised cargo bed and the wheels were being sliced off at the common
   desktop widths, and the photo appeared not to scale with the page. With the
   ratio matched the crop is 0 at every width and the photo scales with the page.

   Do NOT put a fixed height back, and do NOT reach for object-fit: contain to
   absorb one: the studio backdrop runs from rgb(180,182,184) to rgb(245,249,250),
   so letterboxing against any single background colour would show as a band. */
.hero__media {
  flex: 1 1 auto;
  min-width: 0;
  aspect-ratio: 1440 / 810;
  overflow: hidden;
}
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   7. Model cards
   -------------------------------------------------------------------------- */
.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}
.grid-4 {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}

.card {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: box-shadow .2s ease, transform .2s ease;
}
.card:hover {
  box-shadow: 0 10px 28px rgba(0, 0, 0, .07);
  transform: translateY(-2px);
}
.card__media {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--soft);
}
.card__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .45s ease;
}
.card:hover .card__media img { transform: scale(1.04); }

.card__body { padding: 24px; display: flex; flex-direction: column; gap: 10px; flex: 1 1 auto; }
.card__code {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--red);
  text-transform: uppercase;
}
.card__name { font-size: 28px; font-weight: 700; }
.card__desc { font-size: 14px; line-height: 1.6; color: var(--ink-2); }
.card__price {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: auto;
  padding: 6px 0 2px;
  font-family: var(--display);
  color: var(--orange);
}
.card__price .cur { font-size: 19px; font-weight: 700; }
.card__price .amt { font-size: 36px; font-weight: 900; }

.card__actions { display: flex; gap: 10px; }
.card__actions .btn { height: 44px; padding: 0 18px; }

/* --------------------------------------------------------------------------
   8. Stats band
   -------------------------------------------------------------------------- */
.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: center;
}
.stats li {
  text-align: center;
  border-left: 1px solid #dcdcdc;
}
.stats li:first-child { border-left: 0; }
.stats .num {
  font-family: var(--display);
  font-size: 58px;
  font-weight: 900;
  line-height: 1.1;
  color: var(--orange);
}
.stats .lbl {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .08em;
  color: #333333;
  text-transform: uppercase;
  margin-top: 4px;
}

/* --------------------------------------------------------------------------
   9. Compare table
   -------------------------------------------------------------------------- */
.table-wrap {
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}
.table-scroll { overflow-x: auto; }

.cmp { min-width: 720px; font-size: 14px; }
.cmp th, .cmp td {
  height: 62px;
  padding: 0 24px;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--line-soft);
}
.cmp thead th {
  height: 60px;
  background: var(--soft);
  font-family: var(--body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--ink-2);
  text-transform: uppercase;
}
.cmp thead th.is-featured {
  background: var(--red);
  color: var(--white);
}
.cmp tbody tr:last-child th,
.cmp tbody tr:last-child td { border-bottom: 0; }
.cmp tbody th {
  font-weight: 500;
  color: var(--ink-2);
}
.cmp tbody td { color: var(--ink); font-weight: 500; }
.cmp td.is-featured { background: var(--tint); }
.cmp .price { font-family: var(--display); font-size: 24px; font-weight: 700; color: var(--orange); }
.cmp .is-featured .price { color: var(--red); }
.cmp .win { font-weight: 700; color: var(--red); }

/* --------------------------------------------------------------------------
   10. Service cards
   -------------------------------------------------------------------------- */
/* All three service shots are pre-cropped to the same 2.05:1 frame, so a
   matching box means object-fit never has to cut anything away. */
/* Small print under a card grid — used for the ex-works pricing note. */
.note-line {
  margin: 28px auto 0;
  max-width: 760px;
  text-align: center;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-soft);
}
.svc__media { aspect-ratio: 41 / 20; overflow: hidden; background: #e9e9e9; }
.svc__media img { width: 100%; height: 100%; object-fit: cover; }
.svc__name { font-size: 26px; font-weight: 700; text-wrap: balance; }

/* --------------------------------------------------------------------------
   11. CTA band
   -------------------------------------------------------------------------- */
.cta-band { background: var(--red); color: var(--white); }
.cta-band__inner {
  padding: 62px 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 40px;
}
/* max-width 660px means a long heading wraps, and `balance` is what stops the
   last line holding one short word — the Malay CTA copy
   ("Bincang dengan kami tentang muatan Anda") stranded "Anda" on its own line at
   four viewports before this. Same technique already used on .sec-head and
   .svc__name; it costs nothing on the one-line headings. */
.cta-band h2 { font-size: 44px; text-transform: uppercase; max-width: 660px; text-wrap: balance; }
.cta-band p { margin-top: 10px; font-size: 15px; line-height: 1.6; color: #ffffff; max-width: 660px; }
.cta-band__actions { display: flex; gap: 12px; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   12. Footer
   -------------------------------------------------------------------------- */
.site-footer { background: var(--footer); color: var(--white); }
.site-footer__inner { padding: 64px 0 56px; }
.footer-cols {
  display: grid;
  grid-template-columns: 380px 190px 200px 250px;
  justify-content: space-between;
  gap: 40px;
}
.footer-brand .logo__word { color: var(--white); }
.footer-brand p {
  margin-top: 14px;
  font-size: 14px;
  line-height: 1.6;
  color: #a8a8a8;
}
.footer-phone {
  font-family: var(--display);
  font-size: 22px;
  font-weight: 700;
  margin-top: 14px;
}
.footer-mail { font-size: 14px; color: #a8a8a8; margin-top: 6px; }

.footer-col h3 {
  font-family: var(--body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .14em;
  color: #8c8c8c;
  text-transform: uppercase;
  margin-bottom: 12px;
}
.footer-col li + li { margin-top: 12px; }
.footer-col a {
  font-size: 14px;
  color: #e0e0e0;
  /* Was a 17px-tall inline link; 4px of vertical padding reaches 24px. */
  display: inline-block;
  padding: 4px 0;
}
.footer-col a:hover { color: var(--red); }
.footer-col span { font-size: 14px; color: #e0e0e0; }

.footer-bar { background: var(--black); }
.footer-bar__inner {
  min-height: 62px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  font-size: 13px;
  color: #8c8c8c;
  padding-top: 16px;
  padding-bottom: 16px;
}

/* --------------------------------------------------------------------------
   13. Product detail page
   -------------------------------------------------------------------------- */
.breadcrumb { font-size: 13px; color: var(--ink-soft); }
.breadcrumb a:hover { color: var(--red); }

.product {
  display: grid;
  grid-template-columns: 640px minmax(0, 1fr);
  gap: 60px;
  align-items: start;
  padding: 40px 0 68px;
}

.gallery__main {
  aspect-ratio: 16 / 9;
  background: var(--soft);
  border-radius: var(--r-card);
  overflow: hidden;
}
.gallery__main img { width: 100%; height: 100%; object-fit: cover; }

.gallery__thumbs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin-top: 16px;
}
.gallery__thumbs button {
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--r-btn);
  overflow: hidden;
  background: var(--soft);
  cursor: pointer;
}
.gallery__thumbs button.is-active { border-color: var(--red); }
/* height:auto is required — the <img> carries width/height attributes for CLS,
   and the height attribute acts as a presentational hint once the fixed height
   is gone, which would beat aspect-ratio and stretch the thumbnail. */
.gallery__thumbs img { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; }

.product__code {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--red);
  text-transform: uppercase;
  margin-top: 14px;
}
.product h1 { font-size: 62px; margin: 14px 0; text-transform: uppercase; }
.product__lead { font-size: 16px; line-height: 1.65; color: var(--ink-2); }

.price-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 18px 0;
}
.price-row .cur { font-family: var(--display); font-size: 20px; font-weight: 700; color: var(--orange); }
.price-row .amt { font-family: var(--display); font-size: 48px; font-weight: 900; color: var(--orange); }
.price-row .note { font-size: 13px; color: var(--ink-soft); }

.product__rule { height: 1px; background: var(--line); }

.checks { display: grid; gap: 12px; margin: 20px 0; }
.checks li { display: flex; align-items: flex-start; gap: 11px; font-size: 15px; font-weight: 500; color: #333333; }
.checks svg { flex: none; margin-top: 2px; }

.product__actions { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.product__trust { font-size: 13px; color: var(--ink-2); }

/* Feature grid */
.feature { background: var(--white); border-radius: var(--r-card); padding: 26px; }
.feature__icon {
  width: 54px; height: 54px;
  display: grid; place-items: center;
  background: var(--tint-2);
  border-radius: var(--r-btn);
  margin-bottom: 14px;
}
.feature h3 { font-size: 24px; font-weight: 700; margin-bottom: 10px; }
.feature p { font-size: 14px; line-height: 1.6; color: var(--ink-2); }

/* Spec table */
.spec { min-width: 640px; font-size: 15px; }
.spec th, .spec td {
  height: 58px;
  padding: 0 26px;
  text-align: left;
  vertical-align: middle;
}
.spec th {
  width: 380px;
  font-family: var(--body);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-2);
}
.spec td { color: var(--ink); }
.spec tbody tr:nth-child(even) { background: var(--soft-2); }

/* FAQ */
.faq { display: grid; gap: 12px; }
.faq details {
  background: var(--white);
  border-radius: var(--r-btn);
}
.faq summary {
  list-style: none;
  cursor: pointer;
  padding: 22px 26px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  font-size: 16px;
  font-weight: 500;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary .chev { flex: none; transition: transform .2s ease; }
.faq details[open] summary .chev { transform: rotate(180deg); }
.faq .answer {
  padding: 0 26px 22px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-2);
  max-width: 900px;
}

/* "See all frequently asked questions" under a model page's own FAQ. It is a
   plain link rather than a button on purpose: it leaves the page for /faq, and
   the model pages already carry two full-weight red buttons, so a third would
   compete with them. Underlined so it still reads as a link without the button
   chrome. */
.faq__more { margin: 22px 0 0; text-align: center; }
.faq__more a {
  font-weight: 700;
  color: var(--red);
  text-decoration: underline;
}

/* Enquiry */
.enquiry {
  display: grid;
  grid-template-columns: 450px minmax(0, 1fr);
  gap: 60px;
  align-items: start;
}
.enquiry h2 { font-size: 44px; text-transform: uppercase; }
.enquiry .lead { margin-top: 16px; font-size: 15px; line-height: 1.65; color: var(--ink-2); }
.enquiry .contact { margin-top: 16px; display: grid; gap: 10px; }
.enquiry .contact span { font-size: 15px; font-weight: 500; color: #333333; }

.form {
  background: var(--soft);
  border-radius: var(--r-card);
  padding: 32px;
  display: grid;
  gap: 18px;
}
.form__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.field { display: grid; gap: 8px; }
.field label { font-size: 13px; font-weight: 500; color: #444444; }
.field input,
.field textarea {
  width: 100%;
  padding: 13px 14px;
  border: 1px solid #e0e0e0;
  border-radius: var(--r-btn);
  background: var(--white);
  font: inherit;
  font-size: 14px;
  color: var(--ink);
}
.field textarea { min-height: 112px; resize: vertical; }
.field input::placeholder,
.field textarea::placeholder { color: #767676; }
.field input:focus,
.field textarea:focus { outline: 2px solid var(--red); outline-offset: 1px; border-color: var(--red); }

.form__submit { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.form__note { font-size: 12px; line-height: 1.5; color: var(--ink-soft); }
.form__ok {
  margin: 0;
  padding: 14px 16px;
  border-radius: var(--r-btn);
  background: var(--white);
  border-left: 4px solid var(--red);
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-2);
}

.form__err {
  margin: 0;
  padding: 14px 16px;
  border-radius: var(--r-btn);
  background: var(--tint);
  border-left: 4px solid var(--red);
  font-size: 13px;
  line-height: 1.6;
  color: #8f1f1f;
}

/* Honeypot. Parked off-canvas rather than display:none, because some bots skip
   fields that are display:none — the trap only works if a bot fills it. */
.field--hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Cloudflare Turnstile widget on the enquiry form. It is an iframe that
   Cloudflare sizes after its script loads, so reserve the height up front —
   otherwise the Send Enquiry button jumps upward a moment after page load. */
.field--turnstile .cf-turnstile { min-height: 65px; }
.field__note {
  margin: 10px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* Shown instead of the generic send-failure text when the server rejected the
   enquiry because the human check did not pass — "check your connection" would
   send the visitor looking in the wrong place. */
.form__err--check { margin-top: 12px; }

/* Only ever revealed by the <noscript> style block in <head>. Without JS
   the form cannot send (no fetch, no Turnstile, no validation), so the
   form is hidden and this stands in its place. English only: the language
   swap itself needs JS, so a no-JS visitor is on the English page anyway. */
.form__noscript {
  display: none;
  margin: 0;
  padding: 16px 18px;
  border-radius: var(--r-btn);
  background: var(--white);
  border-left: 4px solid var(--red);
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-2);
}
.form__noscript a { color: var(--red-dark); font-weight: 600; }

.field select {
  width: 100%;
  padding: 13px 36px 13px 14px;
  border: 1px solid #e0e0e0;
  border-radius: var(--r-btn);
  background-color: var(--white);
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%23666666' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
}
.field select:focus { outline: 2px solid var(--red); outline-offset: 1px; border-color: var(--red); }

.field input[aria-invalid="true"] { border-color: var(--red); }

/* Long-form text pages — the privacy policy. */
.prose { max-width: 780px; margin: 0 auto; }
.prose h3 { font-size: 22px; font-weight: 700; margin: 34px 0 10px; }
.prose h3:first-child { margin-top: 0; }
.prose p {
  margin: 0 0 12px;
  font-size: 15px;
  line-height: 1.75;
  color: var(--ink-2);
}

/* A section head that sits directly above a data block, with no gap below. */
.pb-0 { padding-bottom: 0; }

/* --------------------------------------------------------------------------
   13b. Utilities
   -------------------------------------------------------------------------- */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Skip-to-content. The first focusable thing on every page, parked above the
   viewport until a keyboard user tabs to it.
   MUST NOT be `display: none` or `visibility: hidden` — neither is focusable,
   which would leave the link unreachable and defeat the point. It is moved with
   `top`, and it clears the sticky header's z-index: 40 at z-index: 50 so the
   focused link is never painted behind the header.
   No transition on purpose: an animated reveal is one more thing that has to be
   suppressed under prefers-reduced-motion, and instant is correct here. */
.skip-link {
  position: absolute;
  left: 12px;
  top: -72px;
  z-index: 50;
  padding: 12px 18px;
  background: var(--white);
  border: 2px solid var(--red);
  border-radius: var(--r-btn);
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
}
.skip-link:focus { top: 12px; }

/* Placeholder block for imagery we do not have yet. Every service shot is real
   now, so nothing uses this — kept for the next gap. Striped so it can never be
   mistaken for a finished photo. */
.ph {
  display: grid;
  place-items: center;
  background: repeating-linear-gradient(45deg, #ececec, #ececec 12px, #f4f4f4 12px, #f4f4f4 24px);
  color: var(--ink-3);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .08em;
  text-align: center;
  text-transform: uppercase;
  padding: 16px;
}

/* --------------------------------------------------------------------------
   14. Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 1240px) {
  :root { --pad-x: 48px; --hero-copy: 520px; }
  .footer-cols { grid-template-columns: 300px 160px 180px 200px; gap: 32px; }
}

@media (max-width: 1080px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .product { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .enquiry { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px; }
}

/* The header collapses to the hamburger BELOW 1100px, not 960.

   Measured overflow thresholds for the full desktop row:
     English -> the row no longer fits under 980px
     Malay   -> no longer fits under 1100px ("Soalan Lazim" / "Bandingkan"
                are much longer than "FAQ" / "Compare")
   960 was already marginal in English and plainly broken in Malay, where the
   CTA button spilled past the right edge from 961-1099px. Collapsing at 1100
   covers every language. 1140 rather than 1100 gives the longest Malay labels
   ~40px of headroom instead of sitting exactly on the boundary (at 1101px
   Malay still overflowed by a hair). The JS that closes the menu on resize
   keys off the same number (js/site.js, initNav) — change both together. */
@media (max-width: 1140px) {
  .section, .section--tight { padding: 56px 0; }
  .hero__inner { flex-direction: column; align-items: stretch; }
  .hero__copy {
    flex: none;
    padding: 48px max(var(--pad-x), 24px) 40px;
    order: 2;
  }
  /* Was height: 340px — the same fixed-height bug in the stacked layout, where
     it cropped 37% off the photo at 960px wide. The aspect-ratio from the base
     rule now applies here too, so the photo keeps its shape. */
  .hero__media { order: 1; }

  .hero__copy h1 { font-size: 48px; }
  .sec-head h1,
.sec-head h2 { font-size: 34px; }
  .product h1 { font-size: 44px; }
  .cta-band h2, .enquiry h2 { font-size: 34px; }
  .cta-band__inner { flex-direction: column; align-items: flex-start; padding: 48px 0; }
  .nav { display: none; }
  .nav.is-open {
    display: flex;
    position: absolute;
    top: 82px; left: 0; right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    padding: 16px var(--pad-x) 24px;
    background: var(--white);
    border-bottom: 1px solid var(--line);
  }
  .nav.is-open a { padding: 12px 0; width: 100%; }
  .nav-toggle { display: block; }
  .header-phone { display: none; }
  .site-header__inner { position: relative; }
}

@media (max-width: 520px) {
  /* The hero CTA sits immediately below the fold, so drop the duplicate header
     button rather than letting the row squeeze. */
  .header-right .btn { display: none; }
}

/* Narrow screens: the spec table's 380px label column pushed every value off
   the screen, so it read as a list of empty labels. Stack label over value. */
@media (max-width: 768px) {
  .spec { min-width: 0; }
  .spec tbody tr { display: block; padding: 14px 20px; }
  .spec th, .spec td { display: block; width: auto; height: auto; padding: 0; }
  .spec th { font-size: 13px; }
  .spec td { margin-top: 3px; }

  /* Keep the row label pinned while the comparison matrix scrolls sideways. */
  .cmp thead th:first-child,
  .cmp tbody th {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--white);
  }
  .cmp thead th:first-child { background: var(--soft); }
}

@media (max-width: 720px) {
  :root { --pad-x: 24px; }
  /* The header is the tightest row on a small phone: brand + language switch +
     CTA + hamburger. Trim every part rather than dropping the CTA, and stop the
     flex items shrinking — a shrunk box keeps its geometry inside the viewport
     while its label spills over the edge, which reads as clipped text. */
  .site-header__inner { gap: 12px; }
  .header-right { gap: 10px; }
  .logo, .logo__mark, .header-right, .lang, .nav-toggle { flex: none; }
  .logo { gap: 8px; }
  .logo__mark { width: 30px; height: 30px; font-size: 19px; }
  .logo__word { font-size: 20px; }
  .lang { padding: 6px 7px; gap: 5px; }
  .header-right .btn--sm { padding: 11px 14px; font-size: 13px; }
  .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 0; }
  .stats li:nth-child(odd) { border-left: 0; }
  .stats .num { font-size: 44px; }
  .footer-cols { grid-template-columns: minmax(0, 1fr); }
  .footer-bar__inner { flex-direction: column; align-items: flex-start; gap: 8px; }
  .form { padding: 22px; }
  .form__row { grid-template-columns: minmax(0, 1fr); }
  .hero__copy h1 { font-size: 38px; }
  .sec-head h1,
.sec-head h2 { font-size: 28px; }
  .sec-head { margin-bottom: 32px; }
  .card__name { font-size: 24px; }
  .card__price .amt { font-size: 30px; }
}


/* --------------------------------------------------------------------------
   15. Compare table — column spotlight
   --------------------------------------------------------------------------
   Hovering a model column gives it the exact treatment the featured column
   carries at rest (red header, tinted cells), so a buyer can read one model at
   a time instead of scanning a 4-column matrix. CSS alone cannot do this: a
   rule cannot select a whole column from a hovered cell, so js/site.js marks
   every cell in that column with .is-hover.
   While the spotlight is on another column the resting highlight steps aside,
   so exactly one column is ever highlighted.
   -------------------------------------------------------------------------- */
.cmp thead th,
.cmp tbody td {
  transition: background-color .28s ease, color .28s ease;
}
.cmp thead th.is-hover {
  background: var(--red);
  color: var(--white);
}
.cmp tbody td.is-hover { background: var(--tint); }
.cmp tbody td.is-hover .price { color: var(--red); }

.cmp.is-hovering thead th.is-featured:not(.is-hover) {
  background: var(--soft);
  color: var(--ink-2);
}
.cmp.is-hovering tbody td.is-featured:not(.is-hover) { background: transparent; }
.cmp tbody td.is-featured .price { transition: color .28s ease; }

@media (prefers-reduced-motion: reduce) {
  .cmp thead th,
  .cmp tbody td,
  .cmp tbody td.is-featured .price { transition: none; }
}


/* --------------------------------------------------------------------------
   16. Homepage hero carousel + model-code legend
   -------------------------------------------------------------------------- */
/* A sliding track, not a cross-fade. The three slides sit side by side at 100%
   each and js/site.js translates the track by -100% per step, so the change is a
   smooth horizontal movement and the next photo visibly arrives from the side
   instead of fading in place.
   The transform is set from JS and must stay there — a CSS class cannot know how
   many steps along the track the current slide is. */
.hero__media { position: relative; }
.hero__track {
  position: absolute;
  inset: 0;
  display: flex;
  transition: transform .55s cubic-bezier(.33, 0, .2, 1);
  will-change: transform;
}
.hero__slide {
  /* static, not absolute: these are now flex items in a row. */
  position: static;
  flex: 0 0 100%;
  margin: 0;
}
.hero__slide img { display: block; width: 100%; height: 100%; object-fit: cover; }

.hero__pager { display: flex; align-items: center; gap: 18px; margin-top: 22px; }
/* Gap is 0: the 4px side padding on each dot now provides the spacing. */
.hero__dots { display: flex; gap: 0; }
.hero__dot {
  /* The BUTTON is the hit area — 24px tall, per WCAG 2.5.8. The visible
     dot is a pseudo-element so it can stay 10px wide. Previously the button
     itself was 10x10, which is essentially unclickable on a phone.
     The 4px side padding replaces the 8px gap on .hero__dots below, so the
     dots sit the same distance apart as before. */
  /* 7px of side padding, not 4: the inactive dot is only 10px wide, so 4px left
     it at 18x24 — still short of the 24x24 WCAG 2.5.8 minimum. 7px puts every
     dot at 24x24 or better (10+14=24 inactive, 30+14=44 active). */
  height: 24px;
  padding: 0 7px;
  border: 0;
  background: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.hero__dot::before {
  content: '';
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #cfcfcf;
  transition: background-color .2s ease, width .2s ease, border-radius .2s ease;
}
.hero__dot.is-active::before {
  width: 30px;
  border-radius: 5px;
  background: var(--red);
}
.hero__model { display: inline-flex; align-items: baseline; gap: 10px; }
.hero__model-code {
  font-family: var(--display);
  font-size: 22px;
  font-weight: 700;
  color: var(--ink);
}
.hero__model-more { font-size: 13px; font-weight: 600; color: var(--red); }
.hero__model:hover .hero__model-more { color: var(--dark); }

/* Contrast. The hero sits on --soft, where the brand red measures 4.37:1 —
   below the 4.5:1 AA line for the 13px eyebrow and the outline button label.
   Red TEXT inside the hero therefore switches to the darker red (about 7:1
   there). Solid red fills keep the brand colour: their label is white, which
   is 4.81:1 on #D53434, and the active pager dot is a shape, which only needs
   3:1 and has it. */
.hero__eyebrow,
.hero .btn--ghost-red,
.hero__model-more {
  color: var(--red-dark);
}
.hero .btn--ghost-red { border-color: var(--red-dark); }

/* Decodes the model naming, directly under the comparison matrix. */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 30px;
  margin-top: 20px;
  padding: 18px 22px;
  background: var(--soft-2);
  border-radius: var(--r-btn);
}
.legend__item { display: flex; align-items: baseline; gap: 8px; font-size: 14px; color: var(--ink-2); }
.legend__item b {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  color: var(--red);
}

@media (prefers-reduced-motion: reduce) {
  /* The transition lives on the track now, so this is where it has to be
     switched off — the slides themselves no longer animate. */
  .hero__track { transition: none; }
}


/* --------------------------------------------------------------------------
   17. Savings calculator (inside the first FAQ item)
   -------------------------------------------------------------------------- */
.answer--calc { max-width: none; }
.calc {
  display: grid;
  /* Fields left, result beside them, assumptions on a full-width line below.
     Five stacked inputs made this widget needlessly tall. */
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  align-items: stretch;
  gap: 22px 26px;
  margin-top: 18px;
  padding: 24px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}
/* Three across puts the five inputs on two comfortable rows, not five.
   align-items:end keeps the inputs on a shared baseline when one label wraps
   ("Electricity price (RM / kWh)" does at this column width). */
.calc__fields {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: end;
  gap: 14px;
}
.calc__field { display: grid; gap: 6px; }
.calc__field label { font-size: 13px; font-weight: 500; color: #444444; }
.calc__field input {
  width: 100%;
  padding: 11px 14px;
  border: 1px solid #e0e0e0;
  border-radius: var(--r-btn);
  background: var(--white);
  font: inherit;
  font-size: 15px;
  color: var(--ink);
}
.calc__field input:focus { outline: 2px solid var(--red); outline-offset: 1px; border-color: var(--red); }

.calc__result {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  padding: 24px;
  border-radius: var(--r-btn);
  background: var(--tint);
}
.calc__big {
  margin: 0;
  font-family: var(--display);
  font-size: 52px;
  font-weight: 900;
  line-height: 1.05;
  color: var(--red);
}
.calc__lbl { margin: 0; font-size: 14px; font-weight: 600; color: var(--ink); }
.calc__sub { margin: 0; font-size: 14px; color: var(--ink-2); }
.calc__note {
  grid-column: 1 / -1;
  margin: 0;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-soft);
}

@media (max-width: 1020px) {
  .calc { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 620px) {
  .calc__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 430px) {
  .calc__fields { grid-template-columns: minmax(0, 1fr); }
}


/* --------------------------------------------------------------------------
   18. Map link (Plaza Conlay) + WhatsApp links
   --------------------------------------------------------------------------
   The address is a Maps deep link, and hovering it shows a static map
   thumbnail. The thumbnail is a local asset built from OSM tiles — no API key,
   no third-party script, nothing fetched at runtime. Hidden below 720px: on
   touch a :hover popover either never fires or sticks after a tap.
   -------------------------------------------------------------------------- */
.map-link { position: relative; display: inline-block; }

.map-link > a {
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.footer-col .map-link > a { text-decoration-color: rgba(224, 224, 224, .35); }
.enquiry .contact .map-link > a { text-decoration-color: rgba(51, 51, 51, .3); }
.map-link:hover > a,
.map-link:focus-within > a { text-decoration-color: currentColor; }

.map-link__hover {
  position: absolute;
  right: 0;
  bottom: calc(100% + 12px);
  width: 300px;
  border-radius: var(--r-btn);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .38);
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: opacity .2s ease, transform .2s ease, visibility .2s;
  pointer-events: none;
  z-index: 30;
}
.map-link__hover img { display: block; width: 100%; height: auto; }
.map-link:hover .map-link__hover,
.map-link:focus-within .map-link__hover {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

@media (max-width: 720px) {
  .map-link__hover { display: none; }
}

/* Phone numbers open WhatsApp, so they should read as links. */
a.header-phone,
a.wa-link { color: inherit; }
a.header-phone:hover,
a.wa-link:hover { color: var(--red); }
.footer-phone a:hover { color: var(--red); }

/* Shown alongside the form error: a real link, not just a phone number in text. */
.form__fallback { margin: 0; }


/* --------------------------------------------------------------------------
   19. Service-card disclosures
   --------------------------------------------------------------------------
   These three "See delivery terms / Read the warranty / Arrange a visit" lines
   used to link to #enquire, which showed nothing but two buttons — a promise
   with no content behind it. They are now inline disclosures. The copy is the
   same text the FAQ uses, and verify-pages.py checks the two cannot drift.
   -------------------------------------------------------------------------- */
.card__more { margin-top: 6px; }
.card__more > summary.link-arrow {
  margin-top: 0;
  list-style: none;
  cursor: pointer;
}
.card__more > summary.link-arrow::-webkit-details-marker { display: none; }
.card__more .chev { transition: transform .2s ease; }
.card__more[open] .chev { transform: rotate(90deg); }
.card__more-text {
  margin: 10px 0 0;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-soft);
}
