/* Qomor Business Park — offer generator.
   Dark amber, matching the building picker so the two read as one product. */

:root{
  --ink:#fff;
  --dim:rgba(255,255,255,.62);
  --faint:rgba(255,255,255,.38);
  --bg:#08080A;
  --amber:#FF9E3D;
  --amber-soft:#FFC27A;
  --amber-deep:#C96A15;
  --glass:rgba(255,255,255,.06);
  --glass-hi:rgba(255,255,255,.10);
  --edge:rgba(255,255,255,.13);
  --edge-hi:rgba(255,255,255,.24);
  --good:#3DD68C;
  --warn:#FFC65A;
  --bad:#FF6B6B;

  /* Layout constants, stated once because two sections are sized from them:
     the floor drawing in step 3, and the payment schedule in step 4, which is
     deliberately the same height (see .schedwrap). Changing the panel width or
     the page padding here keeps the two in step by construction rather than by
     someone remembering to update a number in both places. */
  --page-pad:22px;
  --page-max:1080px;
  --unit-panel:290px;
  --unit-gap:14px;
  /* Fallback only. NOT "every floor drawing is 2412 x 1180" any more — the
     ground plaza is 3600 x 1479 (2.4333). renderPlan() sets this per floor from
     planAspect(); this value only covers the moment before the first floor is
     chosen. Sizing anything from the literal 2.0441 will squash that floor. */
  --plan-aspect:2.0441;

  /* Payment schedule row metrics. These are not free numbers — .schedwrap's
     minimum height is calculated from them so that two and a half years is
     always visible without scrolling, so if you change the schedule's padding
     or type size, change these to match or the guarantee quietly lapses.
       row  = 13px text x 1.5 line-height + 5px padding top and bottom + 1px rule
       head = 10px text x 1.5 line-height + the same padding and rule
     Both are rounded UP from that arithmetic (30.5 -> 31, 26 -> 27) because a
     fractional line-height rounds up when it is laid out, and erring the other
     way clips the eleventh row — which is the one row this whole calculation
     exists to keep on screen. Over-provisioning just shows a little more. */
  --sched-rows:11;               /* down payment + 10 quarters = 2.5 years */
  --sched-row:31px;
  --sched-head:27px;
}
*{box-sizing:border-box}
/* The dark colour is set on <html> as well as <body>. The body's gradient
   propagates to the canvas, but overscroll and the area beyond a short
   document can still paint the default white — which flashes on a dark page. */
