/* ==========================================================================
   Ikonic Tower - front page
   --------------------------------------------------------------------------
   Palette: "Tonalidades de azul", the client deck of 2026-09-08
   (01_build_files_shared/new_ikonictowers.life and .com_color_palet.pdf).
   Five named blues, all hue ~207:
     AZUL PROFUNDO  #0a1e2f   AZUL MARINO  #0f3b5b   AZUL PETROLEO #0e5a7a
     AZUL ACERO     #4682b4   AZUL CIELO   #87ceeb
   The page is #2e75b0, a step deeper than AZUL ACERO, chosen by the owner on
   2026-09-08. DO NOT "restore" it to AZUL ACERO #4682b4. The owner eyedropped
   this shade by hand in Photoshop as a deliberate contrast fix, because
   #4682b4 could not carry passing text at any text colour and #2e75b0 can.
   The deck's named blue is the reference, not the page value. Travertine sand
   and the warm interior light of the night elevation are unchanged.

   READ THIS BEFORE CHANGING ANY COLOUR. On #2e75b0 the cream --shell measures
   4.28 and --shell-2 measures 4.42, both just under the 4.5:1 AA floor. Pure
   white measures 4.90 and is the only text colour that clears it on the page
   itself. Serif headings clear the 3:1 large-text floor either way.

   So the surfaces run the other way. --surf and below go DEEPER than the
   page rather than lighter, which is why the ramp reads inverted against
   every other build in this repo. That is deliberate: it is what keeps the
   prices, the unit specs and the lead form's labels above 4.5:1, and those
   are the revenue path. Borders go lighter instead, so an edge still reads
   against the page.

   The colour is the owner's decision, taken with the numbers in front of
   them. If they ever want the page itself to pass, the smallest change is
   the base: #3a719f is the same colour a shade deeper and takes cream to
   4.98. One line here, one in life.css. check_contrast.py gates the
   surfaces hard and reports the page as a named exception.
   The organising metaphor: a lit window is a residence still for sale.
   ========================================================================== */

