/* ══ contact.css — THE OFFICES PAGE ════════════════════════════════════════════
   Loaded only by contact.html. Every class is prefixed .ct- so it can collide with
   nothing, per the precedent products.css set after `.plate` was introduced twice
   and the later file silently restyled 24 figures (qa/css-collision.py).

   WHY THIS FILE EXISTS. Measured on the shipped page at 1440, in a real browser,
   after a full scroll-walk so every lazy image had been requested and had settled:

       3,244px tall · 345 words · 3 images · 0 SVG · 3 sections · 7 animated
       words per figure 115.0 · dead pixel rows per 900px screen 496

   That last number is the one that indicts the page. 496 of every 900 rows carried
   no ink of any kind — not a rule, not a glyph, not a photograph. More than half of
   every screen was flat navy. Ranked against the other ten pages on both metrics
   taken the same way, this was the plainest page on the site.

   THE DIAGNOSIS, and it is not "not enough decoration". This page owns the only
   content on the site that exists nowhere else: three registered addresses on two
   continents, an office line, and the rule that routes a stranger to an engineer.
   It rendered all of it as four columns of 13px mono headings over 15px grey lists —
   the same treatment the footer gives a nav link. The single most concrete thing
   AIREI can say on a contact page ("we are HERE, and here, and here") was the
   smallest ink in the document, while the largest type on the page was a headline
   that only restates it. That inversion is the defect. Nothing needed to be added
   to fix it; what was already there needed to be sized like it mattered.

   SO: SCALE, NOT COLOUR. The palette is not touched — no new hue enters this file,
   and the one strong colour used is Signal Gold on a display numeral, which is the
   exact and only job DESIGN.md §3 gives that token ("display numerals on dark
   only"). Three glyphs. Boldness here is a ratio: the office index numeral runs to
   104px against an 11.5px mono label in the same cell, ~9:1, where the old block
   ran 15px against 13px, ~1.2:1. Nothing on the page was loud; nothing was
   important either, and those were the same fact.

   WHAT IS DRAWN IS THE HOUSE'S OWN. The map mark on each plate is
   assets/brand/icons/polygon-map.svg, inlined verbatim rather than redrawn, so the
   site keeps one icon language: 24x24 box, fill:none, stroke currentColor,
   stroke-width 1.75, linecap butt, linejoin miter, miterlimit 4. It is one mark on
   one link inside a ruled cell — deliberately NOT the three-icon rounded-square
   feature grid DESIGN.md §8 names in the AI-tell blacklist. There is no card here,
   no radius and no fill: the gutters are the rule, which is the same hairline-grid
   idiom .pr-grid and .figures already use.

   MOTION invents nothing. Reveal is base.css `.rev` + `data-d`, which already
   carries both its reduced-motion arm and — load-bearing — its `@media
   (scripting:none)` arm in shell.css, so every plate is visible with JavaScript
   off. The only other movement is hover, on properties inside
   qa/motion-allow.json's allowlist (border-color, background, color, transform),
   and every animated selector has an exact-match final-state rule in the reduce
   block at the foot of this file. qa/motion-lint.py matches those selectors
   EXACTLY, so the pairs travel together and must not be renamed one at a time.

   NOT DONE HERE, and left for the lane that owns it: ms/hubungi.html is this page's
   Malay twin and still carries the old flat block. It needs the same structure with
   its own translated strings; this file is linked by contact.html only, so the twin
   is unaffected until someone does that deliberately. */

/* ── 1. THE HERO ────────────────────────────────────────────────────────────────
   The hero's right-hand column was empty from the headline down to the section
   rule — the same defect products.css §1 measured and closed on the range page.
   The photograph that used to sit mid-page in a 1:1 band moves up into it, which
   does two things at once: it fills the hero's dead column, and it stops the
   offices section from having to share attention with a picture.

   0.78fr, not 1fr. The copy column holds a headline at clamp(38px,6.2vw,78px) over
   three buttons that must stay on one row at desktop; the photograph is 4:5, so an
   even split makes it 604x755 and pushes the hero well past one screen. At 0.78 it
   settles near 470x588 and the hero closes inside a single 900px viewport with the
   button row unbroken.

   DOM ORDER IS THE MOBILE ORDER. The copy block (which contains the buttons) comes
   first in the source and the figure second, so a phone reader reaches the call to
   action before the photograph, with no flex reorder needed — the same conclusion
   base.css reached for the home hero, in its more durable form. */