html{background:#050506;color-scheme:dark}
html,body{margin:0;min-height:100%}
body{
  background:radial-gradient(120% 70% at 50% 0%,#17171C 0%,#050506 70%) fixed;
  color:var(--ink);
  font:400 15px/1.5 -apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
  font-variant-numeric:tabular-nums;
  padding-bottom:60px;
}

/* ---------- header ---------- */
#top{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:18px 22px;border-bottom:1px solid var(--edge);
}
.brand{display:flex;align-items:center;gap:14px;min-width:0}
/* The wordmark is white-and-gold with a transparent background, so it reads
   only on the dark page — never invert it or place it on white. Height is
   pinned so the intrinsic 606x300 cannot push the header around; width:auto
   keeps the aspect ratio. */
.brand .logo{height:34px;width:auto;display:block;flex:none}
.brand .names{padding-left:14px;border-left:1px solid var(--edge);min-width:0}
.brand .names b{
  display:block;font-size:12.5px;letter-spacing:.15em;text-transform:uppercase;
}
.brand .names span{
  display:block;font-size:10px;color:var(--faint);
  letter-spacing:.11em;text-transform:uppercase;
}
@media (max-width:560px){
  .brand{gap:10px}
  .brand .logo{height:27px}
  .brand .names{padding-left:10px}
  .brand .names b{font-size:11px}
  .brand .names span{font-size:9px}
}

button{font:inherit;color:inherit;cursor:pointer}
.ghost{
  background:var(--glass);border:1px solid var(--edge);border-radius:10px;
  padding:9px 15px;font-size:13px;font-weight:600;
}
.ghost:hover{background:var(--glass-hi);border-color:var(--edge-hi)}
.ghost:disabled{opacity:.5;cursor:default}

/* ---------- sync bar ---------- */
.sync{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:11px 22px;font-size:12.5px;color:var(--dim);
  border-bottom:1px solid var(--edge);background:rgba(0,0,0,.25);
}
.sync .spacer{flex:1}
.sync .dot{
  width:8px;height:8px;border-radius:50%;background:var(--faint);flex:none;
}
.sync.live .dot{background:var(--good);box-shadow:0 0 10px rgba(61,214,140,.8);
  animation:pulse 2.4s ease-in-out infinite}
.sync.stale .dot{background:var(--bad);box-shadow:0 0 10px rgba(255,107,107,.8)}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
.sync b{color:var(--ink);font-weight:600}
@media (prefers-reduced-motion:reduce){.sync.live .dot{animation:none}}

.warnings{
  margin:14px 22px 0;padding:12px 15px;border-radius:12px;
  background:rgba(255,198,90,.10);border:1px solid rgba(255,198,90,.34);
  font-size:12.5px;color:#FFE0A8;
}
.warnings ul{margin:6px 0 0;padding-left:18px}
.warnings li{margin:3px 0}

/* ---------- steps ---------- */
main{padding:var(--page-pad);max-width:var(--page-max);margin:0 auto}
.step{margin-bottom:30px}
.step h2{
  display:flex;align-items:center;gap:10px;margin:0 0 13px;
  font-size:12px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);
}
.step h2 i{
  width:21px;height:21px;border-radius:6px;display:grid;place-items:center;
  background:var(--glass);border:1px solid var(--edge);
  font-style:normal;font-size:11px;color:var(--ink);
}
.step h2 small{font-size:11px;letter-spacing:0;text-transform:none;color:var(--faint);font-weight:400}

/* Compact: these are a means to the floor plan, not the main event. */
.grid{display:grid;gap:8px;grid-template-columns:repeat(auto-fill,minmax(148px,1fr))}
#floors{grid-template-columns:repeat(auto-fill,minmax(132px,1fr))}
.card{
  background:var(--glass);border:1px solid var(--edge);border-radius:12px;
  padding:9px 11px;text-align:start;display:flex;flex-direction:column;gap:8px;
  transition:background .16s,border-color .16s,transform .16s;
}
.card:hover:not(:disabled){background:var(--glass-hi);border-color:var(--edge-hi);transform:translateY(-2px)}
.card.on{border-color:rgba(255,158,61,.85);background:rgba(255,158,61,.13)}
.card:disabled{opacity:.42;cursor:not-allowed}
.card .row{display:flex;align-items:center;justify-content:space-between;gap:8px}
.card .id{
  width:22px;height:22px;border-radius:8px;display:grid;place-items:center;
  background:rgba(255,255,255,.1);font-weight:600;font-size:11px;flex:none;
}
.card.on .id{background:linear-gradient(150deg,var(--amber-soft),var(--amber-deep));color:#1A0E02}
.card .pill{
  font-size:8px;letter-spacing:.1em;text-transform:uppercase;font-weight:600;
  padding:3px 8px;border-radius:999px;white-space:nowrap;
  background:rgba(255,158,61,.2);color:var(--amber-soft);
}
.card .pill.none{background:rgba(255,255,255,.09);color:var(--dim)}
.card .val{font-size:15px;font-weight:600;letter-spacing:-.015em;line-height:1}
.card .lab{font-size:9px;color:var(--faint);letter-spacing:.06em;text-transform:uppercase;margin-top:3px}

/* ---------- units ---------- */
.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:11.5px;color:var(--faint);margin-bottom:11px}
.legend span{display:flex;align-items:center;gap:6px}
.sw{width:10px;height:10px;border-radius:3px;display:block}
.sw.available{background:var(--good)}
.sw.reserved{background:var(--warn)}
.sw.sold{background:var(--bad)}