:root{
  /* ink */
  --ink:#2e75b0;
  --ink-2:#34658c;
  --surf:#2d5a7c;
  --surf-2:#274e6c;
  --surf-3:#21435c;
  --line:#7aabd0;
  --line-soft:#6199c3;

  /* brand */
  --sand:#f0dcc0;
  --sand-lo:#d9bc95;
  --sand-hi:#faf0e2;
  --sea:#cfe3f2;
  --sea-lo:#0e5a7a;
  --sea-deep:#072d3d;
  --gold:#f6dcaa;
  --coral:#e2726a;

  /* text */
  --shell:#f4efe8;
  --shell-2:#eef4f9;
  --muted:#e2ecf4;
  /* --faint carries the lead form's labels and its ::placeholder at 9.5px
     uppercase, so the one revenue path on the site is the least readable
     text on it. On the deep surfaces it measures 4.75, which is the whole
     reason those surfaces are deep. Do not darken it to restore the old
     tiering; lighten the surface it sits on instead, and re-run the check. */
  --faint:#d5e4ef;

  /* type */
  --serif:"Instrument Serif","Bodoni MT",Didot,"Didot LT STD","Hoefler Text",
          "Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
  --sans:"Inter","Inter Tight",-apple-system,BlinkMacSystemFont,"Segoe UI",
          Roboto,"Helvetica Neue",Arial,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,"Cascadia Mono","SF Mono",Menlo,
          Consolas,monospace;

  /* system */
  --r-xs:3px; --r-sm:6px; --r-md:11px; --r-lg:18px; --r-xl:28px;
  --ease:cubic-bezier(.22,.68,0,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
  --max:1240px;
  --pad:clamp(20px,4vw,44px);
}

/* --------------------------------------------------------------- reset -- */
*,*::before,*::after{box-sizing:border-box}
*{margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  background:var(--ink);color:var(--shell);
  font-family:var(--sans);font-size:16px;line-height:1.62;
  font-feature-settings:"kern" 1,"liga" 1;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  overflow-x:hidden;
}
img,svg,video{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button,input,select,textarea{font:inherit;color:inherit}
::selection{background:var(--sand);color:var(--ink)}

.wrap{width:100%;max-width:var(--max);margin-inline:auto;padding-inline:var(--pad)}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:-9999px;top:0;z-index:200;background:var(--sand);
      color:var(--ink);padding:12px 20px;font-weight:600}
.skip:focus{left:12px;top:12px}

:where(a,button,select,input,textarea,[tabindex]):focus-visible{
  outline:2px solid var(--sea);outline-offset:3px;border-radius:var(--r-xs);
}

/* film grain, sits above everything, never intercepts the pointer */
body::after{
  content:"";position:fixed;inset:0;z-index:300;pointer-events:none;opacity:.035;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ------------------------------------------------------- type primitives */
.eyebrow{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.26em;
  text-transform:uppercase;color:var(--sand);font-weight:500;
  display:flex;align-items:center;gap:14px;
}
.eyebrow::after{content:"";height:1px;flex:1;max-width:64px;
  background:linear-gradient(90deg,var(--sand-lo),transparent)}
.eyebrow.c::after{display:none}

h1,h2,h3{font-family:var(--serif);font-weight:400;letter-spacing:-.012em;
  line-height:1.08;text-wrap:balance}
h2{font-size:clamp(31px,4.6vw,54px);line-height:1.1}
h3{font-size:clamp(20px,2.2vw,26px);line-height:1.2}
.lede{color:var(--shell-2);font-size:clamp(16px,1.35vw,18.5px);max-width:60ch;
  text-wrap:pretty}

.num{font-family:var(--mono);font-variant-numeric:tabular-nums;letter-spacing:-.02em}

/* section shell */
/* ── VERTICAL RHYTHM ─────────────────────────────────────────────────────────
   ONE token owns the gap between every section, so the gaps cannot drift apart
   again. --sec-y is HALF the gap: each section contributes its own half at the
   boundary, so section-to-section is always exactly one --sec-gap.

   Measured before this change, at 1280px, the gaps down the front page were
   141, 213, 213, 141, 141, 282, 282, 282, 282. Four different values. The 282s
   came from two full-padded sections meeting; the 141s from the inline
   `style="padding-top:0"` that three sections carried; the 213s from .rail
   using its own smaller value. Every one of those is now one number.

   The old standard gap was 2 x clamp(80,11vw,150) = clamp(160,22vw,300). A 30%
   reduction took it to clamp(112,15.4vw,210); the owner halved that again on
   2026-09-08 because the page read empty. At 1280px the gap goes 282 -> 99px.

   DO NOT reintroduce `style="padding-top:0"` on a section to tighten one
   boundary. That is what produced three of the four values above. Change the
   token, or nothing. */
:root{
  --sec-gap:clamp(56px,7.7vw,105px);
  --sec-y:calc(var(--sec-gap) / 2);
}
.sec{position:relative;padding:var(--sec-y) 0}
.sec-head{max-width:680px;margin-bottom:clamp(38px,5vw,64px)}
.sec-head .eyebrow{margin-bottom:20px}
.sec-head .lede{margin-top:20px}

/* Reveal. THE HIDING HALF IS SCOPED TO .reveal, NOT .js, AND THAT DISTINCTION
   IS THE WHOLE POINT.

   It used to be .js, which the inline head script sets unconditionally. So the
   27 .rv blocks and 3 .rise headline lines on this page were hidden the moment
   the document parsed, and ONLY home.js ever took that off, by adding .in and
   .loaded. If home.js 404'd after a partial deploy, was blocked by an
   extension, or threw anywhere before the reveal ran (burger.addEventListener
   has no null guard), the visitor got a dark hero photograph with no headline
   and no content, on the page that carries the entire sales pitch. The 2500ms
   failsafe existed, but it lived inside the file that had failed.

   Now the inline head script adds .reveal AND arms a timeout to strip it again,
   so the failsafe survives home.js dying. home.js cancels that timeout once it
   is running and takes ownership of the reveal. Scripting off is unaffected:
   neither class is ever set, so nothing is hidden and the page reads plainly. */
.reveal .rv{opacity:0;transform:translateY(26px);
    transition:opacity .9s var(--ease-out),transform .9s var(--ease-out)}
.reveal .rv.in{opacity:1;transform:none}
.rv-d1{transition-delay:.08s}.rv-d2{transition-delay:.16s}
.rv-d3{transition-delay:.24s}.rv-d4{transition-delay:.32s}

/* ------------------------------------------------------------- buttons -- */
.btn{
  --bg:transparent;--fg:var(--shell);--bd:var(--line);
  position:relative;display:inline-flex;align-items:center;gap:10px;
  padding:14px 26px;border:1px solid var(--bd);background:var(--bg);color:var(--fg);
  font-size:13.5px;font-weight:500;letter-spacing:.02em;border-radius:var(--r-sm);
  cursor:pointer;overflow:hidden;isolation:isolate;
  transition:color .45s var(--ease),border-color .45s var(--ease),
             transform .35s var(--ease),box-shadow .45s var(--ease);
}
/* the fill sweeps up from the bottom edge on hover */
.btn::before{
  content:"";position:absolute;inset:0;z-index:-1;background:var(--sand);
  transform:translateY(101%);transition:transform .5s var(--ease);
}
.btn:hover,.btn:focus-visible{color:var(--ink);border-color:var(--sand);
  transform:translateY(-2px);box-shadow:0 14px 34px -18px rgba(220,185,141,.75)}
.btn:hover::before,.btn:focus-visible::before{transform:translateY(0)}
.btn:active{transform:translateY(0)}
.btn .ar{transition:transform .45s var(--ease)}
.btn:hover .ar{transform:translateX(5px)}

.btn-fill{--bg:var(--sand);--fg:var(--ink);--bd:var(--sand)}
.btn-fill::before{background:var(--sand-hi)}
.btn-fill:hover,.btn-fill:focus-visible{color:var(--ink)}

.btn-sea{--bd:var(--sea-lo)}
.btn-sea::before{background:var(--sea)}
.btn-sea:hover,.btn-sea:focus-visible{border-color:var(--sea);
  box-shadow:0 14px 34px -18px rgba(70,130,180,.8)}

/* a text link that draws its own rule */
.lnk{position:relative;color:var(--sand);font-size:13.5px;font-weight:500;
  display:inline-flex;align-items:center;gap:8px;padding-bottom:3px}
.lnk::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:currentColor;transform:scaleX(0);transform-origin:right;
  transition:transform .5s var(--ease)}
.lnk:hover::after,.lnk:focus-visible::after{transform:scaleX(1);transform-origin:left}
.lnk .ar{transition:transform .4s var(--ease)}
.lnk:hover .ar{transform:translateX(4px)}

/* ================================================================== nav == */
.nav{position:fixed;inset:0 0 auto;z-index:100;border-bottom:1px solid transparent;
  transition:background .5s var(--ease),border-color .5s var(--ease),
             backdrop-filter .5s var(--ease),padding .5s var(--ease)}
.nav-in{max-width:var(--max);margin-inline:auto;padding:20px var(--pad);
  display:flex;align-items:center;gap:clamp(16px,2.4vw,32px)}
.nav.solid{background:rgba(7,23,38,.78);backdrop-filter:blur(18px) saturate(1.4);
  border-bottom-color:var(--line-soft)}
.nav.solid .nav-in{padding-block:13px}

/* The bar is transparent until it goes .solid on scroll. That was fine over a
   night render and is not over a morning one: the top strip of the plate is the
   brightest part of it, and the owner reported the menu as hard to read on
   2026-09-09. A gradient band behind the bar gives the links a known luminance
   without turning the bar into a box, and it fades out as .solid takes over so
   the two never stack.
   :has(.hero) scopes it to the two pages that have a photograph up there. Where
   :has is unsupported the rule is dropped and the bar is simply back to what it
   is today, so the fallback is the current state and not a broken one. */
body:has(.hero) .nav::before{content:"";position:absolute;inset:0 0 auto;z-index:-1;
  height:230%;pointer-events:none;
  background:linear-gradient(180deg,rgba(3,8,13,.74) 0%,rgba(3,8,13,.46) 44%,
                             rgba(3,8,13,.18) 74%,transparent 100%);
  transition:opacity .5s var(--ease)}
body:has(.hero) .nav.solid::before{opacity:0}
/* The band alone does not carry the small mono type, which sits at --faint.
   Same two layer shadow the hero copy uses, dropped the moment the bar is
   solid because a solid bar already gives it a flat background. */
body:has(.hero) .nav:not(.solid) .mark b,
body:has(.hero) .nav:not(.solid) .mark small,
body:has(.hero) .nav:not(.solid) .nav-links a,
body:has(.hero) .nav:not(.solid) .lang a{
  text-shadow:0 1px 2px rgba(3,6,8,.62),0 2px 10px rgba(3,6,8,.42)}
body:has(.hero) .nav:not(.solid) .lang a[aria-current="page"]{text-shadow:none}

.mark{display:flex;flex-direction:column;line-height:1;flex:none}
.mark b{font-family:var(--serif);font-size:20px;letter-spacing:.2em;color:var(--sand);
  font-weight:400;transition:color .4s var(--ease),letter-spacing .5s var(--ease)}
.mark small{font-family:var(--mono);font-size:8px;letter-spacing:.34em;color:var(--faint);
  margin-top:5px;transition:color .4s var(--ease)}
.mark:hover b{color:var(--sand-hi);letter-spacing:.24em}
.mark:hover small{color:var(--muted)}

.nav-links{display:flex;gap:clamp(14px,1.9vw,28px);margin-left:auto;list-style:none}
.nav-links a{position:relative;font-size:13px;color:var(--shell-2);letter-spacing:.015em;
  padding-block:6px;transition:color .35s var(--ease);white-space:nowrap}
.nav-links a::after{content:"";position:absolute;left:0;bottom:0;width:100%;height:1px;
  background:var(--sand);transform:scaleX(0);transform-origin:right;
  transition:transform .45s var(--ease)}
.nav-links a:hover{color:var(--sand)}
.nav-links a:hover::after{transform:scaleX(1);transform-origin:left}

.lang{display:flex;border:1px solid var(--line);border-radius:var(--r-sm);
  overflow:hidden;flex:none}
/* Real links now, one page per locale, so the switch works without scripting. */
.lang a{display:block;background:none;border:0;font-family:var(--mono);font-size:10.5px;
  font-weight:500;letter-spacing:.1em;color:var(--muted);padding:6px 11px;
  cursor:pointer;transition:background .35s var(--ease),color .35s var(--ease)}
.lang a:hover{color:var(--sand);background:rgba(220,185,141,.08)}
.lang a[aria-current="page"]{background:var(--sand);color:var(--ink)}

.nav-cta{flex:none;padding:10px 18px;font-size:12.5px}
.burger{display:none;background:none;border:1px solid var(--line);border-radius:var(--r-sm);
  width:42px;height:38px;cursor:pointer;position:relative;flex:none;
  transition:border-color .35s var(--ease)}
.burger:hover{border-color:var(--sand)}
.burger i{position:absolute;left:11px;right:11px;height:1px;background:var(--shell);
  transition:transform .45s var(--ease),opacity .3s var(--ease)}
.burger i:nth-child(1){top:14px}
.burger i:nth-child(2){top:19px}
.burger i:nth-child(3){top:24px}
.burger[aria-expanded="true"] i:nth-child(1){transform:translateY(5px) rotate(45deg)}
.burger[aria-expanded="true"] i:nth-child(2){opacity:0}
.burger[aria-expanded="true"] i:nth-child(3){transform:translateY(-5px) rotate(-45deg)}

.drawer{position:fixed;inset:0;z-index:99;background:rgba(7,23,38,.97);
  backdrop-filter:blur(20px);display:flex;flex-direction:column;justify-content:center;
  gap:6px;padding:0 var(--pad);opacity:0;visibility:hidden;
  transition:opacity .45s var(--ease),visibility .45s}
.drawer.open{opacity:1;visibility:visible}
.drawer a{font-family:var(--serif);font-size:clamp(27px,7vw,42px);color:var(--shell);
  padding:9px 0;border-bottom:1px solid var(--line-soft);
  transition:color .35s var(--ease),padding-left .45s var(--ease)}
.drawer a:hover{color:var(--sand);padding-left:14px}

@media (max-width:1080px){
  .nav-links,.nav-cta{display:none}
  .burger{display:block;margin-left:auto}
  .lang{margin-left:0}
}

/* ================================================================= hero == */
/* The hero grows past 100svh when the copy needs it. Clipping lives on the
   background layer, not here, so a short or landscape screen never crops the
   headline. */
.hero{position:relative;min-height:100svh;display:flex;align-items:flex-end;
  padding:132px 0 0}
.hero-bg{position:absolute;inset:0;z-index:0;overflow:clip}
/* Was brightness(.44), which existed to hold back a night render lit from
   inside. The plate is now a morning elevation, so the grade comes off almost
   entirely; leaving it would have put the hero straight back to dark, which is
   the thing the swap was for. Everything below is what the white h1 needs to
   stay readable, and it is measured, not guessed: see the veils. */
/* Right aligned on purpose. The beach and the sea live in the last sixth of
   the plate, so anything short of 100% crops the one thing that says the
   building is on the water. */
.hero-bg img{width:100%;height:100%;object-fit:cover;object-position:100% 50%;
  filter:brightness(.86) contrast(1.02) saturate(1.04);
  transform:scale(1.03);animation:drift 34s var(--ease) infinite alternate}
/* Was 1.09 to 1.17, cut when the plate was only 2461 px wide and a 17%
   magnification was spending resolution the file did not have. The plate is
   3840 px wide as of 2026-09-09 so the headroom is back, but the smaller drift
   was kept: the owner approved this motion and the zoom was never the point. */
@keyframes drift{
  from{transform:scale(1.03) translate3d(0,0,0)}
  to{transform:scale(1.08) translate3d(-1.6%,-1.0%,0)}
}
/* three stacked veils: colour grade, vertical falloff, corner vignette */
/* The horizontal veil holds its weight much further right than it used to,
   then falls off fast. That shape is deliberate: the copy column ends at 61%
   of the frame on a desktop, so everything left of it can be dark for the text
   while the sea and the sky on the right stay at full morning brightness.
   Measured on the composited hero, worst pixel behind the type, not average:
   h1 6.98:1 against the 3:1 large-text floor, body 12.22:1, eyebrow 5.53:1
   against the 4.5 floor it needs as small text. */
.hero-bg::before{content:"";position:absolute;inset:0;
  background:
    linear-gradient(96deg,rgba(3,6,8,.94) 0%,rgba(3,6,8,.89) 34%,
                    rgba(5,15,25,.52) 62%,rgba(7,23,38,.10) 82%,
                    rgba(10,30,47,.26) 100%),
    linear-gradient(180deg,rgba(7,23,38,.66) 0%,transparent 24%,
                    transparent 52%,rgba(7,23,38,.90) 100%)}
.hero-bg::after{content:"";position:absolute;inset:0;
  background:radial-gradient(124% 90% at 48% 42%,transparent 34%,rgba(3,6,8,.62) 100%)}
/* On a phone the copy column is the whole frame, so a veil that only holds the
   left third leaves the paragraph sitting on lit glass. It goes flat instead.
   Same measurement, worst pixel behind the type, at 375 px: h1 5.07:1,
   body 5.42:1, eyebrow 8.65:1. The 720 px cut matches the <source> that swaps
   in the portrait plate, so the veil and the crop change together. */
@media (max-width:720px){
  .hero-bg::before{
    background:
      linear-gradient(rgba(4,12,20,.55),rgba(4,12,20,.55)),
      linear-gradient(180deg,rgba(7,23,38,.66) 0%,transparent 24%,
                      transparent 52%,rgba(7,23,38,.90) 100%)}
}

/* The hero type sits on a photograph, and a ratio measured against the worst
   pixel can pass while the letters still read weak, because a photograph puts
   a different luminance under every stroke. The owner reported exactly that on
   2026-09-09 with every number already passing, and chose the shadow over
   darkening the plate further.
   A scrim panel behind the copy column was built the same day and REMOVED the
   same day: its soft edge still read as a distinct line on the plate, it
   reached too far right, and its horizontal bleed pushed the phone layout off
   the right of the page. Do not build it again. The owner's instruction is that
   the words carry a shadow and the photograph carries nothing.
   Flat black at 98% on the two dense layers, at the owner's request on
   2026-09-09, rather than the near black of the veils. The two outer layers
   rose with them so the ladder still fades instead of flattening into a slab.
   The same values run on the phone and the desktop: the owner asked for one
   number, not a per breakpoint pair.
   It is a halo, not a drop shadow. Every layer sits at offset 0 0, so the dark
   comes out of the glyph evenly on all four sides and reads as a stroke with a
   faded edge, which is what the owner asked for on 2026-09-09. A drop shadow
   only protects the side it falls on and leaves the opposite edge of the stroke
   sitting on bare photograph.
   The blur ladder is what makes the fade. CSS text-shadow has no spread, so the
   reach has to be built by stacking: the tight layers give the letter its own
   dense edge, the wide ones carry the dark out to roughly 8 px and fall away.
   A blur radius reaches about half its value past the glyph, so 16 px is the
   layer doing the 8 px, not a 16 px halo.
   Applied only inside .hero. Nothing else on the site has type over a photo. */
.hero h1,
.hero-copy p{text-shadow:
  0 0 3px rgba(0,0,0,.98),0 0 6px rgba(0,0,0,.98),
  0 0 10px rgba(0,0,0,.82),0 0 16px rgba(0,0,0,.58)}
/* The eyebrow is 10 px mono. The same ladder on it reads as a smudge rather
   than a stroke, so it gets the same shape at two thirds the reach. */
.hero .eyebrow{text-shadow:
  0 0 2px rgba(0,0,0,.98),0 0 4px rgba(0,0,0,.98),
  0 0 7px rgba(0,0,0,.8),0 0 11px rgba(0,0,0,.56)}
/* The phone gets the same halo 50% wider and at 98% on every layer, at the
   owner's direction on 2026-09-09. It needs it: the desktop copy column sits on
   the dark left of the plate, while on a phone the column is the whole frame
   and the lede lands on open sky and sea, which is the brightest thing in the
   photograph. Every radius here is the desktop radius times 1.5. The 720 px cut
   matches the <source> that swaps in the portrait plate, so the halo and the
   crop change together. */
@media (max-width:720px){
  .hero h1,
  .hero-copy p{text-shadow:
    0 0 4.5px rgba(0,0,0,.98),0 0 9px rgba(0,0,0,.98),
    0 0 15px rgba(0,0,0,.98),0 0 24px rgba(0,0,0,.98)}
  .hero .eyebrow{text-shadow:
    0 0 3px rgba(0,0,0,.98),0 0 6px rgba(0,0,0,.98),
    0 0 10.5px rgba(0,0,0,.98),0 0 16.5px rgba(0,0,0,.98)}
}

.hero-in{position:relative;z-index:3;width:100%;padding-bottom:clamp(30px,5vw,58px)}
.hero-grid{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:clamp(28px,5vw,70px);
  align-items:end}
.hero-copy{max-width:790px}
.hero .eyebrow{margin-bottom:26px}
.hero h1{font-size:clamp(46px,8.2vw,104px);line-height:.98;letter-spacing:-.028em;
  margin-bottom:26px}
.hero h1 em{font-style:italic;color:var(--sand)}
/* each line rises on load */
.rise{display:block;overflow:hidden}
.rise > span{display:block;transition:transform 1.15s var(--ease-out)}
.reveal .rise > span{transform:translateY(105%)}
.loaded .rise > span{transform:none}
.loaded .rise:nth-child(2) > span{transition-delay:.11s}
.loaded .rise:nth-child(3) > span{transition-delay:.22s}

.hero .lede{margin-bottom:36px;max-width:50ch;font-size:clamp(16px,1.5vw,19px)}
.hero-cta{display:flex;gap:12px;flex-wrap:wrap}

/* live availability card, right of the headline */
.pulse{position:relative;min-width:246px;padding:24px 26px 22px;
  border:1px solid var(--line);border-radius:var(--r-md);
  background:linear-gradient(158deg,rgba(13,39,64,.86),rgba(7,23,38,.72));
  backdrop-filter:blur(16px);overflow:hidden;
  transition:border-color .5s var(--ease),transform .5s var(--ease),
             box-shadow .5s var(--ease)}
.pulse::before{content:"";position:absolute;top:0;left:0;right:0;height:1px;
  background:linear-gradient(90deg,transparent,var(--sea),transparent);
  transform:translateX(-100%);transition:transform 1.1s var(--ease)}
.pulse:hover{border-color:var(--sea-lo);transform:translateY(-4px);
  box-shadow:0 26px 60px -34px rgba(70,130,180,.6)}
.pulse:hover::before{transform:translateX(100%)}
.pulse-top{display:flex;align-items:center;gap:9px;margin-bottom:16px}
.dot{width:6px;height:6px;border-radius:50%;background:var(--sea);flex:none;
  box-shadow:0 0 0 0 rgba(70,130,180,.65);animation:ping 2.6s var(--ease) infinite}
@keyframes ping{
  0%{box-shadow:0 0 0 0 rgba(70,130,180,.55)}
  70%{box-shadow:0 0 0 11px rgba(70,130,180,0)}
  100%{box-shadow:0 0 0 0 rgba(70,130,180,0)}
}
.pulse-top span{font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--muted)}
.pulse b{display:block;font-family:var(--mono);font-size:clamp(40px,4.6vw,54px);
  font-weight:400;line-height:1;color:var(--shell);letter-spacing:-.04em}