.ct-herogrid{display:grid;gap:clamp(26px,3.6vw,52px);grid-template-columns:minmax(0,1fr)}
@media (min-width:1000px){
  .ct-herogrid{grid-template-columns:minmax(0,1fr) minmax(0,0.78fr);align-items:start}
}
.ct-herofig{margin:0}
.ct-herofig img{width:100%;height:auto;aspect-ratio:4/5;object-fit:cover;display:block;
  background:var(--panel-navy);border:1px solid var(--hairline)}
/* 4:5 and not the 1:1 the shared .pband uses. base.css §.pband argues the square
   for a band that must sit BESIDE a caption column and keep a horizontal rhythm;
   this frame sits beside the hero copy instead, which is already 588px of type, so
   the picture can be its own shape and discard nothing. Every jpg in
   assets/img/45/ is 0.800, so 4:5 is a zero-crop frame for this file. */
.ct-herofig figcaption{margin-top:14px;max-width:44ch;font-size:14.5px;line-height:1.5;
  color:var(--ink-subtle)}
.ct-herofig .k{font:600 11.5px/1 var(--f-mono);letter-spacing:.2em;text-transform:uppercase;
  color:var(--airei-blue-lift);margin:0 0 9px}

/* ── 2. THE OFFICE PLATES ───────────────────────────────────────────────────────
   The page's hero moment, and the reason this file exists.

   THE GUTTERS ARE THE RULE. `gap:1px` over a --hairline-strong ground with each
   plate painting its own --deep-water: the 1px the background shows through IS the
   hairline between plates, so three plates need three rules and not six borders,
   and they can never disagree about which side owns the line. Same idiom as
   .pr-grid and .figures.

   THE THREE COLUMNS ARE THREE DIFFERENT KINDS OF FACT, which is why they are
   columns and not a paragraph: an index and what kind of office it is · the city
   and what it does · the postal address and the way to open it. Reading across a
   plate answers "where is it, and why would I call it" in one pass. Below 900px
   they stack in that same order, which is also the order they matter in.

   THE WIDEST SHARE GOES TO THE ADDRESS, not to the display type. An address line
   that wraps reads as two addresses, so column three takes 1fr and the city column
   0.62fr — enough for "Coimbatore" at the 50px display ceiling, and no more.
   Measured against the longest line in the set, "Pusat Perdagangan Canning II,
   30350 Ipoh", which must hold on one line from 900px up. */
.ct-offices{display:grid;gap:1px;background:var(--hairline-strong);
  border-block:1px solid var(--hairline-strong);margin-top:clamp(30px,3.4vw,46px)}
.ct-office{background:var(--deep-water);padding:clamp(26px,3vw,44px) 0;
  display:grid;gap:clamp(16px,2.2vw,34px);grid-template-columns:minmax(0,1fr);
  transition:background var(--d2) var(--e-cubic)}
@media (min-width:900px){
  .ct-office{grid-template-columns:minmax(150px,0.5fr) minmax(0,0.62fr) minmax(0,1fr);
    align-items:start}
}
@media (hover:hover) and (pointer:fine){.ct-office:hover{background:var(--panel-navy)}}

/* THE NUMERAL IS THE SCALE CONTRAST. Fraunces at up to 104px against the 11.5px
   mono label directly beneath it — a ratio of about 9:1 inside one cell, where the
   old block ran 15px against 13px. Signal Gold is not a decision this file makes:
   DESIGN.md §3 assigns that token to "display numerals on dark only" and this is
   the only place on the page one exists. Three glyphs, so the ≤10% accent budget is
   not in play.
   line-height .82 and a -0.055em left inset: Fraunces' figures carry noticeable
   side bearing at display size, and without the pull the numeral optically hangs
   inside the column while the label under it sits flush. Measured against the
   label's left edge, not assumed. */