.units{display:grid;gap:7px;grid-template-columns:repeat(auto-fill,minmax(118px,1fr))}
.unit{
  border-radius:11px;padding:9px 10px;text-align:start;
  background:var(--glass);border:1px solid var(--edge);
  display:flex;flex-direction:column;gap:3px;
}
.unit .code{font-size:12.5px;font-weight:600;letter-spacing:.02em}
.unit .meta{font-size:10px;color:var(--faint)}
.unit .price{font-size:11px;color:var(--dim)}
.unit.available{border-color:rgba(61,214,140,.5)}
.unit.available:hover{background:rgba(61,214,140,.14)}
.unit.reserved{opacity:.6;border-color:rgba(255,198,90,.42);cursor:not-allowed}
.unit.sold{opacity:.42;border-color:rgba(255,107,107,.38);cursor:not-allowed}
.unit.on{border-color:var(--amber);background:rgba(255,158,61,.16)}

/* ---------- plan + schedule ---------- */
.unitcard{
  background:var(--glass);border:1px solid var(--edge);border-radius:15px;
  padding:15px 17px;margin-bottom:14px;display:grid;gap:10px;
  grid-template-columns:repeat(auto-fit,minmax(140px,1fr));
}
.unitcard div span{display:block;font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.unitcard div b{display:block;font-size:16px;font-weight:600;margin-top:3px}

/* Payment plan is a dropdown. The native control is kept — it is the one
   widget every buyer and every agent already knows how to drive, and on a phone
   it opens the OS picker instead of a bespoke menu that has to be re-learned. */
.plans{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:16px}
.planLabel{
  font-size:10.5px;color:var(--faint);letter-spacing:.11em;text-transform:uppercase;
}
.planSelect{
  flex:1 1 300px;max-width:460px;
  appearance:none;-webkit-appearance:none;
  background:var(--glass);border:1px solid var(--edge);border-radius:11px;
  color:var(--ink);font:inherit;font-size:14px;font-weight:600;
  padding:12px 40px 12px 15px;cursor:pointer;
  /* Chevron drawn in CSS so the control needs no image and no extra markup. */
  background-image:
    linear-gradient(45deg,transparent 50%,var(--dim) 50%),
    linear-gradient(135deg,var(--dim) 50%,transparent 50%);
  background-position:calc(100% - 21px) 51%,calc(100% - 15px) 51%;
  background-size:6px 6px,6px 6px;
  background-repeat:no-repeat;
}
.planSelect:hover{background-color:var(--glass-hi);border-color:var(--edge-hi)}
.planSelect:focus-visible{outline:2px solid var(--amber);outline-offset:2px}
/* The popup list is drawn by the OS, which does not inherit the page's dark
   theme — set both so the options are not black-on-black on Windows. */
.planSelect option{background:#15151A;color:var(--ink)}

/* ---------- offer ---------- */
.offerbar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:20px}
.cta{
  background:linear-gradient(150deg,var(--amber-soft),var(--amber-deep));
  color:#1A0E02;border:0;border-radius:11px;
  padding:13px 22px;font-size:14px;font-weight:700;
  box-shadow:0 0 24px rgba(255,158,61,.30);
}
.cta:hover{filter:brightness(1.07)}
.cta:disabled{opacity:.55;cursor:default;box-shadow:none}
.offerbar span{font-size:12.5px;color:var(--dim)}
.offerbar span.bad{color:var(--bad)}

.summary{
  display:grid;gap:9px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  margin-bottom:16px;
}
.summary div{background:var(--glass);border:1px solid var(--edge);border-radius:12px;padding:12px 14px}
.summary span{display:block;font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.summary b{display:block;font-size:17px;font-weight:600;margin-top:4px}

/* ---------- the schedule, boxed and scrolled ----------
   A 10-year plan is 40 instalments plus a divider per year. Run down the page
   it buried the Send button under a screen and a half of table, so the agent
   scrolled past the whole schedule every time to reach the one control they
   need. It now sits in a fixed rectangle and scrolls inside itself.

   THE HEIGHT IS THE FLOOR DRAWING'S HEIGHT, on the user's instruction that the
   two match. The drawing is `width:100%` of step 3's left grid column, so its
   rendered height is that column's width divided by the drawing's aspect —
   computed here from the same variables that lay step 3 out, so the two cannot
   drift apart. Below 900px the grid collapses to one column and the drawing
   spans the full content width, so the second rule follows it.

   THE TWO NO LONGER MATCH ON DESKTOP, as of the new client drawings 2026-08-16.
   They are much wider and shorter than the old sheets (aspect 2.0441 against
   1.4155), so the drawing now computes to about 358px where the schedule's
   two-and-a-half-year floor is 399px. Both cannot hold at once, and the floor
   wins: matching the drawing exactly would show roughly nine rows, which is the
   scrolling problem the user asked to fix. Flagged rather than silently
   reverted — if the matched height matters more than the eleven rows, lower
   --sched-rows, do not remove the floor. */
.schedwrap{
  height:calc((min(100vw, var(--page-max)) - 2 * var(--page-pad)
               - var(--unit-panel) - var(--unit-gap)) / var(--plan-aspect));
  /* THE FLOOR IS TWO AND A HALF YEARS, on the user's instruction 2026-08-16.
     A customer's first question is what the next few years cost, and an agent
     should not have to scroll a box to answer it. That is the down payment plus
     ten quarterly instalments = 11 rows (there are no separate year-heading
     rows — the year opens on the instalment's own row), plus the sticky header
     and the sticky total, which do not scroll and so cannot be counted.

     Written as arithmetic rather than a round number so it stays true if the
     row metrics change: --sched-row is set from the padding and type size a few
     rules down, and if either moves, this floor moves with it. */
  min-height:calc(var(--sched-rows) * var(--sched-row)
                  + var(--sched-head) + var(--sched-row));
  overflow:auto;
  overscroll-behavior:contain;   /* don't hand the scroll to the page at the end */
  border:1px solid var(--edge);border-radius:14px;background:var(--glass);
}
@media (max-width:900px){
  .schedwrap{height:calc((100vw - 2 * var(--page-pad)) / var(--plan-aspect))}
}
/* Sticky, and OPAQUE: over a translucent --glass the rows scroll through the
   header and both become unreadable. --bg is the page's own solid colour. */
.schedwrap thead th{position:sticky;top:0;z-index:2;background:#14141A}
/* The total is the figure the customer is being asked to agree to, so it stays
   on screen instead of living at the bottom of a scroll nobody reaches. */
.schedwrap tr.total td{position:sticky;bottom:0;z-index:2;background:#14141A}

table{width:100%;border-collapse:collapse;font-size:13px}
th,td{padding:8px 10px;text-align:start;border-bottom:1px solid var(--edge)}
/* The schedule's rows are tighter than the app's other tables, which is how two
   and a half years fit without either shrinking the type — the offer was just
   enlarged for legibility, so shrinking it back here would undo that — or
   growing the box past the floor drawing it is deliberately matched to.
   Keep in step with --sched-row above.

   nowrap is part of the same guarantee, not a cosmetic choice: a wrapped date
   or label makes a row two lines tall, and eleven rows would then no longer fit
   the height calculated for them. On a phone too narrow for six columns the box
   scrolls sideways instead, which costs a swipe and keeps the two and a half
   years the user asked for. */
.schedwrap th,.schedwrap td{padding:5px 10px;white-space:nowrap}
th{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);font-weight:600}
td.num,th.num{text-align:end}
/* The year column is narrow and quiet until a year opens on it. */
td.yr,th.yr{width:64px;white-space:nowrap}
/* The row a year starts on: bold, tinted, and carrying the yearly share. It
   replaces the full-width divider row the table used to spend on each year. */
tr.opens td{background:rgba(255,255,255,.05);font-weight:700}
tr.opens td.yr{color:var(--amber-soft);letter-spacing:.04em}
/* The down payment is the one row a customer looks for first, so it is the one
   row given the accent. The reference layout uses the client's red here; ours
   is amber because that is this document's accent — same job, our palette. */
tr.dp td{background:rgba(255,158,61,.10);color:var(--amber-soft);font-weight:700}
tr.milestone td{color:var(--amber-soft)}
tr.total td{font-weight:700;border-top:2px solid var(--edge-hi);border-bottom:0}

footer{padding:0 22px;max-width:1080px;margin:0 auto}
footer p{font-size:11.5px;color:var(--faint);line-height:1.6;border-top:1px solid var(--edge);padding-top:14px}
.empty{color:var(--faint);font-size:13px;padding:14px 0}

/* ---------- masterplan render (step 1) ---------- */
#hero{
  position:relative;border-radius:16px;overflow:hidden;border:1px solid var(--edge);
  background:#000;margin-bottom:12px;aspect-ratio:2000/1260;
}
#hero img{position:absolute;inset:0;width:100%;height:100%;object-fit:fill;display:block}
#heroSvg{position:absolute;inset:0;width:100%;height:100%;z-index:2}
#heroSvg polygon{
  fill:rgba(150,150,156,.10);stroke:rgba(200,200,208,.34);stroke-width:1.6;
  cursor:pointer;transition:fill .18s,stroke .18s;vector-effect:non-scaling-stroke;
}
/* All four buildings look and behave identically. Availability is reported by
   the cards and the tooltip, never by the colour of the massing — colouring by
   stock made two buildings glow amber and two grey, which read as a rendering
   fault rather than as information. */
#heroSvg polygon.hot:not(.sel){
  fill:rgba(190,202,218,.30);stroke:#E4EDF7;filter:url(#glowCool);
}
/* Selected: steady amber. No flicker — an irregular animation reads as a
   glitch, not as emphasis. */