.pulse b i{font-style:normal;font-size:.42em;color:var(--muted);margin-left:6px;
  letter-spacing:0}
.pulse p{margin-top:11px;font-size:12.5px;color:var(--shell-2);line-height:1.5}
.pulse .bar{margin-top:16px;height:3px;border-radius:2px;background:var(--surf-3);
  overflow:hidden}
/* The fill width is the AVAILABLE share of the tower and it is emitted by
   build_home.py as --fill on the .bar element. It used to be typed here as
   44.9%, so selling a residence changed the number above the bar and left the
   bar itself frozen. A stylesheet is the wrong place to record inventory. */
.pulse .bar i{display:block;height:100%;width:var(--fill,44.9%);border-radius:2px;
  background:linear-gradient(90deg,var(--sea-lo),var(--sea));
  transition:width 1.7s var(--ease-out) .35s}
.reveal .pulse .bar i{width:0}
.reveal .pulse.in .bar i{width:var(--fill,44.9%)}
.pulse .bar-cap{display:flex;justify-content:space-between;margin-top:8px;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;color:var(--faint)}

.scroll-cue{position:absolute;bottom:26px;left:50%;translate:-50% 0;z-index:4;
  display:flex;flex-direction:column;align-items:center;gap:10px;
  font-family:var(--mono);font-size:9px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--faint);background:none;border:0;cursor:pointer;
  transition:color .4s var(--ease)}
.scroll-cue:hover{color:var(--sand)}
.scroll-cue i{width:1px;height:40px;overflow:hidden;position:relative;
  background:var(--line)}
.scroll-cue i::after{content:"";position:absolute;inset:0;background:var(--sand);
  animation:slide 2.4s var(--ease) infinite}
@keyframes slide{0%{transform:translateY(-100%)}60%,100%{transform:translateY(100%)}}
@media (max-width:900px),(max-height:820px){.scroll-cue{display:none}}
@media (max-width:1024px){
  .hero-grid{grid-template-columns:1fr}
  .pulse{width:100%;max-width:340px}
}

/* ================================================================ ticker = */
/* The ticker sits flush against the bottom of the hero on purpose: it is a band
   riding the hero edge, not a row of its own. Below it, it owes the same half
   gap every other row contributes, or the first content section would sit
   closer than all the rest. */
.ticker{border-block:1px solid var(--line-soft);background:var(--ink-2);
  overflow:hidden;position:relative;margin-bottom:var(--sec-y)}
.ticker::before,.ticker::after{content:"";position:absolute;top:0;bottom:0;width:110px;
  z-index:2;pointer-events:none}
.ticker::before{left:0;background:linear-gradient(90deg,var(--ink-2),transparent)}
.ticker::after{right:0;background:linear-gradient(270deg,var(--ink-2),transparent)}
.ticker-track{display:flex;width:max-content;animation:march 46s linear infinite}
.ticker:hover .ticker-track{animation-play-state:paused}
@keyframes march{to{transform:translateX(-50%)}}
.ticker-track span{display:flex;align-items:center;gap:16px;padding:14px 24px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.17em;text-transform:uppercase;
  color:var(--muted);white-space:nowrap}
.ticker-track span::after{content:"";width:3px;height:3px;border-radius:50%;
  background:var(--sand-lo)}
.ticker-track b{color:var(--sand);font-weight:500}