.ct-idx{font-family:var(--f-display);font-weight:400;font-size:clamp(52px,6.2vw,104px);
  line-height:.82;color:var(--signal-gold);margin:0 0 12px;margin-left:-.055em;
  font-variant-numeric:tabular-nums}
.ct-kind{font:600 11.5px/1.5 var(--f-mono);letter-spacing:.2em;text-transform:uppercase;
  color:var(--airei-blue-lift);margin:0}
.ct-city{font-family:var(--f-display);font-weight:420;font-size:clamp(30px,3.4vw,50px);
  line-height:1.02;letter-spacing:-0.0068em;margin:0 0 14px;color:var(--ink)}
.ct-what{margin:0;font-size:clamp(16px,1.5vw,18.5px);line-height:1.5;color:var(--ink-muted);
  max-width:32ch}
.ct-addr{list-style:none;margin:0;padding:0;display:grid;gap:5px}
.ct-addr li{font-size:15.5px;line-height:1.5;color:var(--ink-muted)}
.ct-addr li:first-child{color:var(--ink)}

/* THE MAP LINK IS AN AFFORDANCE, NOT A FOOTNOTE. It was the fourth item of a grey
   <ul>, indistinguishable from the address lines above it; it is now the only
   interactive thing in the plate and reads as one.
   inline-flex + padding-block, so the hit box clears the 24px WCAG 2.5.8 floor on
   touch without changing the line box — the same correction products.css made for
   its card asks after measuring 19px targets at 390px.
   min-height raises the measured box from 30px to 32px. 30 already cleared the 24px
   AA floor this rule was written against; 32 is the CRO audit's bar and the practical
   one — this link is tapped on a phone held in a mill, and the two pixels cost nothing
   because the icon and line box are unchanged. */
.ct-map{display:inline-flex;align-items:center;gap:9px;margin-top:16px;padding-block:5px;min-height:32px;
  font:500 12.5px/1.4 var(--f-mono);letter-spacing:.11em;text-transform:uppercase;
  color:var(--ink);border-bottom:1px solid var(--hairline-strong);
  transition:color var(--d2) var(--e-cubic),border-color var(--d2) var(--e-cubic)}
.ct-map svg{flex:none;width:19px;height:19px;color:var(--crude-amber)}
@media (hover:hover) and (pointer:fine){
  .ct-map:hover{color:var(--signal-gold);border-color:var(--signal-gold)}
}

/* ── 3. THE REACH RAIL ──────────────────────────────────────────────────────────
   "Reach a person" used to be the FOURTH COLUMN of the address grid, which is a
   category error with a visual cost: a phone number is not an address, and putting
   it in the same grid forced it to the same 15px as a postcode. On a contact page
   the office line is the answer to the page's own question, so it is set like one.

   ASYMMETRIC ON PURPOSE — 40/20/20/20, not four equal quarters. The lead cell takes
   two of the four tracks and its number runs to 42px; the other three run to 21px.
   Four equal cells would say these are four equivalent options, and they are not:
   one of them is a phone that a person picks up. */
.ct-reach{display:grid;gap:1px;background:var(--hairline-strong);
  border-block:1px solid var(--hairline-strong);margin-top:clamp(34px,4vw,58px);
  grid-template-columns:minmax(0,1fr)}