#heroSvg polygon.sel,
#heroSvg polygon.sel.hot{
  fill:rgba(255,158,61,.46);stroke:#FFE0BC;stroke-width:2.6;filter:url(#glowF);
}

#heroTip,#planTip{
  position:absolute;z-index:4;pointer-events:none;opacity:0;
  transform:translate(-50%,-136%);transition:opacity .15s;
  background:rgba(9,9,11,.92);border:1px solid var(--edge-hi);
  padding:6px 11px;border-radius:10px;white-space:nowrap;
  box-shadow:0 8px 26px rgba(0,0,0,.66);
}
#heroTip.on,#planTip.on{opacity:1}
#heroTip b,#planTip b{display:block;font-size:12px;font-weight:600}
#heroTip i,#planTip i{display:block;font-style:normal;font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--amber-soft)}
#heroTip.off i,#planTip.off i{color:var(--faint)}

/* ---------- floor plan with pins (step 3) ---------- */
#planWrap{
  /* aspect-ratio, not "however tall the image is". Once the stage can be zoomed
     past 100% the drawing no longer sets this box's height, and without a fixed
     shape the whole page would grow on every zoom step. --plan-aspect is set
     per floor by renderPlan(), because the ground plaza is 2.4333 and the rest
     are 2.0441. */
  position:relative;border-radius:14px;border:1px solid var(--edge);
  background:#fff;margin-bottom:12px;
  aspect-ratio:var(--plan-aspect);
  /* hidden until actually zoomed. At 100% the stage is exactly as wide as this
     box, and sub-pixel rounding on that equality is enough to raise a stray
     scrollbar on the unzoomed plan — which then shifts the drawing and every
     pin with it. The .zoomed class is set by setZoom only above 1x. */
  overflow:hidden;overscroll-behavior:contain;
}
#planWrap.zoomed{overflow:auto;touch-action:pan-x pan-y;cursor:grab}
#planWrap[hidden]{display:none}
/* The zoomed layer. Width in percent so the image and the pins, both sized in
   percentages of it, scale as one and stay registered to each other. */