/* ================================================================ tower == */
.tower{background:var(--ink-2);position:relative;overflow:clip}
.tower::before{content:"";position:absolute;inset:0;z-index:0;opacity:.2;
  background:url("../img/tower/fachada.jpg?v=202609092002") center 26%/cover no-repeat;
  mask-image:linear-gradient(180deg,transparent,#000 22%,#000 62%,transparent)}
.tower::after{content:"";position:absolute;inset:0;z-index:1;
  background:radial-gradient(96% 62% at 50% 40%,rgba(220,185,141,.055),transparent 68%)}
.tower > *{position:relative;z-index:2}

/* Both columns stretch to the taller of the two and each distributes its own
   slack, so the elevation and the detail-plus-plan column start and finish on
   the same lines whichever one happens to be taller at a given width. */
.tower-grid{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:clamp(28px,4.6vw,68px);align-items:stretch}

/* the elevation: 12 rows of cells, a lit cell is a residence still for sale */
.elev{border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;
  background:linear-gradient(178deg,rgba(11,34,51,.94),rgba(7,23,38,.97));
  position:relative;display:flex;flex-direction:column}
/* cursor-tracked spotlight, positions come from JS custom properties */
.elev::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  opacity:0;transition:opacity .6s var(--ease);
  background:radial-gradient(320px circle at var(--mx,50%) var(--my,50%),
             rgba(220,185,141,.13),transparent 68%)}
.elev:hover::before{opacity:1}

.elev-head{display:flex;align-items:baseline;gap:14px;padding:18px 22px;
  border-bottom:1px solid var(--line-soft);position:relative;z-index:1}
.elev-head h3{font-size:17px}
.elev-head span{margin-left:auto;font-family:var(--mono);font-size:10px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}

/* The twelve floors take whatever height is left and share it evenly, which is
   what opens the gaps between the rows. */
.floors{padding:14px 12px 18px;position:relative;z-index:1;
  flex:1;display:flex;flex-direction:column;justify-content:space-evenly}
.floor{display:grid;grid-template-columns:46px minmax(0,1fr) 62px;align-items:center;
  gap:12px;padding:3px 10px;border-radius:var(--r-sm);position:relative;
  transition:background .4s var(--ease)}
.floor:hover{background:rgba(220,185,141,.05)}
/* a hairline sweeps across the floor on hover */
.floor::after{content:"";position:absolute;left:10px;right:10px;bottom:0;height:1px;
  background:linear-gradient(90deg,transparent,var(--sand-lo),transparent);
  transform:scaleX(0);transition:transform .6s var(--ease)}
.floor:hover::after{transform:scaleX(1)}

.floor-n{font-family:var(--mono);font-size:11px;color:var(--faint);letter-spacing:.06em;
  transition:color .35s var(--ease)}
.floor:hover .floor-n{color:var(--sand)}
.floor-n b{font-weight:500;font-size:13px;color:var(--muted);
  transition:color .35s var(--ease)}
.floor:hover .floor-n b{color:var(--sand-hi)}

.cells{display:flex;gap:5px;justify-content:center}
.cells.ph{justify-content:center;gap:8px}

/* one cell = one residence */
.cell{position:relative;flex:1 1 0;min-width:0;height:26px;border-radius:var(--r-xs);
  border:1px solid var(--line);background:var(--surf);cursor:pointer;padding:0;
  transition:background .4s var(--ease),border-color .4s var(--ease),
             transform .35s var(--ease),box-shadow .45s var(--ease)}
.cell::before{content:"";position:absolute;inset:3px;border-radius:1px;
  background:rgba(255,255,255,.02);transition:background .4s var(--ease)}
.cells.ph .cell{max-width:76px;height:32px}

/* sold: a dark window */
.cell[data-s="vendido"]{background:#0a1c2b;border-color:#16334b;cursor:default}
.cell[data-s="vendido"]::before{background:rgba(255,255,255,.012)}

/* reserved: outlined, waiting */
.cell[data-s="reservado"]{border-color:rgba(227,169,78,.5);
  background:rgba(227,169,78,.07)}
.cell[data-s="reservado"]::before{background:rgba(227,169,78,.1)}

/* available: a lit window */
.cell[data-s="disponible"]{border-color:rgba(220,185,141,.42);
  background:linear-gradient(180deg,rgba(220,185,141,.2),rgba(220,185,141,.09));
  box-shadow:0 0 15px -6px rgba(220,185,141,.5)}
.cell[data-s="disponible"]::before{background:rgba(240,220,192,.14)}
.cell[data-s="disponible"]:hover,.cell[data-s="disponible"]:focus-visible{
  border-color:var(--sand-hi);transform:translateY(-3px) scale(1.06);
  background:linear-gradient(180deg,var(--sand),rgba(220,185,141,.5));
  box-shadow:0 0 30px -4px rgba(220,185,141,.85),0 10px 22px -12px rgba(0,0,0,.9);
  z-index:3}
.cell[data-s="reservado"]:hover{border-color:var(--gold);transform:translateY(-2px)}
.cell[aria-pressed="true"]{outline:1px solid var(--sand-hi);outline-offset:2px}

/* the penthouse levels get their own weight */
.floor.crown{padding-block:6px}
.floor.crown .floor-n b{color:var(--sand)}

.floor-cap{font-family:var(--mono);font-size:10px;color:var(--faint);text-align:right;
  letter-spacing:.04em;transition:color .35s var(--ease)}
.floor:hover .floor-cap{color:var(--shell-2)}
.floor-cap.none{color:#3d5670}

.elev-key{display:flex;flex-wrap:wrap;gap:16px;padding:14px 22px 18px;
  border-top:1px solid var(--line-soft);position:relative;z-index:1}
.elev-key span{display:flex;align-items:center;gap:8px;font-family:var(--mono);
  font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint)}
.elev-key i{width:14px;height:9px;border-radius:2px;border:1px solid var(--line);
  background:var(--surf)}
.elev-key i.a{border-color:rgba(220,185,141,.5);
  background:linear-gradient(180deg,rgba(220,185,141,.35),rgba(220,185,141,.12));
  box-shadow:0 0 10px -3px rgba(220,185,141,.7)}
.elev-key i.r{border-color:rgba(227,169,78,.55);background:rgba(227,169,78,.12)}
.elev-key i.v{background:#0a1c2b;border-color:#16334b}

/* the detail panel */
/* The detail card and the floor plan travel together as one column. Slack goes
   between them, so the plan's bottom edge lands on the elevation's. */
.tower-side{display:flex;flex-direction:column;justify-content:space-between;gap:12px}
.detail{border:1px solid var(--line);border-radius:var(--r-lg);
  background:linear-gradient(160deg,var(--surf),rgba(7,23,38,.9));overflow:hidden;
  transition:border-color .5s var(--ease),box-shadow .5s var(--ease)}
.detail:hover{border-color:var(--line);box-shadow:0 30px 70px -46px rgba(0,0,0,.95)}
.detail-hd{padding:24px 26px 20px;border-bottom:1px solid var(--line-soft);
  display:flex;align-items:flex-start;gap:16px}
.detail-hd .u{font-family:var(--serif);font-size:clamp(38px,4.2vw,52px);line-height:.9;
  color:var(--shell)}
.detail-hd .meta{margin-left:auto;text-align:right}
.tag{display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);
  font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;padding:5px 10px;
  border-radius:100px;border:1px solid var(--line);color:var(--muted)}
.tag.a{color:var(--sand);border-color:rgba(220,185,141,.4);
  background:rgba(220,185,141,.07)}
.tag.r{color:var(--gold);border-color:rgba(227,169,78,.4);background:rgba(227,169,78,.07)}
.tag.v{color:var(--faint)}
.detail-hd .lvl{display:block;margin-top:9px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}

.specs{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line-soft)}
.spec{background:var(--surf);padding:18px 14px;text-align:center;
  transition:background .4s var(--ease)}
.spec:hover{background:var(--surf-2)}
.spec b{display:block;font-family:var(--mono);font-size:20px;font-weight:400;
  color:var(--shell);letter-spacing:-.03em;line-height:1}
.spec b sub{font-size:.55em;color:var(--muted);vertical-align:baseline;margin-left:2px}
.spec span{display:block;margin-top:8px;font-family:var(--mono);font-size:8.5px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}

.price{padding:24px 26px;display:flex;align-items:flex-end;gap:14px;flex-wrap:wrap;
  border-top:1px solid var(--line-soft)}
.price .p{font-family:var(--mono);font-size:clamp(24px,2.7vw,32px);color:var(--sand);
  letter-spacing:-.03em;line-height:1}
.price .c{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;color:var(--faint);
  padding-bottom:3px}
.price .n{width:100%;font-size:12.5px;color:var(--muted);line-height:1.5}
.detail-cta{display:flex;gap:10px;padding:0 26px 26px;flex-wrap:wrap}
.detail-cta .btn{flex:1 1 auto;justify-content:center;padding-block:13px;font-size:13px}

/* ------------------------------------------------- floor plan, under detail */
/* The plate is a light architectural render. Against the dark column it reads
   as a lit drafting table, which is the point. It is the plan of the whole
   level, not of the single unit, and the caption says so. */
.plan{border:1px solid var(--line);border-radius:var(--r-lg);
  overflow:hidden;background:var(--surf);
  transition:border-color .5s var(--ease),box-shadow .5s var(--ease)}
.plan:hover{border-color:rgba(220,185,141,.42);
  box-shadow:0 30px 68px -44px rgba(0,0,0,.95)}

.plan-media{position:relative;overflow:hidden;aspect-ratio:1000/564;
  background:#08192a;cursor:zoom-in}
.plan-media img{width:100%;height:100%;object-fit:contain;
  filter:brightness(.86) contrast(1.03) saturate(.94);
  transform-origin:var(--px,50%) var(--py,50%);
  transition:transform .5s var(--ease),filter .55s var(--ease),opacity .3s var(--ease)}
/* cursor-tracked magnification, origin follows the pointer */
@media (hover:hover) and (pointer:fine){
  .plan-media:hover img{transform:scale(2.2);
    filter:brightness(1.04) contrast(1.06) saturate(1)}
  .plan-media:hover img{transition:transform .28s var(--ease),filter .4s var(--ease)}
}
.plan-media.zoom img{transform:scale(2.2);filter:brightness(1.04) contrast(1.06)}
.plan-media.swap img{opacity:0}

.plan-hint{position:absolute;left:14px;bottom:12px;z-index:2;font-family:var(--mono);
  font-size:9.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);
  background:rgba(7,23,38,.74);backdrop-filter:blur(8px);padding:6px 11px;
  border-radius:100px;border:1px solid var(--line-soft);pointer-events:none;
  transition:opacity .4s var(--ease),transform .4s var(--ease)}
.plan-media:hover .plan-hint{opacity:0;transform:translateY(5px)}

.plan figcaption{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;
  padding:15px 20px;border-top:1px solid var(--line-soft)}
.plan figcaption b{font-family:var(--serif);font-size:19px;font-weight:400;
  color:var(--shell);transition:color .4s var(--ease)}
.plan:hover figcaption b{color:var(--sand)}
.plan figcaption span{margin-left:auto;font-family:var(--mono);font-size:10px;
  letter-spacing:.13em;text-transform:uppercase;color:var(--faint);
  transition:color .4s var(--ease)}
.plan:hover figcaption span{color:var(--shell-2)}

@media (max-width:960px){
  .tower-grid{grid-template-columns:1fr}
  .specs{grid-template-columns:repeat(2,1fr)}
  .plan-media{cursor:pointer}
}

/* ============================================================ price rail = */
/* The price rail is a narrow band, but it is still a row, so it carries the
   same half gap as everything else. Its own smaller value is what made the two
   boundaries around it 213px while its neighbours were 282px. */
.rail{border-block:1px solid var(--line-soft);background:var(--ink-2);
  padding:var(--sec-y) 0}
.rail-hd{display:flex;align-items:flex-end;gap:20px;flex-wrap:wrap;margin-bottom:38px}
.rail-hd h3{font-size:clamp(21px,2.3vw,27px)}
.rail-hd p{margin-left:auto;font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint)}
/* THE TRACK SCROLLS, THE INNER HOLDS THE AXIS.
   Measured at 375px before this change: 35 pins in a 335px track, median gap
   5px, MINIMUM GAP 0px. Pins physically overlapped and no single one could be
   tapped. A price axis with 77 marks on it needs room, and a phone does not
   have it, so the axis keeps its own width and the track scrolls to it.
   padding-top is what keeps the pin labels visible: overflow-x:auto clips the
   y axis too, and the labels sit above the pins. */
/* padding-inline is load bearing. A pin label is centred on its pin and can be
   110px wide, so the labels on the first and last pins hang past the ends of
   the axis. Absolutely positioned children still count toward scroll width, so
   without this the desktop track scrolled 65px for labels nobody could reach.
   The padding gives them somewhere to hang. */
.rail-track{position:relative;overflow-x:auto;overflow-y:hidden;padding-top:38px;
  padding-inline:68px;
  scrollbar-width:thin;scrollbar-color:var(--line) transparent;
  overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch}
.rail-track::-webkit-scrollbar{height:3px}
.rail-track::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px}
.rail-inner{position:relative;height:96px;min-width:100%;
  border-bottom:1px solid var(--line)}