@media (min-width:720px){.ct-reach{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* FIVE TRACKS, NOT FOUR — and this was a real bug, caught in the render and not in
   the code. Four cells with the lead spanning two needs FIVE tracks; declaring four
   made the row overflow, wrapped "Careers" onto a second row, and left three empty
   tracks showing the --hairline-strong ground as a 277x139px grey slab. At the 1px
   gap that colour IS the hairline between cells; across an empty half-row it is the
   loudest object on the page. Measured before the fix: grid-template-rows
   "178.094px 138.5px" (two rows) with cell 4 back at x=164.
   Five equal tracks give the lead 2/5 and the other three 1/5 each, which is the
   40/20/20/20 the paragraph above claims — the arithmetic and the prose now agree. */
@media (min-width:1040px){.ct-reach{grid-template-columns:repeat(5,minmax(0,1fr))}}
.ct-r{background:var(--deep-water);padding:clamp(20px,2.2vw,30px) clamp(18px,2vw,28px);
  display:flex;flex-direction:column;gap:9px}
@media (min-width:1040px){.ct-r--lead{grid-column:span 2}}
.ct-r .k{font:600 11.5px/1 var(--f-mono);letter-spacing:.2em;text-transform:uppercase;
  color:var(--airei-blue-lift);margin:0}
.ct-r .v{font-family:var(--f-mono);font-variant-numeric:tabular-nums;font-weight:500;
  font-size:clamp(17px,1.7vw,21px);line-height:1.25;color:var(--ink);
  overflow-wrap:break-word}
/* The lead number, and the only reason this rail is asymmetric.

   THIS RULE USED TO SET FRAUNCES, ON A CLAIM I HAD NOT TESTED. The comment here read:
   "at 42px the mono's tabular advance opens gaps between the digit groups wide enough
   to read as three separate numbers". That was reasoning from base.css's tabular-nums
   finding, not a measurement, and rendering the two side by side at 42px refutes it:

     Geist Mono 500  reads as one continuous string, "+603-8685 0888", with a full-size
                     "+" on the number's own baseline. Setting the same string WITHOUT
                     tabular-nums is visually identical, so the tabular advance was
                     never the cause of anything here.
     Fraunces 400    renders "+" small and low — at 42px it reads as a smudge or a
                     stray hyphen rather than a country-code plus — and closes the
                     space before "0888" until the last two groups nearly touch.

   Scaling and raising the "+" inside the Fraunces version was tried too and is worse:
   the lifted glyph collides with the "6" beside it.

   So the face changes to mono, which is also the more honest choice: DESIGN.md §4
   gives Geist Mono the DATA voice ("OER readouts, batch IDs, telemetry stamps"), and a
   dialable number is data, not display. Fraunces stays where it belongs, on the city
   names and the headings.
   word-spacing closes the one real mono artefact — a full mono word-space between
   "8685" and "0888" that is wider than the string needs — without touching the digit
   advance, so the numerals stay on their tabular grid. */
.ct-r--lead .v{font-family:var(--f-mono);font-weight:500;letter-spacing:-0.005em;
  word-spacing:-.22em;font-size:clamp(26px,3vw,42px);line-height:1.1}
.ct-r .n{margin:0;font-size:14.5px;line-height:1.5;color:var(--ink-subtle)}
/* The phone, email and WhatsApp links in this rail are the most conversion-critical tap
   targets on the site — on a phone they ARE the contact page. They carried no coarse-pointer
   floor while shell/base/home/proof/news all moved to 44px, so this rail was the one place
   the standard did not reach.
   The bug shows at 414px and NOT at 390px, which is the opposite of the usual direction and
   the reason a single-width mobile pass would miss it: `font-size:clamp(17px,1.7vw,21px)` at
   line-height 1.25 is a 22px line box, and at 390px "info@airei.com.my" wraps to two lines
   (44px, incidentally passing) while at 414px it fits on one and the target drops to 22px.
   Measured on all 25 pages at both widths (is_mobile + has_touch): 3 occurrences, 414px only.
   `.ct-r--lead .v` is unaffected — its 26-42px display face already clears the floor. */
/* A SECOND, SMALLER FLOOR THAT IS NOT POINTER-SCOPED. The coarse-pointer rule below
   is a sibling lane's and is left exactly as written; it closes the 44px touch case
   it describes. It cannot reach the case measured here, which is a FINE pointer:
   sweeping 360/390/430/768/900/1040/1280/1440/1920 (mobile widths with is_mobile +
   has_touch, so the touch arm is graded as the touch arm), three targets fall under
   the WCAG 2.5.8 24px minimum at desktop widths only —

       900px   info@airei.com.my · +60 16-807 8685 · Open roles   22.3px
       1040px  Open roles                                          23.1px

   and nowhere else, because that is the band where `clamp(17px,1.7vw,21px)` is still
   pinned at its 17px floor over a 1.25 line-height. 2.5.8 is not pointer-scoped, so
   the floor should not be either.
   min-height and not padding-block: all three are direct children of the `.ct-r`
   flex column and therefore block-level flex items, where min-height applies
   cleanly. (The `.ct-r .n a` links are genuinely inline and would need padding-block
   instead — they measure 21.75px + their paragraph's leading and did not fail.)
   Additive by construction: on a coarse pointer the 44px rule below is later AND in
   a media block, so it still wins. */
.ct-r a.v{min-height:24px}
@media (pointer:coarse){
  .ct-r a.v{min-height:44px;display:flex;align-items:center}
}
.ct-r a.v,.ct-r .n a{border-bottom:1px solid var(--hairline-strong);
  transition:color var(--d2) var(--e-cubic),border-color var(--d2) var(--e-cubic)}
@media (hover:hover) and (pointer:fine){
  .ct-r a.v:hover,.ct-r .n a:hover{color:var(--signal-gold);border-color:var(--signal-gold)}
}

/* ── 4. THE CLOSING BAND ────────────────────────────────────────────────────────
   The last section carried a heading, two lines and two buttons over an entirely
   empty right half — no figure at all, on the block that asks for the call. It
   takes the night walkway, at 4:5 and at size, because a photograph at genuine
   scale is worth more here than any drawn device.

   The picture is model-made and carries no disclosure of its own; it is mood and not
   a specific AIREI installation, and the copy beside it says nothing the picture is
   being used to prove. (It used to be disclosed via a #pictures explainer on the home
   page — removed 2026-08-27 by operator ruling: a company does not publish an appendix
   auditing its own photography. The obligation survives as PLACEMENT — never a
   generated person beside a named customer, a quote or a result figure — enforced by
   qa/portrait-figure-gap.py, not by prose.)

   1fr 0.88fr and the figure SECOND in the source: on a phone the reader gets the
   ask and the buttons before the photograph, same rule as the hero. */
.ct-close{display:grid;gap:clamp(26px,3.6vw,52px);grid-template-columns:minmax(0,1fr);
  align-items:center}
@media (min-width:960px){
  .ct-close{grid-template-columns:minmax(0,1fr) minmax(0,0.88fr)}
}
.ct-closefig{margin:0}
.ct-closefig img{width:100%;height:auto;aspect-ratio:4/5;object-fit:cover;display:block;
  background:var(--panel-navy);border:1px solid var(--hairline)}
.ct-closefig figcaption{margin-top:14px;max-width:46ch;font-size:14.5px;line-height:1.5;
  color:var(--ink-subtle)}
.ct-closefig .k{font:600 11.5px/1 var(--f-mono);letter-spacing:.2em;text-transform:uppercase;
  color:var(--airei-blue-lift);margin:0 0 9px}

/* ── 5. REDUCED MOTION ──────────────────────────────────────────────────────────
   One exact-match final-state rule per animated selector above. qa/motion-lint.py's
   REDUCED rule compares normalised selectors literally, so these four strings must
   stay byte-identical to the four that declare a transition. base.css already
   carries the arm for `.rev`; it is not repeated here. */
@media (prefers-reduced-motion:reduce){
  .ct-office{transition:none}
  .ct-map{transition:none}
  .ct-r a.v,.ct-r .n a{transition:none}
}

/* ── 6. PRINT ───────────────────────────────────────────────────────────────────
   base.css §PRINT inverts the palette to ink-on-paper. Chromium omits background
   colours by default, so the hairline-gap idiom vanishes in print and each plate
   has to state its own rule. A plate is an atom — an office and its address must
   not split across a page boundary — and the gold numeral needs the print-safe
   metal (#7A5310, DESIGN.md §3b) because Signal Gold on white is unreadable. */
@media print{
  .ct-offices,.ct-reach{background:none;border-color:rgba(16,24,32,.22)}
  .ct-office,.ct-r{background:none;border-top:1px solid rgba(16,24,32,.22);
    break-inside:avoid;page-break-inside:avoid}
  .ct-idx{color:#7A5310}
  .ct-map svg{color:#7A5310}
  .ct-herogrid,.ct-close{grid-template-columns:1fr}
  .ct-herofig,.ct-closefig{break-inside:avoid;page-break-inside:avoid}
}