#planStage{position:relative;width:calc(100% * var(--plan-zoom,1));min-width:100%}
#planImg{width:100%;display:block}
#planPins{position:absolute;inset:0;z-index:2}
.legend .zoombtn{
  min-width:30px;font-size:14px;line-height:1;padding:4px 9px;font-variant-numeric:tabular-nums;
}
#planZoomLevel{
  font-size:11.5px;color:var(--dim);min-width:42px;text-align:center;
  font-variant-numeric:tabular-nums;
}
/* THE BUTTON IS THE TOUCH TARGET, THE ::before IS THE DOT. They are sized
   separately on purpose: the dot has to stay small enough to sit inside a room
   on a drawing where a room is about 30px wide, while the target has to stay
   big enough for a fingertip. Drawing one circle at a size that satisfies both
   is impossible, so the button is a transparent 34px disc and only the dot is
   painted. Sizes are in px, not a fraction of the drawing, so they do not
   shrink with the image on a phone — which is exactly where the tap matters. */
#planPins .pin{
  /* A 6px target AT 100%, scaled with the drawing.
     The closest two pins in the project sit 6.73px apart at 100% zoom, measured
     across all four floors — the 9.5px every earlier version was sized against
     is the MEDIAN gap, not the minimum. 6 is the largest target that fits under
     6.73, so no two targets touch and a click can never be handed to the
     neighbouring unit; it either hits the pin under the pointer or hits nothing.

     MULTIPLIED BY THE ZOOM, which is what keeps that true at every scale rather
     than only at 100%: the gap between pins scales with the drawing, so a
     target that scales with it holds the same 6 : 6.73 ratio all the way up.
     At 4x the target is 24px against a 26.9px gap — still no overlap, and now
     a fingertip instead of a mouse.
     An earlier version held the target at a fixed 6px on the theory that zoom
     should push pins apart rather than enlarge them. It does push them apart,
     but it also left a 6px target adrift in 27px of dead space, so zooming in
     made the pin harder to hit and visually insignificant against the enlarged
     rooms. Do not go back to it. See CONFIG.pinDotPx for the full sums. */
  position:absolute;
  width:calc(6px * var(--plan-zoom,1));height:calc(6px * var(--plan-zoom,1));
  transform:translate(-50%,-50%);
  margin:0;padding:0;border:0;background:none;cursor:pointer;
  display:grid;place-items:center;-webkit-tap-highlight-color:transparent;
}
#planPins .pin::before{
  /* --pin-dot is set from CONFIG.pinDotPx at startup; the fallback keeps the
     pins visible if that ever fails to apply. Scaled by the zoom for the same
     reason as the target above — a dot that stayed 4px while the rooms grew
     fourfold reads as a speck of dust on the drawing.
     Keep the dot no wider than the target: a dot spilling past its own hit area
     invites clicks on the part of it that does not respond.
     The ring and the shadow are NOT scaled. They are there to separate the dot
     from whatever it sits on, and a 6px white ring at 4x would swallow the room
     the dot is marking. */
  content:'';border-radius:50%;
  width:calc(var(--pin-dot,4px) * var(--plan-zoom,1));
  height:calc(var(--pin-dot,4px) * var(--plan-zoom,1));
  border:1.5px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.55);
  transition:width .15s,height .15s;
}
#planPins .pin.available::before{background:#1FA971}
#planPins .pin.reserved{cursor:not-allowed}
#planPins .pin.reserved::before{background:#E0A21C}
/* Sold is RED, never grey — grey reads as "not released". */
#planPins .pin.sold{cursor:not-allowed}
#planPins .pin.sold::before{background:#D93B3B}
/* Hover and selection grow from whatever size the dot is set to, so a bigger
   --pin-dot does not make the highlight smaller than the dot itself.
   These are PAINT only — ::before is not the hit area, so a highlight wider
   than 6px confirms the choice without letting the pin steal a neighbour's
   click. That is why they can be generous where the target cannot. */