.rail-inner::before{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:linear-gradient(90deg,var(--sea-lo),var(--sand),var(--gold))}
.pin{position:absolute;bottom:0;width:2px;border-radius:2px 2px 0 0;
  background:linear-gradient(180deg,var(--sand),rgba(220,185,141,.25));
  transform-origin:bottom;cursor:pointer;
  transition:transform .8s var(--ease-out),background .3s var(--ease),
             width .3s var(--ease),box-shadow .4s var(--ease)}
.reveal .rail .pin{transform:scaleY(0)}
.reveal .rail.in .pin{transform:scaleY(1)}
.pin::after{content:"";position:absolute;inset:-9px -8px 0}
.pin:hover,.pin:focus-visible{width:4px;background:var(--sand-hi);
  box-shadow:0 0 20px 1px rgba(240,220,192,.75);outline:none}
.pin b{position:absolute;bottom:calc(100% + 9px);left:50%;translate:-50% 0;
  font-family:var(--mono);font-size:10px;letter-spacing:.04em;color:var(--ink);
  background:var(--sand-hi);padding:5px 9px;border-radius:var(--r-xs);white-space:nowrap;
  opacity:0;transform:translateY(5px) scale(.94);pointer-events:none;
  transition:opacity .3s var(--ease),transform .3s var(--ease)}
/* :focus as well as :focus-visible. A tap on a phone focuses the button but
   does not always satisfy :focus-visible, which left the label unreachable on
   exactly the devices that have no hover. */
.pin:hover b,.pin:focus b,.pin:focus-visible b{opacity:1;transform:none}

/* THE SOLD RESIDENCES.
   Short, muted, inert. They carry no price because the source has none for
   them: see the long comment in build_home.py. Shorter than any available pin
   on purpose, so a residence that is gone can never read as louder than one
   that is still for sale. */
.pin.gone{width:2px;background:linear-gradient(180deg,var(--line),rgba(38,52,61,.25));
  pointer-events:none;cursor:default}

.rail-scale{display:flex;justify-content:space-between;margin-top:14px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;color:var(--faint)}
.rail-scale b{color:var(--sand);font-weight:400}
.rail-key{display:flex;flex-wrap:wrap;gap:10px 26px;margin-top:18px;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--faint)}
.rail-key span{display:inline-flex;align-items:center;gap:9px}
.rail-key i{width:2px;height:13px;border-radius:2px;flex:none}
.rail-key .a{background:var(--sand)}
.rail-key .v{background:var(--line)}

@media (max-width:820px){
  /* Seventy seven marks cannot share 335px, so the axis keeps its own width
     and the track scrolls to it. At 760px the available pins sit about 21px
     apart, and the widened hit area below clears the 24px touch minimum. */
  .rail-inner{min-width:760px}
  .pin::after{inset:-11px -11px 0}
  .rail-hd{gap:10px}
  .rail-hd p{margin-left:0}
}

/* ============================================================== typology = */
.types{display:grid;grid-template-columns:repeat(auto-fit,minmax(238px,1fr));
  gap:clamp(12px,1.6vw,20px)}
.type{position:relative;padding:30px 26px 26px;border:1px solid var(--line);
  border-radius:var(--r-md);background:var(--surf);overflow:hidden;isolation:isolate;
  display:flex;flex-direction:column;min-height:280px;
  transition:transform .55s var(--ease),border-color .55s var(--ease),
             box-shadow .55s var(--ease)}
.type::before{content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(168deg,rgba(220,185,141,.13),transparent 62%);
  opacity:0;transition:opacity .55s var(--ease)}
/* a rule grows down the left edge on hover */
.type::after{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;
  background:linear-gradient(180deg,var(--sand),transparent);
  transform:scaleY(0);transform-origin:top;transition:transform .6s var(--ease)}
.type:hover{transform:translateY(-6px);border-color:rgba(220,185,141,.36);
  box-shadow:0 34px 66px -42px rgba(0,0,0,.98)}
.type:hover::before{opacity:1}
.type:hover::after{transform:scaleY(1)}
.type-n{font-family:var(--mono);font-size:10px;letter-spacing:.2em;color:var(--faint);
  margin-bottom:16px}
.type h3{margin-bottom:6px}
.type .sub{font-size:13px;color:var(--muted);margin-bottom:22px}
.type dl{margin-top:auto;display:grid;gap:9px}
.type div{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  padding-bottom:9px;border-bottom:1px solid var(--line-soft)}
.type dt{font-family:var(--mono);font-size:9.5px;letter-spacing:.15em;
  text-transform:uppercase;color:var(--faint)}
.type dd{font-family:var(--mono);font-size:14px;color:var(--shell)}
.type dd.hi{color:var(--sand)}
.type .go{margin-top:20px}

/* =============================================================== gallery = */
.plates{display:grid;grid-template-columns:repeat(12,1fr);gap:clamp(10px,1.3vw,16px)}
.plate{position:relative;overflow:hidden;border-radius:var(--r-md);
  border:1px solid var(--line-soft);background:var(--surf);cursor:pointer;
  transition:border-color .55s var(--ease)}
.plate img{width:100%;height:100%;object-fit:cover;
  transition:transform 1.2s var(--ease-out),filter .8s var(--ease)}
.plate::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,transparent 38%,rgba(7,23,38,.9) 100%);
  opacity:.86;transition:opacity .6s var(--ease)}
.plate:hover{border-color:rgba(220,185,141,.42)}
.plate:hover img{transform:scale(1.07)}
.plate:hover::after{opacity:.62}
.plate figcaption{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:22px 24px;
  transition:transform .6s var(--ease)}
.plate:hover figcaption{transform:translateY(-4px)}
.plate figcaption b{display:block;font-family:var(--serif);font-size:clamp(19px,2vw,25px);
  color:var(--shell);line-height:1.15}
.plate figcaption span{display:block;margin-top:7px;font-family:var(--mono);font-size:10px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--sand);opacity:0;
  transform:translateY(7px);
  transition:opacity .5s var(--ease) .05s,transform .5s var(--ease) .05s}
.plate:hover figcaption span{opacity:1;transform:none}
.p-lg{grid-column:span 7;aspect-ratio:16/10}
.p-md{grid-column:span 5;aspect-ratio:4/3.4}
.p-sm{grid-column:span 4;aspect-ratio:4/3.1}
@media (max-width:820px){
  .p-lg,.p-md{grid-column:span 12;aspect-ratio:16/10}
  .p-sm{grid-column:span 6;aspect-ratio:1/1}
}

/* ============================================================= amenities = */
/* FLEX, NOT GRID, AND THAT IS THE WHOLE POINT.
   This was `grid-template-columns:repeat(auto-fit,minmax(180px,1fr))`. At
   1280px that resolved to SIX columns for SEVEN cards, so the second row held
   one card and five empty cells: roughly 960x195px of dead slab. Any count
   that is not an exact multiple of the column count leaves the same hole, so
   it would have come back the moment EKPM added an amenity.
   Flex wrap has no cells to leave empty. The items on a short last row grow to
   consume the leftover width, so the block is solid at ANY number of amenities
   and at any viewport, with no media query counting cards. */
.amen{display:flex;flex-wrap:wrap;
  gap:1px;background:var(--line-soft);border:1px solid var(--line-soft);
  border-radius:var(--r-md);overflow:hidden}
/* Basis picks the row length; grow fills whatever is left over. At the current
   seven this gives a 4 + 3 split, both rows full edge to edge. */
.am{position:relative;background:var(--ink-2);padding:30px 24px 28px;overflow:hidden;
  flex:1 1 250px;min-width:min(100%,250px);
  transition:background .55s var(--ease)}
.am::before{content:"";position:absolute;inset:0;
  background:radial-gradient(120% 100% at 50% 118%,rgba(70,130,180,.16),transparent 66%);
  opacity:0;transition:opacity .55s var(--ease)}
.am:hover{background:var(--surf)}
.am:hover::before{opacity:1}
.am-n{position:relative;font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;
  color:var(--faint);transition:color .45s var(--ease)}
.am:hover .am-n{color:var(--sea)}
.am b{position:relative;display:block;margin-top:16px;font-family:var(--serif);
  font-size:21px;line-height:1.16;color:var(--shell);
  transition:transform .55s var(--ease)}
.am:hover b{transform:translateX(5px)}
.am p{position:relative;margin-top:9px;font-size:13px;color:var(--muted);line-height:1.55}

.servs{display:flex;flex-wrap:wrap;gap:9px;margin-top:clamp(26px,3.4vw,40px)}
.serv{display:inline-flex;align-items:center;gap:10px;padding:11px 18px;
  border:1px solid var(--line);border-radius:100px;font-size:13px;color:var(--shell-2);
  cursor:default;transition:border-color .45s var(--ease),color .45s var(--ease),
    background .45s var(--ease),transform .4s var(--ease)}
.serv i{width:4px;height:4px;border-radius:50%;background:var(--faint);flex:none;
  transition:background .45s var(--ease),box-shadow .45s var(--ease)}
.serv:hover{border-color:rgba(70,130,180,.45);color:var(--shell);
  background:rgba(70,130,180,.06);transform:translateY(-2px)}
.serv:hover i{background:var(--sea);box-shadow:0 0 9px 1px rgba(70,130,180,.8)}

/* ============================================================== location = */
.loc{position:relative;overflow:clip}
.loc-media{position:relative;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid var(--line);aspect-ratio:16/8.4;margin-bottom:clamp(34px,4.6vw,58px)}
.loc-media img{width:100%;height:100%;object-fit:cover;
  transition:transform 1.4s var(--ease-out)}
.loc-media:hover img{transform:scale(1.045)}
.loc-media::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(7,23,38,.28) 0%,transparent 34%,
             rgba(7,23,38,.82) 100%)}
.loc-stamp{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:clamp(22px,3vw,34px);
  display:flex;align-items:flex-end;gap:22px;flex-wrap:wrap}
.loc-stamp b{font-family:var(--serif);font-size:clamp(23px,3.1vw,38px);line-height:1.1}
.loc-stamp span{margin-left:auto;font-family:var(--mono);font-size:11px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--sand)}

.dists{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1px;
  background:var(--line-soft);border:1px solid var(--line-soft);border-radius:var(--r-md);
  overflow:hidden}
.dist{background:var(--ink-2);padding:26px 22px;transition:background .5s var(--ease)}
.dist:hover{background:var(--surf)}
.dist b{display:block;font-family:var(--mono);font-size:26px;color:var(--sand);
  letter-spacing:-.03em;line-height:1;transition:transform .5s var(--ease)}
.dist:hover b{transform:translateY(-3px)}
.dist b sub{font-size:.44em;color:var(--muted);vertical-align:baseline;margin-left:3px}
.dist span{display:block;margin-top:10px;font-size:12.5px;color:var(--muted)}

/* ============================================================ fideicomiso = */
.fid{background:
  linear-gradient(150deg,rgba(7,45,61,.5),rgba(7,23,38,.7)),var(--ink-2);
  border-block:1px solid var(--line-soft);position:relative;overflow:clip}
.fid::before{content:"";position:absolute;top:-40%;right:-10%;width:60%;aspect-ratio:1;
  border-radius:50%;background:radial-gradient(circle,rgba(70,130,180,.1),transparent 66%);
  pointer-events:none}
.fid-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.82fr);
  gap:clamp(30px,5vw,72px);align-items:center}
.steps{display:grid;gap:1px;background:var(--line-soft);border:1px solid var(--line-soft);
  border-radius:var(--r-md);overflow:hidden}
.step{background:rgba(11,34,51,.9);padding:20px 24px;display:flex;gap:18px;
  align-items:flex-start;transition:background .5s var(--ease),padding-left .5s var(--ease)}
.step:hover{background:var(--surf-2);padding-left:30px}
.step em{font-family:var(--mono);font-style:normal;font-size:11px;color:var(--sea);
  padding-top:3px;flex:none;letter-spacing:.1em;transition:color .4s var(--ease)}
.step:hover em{color:var(--sea)}
.step b{display:block;font-size:15px;font-weight:500;color:var(--shell);margin-bottom:4px}
.step p{font-size:13px;color:var(--muted);line-height:1.55}
@media (max-width:900px){.fid-grid{grid-template-columns:1fr}}

/* ============================================================= developer = */
.dev{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
  gap:clamp(28px,4.6vw,64px);align-items:center}
.dev-fig{position:relative;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid var(--line);aspect-ratio:4/3.2}
.dev-fig img{width:100%;height:100%;object-fit:cover;filter:saturate(.72) contrast(1.06);
  transition:transform 1.3s var(--ease-out),filter .8s var(--ease)}
.dev-fig:hover img{transform:scale(1.06);filter:saturate(1) contrast(1)}
@media (max-width:860px){.dev{grid-template-columns:1fr}}

/* =============================================================== contact = */
.contact{background:var(--ink-2);border-top:1px solid var(--line-soft);position:relative;
  overflow:clip}
.contact::before{content:"";position:absolute;inset:0;opacity:.14;
  background:url("../img/gallery/render-cuatro.webp?v=202609092002") center/cover no-repeat;
  mask-image:linear-gradient(180deg,#000,transparent 74%)}
.contact > *{position:relative}
.c-grid{display:grid;grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
  gap:clamp(32px,5vw,76px);align-items:start}

.form{display:grid;gap:16px}
.row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.field{position:relative}
.field label{display:block;font-family:var(--mono);font-size:9.5px;letter-spacing:.17em;
  text-transform:uppercase;color:var(--faint);margin-bottom:9px;
  transition:color .4s var(--ease)}
.field:focus-within label{color:var(--sand)}
.field input,.field select,.field textarea{
  width:100%;background:var(--surf);border:1px solid var(--line);border-radius:var(--r-sm);
  padding:13px 15px;font-size:14.5px;color:var(--shell);
  transition:border-color .45s var(--ease),background .45s var(--ease),
             box-shadow .45s var(--ease)}
.field textarea{min-height:112px;resize:vertical;line-height:1.6}
.field select{appearance:none;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%237c8b95' stroke-width='1.4'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 15px center;padding-right:40px}
.field input:hover,.field select:hover,.field textarea:hover{border-color:var(--line)}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none;border-color:var(--sand);background:var(--surf-2);
  box-shadow:0 0 0 3px rgba(220,185,141,.1)}
/* The rule above removes the outline on EVERY focus, including keyboard focus,
   and replaces it with a 3px shadow at 10% opacity. On --surf-2 that is close
   to invisible, so a keyboard user tabbing the one revenue path on the site had
   almost no indication of where they were. :focus-visible only matches when the
   browser decides the focus deserves a visible ring, which in practice means
   keyboard and not a mouse click, so the designed look survives for pointer
   users and the accessible ring comes back for everyone else. It is listed
   after, and outline is not in the rule above, so nothing has to out-specify
   anything. */
.field input:focus-visible,.field select:focus-visible,.field textarea:focus-visible{
  outline:2px solid var(--sand);outline-offset:2px}
.field input::placeholder,.field textarea::placeholder{color:var(--faint)}
.field optgroup{background:var(--surf);color:var(--muted);font-style:normal}
.field option{background:var(--surf);color:var(--shell)}
.form-foot{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin-top:6px}
.form-foot p{font-size:11.5px;color:var(--faint);line-height:1.5;max-width:44ch}
.form-foot a{color:var(--muted);text-decoration:underline;text-underline-offset:2px;
  transition:color .35s var(--ease)}
.form-foot a:hover{color:var(--sand)}

/* Honeypot. A real field, in the markup, that no human ever sees or reaches:
   it is off the tab order, hidden from assistive tech, and clipped out of the
   layout. A form-filling bot reads the DOM, sees a text input labelled
   "Empresa", and fills it. api/lead.php reads that as a spam signal.

   Clipped rather than display:none. Some bots skip fields that are
   display:none or visibility:hidden precisely because that is the naive way to
   build one, so the trap has to look like a field that is merely positioned
   somewhere else.

   Never reuse .hp for anything a person is meant to see. A field that lands in
   here by accident is a field nobody can fill and nobody can find. */
.hp{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden}

/* The human slider. The other half of the anti-bot pair, and unlike the
   honeypot above this one is meant to be seen and used, so it is styled like
   every other field rather than hidden. A native range input keeps the keyboard
   and screen reader behaviour the browser already ships; do not replace it with
   a div. */
.ik-check input[type=range]{
  width:100%;
  margin:6px 0 0;
  accent-color:var(--sand);
  cursor:pointer;
  /* Comfortably past the 44px touch target on a phone without the thumb
     colliding with the field below it. */
  height:28px;
}
.ik-check input[type=range]:focus-visible{
  outline:2px solid var(--sand);
  outline-offset:3px;
}
.ik-hint{
  display:block;
  margin-top:2px;
  font-family:var(--mono);
  font-size:9.5px;
  letter-spacing:.17em;
  color:var(--muted);
}

/* Intake feedback. Written by assets/js/home.js after a submit, replacing the
   old behaviour where every submission was thrown straight at WhatsApp. */
.form-msg{font-size:13px;line-height:1.55;padding:12px 14px;border-radius:var(--r-md);
  border:1px solid var(--line);background:var(--surf);color:var(--muted)}
.form-msg[data-state="ok"]{border-color:rgba(220,185,141,.45);color:var(--sand)}
.form-msg[data-state="error"]{border-color:rgba(220,140,140,.45)}
.form-msg[hidden]{display:none}
/* The WhatsApp escape hatch, appended by home.js when the intake fails. It has
   to read as the thing to click: this is the last chance to keep a buyer who
   just watched a form fail, and it replaced a window.open that the popup
   blocker was silently discarding. */
.form-msg-wa{display:inline-block;margin-top:8px;padding:8px 14px;border-radius:var(--r-sm);
  background:var(--sand);color:#071726;font-weight:600;text-decoration:none}
.form-msg-wa:hover,.form-msg-wa:focus-visible{filter:brightness(1.08);text-decoration:none}

.c-cards{display:grid;gap:11px;margin-top:30px}
.c-card{display:flex;align-items:center;gap:16px;padding:18px 20px;
  border:1px solid var(--line);border-radius:var(--r-md);background:var(--surf);
  transition:border-color .5s var(--ease),transform .45s var(--ease),
             background .5s var(--ease)}
.c-card:hover{border-color:rgba(220,185,141,.4);transform:translateX(5px);
  background:var(--surf-2)}
.c-card i{width:34px;height:34px;flex:none;border-radius:50%;border:1px solid var(--line);
  display:grid;place-items:center;font-family:var(--mono);font-size:11px;color:var(--sand);
  transition:border-color .45s var(--ease),background .45s var(--ease)}
.c-card:hover i{border-color:var(--sand);background:rgba(220,185,141,.1)}
.c-card b{display:block;font-size:14.5px;font-weight:500}
.c-card span{display:block;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--faint);margin-top:3px}
@media (max-width:900px){.c-grid{grid-template-columns:1fr}.row{grid-template-columns:1fr}}

/* ================================================================ footer = */
.foot{border-top:1px solid var(--line-soft);background:var(--ink);
  padding:clamp(52px,7vw,84px) 0 34px}
.foot-top{display:grid;grid-template-columns:minmax(0,1.3fr) repeat(3,minmax(0,1fr));
  gap:clamp(24px,3.4vw,48px);padding-bottom:44px;border-bottom:1px solid var(--line-soft)}
.foot h4{font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--faint);margin-bottom:18px;font-weight:500}
.foot ul{list-style:none;display:grid;gap:11px}
.foot li a,.foot address a{font-size:13.5px;color:var(--shell-2);position:relative;
  display:inline-block;transition:color .4s var(--ease),transform .4s var(--ease)}
.foot li a:hover,.foot address a:hover{color:var(--sand);transform:translateX(4px)}
.foot address{font-style:normal;font-size:13.5px;color:var(--shell-2);line-height:1.85}
.foot-brand p{margin-top:18px;font-size:13.5px;color:var(--muted);max-width:34ch}
.foot-bot{display:flex;align-items:center;gap:18px;flex-wrap:wrap;padding-top:26px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;color:var(--faint)}
.foot-bot .r{margin-left:auto;display:flex;gap:18px}
.foot-bot a{transition:color .35s var(--ease)}
.foot-bot a:hover{color:var(--sand)}
@media (max-width:820px){.foot-top{grid-template-columns:1fr 1fr}}

/* floating WhatsApp */
.wa{position:fixed;right:20px;bottom:20px;z-index:90;display:flex;align-items:center;
  gap:0;padding:14px;border-radius:100px;background:var(--sand);color:var(--ink);
  box-shadow:0 16px 40px -16px rgba(220,185,141,.7);overflow:hidden;
  transition:gap .5s var(--ease),padding .5s var(--ease),background .4s var(--ease),
             transform .4s var(--ease)}
.wa span{max-width:0;overflow:hidden;white-space:nowrap;font-size:13.5px;font-weight:600;
  transition:max-width .55s var(--ease)}
.wa:hover{gap:10px;padding-inline:18px 22px;background:var(--sand-hi);
  transform:translateY(-3px)}
.wa:hover span{max-width:190px}
.wa svg{flex:none}

/* ---------------------------------------------------------------- motion */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important}
  .reveal .rv{opacity:1;transform:none}
  .reveal .rise > span{transform:none}
  .reveal .pulse .bar i{width:var(--fill,44.9%)}
  .reveal .rail .pin{transform:scaleY(1)}
  .hero-bg img{animation:none;transform:scale(1.04)}
}