#planPins .pin.available:hover::before{
  width:calc((var(--pin-dot,4px) + 4px) * var(--plan-zoom,1));
  height:calc((var(--pin-dot,4px) + 4px) * var(--plan-zoom,1));
}
#planPins .pin.on::before{
  background:#FF9E3D;border-color:#1A0E02;border-width:2.5px;
  width:calc((var(--pin-dot,4px) + 6px) * var(--plan-zoom,1));
  height:calc((var(--pin-dot,4px) + 6px) * var(--plan-zoom,1));
}
/* Keyboard focus must be visible — the pins are real buttons and tabbable. */
#planPins .pin:focus-visible{outline:none}
#planPins .pin:focus-visible::before{box-shadow:0 0 0 3px var(--amber-soft)}

.legend .spacer{flex:1}
.ghost.small{padding:5px 11px;font-size:11.5px}

/* ---------- step 3: drawing + unit panel ---------- */
#unitLayout{
  display:grid;grid-template-columns:1fr var(--unit-panel);
  gap:var(--unit-gap);align-items:start;
}
@media (max-width:900px){#unitLayout{grid-template-columns:1fr}}

#unitPanel{
  background:var(--glass);border:1px solid var(--edge);border-radius:14px;
  display:flex;flex-direction:column;max-height:640px;overflow:hidden;
}
.panelhead{padding:12px 13px;border-bottom:1px solid var(--edge);display:grid;gap:8px}
.panelhead label{display:grid;grid-template-columns:44px 1fr;align-items:center;gap:8px}
.panelhead label span{font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.panelhead select{
  background:#121215;color:var(--ink);border:1px solid var(--edge);border-radius:8px;
  padding:6px 8px;font:inherit;font-size:12px;width:100%;
}
.panelhead p{margin:2px 0 0;font-size:11px;color:var(--faint)}

#units{overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:5px;min-height:0}
#units .unit{
  display:grid;grid-template-columns:1fr auto;gap:2px 8px;align-items:center;
  border-radius:9px;padding:7px 9px;text-align:start;
  background:transparent;border:1px solid transparent;
}
#units .unit .code{font-size:12.5px;font-weight:600}
#units .unit .meta{font-size:10px;color:var(--faint);grid-column:1}
#units .unit .price{font-size:11.5px;color:var(--dim);grid-column:2;grid-row:1/3;text-align:end}
#units .unit.available{border-color:rgba(61,214,140,.34)}
#units .unit.available:hover{background:rgba(61,214,140,.13)}
#units .unit.reserved{opacity:.62;cursor:not-allowed}
#units .unit.sold{opacity:.44;cursor:not-allowed}
#units .unit.on{border-color:var(--amber);background:rgba(255,158,61,.17)}