/* ============================================================ back to top =
   Scaffold: z.warp.sovereign.projects.template/scaffolds/back-to-top/
   Themed to the Ikonic palette and raised to 84px so it clears the floating
   WhatsApp pill, which owns the bottom right corner and should keep it.
   ========================================================================== */
:root{
  --btt-offset-x: 20px;
  --btt-offset-y: 84px;
  --btt-bg:      rgba(11,34,51,.88);
  --btt-fg:      var(--shell-2);
  --btt-border:  var(--line);
  --btt-shadow:  0 18px 44px -22px rgba(0,0,0,.95);
  --btt-radius:  100px;
  --btt-font:    var(--mono);
  --btt-size:    10px;
}
.backtotop{
  position: fixed;
  right: var(--btt-offset-x);
  bottom: var(--btt-offset-y);
  z-index: 999;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity .25s ease, transform .25s ease;
}
.backtotop.show{
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.backtotop a{
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border-radius: var(--btt-radius);
  border: 1px solid var(--btt-border);
  background: var(--btt-bg);
  color: var(--btt-fg);
  box-shadow: var(--btt-shadow);
  text-decoration: none;
  font-family: var(--btt-font);
  font-size: var(--btt-size);
  line-height: 1;
  white-space: nowrap;
  letter-spacing: .15em;
  text-transform: uppercase;
  backdrop-filter: blur(14px);
  transition: transform .4s var(--ease), border-color .4s var(--ease),
              color .4s var(--ease), box-shadow .4s var(--ease);
}
.backtotop a:hover{
  transform: translateY(-3px);
  color: var(--ink);
  border-color: var(--sand);
  background: var(--sand);
  box-shadow: 0 18px 40px -20px rgba(220,185,141,.8);
}

/* The fade is decoration. The scroll itself is handled in JS, which checks the
   same query and jumps instead of animating. */
@media (prefers-reduced-motion: reduce){
  .backtotop{ transition: none }
  .backtotop a:hover{ transform: none }
}

/* ==========================================================================
   Sub pages: one per available residence, plus the two legal documents
   --------------------------------------------------------------------------
   Everything here reuses the front page primitives (.wrap, .sec, .btn, .specs,
   .price, .detail-hd, .plan, .foot). Only the layouts that do not exist on the
   front page are defined below.

   These pages have no hero, so the fixed nav would sit on top of the first
   line. .usec carries the offset instead of the nav carrying a variant.
   ========================================================================== */
/* Sub pages have no hero, so the fixed nav would sit on top of the first line.
   The 74px clears the nav; below that the page uses the same half gap as every
   other row so the sub pages breathe like the front page. */
.usec{padding:calc(74px + var(--sec-y)) 0 var(--sec-y)}

.crumb{display:flex;flex-wrap:wrap;align-items:center;gap:9px;margin-bottom:clamp(26px,4vw,44px);
  font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase}
.crumb a{color:var(--muted);transition:color .3s var(--ease)}
.crumb a:hover{color:var(--sand)}
.crumb span[aria-hidden]{color:var(--faint)}
.crumb span[aria-current]{color:var(--shell-2)}

.uhead{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.86fr);
  gap:clamp(30px,5vw,64px);align-items:start}
.uhead h1{font-family:var(--serif);font-weight:400;line-height:1.02;letter-spacing:-.015em;
  font-size:clamp(38px,6.4vw,74px);color:var(--shell);margin:14px 0 20px}

/* The unit box repeats the front page detail pane so a buyer who clicked
   through from the elevation lands on something they recognise. */
.ubox{border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surf);
  overflow:hidden;position:sticky;top:92px}
.ubox .detail-hd,.ubox .specs,.ubox .price{border-color:var(--line-soft)}
.ubox .detail-cta{display:flex;flex-wrap:wrap;gap:11px;padding:0 26px 20px}
.ubox .detail-cta .btn{flex:1 1 auto;justify-content:center}
.ubox-back{padding:0 26px 24px;margin:0}

.detail-ficha{padding:0 26px 22px;margin:0}
.detail-ficha[hidden]{display:none}

/* plan plus the shared-amenities list */
.uplan{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
  gap:clamp(28px,4vw,54px);align-items:start}
.uplan-side .lede{margin-bottom:34px}
.uh{font-family:var(--serif);font-weight:400;letter-spacing:-.01em;color:var(--shell);
  font-size:clamp(23px,2.7vw,33px);line-height:1.12;margin-bottom:18px}
.ulist{list-style:none;display:grid;gap:13px}
.ulist li{position:relative;padding-left:22px;color:var(--shell-2);font-size:14.5px;line-height:1.55}
.ulist li::before{content:"";position:absolute;left:0;top:9px;width:8px;height:1px;
  background:var(--sand-lo)}

/* neighbour and similar-residence cards */
.ucards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px;margin-top:8px}
.ucard{display:grid;gap:5px;padding:20px 21px;border:1px solid var(--line-soft);
  border-radius:var(--r-md);background:var(--surf);
  transition:border-color .4s var(--ease),background .4s var(--ease),transform .4s var(--ease)}
.ucard:hover,.ucard:focus-visible{border-color:var(--sand-lo);background:var(--surf-2);transform:translateY(-2px)}
.ucard b{font-family:var(--serif);font-size:20px;font-weight:400;color:var(--shell)}
.ucard:hover b{color:var(--sand)}
.ucard-lv,.ucard-m2{font-family:var(--mono);font-size:10px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--faint)}
.ucard-m2{color:var(--muted);letter-spacing:.08em;text-transform:none;font-size:11.5px}
.ucard-p{font-family:var(--mono);font-size:15px;color:var(--sand);margin-top:5px}

.ufid{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:26px}
.ufid .lede{margin-top:12px}

/* legal documents */
.legal h1{font-family:var(--serif);font-weight:400;line-height:1.05;letter-spacing:-.015em;
  font-size:clamp(34px,5.4vw,60px);color:var(--shell);margin-bottom:22px}
.lbody{max-width:74ch;margin-top:clamp(34px,5vw,58px)}
.lblock{padding-top:30px;margin-top:30px;border-top:1px solid var(--line-soft)}
.lblock:first-child{border-top:0;margin-top:0;padding-top:0}
.lblock h2{font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--sand);margin-bottom:16px}
.lblock p{color:var(--shell-2);font-size:15px;line-height:1.72;margin-bottom:13px}
.lblock p:last-child{margin-bottom:0}
.lfoot{margin-top:40px;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--faint)}

/* The weight and size of the download link beside it, which is what it was
   doing before this rule existed: a bare span inherits body text, so "PDF, 4.4
   MB" read as loudly as the button it qualifies. It is a caveat about the file
   size, and it should read like one. Sits on its own line under the button on
   narrow screens, where a 4 MB warning matters most. */
.ldoc{display:flex;flex-wrap:wrap;align-items:center;gap:12px}
.dlnote{font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--faint)}

@media (max-width:960px){
  .uhead,.uplan{grid-template-columns:1fr}
  .ubox{position:static}
}


/* ========================================================== issue reporter =
   Owner only. The button is created by home.js and only after the server has
   confirmed an admin session, so a visitor never sees any of this.

   Stacked above the two controls that already own the bottom right corner: the
   WhatsApp pill at 20px and the back to top pill at 84px.
   ========================================================================== */
.fixbtn{
  position: fixed;
  right: 20px;
  bottom: 148px;
  /* Below .drawer (z-index 99), not above it. At 999 the pill floated on top
     of the full screen mobile navigation and overlapped whatever link sat at
     bottom:148px. It still clears the page content, which is all it needs to
     do. */
  z-index: 95;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  border: 1px solid var(--sand);
  border-radius: 100px;
  background: rgba(11,34,51,.88);
  color: var(--sand);
  font-family: var(--mono);
  font-size: 10px;
  line-height: 1;
  letter-spacing: .15em;
  text-transform: uppercase;
  cursor: pointer;
  backdrop-filter: blur(14px);
  box-shadow: 0 18px 44px -22px rgba(0,0,0,.95);
}
.fixbtn:hover{ background: var(--sand); color: var(--ink) }

/* Native <dialog>, so Escape, the backdrop and the focus trap are the
   browser's job rather than three more listeners in home.js. */
.fixdlg{
  width: min(440px, calc(100vw - 32px));
  /* The site reset zeroes every margin, which kills the UA stylesheet rule
     that centres a modal dialog. Restored here rather than carved out of the
     reset. */
  margin: auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surf);
  color: var(--shell);
}
.fixdlg::backdrop{ background: rgba(7,23,38,.72) }
.fixdlg form{ padding: 20px }
.fixdlg h2{
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sand);
  margin-bottom: 14px;
}
.fixdlg .fixkind{ display: flex; gap: 8px; margin-bottom: 12px }
.fixdlg .fixkind label{
  flex: 1 1 0;
  text-align: center;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  font-size: 13px;
  cursor: pointer;
}
.fixdlg .fixkind input{ position: absolute; opacity: 0; pointer-events: none }
.fixdlg .fixkind input:checked + span{ color: var(--sand) }
.fixdlg .fixkind label:has(input:checked){ border-color: var(--sand) }
/* :has() is recent. Without it the checked state still reads, because the
   label text colour above does not depend on it. */
.fixdlg .fixkind input:focus-visible + span{ outline: 2px solid var(--sand); outline-offset: 3px }

.fixdlg textarea{
  width: 100%;
  min-height: 120px;
  resize: vertical;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--ink);
  color: var(--shell);
  font: inherit;
  font-size: 14px;
}
.fixdlg textarea:focus{ outline: none; border-color: var(--sand) }