/* ---------- compact building bar (replaces the building cards) ---------- */
#buildingBar{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
#buildingBar .chip{
  width:30px;height:30px;border-radius:9px;display:grid;place-items:center;flex:none;
  background:rgba(255,255,255,.1);font-weight:700;font-size:13px;
}
#buildingBar .chip.on{background:linear-gradient(150deg,var(--amber-soft),var(--amber-deep));color:#1A0E02}
#buildingSel{
  background:#121215;color:var(--ink);border:1px solid var(--edge);border-radius:9px;
  padding:7px 10px;font:inherit;font-size:13px;
}
#buildingMeta{font-size:11.5px;color:var(--faint)}

/* ---------- collapsed filter ---------- */
#fltBox summary{
  cursor:pointer;font-size:11px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--amber-soft);list-style:none;padding:4px 0;
}
#fltBox summary::-webkit-details-marker{display:none}
#fltBox summary::before{content:"▸ ";font-size:10px}
#fltBox[open] summary::before{content:"▾ "}
#fltBox[open]{display:grid;gap:8px;margin-top:4px}

/* ---------- saving callout ---------- */
.saving{
  margin-bottom:16px;padding:13px 15px;border-radius:12px;
  background:rgba(61,214,140,.10);border:1px solid rgba(61,214,140,.36);
}
.saving b{display:block;font-size:17px;font-weight:700;color:#7CE8B4}
.saving span{display:block;font-size:11.5px;color:var(--dim);margin-top:4px;line-height:1.5}

/* ---------- right-to-left ----------
   Arabic, added 2026-08-14. There is deliberately almost nothing here: the
   layout was already built on flexbox, grid and logical text-align, all of
   which follow the document's `dir` on their own. Three things do not.

   NOT included, and it was the first instinct: mirrored grid-template-columns.
   Grid's inline axis already reverses under dir="rtl", so #unitLayout's
   `1fr 290px` puts the panel on the left in Arabic by itself and `grid-column:2`
   lands on the correct side in both. Restating them mirrored flips them a
   second time and puts everything back where it started. */

/* 1. Type. No Arabic webfont is loaded — that would put a download back on the
      first-paint path this app spent a session clearing, and every phone it
      runs on ships a good Arabic face. Name the best ones, let the system pick. */
[lang="ar"] body,
[lang="ar"] button,
[lang="ar"] select,
[lang="ar"] input{
  font-family:"Segoe UI","Noto Naskh Arabic","Geeza Pro",Tahoma,system-ui,sans-serif;
}

/* 2. Figures stay left-to-right. ONLY elements whose whole content is a number
      may go here, and that restriction is the entire point: the first version
      also listed #syncCounts and .unitcard b, which hold Arabic WORDS beside
      their numbers. Forcing direction:ltr on a run of Arabic reverses the order
      its segments appear in, and "530 وحدة · 108 متاحة" rendered as
      "530 متاحة · 108 وحدة" — the two counts silently swapped labels. */
[lang="ar"] .num,
[lang="ar"] .price{
  direction:ltr;
  unicode-bidi:isolate;
}

/* 3. The sheet warnings are English whichever language the UI is in — they name
      spreadsheet rows and unit codes and are read by the agent, not the
      customer. Left in an RTL paragraph the bidi algorithm moves each trailing
      full stop to the front of the line. Set the block LTR and they read
      normally. */
[lang="ar"] .warnings li{direction:ltr;text-align:start}

/* 4. The language switch stays in the PHYSICAL top-right corner in both
      languages. Under plain RTL the header's flex row starts at the right, so
      the switch landed top-LEFT in Arabic and sat on top of the wordmark's
      breathing room. Reversing the main axis in RTL puts the wordmark left and
      the switch right again.

      BOTH rows have to reverse, not just #top. .brand is itself a flex row that
      RTL had already mirrored, so reversing only the outer one left the lockup
      reading [strapline][wordmark] with the logo floating in the middle of the
      bar. Reversing both restores the shape the header has in English: wordmark
      hard against the left edge, switch hard against the right, and only the
      words inside change language. Two phones side by side on a desk then
      differ by their text and nothing else, which is the point. */
[dir="rtl"] #top,
[dir="rtl"] .brand{flex-direction:row-reverse}

/* The names block sits to the RIGHT of the wordmark again, so its divider and
   padding are the base LTR ones and no swap is needed. Only the alignment has
   to be said out loud: the block keeps direction:rtl — the strapline is Arabic
   and must resolve as Arabic — but its lines are pulled back against the
   divider rather than away from it. */
[dir="rtl"] .brand .names{text-align:left}

/* The switch is always LTR: "EN | AR" is a fixed pair of Latin codes, not a
   sentence, and mirroring it in Arabic would only make the same control look
   different on two screens sitting side by side on a desk. */
.langsw{
  display:flex;direction:ltr;flex:none;
  border:1px solid var(--edge);border-radius:999px;
  background:var(--glass);padding:2px;
}
.langsw button{
  background:none;border:0;border-radius:999px;
  padding:4px 10px;font-size:11px;font-weight:700;letter-spacing:.07em;
  color:var(--faint);line-height:1.5;
}
.langsw button:hover{color:var(--ink)}
/* aria-pressed carries the state, so the highlight is driven off it rather
   than off a class of its own — the styling cannot then drift from what a
   screen reader announces. */
.langsw button[aria-pressed="true"]{
  background:var(--glass-hi);color:var(--ink);
  box-shadow:inset 0 0 0 1px var(--edge-hi);
}

/* 5. Refresh moved out of the header and in beside the timestamp. Small enough
      not to compete with the status text it sits next to, but still a real tap
      target on a phone. */
.ghost.tiny{
  padding:3px 10px;font-size:11px;font-weight:600;border-radius:8px;
  flex:none;line-height:1.6;
}