.fixdlg .fixpage{
  color: var(--muted);
  font-size: 11.5px;
  margin: 9px 0 13px;
  overflow-wrap: anywhere;
}
.fixdlg .fixrow{ display: flex; align-items: center; gap: 10px; flex-wrap: wrap }
.fixdlg button{
  border-radius: 9px;
  font: inherit;
  font-size: 13.5px;
  padding: 9px 16px;
  cursor: pointer;
}
.fixdlg .fixsend{ border: 1px solid var(--sand); background: var(--sand); color: var(--ink) }
.fixdlg .fixcancel{ border: 1px solid var(--line); background: transparent; color: var(--muted) }
.fixdlg .fixcancel:hover{ color: var(--shell) }
.fixdlg .fixall{ margin-left: auto; color: var(--muted); font-size: 12px }
.fixdlg .fixmsg{ font-size: 13px; margin-top: 11px; min-height: 1.2em }
.fixdlg .fixmsg.bad{ color: #f08a8a }
.fixdlg .fixmsg.good{ color: #77d39b }

@media (prefers-reduced-motion: reduce){
  .fixbtn{ transition: none }
}

/* ==========================================================================
   Level picker, level dialog, walkthrough video, amenity slider
   --------------------------------------------------------------------------
   Added 2026-09-08. The picker is a plain <svg> laid over the render at
   inset:0 with the image's own pixel box as its viewBox, so the twelve shapes
   track the picture at every width with no library and no resize handler.
   The old site needed jQuery plus imagemapster for the same effect, because
   <area> coordinates do not scale. An SVG viewBox does, for free.
   ========================================================================== */

/* ----------------------------------------------------------- level picker -- */
.pick{max-width:900px;margin-inline:auto}
.pick-media{
  position:relative;overflow:hidden;background:var(--surf);
  border:1px solid var(--line);border-radius:var(--r-lg);
}
.pick-media > img{display:block;width:100%;height:auto}
.pick-map{position:absolute;inset:0;width:100%;height:100%}

/* Transparent until pointed at. The render is the artwork; twelve permanently
   tinted bands over it would be worse than the thing it replaces. */
.pick-map .lv{
  fill:rgba(70,130,180,0);stroke:rgba(240,220,192,0);stroke-width:6;
  cursor:pointer;transition:fill .22s var(--ease),stroke .22s var(--ease);
}
.pick-map .lv:hover,
.pick-map .lv:focus-visible{
  fill:rgba(70,130,180,.30);stroke:var(--sand-hi);outline:none;
}
/* The numbers sit over sky, cloud and building in turn, so none of the three
   can be assumed as a background. paint-order draws a dark stroke UNDER the
   fill, which reads as a halo and keeps every number legible on all of them.
   Level 7 vanished into a cloud without it. */
.pick-map .lvn{
  fill:var(--shell-2);font-family:var(--mono);font-size:54px;text-anchor:end;
  paint-order:stroke;stroke:rgba(7,23,38,.62);stroke-width:7px;
  pointer-events:none;transition:fill .22s var(--ease);
}
/* Adjacent sibling, and the generator emits <polygon> immediately followed by
   its own <text>. Keep that order or the number stops lighting up. */
.pick-map .lv:hover + .lvn,
.pick-map .lv:focus-visible + .lvn{fill:var(--sand-hi)}

.pick-note{color:var(--faint);font-size:12.5px;margin-top:18px;max-width:640px}

@media (max-width:600px){
  .pick-map .lvn{font-size:74px}
}

/* ----------------------------------------------------------- level dialog -- */
/* Centred by hand, and it has to be. The UA centres a modal dialog with
   margin:auto, and the reset at the top of this file (*{margin:0}) wins over
   that, so the panel pinned itself to the top left corner behind its own
   backdrop and the whole dialog read as a black screen. Do not swap this for
   margin:auto. */
.lvdlg{
  position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
  border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surf);color:var(--shell);
  padding:clamp(22px,4vw,34px);width:min(94vw,980px);max-height:90vh;
  overflow-y:auto;
}
.lvdlg::backdrop{background:rgba(7,23,38,.80)}
.lvdlg-x{
  position:absolute;top:10px;right:12px;width:34px;height:34px;
  display:grid;place-items:center;border:1px solid var(--line);
  border-radius:50%;background:transparent;color:var(--muted);
  font-size:20px;line-height:1;cursor:pointer;
}
.lvdlg-x:hover{color:var(--shell);border-color:var(--sand)}
.lvdlg-t{font-family:var(--serif);font-size:clamp(26px,4vw,36px);line-height:1.1}
.lvdlg-sub{color:var(--muted);font-size:13px;margin-top:8px}
.lvpane{display:block}
.lvpane[hidden]{display:none}

/* ------------------------------------------------------------ the plate ---
   The SVG carries the same viewBox as the plate image's intrinsic 1000x564 and
   sits over it at inset:0, so the shapes track the image at every viewport
   width with no script and no resize handler. Both are inside one positioned
   box for that reason. */
.lvplate{
  position:relative;margin-top:18px;background:#f4f2ee;
  border:1px solid var(--line-soft);border-radius:var(--r-md);overflow:hidden;
}
.lvplate img{display:block;width:100%;height:auto}
.lvmap{position:absolute;inset:0;width:100%;height:100%}

/* A sold residence is painted and INERT. It is never hidden and never removed,
   so the plate keeps telling the truth about the whole floor. pointer-events
   is off so it can never swallow a click meant for the residence next door. */
.lvsold{fill:rgba(206,84,84,.34);stroke:rgba(150,48,48,.30);stroke-width:1.5;pointer-events:none}

.lvunit{
  fill:rgba(96,168,116,.34);stroke:rgba(44,110,68,.55);stroke-width:1.5;
  cursor:pointer;transition:fill .16s ease;
}
.lvunit:hover,.lvunit:focus{fill:rgba(96,168,116,.62);outline:none}
.lvunit:focus-visible{stroke:var(--sand);stroke-width:3}

.lvkey{
  list-style:none;display:flex;flex-wrap:wrap;gap:8px 20px;
  margin-top:14px;font-size:12.5px;color:var(--muted);
}
.lvkey li{display:flex;align-items:center;gap:8px}
.lvkey i{width:12px;height:12px;border-radius:3px;border:1px solid}
.lvkey i.a{background:rgba(96,168,116,.55);border-color:rgba(44,110,68,.7)}
.lvkey i.v{background:rgba(206,84,84,.45);border-color:rgba(150,48,48,.6)}
.lvdlg-note{margin-top:12px;font-size:13px;color:var(--muted)}

/* ------------------------------------------------- the residence card --- */
.lvback{
  display:inline-flex;align-items:center;gap:8px;background:transparent;
  border:1px solid var(--line);border-radius:999px;padding:7px 15px;
  color:var(--muted);font-size:12.5px;cursor:pointer;margin-bottom:18px;
}
.lvback:hover{color:var(--shell);border-color:var(--sand)}
.lvu-t{font-family:var(--serif);font-size:clamp(26px,4vw,36px);line-height:1.1}
.lvu-lv{color:var(--muted);font-size:13px;margin-top:6px}
.lvu-price{font-family:var(--mono);font-size:clamp(20px,3vw,26px);color:var(--sand);margin-top:12px}

.lvu-tab{
  display:grid;grid-template-columns:1fr auto;gap:0;margin-top:20px;
  border-top:1px solid var(--line-soft);
}
.lvu-tab dt,.lvu-tab dd{
  padding:11px 2px;border-bottom:1px solid var(--line-soft);font-size:13.5px;
}
.lvu-tab dt{color:var(--muted)}
.lvu-tab dd{text-align:right;font-family:var(--mono);color:var(--shell)}

.lvu-cta{display:flex;flex-wrap:wrap;gap:12px;margin-top:22px}
.lvu-cta .btn{cursor:pointer}

/* THE POINTER TO THE FORM, AND WHY IT IS LOUD.
   The developer's own popup hides its enquiry form below the fold with nothing
   on screen to say it is there. It is found by accident or not at all. This
   note is bordered and tinted so it reads as an instruction rather than as
   small print, and it names what the button above it actually does. */
.lvu-hint{
  margin-top:16px;padding:12px 14px;font-size:13px;line-height:1.55;
  color:var(--shell);background:rgba(70,130,180,.08);
  border:1px solid rgba(70,130,180,.34);border-radius:var(--r-md);
}
.lvu-fine{margin-top:14px;font-size:11.5px;line-height:1.6;color:var(--faint)}

/* ---------------------------------------------------------------- video --- */
.vid-frame{
  position:relative;aspect-ratio:16/9;overflow:hidden;background:#000;
  border:1px solid var(--line);border-radius:var(--r-lg);
}
.vid-frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}

/* The poster facade. The frame is an <a> until someone clicks it, so the page
   ships one 178 kB image instead of the YouTube player and its cookies. */
.vid-frame{display:block;cursor:pointer}
.vid-frame img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.vid-play{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:clamp(56px,7vw,84px);aspect-ratio:1;border-radius:50%;
  background:rgba(10,30,47,.62);border:2px solid var(--shell);
  transition:background .18s ease,transform .18s ease;
}
/* The triangle is a rotated square corner, so no glyph and no SVG file. */
.vid-play::after{
  content:"";position:absolute;left:56%;top:50%;transform:translate(-50%,-50%);
  border-style:solid;border-width:.55em 0 .55em .95em;
  border-color:transparent transparent transparent var(--shell);
  font-size:clamp(15px,1.9vw,22px);
}
.vid-frame:hover .vid-play,
.vid-frame:focus-visible .vid-play{background:var(--ink-2);transform:translate(-50%,-50%) scale(1.06)}
@media (prefers-reduced-motion:reduce){.vid-play{transition:none}}

/* --------------------------------------------------------------- slider --- */
/* Scroll snap does the whole job. The two buttons scroll by one viewport of
   the track, so there is no index to keep, nothing to get out of sync, and a
   swipe on a phone works with no JS at all. */
.slider{position:relative;margin-top:clamp(26px,4vw,44px)}
.slider-track{
  display:flex;gap:16px;overflow-x:auto;
  scroll-snap-type:x mandatory;scroll-behavior:smooth;
  scrollbar-width:none;border-radius:var(--r-lg);
}
.slider-track::-webkit-scrollbar{display:none}
.slider-track:focus-visible{outline:1px solid var(--sand);outline-offset:4px}
.slider-track figure{flex:0 0 100%;scroll-snap-align:center}
.slider-track img{display:block;width:100%;height:auto;border-radius:var(--r-lg)}
.slider-btn{
  position:absolute;top:50%;transform:translateY(-50%);
  width:46px;height:46px;border-radius:50%;cursor:pointer;
  display:grid;place-items:center;font-size:17px;
  border:1px solid var(--line);background:rgba(11,34,51,.86);color:var(--shell);
}
.slider-btn:hover{border-color:var(--sand);color:var(--sand)}
.slider-btn.prev{left:12px}
.slider-btn.next{right:12px}

@media (prefers-reduced-motion: reduce){
  .pick-map .lv,.pick-map .lvn{transition:none}
  .slider-track{scroll-behavior:auto}
}
