/* ============================================================================
   JOY GROUP — AGENT PORTAL, visual prototype.

   Its own stylesheet, NOT site.css. The portal is a tool, not a page of the
   marketing site: it needs a filter bar, a dense result list and an overlay,
   none of which exist there, and it needs none of the sections that do. It
   borrows the TOKENS (one import) and the .joy-btn family, and nothing else.

   Shell: minimal. Wordmark, the agent's name, a language switch, a sign-out.
   No drawer, no marketing footer. An agent signing in to check availability
   should not be handed a menu of company pages, and the login screen in
   particular has to read as quiet and serious rather than as a landing page.
   ========================================================================= */

/* NOTE: the spacing scale has no --space-7 (1,2,3,4,5,6,8,10,12,16,20,25).
   An undefined custom property makes the whole declaration invalid, so
   `padding: var(--space-7)` computed to 0 and the overlay's text ran to the
   panel edge. Check a token exists before reaching for it. */

/* ---------- the language switch ----------
   Restated here, not inherited: this file is loaded WITHOUT site.css, so the two
   rules that hide the language you are not reading have to come with it. They are
   a mechanism rather than a piece of the locked footer/drawer design, and the
   portal is a separate document — so this is the one place a copy is right.
   Same `joy-lang` key as the marketing site, so the choice carries across. */
html:not([data-lang="en"]) .L-en { display: none !important; }
html[data-lang="en"] .L-al { display: none !important; }

/* ---------- .joy-btn, ported ----------
   The CTA primitive lives in site.css, which this portal does not load — so the
   sign-out was an unstyled anchor: display block, no pill, and ink text sitting
   on the ink bar, which is to say invisible. This is the "minimal subset" the
   portal was always going to need: the tokens, the form field, and this.
   Ported, not re-designed — identical to site.css so the two never drift into
   two different buttons. The deliberate absence of a filled variant comes with
   it: on this site a filled pill IS the hover state, so one at rest reads as
   pressed. Order carries priority instead. */
.joy-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 13px 26px;
  font-family: var(--font-ui); font-weight: var(--fw-medium); font-size: 12px;
  letter-spacing: 0.14em; text-transform: uppercase; line-height: 1;
  background: transparent; border: var(--hairline) solid transparent;
  border-radius: 999px; text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard); }
.joy-btn[hidden] { display: none; }
.joy-btn--on-dark  { color: var(--crisp-white); border-color: var(--white-a62); }
.joy-btn--on-light { color: var(--ink-black);  border-color: var(--rule); }
.joy-btn__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 0; margin-left: 0; opacity: 0; overflow: hidden; font-size: 1.05em;
  transform: translateX(-6px);
  transition: width var(--dur-base) var(--ease-standard),
              margin-left var(--dur-base) var(--ease-standard),
              opacity var(--dur-base) var(--ease-standard),
              transform var(--dur-base) var(--ease-standard); }
.joy-btn:hover, .joy-btn:focus-visible {
  background: var(--ink-black); color: var(--crisp-white); border-color: var(--ink-black);
  outline: none; }
.joy-btn:hover .joy-btn__icon, .joy-btn:focus-visible .joy-btn__icon {
  width: 0.95em; margin-left: 7px; opacity: 1; transform: translateX(0); }
.joy-btn--on-dark:hover, .joy-btn--on-dark:focus-visible {
  background: var(--crisp-white); color: var(--ink-black); border-color: var(--crisp-white); }

/* No anchor-underline reset needed on .joy-btn: its own `border:` shorthand already
   overrides the design system's `a { border-bottom }`, and a `border-bottom: 0`
   added "for safety" does real damage — it zeroes the BOTTOM of the pill, leaving
   an outline open at the base. .pt-bar__mark carries its own `border: 0`. */

/* ---------- shell ---------- */
/* A column, so the footer sits at the bottom of the VIEWPORT when the content is
   short and after the content when it is long. This replaces a guessed footer
   height in the login page's min-height — the guess was 96 against a real 86, so
   the login screen scrolled by 14px, which on a page holding two fields and a
   button reads as something being wrong. It also catches the list's empty state,
   where a filter matching nothing used to leave the footer riding up the screen. */
.pt-body { margin: 0; background: var(--bg-page); color: var(--text-primary);
  font-family: var(--font-ui);
  min-height: 100svh; display: flex; flex-direction: column; }
.pt-login { flex: 1 1 auto; min-height: 0; }
.pt-main { flex: 1 0 auto; }

.pt-bar { position: sticky; top: 0; z-index: 40; display: flex; align-items: center;
  justify-content: space-between; gap: var(--space-5);
  height: 72px; padding: 0 clamp(20px, 3vw, 40px);
  padding-top: var(--sa-t); background: var(--ink-black); color: var(--crisp-white); }
/* flex: 0 0 auto, or the width above is a suggestion. The bar is a flex row and
   a flex item shrinks by default, so at 320 the sign-out — which is a pill, not
   the marketing header's small burger — squeezed the mark to 125px. The
   marketing header holds 168 at that width, and holding it is the whole point. */
.pt-bar__mark { flex: 0 0 auto;
  display: flex; align-items: center; gap: 14px; text-decoration: none; border: 0; }
/* THE WORDMARK IS THE MARKETING SITE'S, at the marketing site's size. It was
   sized by HEIGHT here — 20px, rendering 115.73 wide — against the 208px the
   header carries on every other page, so the portal wore the same mark at 56% of
   itself and read as a lesser thing than the site it belongs to.
   Sized by WIDTH now, exactly as #site-header .logo img is in site.css and as
   `logoW` is on the homepage: 208 above 768 and 168 below, which is one number
   in three places rather than a third opinion. At the mark's 5.7878 ratio that
   is 35.94px tall inside a 72px bar, and 29.03 inside the phone's 60px one. */
.pt-bar__mark img { width: 208px; height: auto; display: block; }
/* The word that says which of Joy Group's things this is. Set like the site's
   kickers, at the size the header can carry without competing with the mark. */
.pt-bar__where { font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
  font-weight: var(--fw-medium); color: var(--white-a62); }
.pt-bar__right { display: flex; align-items: center; gap: var(--space-4); }
.pt-who { font-size: 13px; letter-spacing: 0.02em; color: var(--white-a62); }
/* The switch left the bar: this portal is for Albanian agents, and a language
   control is the last thing most of them will ever touch. It lives in the footer,
   and the footer is ink, so it is lit the way the header's type is. */
.pt-lang { display: inline-flex; align-items: center; gap: 6px; }
.pt-lang button { appearance: none; background: none; border: 0; cursor: pointer;
  padding: 4px 2px; font-family: var(--font-ui); font-size: 11px; font-weight: var(--fw-medium);
  letter-spacing: 0.12em; color: var(--white-a62); }
.pt-lang button[aria-pressed="true"] { color: var(--crisp-white); }
/* A RING, not a colour it already wears. This said `color: crisp-white` — which
   is exactly what the PRESSED button is at rest — while also removing the
   browser's own outline, so tabbing onto the language you are already reading
   changed nothing at all and took the default indicator away with it. */
.pt-lang button:focus-visible { outline: 1px solid var(--crisp-white);
  outline-offset: 3px; color: var(--crisp-white); }
.pt-lang > span { color: var(--white-a22); font-size: 11px; }

/* Clipped, not hidden: `hidden` takes an element out of the accessibility tree
   as well as off the screen, which is the opposite of what a label for an icon
   button is for. */
.pt-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ---------- the small footer, INK ----------
   White with a hairline over it, this was a line of small grey type under a
   white page: nothing about it said the page had ended, and on the login screen
   it read as a third paragraph. What ends a page is the ground changing — the
   marketing footer has always known that, and the bar at the top of this one
   already proves the portal can carry ink. So the footer is ink, and the
   hairline comes off with it: a tint edge does not need a rule to be an edge.
   The WhatsApp pill is the third thing in it and the reason it grew a right
   side — number, language, and the two of them kept apart from the address. */
.pt-foot { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3) var(--space-5); flex-wrap: wrap;
  /* 24, not 32. The pill is 38px tall where the type it replaced was 18, and at
     the old padding the footer went 86 -> 105 — which put the login page one
     pixel over a 900-tall screen and gave a page holding two fields a scrollbar.
     Tightening the padding by the difference puts the footer back at the 86 it
     has always been, so nothing downstream of it moved. */
  padding: var(--space-3) clamp(20px, 3vw, 40px) calc(var(--space-3) + var(--sa-b));
  background: var(--ink-black);
  font-size: 11px; letter-spacing: 0.04em; color: var(--white-a62); }
.pt-foot__side { display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-2) var(--space-5); }
/* WhatsApp is a CIRCLE, and it is the only thing on the right. The number came
   off it: what an agent needs is the way to reach the desk, and the number was
   the widest element in a footer whose whole brief was "small and concise".
   40 on a mouse, 44 under a thumb — the smaller figure keeps the footer at the
   86px it has always been, which is what the login page's one-screen fit is
   measured against. It is lit and inverts like .joy-btn--on-dark, so the portal
   has one button language rather than a second one invented for the footer. */
.pt-wa { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%;
  background: transparent; border: var(--hairline) solid var(--white-a62);
  color: var(--crisp-white); text-decoration: none;
  transition: background-color var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard); }
.pt-wa svg { width: 19px; height: 19px; display: block; }
.pt-wa:hover, .pt-wa:focus-visible { outline: none;
  background: var(--crisp-white); color: var(--ink-black); border-color: var(--crisp-white); }
.pt-foot__who { letter-spacing: 0.16em; font-weight: var(--fw-medium); color: var(--crisp-white); }
.pt-foot__help a { color: var(--crisp-white); border-bottom-color: var(--white-a22); }
.pt-foot__help a:hover { color: var(--crisp-white); border-bottom-color: var(--white-a62); }

/* ---------- login ---------- */
/* space-8, not space-10. The card is 596 tall and the bar and footer take 158
   between them, which left 742 of a 900-tall screen against a 756 requirement -
   so a login page with two fields on it scrolled by fourteen pixels. 64 top and
   bottom is breathing room without being the reason the page does not fit. */
/* A PHOTOGRAPH BEHIND IT, and a scrim over that. The picture is one of the
   bright ones — white facades against a pale sky — so nothing laid on it has an
   edge of its own, and this design system does not allow a shadow to invent one.
   The scrim is neutral ink: it darkens without shifting a hue, which is the line
   between seating content and the cool duotone that was rejected for the
   marketing photography. Deeper on the phone, where the type sits straight on
   the picture instead of on a card.
   Two files, one per band — 2400px behind the desktop (448 KB), 1200px behind
   the phone (160 KB). Same discipline as Images/construction/: the camera master
   stays on disk, only the derivative ships. */
.pt-login { position: relative; display: grid; place-items: center;
  padding: var(--space-8) clamp(20px, 4vw, 40px) calc(var(--space-8) + var(--sa-b));
  background-image: url("/assets/agents/login.jpg");
  background-size: cover; background-position: center; }
.pt-login::before { content: ""; position: absolute; inset: 0;
  background: rgba(20, 20, 20, 0.45); }

/* DESKTOP: the original stack inside a white card, at the user's ask. The gaps
   between kicker, name and note are deliberately tight — 6, 16, 32 against the
   24, 32, 48 they used to be — because the card has to hold everything on one
   screen and that space was the cheapest to buy. */
.pt-login__card { position: relative; width: 100%; max-width: 420px;
  background: var(--crisp-white); border: var(--hairline) solid var(--rule);
  padding: var(--space-5) var(--space-4) var(--space-4); }
.pt-login__kicker { margin: 0 0 6px; font-size: 12px; letter-spacing: 0.16em;
  text-transform: uppercase; font-weight: var(--fw-medium); color: var(--graphite); }
.pt-login__title { margin: 0 0 var(--space-2); font-family: var(--font-display);
  font-weight: var(--fw-display-light); font-size: var(--text-display-4-size);
  line-height: 1.1; letter-spacing: var(--text-display-4-ls); color: var(--text-primary); }
.pt-login__note { margin: 0 0 var(--space-4); max-width: 44ch;
  font-weight: var(--fw-light); font-size: 15px; line-height: 1.7; color: var(--text-secondary); }
.pt-form { display: flex; flex-direction: column; gap: var(--space-3); }
.pt-form label { display: block; }
.pt-form .pt-lbl { display: block; margin-bottom: 8px; font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; font-weight: var(--fw-medium); color: var(--graphite); }
.pt-form input { width: 100%; height: 52px; box-sizing: border-box;
  border: var(--hairline) solid var(--rule); border-radius: 2px; background: var(--crisp-white);
  padding: 0 16px; font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: 16px; color: var(--ink-black);
  transition: border-color var(--dur-base) var(--ease-standard); }
.pt-form input:focus { outline: none; border-color: var(--ink-black); }
.pt-form .joy-btn { margin-top: var(--space-3); justify-content: center; height: 52px; }
.pt-login__apply { margin: var(--space-4) 0 0; padding-top: var(--space-3);
  border-top: var(--hairline) solid var(--rule);
  font-size: 14px; line-height: 1.7; color: var(--text-secondary); }

/* SHORT LAPTOPS. The outer padding was solved for a 900-tall screen in ENGLISH;
   the Albanian note wraps to three lines, which makes the card 613 against 587,
   and 613 plus 2x64 of surround is 741 in the 740 a 900-tall screen leaves. So
   1440x900 was one pixel over and 1366x768 was 133 over, with a full-bleed
   photograph scrolling along with it.
   Gated at 920 rather than 860 so that 900 is inside it: the surround comes in,
   the card's own padding tightens, and both fit exactly. Above 920 nothing
   changes — a tall monitor keeps the roomier card.
   min-width: 769 because a phone has its own composition and scrolls by nature —
   this is about a laptop that is wide but short.
   PLACED AFTER .pt-login__apply, not before it: a media query adds no
   specificity, so an override written above the base rule it overrides simply
   loses the cascade. */
@media (min-width: 769px) and (max-height: 920px) {
  .pt-login { padding-top: var(--space-3);
    padding-bottom: calc(var(--space-3) + var(--sa-b)); }
  /* .pt-login__card, not __body — the card was flattened when the phone stopped
     using one, and this rule was still naming a wrapper that no longer exists,
     so a third of the compression was doing nothing. */
  .pt-login__card { padding: var(--space-4); }
  .pt-login__note { margin-bottom: var(--space-3); }
  .pt-login__apply { margin-top: var(--space-3); padding-top: var(--space-2); }
  .pt-form { gap: var(--space-2); }
  .pt-form .joy-btn { margin-top: var(--space-2); }
}

.pt-login__apply a { color: var(--ink-black); }

/* ---------- filter bar ---------- */
.pt-main { padding: var(--space-8) clamp(20px, 3vw, 40px) calc(var(--space-10) + var(--sa-b)); }
.pt-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-5); flex-wrap: wrap; margin-bottom: var(--space-6); }
.pt-head h1 { margin: 0; font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: var(--text-display-4-size); line-height: 1.1; letter-spacing: var(--text-display-4-ls); }
/* It heads the results column now, not the page title's right margin. 14px,
   because at 13 it was the smallest thing on the screen as well as the loneliest,
   and it is the only line that tells an agent a filter is hiding eight units. */
/* RIGHT, at every width. max-width has to come off with it: the design system
   caps every <p> at a reading measure, which lands this one at 538px inside a
   1196px column, so text-align alone would have stopped the figure a third of the
   way short of the edge it is meant to line up with. A two-word line has no
   measure worth protecting.
   On a phone the table is full bleed but the count is not — it stays inside the
   page's 20px gutter, so it ends exactly where the price column ends in every row
   beneath it (355 at 375 wide, 370 at 390, 410 at 430). Aligning it to the
   table's own edge instead would push it to the bezel and line it up with
   nothing. */
.pt-count { margin: 0 0 var(--space-3); font-size: 14px; letter-spacing: 0.02em;
  color: var(--graphite); max-width: none; text-align: right; }
.pt-results { min-width: 0; }
/* AND OUT OF THE COLUMN'S FLOW from 901, where the sidebar exists.
   In flow the count's line plus its margin was 47px of height at the top of the
   results column, so the table started at 398 while the filter panel started at
   351 — the two columns began on different edges, and once the count moved right
   the left half of that band was empty white. Taking it out of flow lets the
   table start where the panel does; the count sits in .pt-head's own 48px bottom
   margin instead, which was already empty.
   bottom: 100% puts its bottom edge on the column's top edge, and the 12px margin
   lifts it off: 13px clear of the header above, 12px clear of the table below,
   out of 25px of slack. It cannot collide however tall the header grows — the
   band is .pt-head's margin, and the count is measured from the column top, not
   from the header.
   Not below 901: there the filter is stacked ABOVE the results rather than beside
   them, so there is no second column top to line up with, and the count stays in
   flow where it reads as the caption to the run underneath it. */
@media (min-width: 901px) {
  .pt-results { position: relative; }
  .pt-count { position: absolute; right: 0; bottom: 100%; margin: 0 0 12px; }
}

/* ---------- the layout: filter left, list right ----------
   The filter is a COLUMN on desktop and it sticks, so it can be changed while
   the list scrolls past. `align-items: start` is what lets it stick — a default
   stretched grid item is as tall as the row and has nothing to stick within. The
   offset clears the sticky 72px bar. */
.pt-layout { display: grid; grid-template-columns: 300px 1fr;
  gap: var(--space-8); align-items: start; }
.pt-filters { position: sticky; top: calc(72px + var(--space-5) + var(--sa-t));
  display: grid; gap: var(--space-4); align-content: start;
  padding: var(--space-5); background: var(--bg-alt);
  /* A pinned panel taller than the space it is pinned in can never be scrolled
     into view — sticky does not move, so there is no scroll position that
     reveals its bottom. The panel is ~589px and pins at 112, so on anything
     under ~700px tall the Clear button was permanently below the fold. Capping
     it to the room available and letting it scroll inside itself is the only
     arrangement where every control stays reachable at every height. */
  max-height: calc(100svh - 72px - var(--space-5) - var(--sa-t) - var(--space-5));
  overflow-y: auto; }
.pt-f { display: flex; flex-direction: column; gap: 8px; }
.pt-f > span { font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  font-weight: var(--fw-medium); color: var(--graphite); }
/* :not([type="range"]) is load-bearing. The range inputs live inside a .pt-f, so
   this rule was giving them a 44px bordered white box and 14px of side padding -
   which pushed both thumbs off the 2px track they are supposed to ride and inset
   them from the ends of it. The number boxes below the track DO want this. */
.pt-f select, .pt-f input:not([type="range"]) { width: 100%; height: 44px; box-sizing: border-box;
  -webkit-appearance: none; appearance: none;
  border: var(--hairline) solid var(--rule); border-radius: 2px; background: var(--crisp-white);
  padding: 0 14px; font-family: var(--font-ui); font-weight: var(--fw-light);
  font-size: 15px; color: var(--ink-black); }
/* the chevron CSS cannot draw on a native select, so it is painted on */
.pt-f select { background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-black) 50%),
    linear-gradient(135deg, var(--ink-black) 50%, transparent 50%);
  background-position: calc(100% - 18px) 19px, calc(100% - 13px) 19px;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 34px; }
.pt-f select:focus, .pt-f input:not([type="range"]):focus { outline: none; border-color: var(--ink-black); }
.pt-f--reset { justify-content: flex-end; }
.pt-f--reset button { height: 44px; appearance: none; cursor: pointer;
  background: none; border: var(--hairline) solid var(--rule); border-radius: 2px;
  font-family: var(--font-ui); font-size: 12px; letter-spacing: 0.14em;
  text-transform: uppercase; font-weight: var(--fw-medium); color: var(--graphite);
  transition: color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard); }
.pt-f--reset button:hover { color: var(--ink-black); border-color: var(--ink-black); }


/* ---------- the phone's filter toggle ----------
   Nothing above 768: there the filter is a sidebar with its own column. */
.pt-filter-toggle { display: none; }

/* ---------- the size range ----------
   Two <input type="range"> stacked on one track. Both are transparent and
   pointer-events: none so the painted track shows through and neither input
   swallows clicks meant for the other; only the THUMBS take the pointer back.
   Without that, whichever input is on top captures the whole width and the
   lower handle cannot be grabbed at all. */
.pt-range__wrap { position: relative; height: 26px; }
.pt-range__track { position: absolute; left: 0; right: 0; top: 12px; height: 2px;
  background: var(--soft-stone); }
.pt-range__fill { position: absolute; top: 0; height: 2px; background: var(--ink-black); }
.pt-range__in { position: absolute; left: 0; top: 0; width: 100%; height: 26px;
  margin: 0; padding: 0; background: transparent; pointer-events: none;
  -webkit-appearance: none; appearance: none; border: 0; }
.pt-range__in:focus { outline: none; }
.pt-range__in::-webkit-slider-thumb { -webkit-appearance: none; pointer-events: auto;
  width: 16px; height: 16px; border-radius: 50%; cursor: grab;
  background: var(--crisp-white); border: var(--hairline) solid var(--ink-black);
  box-shadow: 0 0 0 3px var(--bg-alt); }
.pt-range__in::-moz-range-thumb { pointer-events: auto;
  width: 16px; height: 16px; border-radius: 50%; cursor: grab; border: 1px solid var(--ink-black);
  background: var(--crisp-white); box-shadow: 0 0 0 3px var(--bg-alt); }
.pt-range__in:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--bg-alt), 0 0 0 5px var(--ink-black); }
.pt-range__nums { display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: end; gap: 8px; }
.pt-range__nums label { display: block; }
/* 11px, like every other small label here. 10 was the only one of its size in the
   whole portal and read as a mistake rather than a distinction. */
.pt-range__lbl { display: block; margin-bottom: 6px; font-size: 11px; letter-spacing: 0.16em;
  text-transform: uppercase; font-weight: var(--fw-medium); color: var(--graphite); }
.pt-range__dash { padding-bottom: 13px; color: var(--graphite); }

/* ---------- the list ---------- */
/* A LIST, at the user's instruction, not a card grid: an agent is scanning for a
   fit against a client's brief, and rows compare where cards do not. The
   photography lives one click in, on the overlay. */
/* Two tables, not one list with headings in it. Each run is a boxed table with
   an ink head — the hairline goes right around the pair, at the user's ask, and
   the runs are set apart by white between the boxes rather than by a bigger gap
   inside one continuous rule. */
.pt-list { display: flex; flex-direction: column; gap: var(--space-8); }
.pt-row { display: grid; align-items: center; gap: var(--space-4);
  grid-template-columns: 1.5fr 1fr 1.4fr 1fr 24px;
  width: 100%; text-align: left; padding: var(--space-4) var(--space-3);
  background: none; border: 0; border-bottom: var(--hairline) solid var(--rule);
  cursor: pointer; font-family: var(--font-ui);
  transition: background var(--dur-fast) var(--ease-standard); }
.pt-row:hover, .pt-row:focus-visible { background: var(--bg-alt); outline: none; }
.pt-row__unit { font-size: 16px; font-weight: var(--fw-medium); color: var(--ink-black); }
/* 13px at 400, at EVERY width, phone included. The colour was never the problem
   — graphite is #6B6B6B, 5.3:1 on white, comfortably past AA — it is the stroke:
   Neue Haas Light at 12px is genuinely faint. Size and weight are the two levers
   and both move a little rather than either being pushed far.
   NOT a tag. The project line was briefly 11px uppercase ink at 500 on the phone,
   and the user's verdict was that bold made it indistinguishable: set like that
   it stopped reading as the unit's subtitle and started competing with the unit's
   own name above it. Bigger, not louder — and one treatment at every width
   rather than two ideas about the same line. */
.pt-row__sub { display: block; margin-top: 3px; font-size: 13px; font-weight: var(--fw-regular);
  letter-spacing: 0.02em; color: var(--graphite); }
.pt-cell { font-size: 14px; font-weight: var(--fw-light); color: var(--text-secondary); }
/* A tag, not a sentence. At 14px light it was the same voice as the sub-line two
   inches to its left, which already says the same word — so the eye read the type
   twice and neither time as a category. Uppercase and tracked, it is scannable
   down the column, which is the only reason to give a repeated word its own
   column at all. */
.pt-cell--type { font-size: 11px; font-weight: var(--fw-medium); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--graphite); }
/* Total size for an apartment, villa size for a villa — the headline figure the
   user named as the one that matters, with the breakdown under it rather than
   instead of it. */
.pt-size__lead { font-size: 16px; font-weight: var(--fw-medium); color: var(--ink-black); }
.pt-size__parts { display: block; margin-top: 3px; font-size: 13px; font-weight: var(--fw-regular);
  color: var(--graphite); }
/* SALE OR RENT, against the price. It used to be the last word of the grey
   sub-line — 12px, the same grey as everything around it, in the place a reader's
   eye has already left. Whether a unit is sold or let is the second thing an
   agent needs to know after what it costs, so it is said where they are already
   looking, as a tag rather than as more sentence. The tint is what makes it a
   tag; on a hovered row the row itself goes mist, so the tag goes white to stay
   one. */
/* The bedroom count, in the Albanian convention the client already thinks in.
   Set as the deal tag is, so the row carries one tag on each side — what you get
   on the left, how you get it on the right — rather than two unrelated
   treatments. Same inversion on hover, for the same reason. */
.pt-beds { display: inline-block; margin-left: 10px; padding: 3px 8px;
  background: var(--bg-alt); vertical-align: 1px;
  font-size: 11px; letter-spacing: 0.06em; font-weight: var(--fw-medium);
  color: var(--ink-black); white-space: nowrap; }
.pt-row:hover .pt-beds, .pt-row:focus-visible .pt-beds { background: var(--crisp-white); }

.pt-price { display: flex; flex-direction: column; align-items: flex-end; gap: 7px;
  text-align: right; }
.pt-price__v { font-size: 16px; font-weight: var(--fw-medium); color: var(--ink-black); }
.pt-deal { display: inline-block; padding: 3px 8px; background: var(--bg-alt);
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: var(--fw-medium); color: var(--ink-black); white-space: nowrap; }
.pt-row:hover .pt-deal, .pt-row:focus-visible .pt-deal { background: var(--crisp-white); }
.pt-chev { justify-self: end; color: var(--soft-stone); font-size: 15px; }
.pt-row:hover .pt-chev { color: var(--ink-black); }

/* ---------- the overlay ---------- */
.pt-ov[hidden] { display: none !important; }
.pt-ov { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center;
  padding: clamp(16px, 3vw, 40px); }
.pt-ov__scrim { position: absolute; inset: 0; background: rgba(20,20,20,0.62); border: 0; }
/* The TEXT scrolls, not the panel. A grid item stretches to the row, and the row
   is as tall as the taller column — so once the price terms made the body column
   taller than the screen, the media column stretched with it and everything
   pinned to the bottom of the image stage (the floor caption, the dots) sat below
   the panel's own bottom edge, off screen. Capping the panel and letting the body
   scroll inside it fixes that and is better besides: the image stays put while
   the specification is read beside it. min-height: 0 on both, or a grid item
   refuses to shrink below its content and the overflow never engages. */
.pt-ov__panel { position: relative; width: min(1040px, 100%); max-height: 90svh; overflow: hidden;
  background: var(--crisp-white); display: grid; grid-template-columns: 1.25fr 1fr;
  /* minmax(0, 1fr), not the implicit auto row. max-height caps the CONTAINER, but
     an auto row still sizes to its content and overflows straight back out of it -
     which is why min-height: 0 on the children changed nothing on its own. */
  grid-template-rows: minmax(0, 1fr); }
.pt-ov__panel > .pt-ov__media { min-height: 0; }
.pt-ov__body { min-height: 0; overflow-y: auto; }
.pt-ov__media { background: var(--bg-alt); min-height: 320px; display: flex; flex-direction: column; }

/* Two tabs, not a strip of numbered thumbnails. "Photo 1 / Photo 2" made the
   reader count; "Images / Floor plan" tells them what they are choosing between. */
.pt-tabs { display: flex; gap: var(--space-5); padding: var(--space-4) var(--space-5) 0; }
.pt-tab { appearance: none; background: none; border: 0; cursor: pointer; padding: 0 0 10px;
  font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: var(--fw-medium); color: var(--graphite);
  border-bottom: 2px solid transparent;
  transition: color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard); }
.pt-tab[aria-pressed="true"] { color: var(--ink-black); border-bottom-color: var(--ink-black); }
/* This painted --bg-alt on a ground that IS --bg-alt: .pt-ov__media carries the
   same token, so the focused tab and its 6px halo measured 1.00:1 against what
   was already behind them — invisible — and on the SELECTED tab, which is ink at
   rest, the colour change was a no-op too. An ink ring is the one thing that
   shows on this ground, and it cannot be confused with the underline that means
   selected. (Predates this session; found in the sweep and fixed here.) */
.pt-tab:focus-visible { outline: 1px solid var(--ink-black); outline-offset: 3px;
  color: var(--ink-black); }

.pt-stage { position: relative; flex: 1; min-height: 280px; overflow: hidden; }
/* CONTAIN, never cover. The photography is landscape and the floor plans all
   are, and the desktop stage measured 578x753 — a ratio of 0.77. A 3:2 render at
   `cover` shows 51% of its width and the rest is thrown away; a cropped floor
   plan is not a floor plan at all. Nothing here may be cropped.
   The ground goes from soft-stone to mist so the surround reads as a mount round
   the image rather than a slab behind it. */
/* display: BLOCK, not grid. As a grid with place-items:center the row was auto
   sized by the image, so the img's own `height: 100%` had no definite row to
   resolve against and fell back to auto — a 1400x990 plan came out 578x409
   inside a 385-tall stage and lost 24px off the top and bottom to the stage's
   overflow:hidden. `contain` never got the chance to engage. Absolutely
   positioned with inset:0 the frame's height IS definite, so the image fills it
   and contain letterboxes inside it, which is the whole point. */
.pt-frame { position: absolute; inset: 0; background: var(--bg-alt); }
.pt-frame img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* ON the margins, as asked — vertically centred, one at each edge of the stage.
   White on a ring so they hold against a photograph of any brightness; there is
   no photography yet and they still must not disappear against the first one. */
.pt-arrow { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 40px; height: 40px; display: grid; place-items: center; cursor: pointer;
  background: rgba(255, 255, 255, 0.92); border: var(--hairline) solid var(--rule);
  border-radius: 50%; font-size: 15px; line-height: 1; color: var(--ink-black);
  transition: background var(--dur-fast) var(--ease-standard); }
.pt-arrow:hover { background: var(--crisp-white); }
/* Focus INVERTS rather than brightening. The hover state takes a 92%-white disc to
   100% white, which as a focus indicator is invisible — the one state a keyboard
   user has no other way of finding. Inverting matches the house idiom, where
   .joy-btn's focus is its hover state filled. */
.pt-arrow:focus-visible { outline: none;
  background: var(--ink-black); color: var(--crisp-white); border-color: var(--ink-black); }
.pt-arrow--prev { left: var(--space-3); }
.pt-arrow--next { right: var(--space-3); }
.pt-arrow[hidden] { display: none; }

/* The floor's NAME, because three empty frames with three dots under them do not
   say which floor you are looking at — and until there is imagery, the name is
   the only thing that does. Sits above the dots, centred with them. */
.pt-caption { position: absolute; left: 0; right: 0; bottom: 28px; z-index: 2;
  margin: 0; text-align: center;
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: var(--fw-medium); color: var(--ink-black); }
.pt-caption:empty { display: none; }

.pt-dots { position: absolute; left: 0; right: 0; bottom: 12px; z-index: 2;
  display: flex; justify-content: center; gap: 6px; }
.pt-dots i { width: 6px; height: 6px; border-radius: 50%; background: rgba(30,30,30,0.28); }
.pt-dots i.is-on { background: var(--ink-black); }
.pt-ov__body { padding: var(--space-6); }
.pt-ov__close { position: absolute; top: 10px; right: 10px; z-index: 2; width: 36px; height: 36px;
  cursor: pointer; background: var(--crisp-white); border: var(--hairline) solid var(--rule);
  border-radius: 50%; font-size: 15px; line-height: 1; color: var(--ink-black); }
/* The unit's name is the first thing read and the thing an agent says on the
   phone, so it stops being the same Light the whole site whispers in. Begum
   ships Regular, Medium, Semibold and Bold as real files here — this is a
   genuine weight, not a synthesised smear. Medium rather than Bold: at 28px
   against 11px tracked labels, Bold shouts, and the point is presence, not
   volume. 600 and 700 exist if more is wanted. */
.pt-ov__title { margin: 0 0 2px; font-family: var(--font-display);
  font-weight: var(--fw-medium); font-size: var(--text-display-5-size); line-height: 1.2; }
.pt-ov__where { margin: 0 0 var(--space-5); font-size: 13px; letter-spacing: 0.02em; color: var(--graphite); }
.pt-specs { margin: 0 0 var(--space-6); border-top: var(--hairline) solid var(--rule); }
.pt-spec { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4);
  padding: 12px 0; border-bottom: var(--hairline) solid var(--rule); }
.pt-spec dt { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: var(--fw-medium); color: var(--graphite); }
.pt-spec dd { margin: 0; font-size: 15px; font-weight: var(--fw-light); color: var(--ink-black); }
/* The one figure the user singled out as the most important gets the weight. */
.pt-spec--lead dd { font-size: 20px; font-weight: var(--fw-medium); }
.pt-ov__cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.pt-ov__note { flex: 1 1 100%; margin: 0; font-size: 12px; font-weight: var(--fw-light);
  line-height: 1.6; color: var(--graphite); }
/* An anchor with no href is already unclickable and unfocusable; this is what
   makes that visible rather than a button that merely does nothing. */
.joy-btn.is-off { opacity: 0.4; cursor: default; pointer-events: none; }
.pt-ov__note:empty { display: none; }


/* ---------- small helps for a first-time reader ---------- */
.pt-hint { margin: var(--space-2) 0 0; max-width: 56ch;
  font-size: 14px; font-weight: var(--fw-light); line-height: 1.6; color: var(--text-secondary); }

/* "Showing 4 of 11" rather than a bare count: a number on its own does not tell
   someone that a filter is hiding the rest. */
.pt-count strong { font-weight: var(--fw-medium); color: var(--ink-black); }

/* The clear button goes quiet when there is nothing to clear, so the one moment
   it matters it is the only live control in the panel. */
.pt-f--reset button[disabled] { opacity: 0.38; cursor: default; }
.pt-f--reset button[disabled]:hover { color: var(--graphite); border-color: var(--rule); }

.pt-empty { padding: var(--space-10) 0; text-align: center; color: var(--graphite); font-size: 15px; }
.pt-empty .joy-btn { margin-top: var(--space-4); }

/* ---------- what the price includes ---------- */
.pt-terms { margin: 0 0 var(--space-6); }
.pt-terms h3 { margin: 0 0 var(--space-2); font-family: var(--font-ui);
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: var(--fw-medium); color: var(--graphite); }
.pt-terms ul { margin: 0 0 var(--space-4); padding: 0; list-style: none; }
.pt-terms li { position: relative; padding: 3px 0 3px 20px;
  font-size: 14px; font-weight: var(--fw-light); line-height: 1.55; color: var(--text-primary); }
/* A tick and a cross drawn as text, so the two lists are told apart at a glance
   without colour — this palette has no green and no red to spend. */
.pt-terms li::before { position: absolute; left: 0; top: 3px; font-size: 13px; }
.pt-terms .is-in  li::before { content: "+"; color: var(--ink-black); }
.pt-terms .is-out li::before { content: "\2013"; color: var(--graphite); }
.pt-terms .is-out li { color: var(--text-secondary); }
.pt-terms__none { margin: 0 0 var(--space-4); font-size: 14px; font-weight: var(--fw-light);
  line-height: 1.55; color: var(--graphite); }
.pt-terms__ask { margin: 0; padding-top: var(--space-3);
  border-top: var(--hairline) solid var(--rule);
  font-size: 12px; line-height: 1.6; color: var(--graphite); }

/* A word on how the apartment figures relate, because "net", "common" and
   "total" only look self-evident to someone who already knows. */
.pt-specs__note { margin: calc(var(--space-2) * -1) 0 var(--space-5);
  font-size: 12px; font-weight: var(--fw-light); color: var(--graphite); }

/* ---------- the standing notice, and its dismiss ---------- */
.pt-demo { display: flex; align-items: flex-start; gap: var(--space-3);
  padding: 12px clamp(20px, 3vw, 40px); background: var(--soft-stone);
  font-size: 12px; line-height: 1.6; letter-spacing: 0.04em; color: var(--ink-black); }
/* The X goes to the band's right edge, not to the end of the sentence: the
   design system caps every <p> at a reading measure, so a paragraph in a 1640px
   band stops at 461 and a button that merely followed it landed in the middle of
   the screen. `margin-left: auto` puts it where a dismiss belongs whatever the
   text does. The measure is widened a little here — this is a 12px notice, not
   body copy, and 461px of it was two lines against 1100px of empty band. */
.pt-demo__t { margin: 0; flex: 1 1 auto; max-width: 96ch; }
/* Pulled out to the band's own edges with negative margins, so the target is 32px
   without the band growing to hold it. */
.pt-demo__x { flex: 0 0 auto; appearance: none; background: none; border: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; margin: -5px -8px -5px auto;
  font-size: 12px; line-height: 1; color: var(--ink-black); opacity: 0.65;
  transition: opacity var(--dur-fast) var(--ease-standard),
              background-color var(--dur-fast) var(--ease-standard); }
.pt-demo__x:hover { opacity: 1; }
.pt-demo__x:focus-visible { outline: none; opacity: 1; background: var(--crisp-white); }
html.notice-off .pt-demo { display: none; }

/* ---------- the type groups ---------- */
/* Each run is a TABLE with a head, not a heading over a list. The head is ink and
   the box is a hairline all the way round, both at the user's ask — and the two
   together are what make the end of the villas and the start of the apartments
   impossible to scroll past, which was the other half of it.
   The name stays Begum, now in capitals: the display face is what the rest of
   this site uses to say "new section", and the row's own type cell goes the other
   way into a small tracked tag, so the two never read as two sizes of one thing.
   The count is no longer a label — it is a number, and an agent scanning for how
   much is on offer reads it before the word next to it. */
.pt-grp { border: var(--hairline) solid var(--rule); background: var(--crisp-white); }
.pt-group { display: flex; align-items: center; gap: var(--space-3);
  width: 100%; margin: 0; padding: 15px var(--space-3);
  background: var(--ink-black); color: var(--crisp-white);
  border: 0; text-align: left; font-family: var(--font-ui); }
.pt-group__name { font-family: var(--font-display); font-weight: var(--fw-display-light);
  font-size: calc(var(--text-display-5-size) * 0.72); line-height: 1.15;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--crisp-white); }
/* (5), and the brackets are CSS so the number in the DOM stays a number.
   Vertically the two do not centre by themselves: the name is Begum caps at
   line-height 1.15 and the count is Neue Haas with parentheses, and each box
   centres its own line rather than its own ink. The shift below is measured —
   see the note where it is set. */
.pt-group__n { font-size: 18px; font-weight: var(--fw-light); letter-spacing: 0.02em;
  color: var(--white-a62); line-height: 1; }
.pt-group__n::before { content: "("; }
.pt-group__n::after  { content: ")"; }
/* THE SHIFT. align-items centres each span's LINE BOX; what has to line up is the
   ink, and neither face puts its ink in the middle of its own box.

   MEASURE THE BASELINE, DO NOT DERIVE IT. The first attempt at this solved the
   baseline from canvas fontBoundingBoxAscent/Descent and was wrong by 0.84px: at
   20.16px Chrome reports those as a rounded 19 and 7, and the metrics it actually
   LAYS BEGUM OUT WITH are different again. A zero-height inline-block with
   vertical-align:baseline aligns its top to the real baseline and reports it to
   three decimals — that is the only figure to trust here.
   Probed at 1440x900, both group heads, after document.fonts.ready:
     name  Begum 20.16px / line-height 23.184 -> baseline 17.000px from box top
           (0.84325em, NOT the 0.885em the font metrics imply). Ink 13.810 above
           it and 0.222 below, so the caps' centre rides 1.380px = 0.0682em HIGH
           of the box centre.
     count Haas 18px / line-height 18 -> baseline 15.000. "(5)" inks 13.572 above
           and 2.124 below, so the brackets' centre rides 0.276px = 0.0153em LOW.
   Up by the sum, 1.656px, and written against the token because the caps half
   scales with the name (0.0682 x 0.72 = 0.0491 per token px) while the brackets
   half is fixed at the count's own 18px.
   --text-display-5-size is a FLAT 28px on this page — the 769+ clamp lives in
   site.css:1780 and portal.css does not load site.css — so this computes to the
   same -1.65px at every width. It is written as the token anyway so the shift
   follows if the name's size is ever changed.
   Verified after: residual 0.01px, overshoot 0.83 above the cap band and 0.83
   below it. Before, it was 0.726px low and hung 1.56px under the baseline against
   0.11px over the cap top — a 14.7x imbalance. */
.pt-group__n { position: relative;
  top: calc(var(--text-display-5-size) * -0.0491 - 0.276px); }
/* The box supplies the line under the last row; the row's own would double it. */
.pt-rows .pt-row:last-child { border-bottom: 0; }

/* ---------- the phone's filter pills ----------
   Hidden here and shown in the phone block, not the other way round: the sidebar
   shows its own state at every other width, so a second copy of it there would be
   the same information said twice. */
.pt-chips { display: none; }
.pt-chip { appearance: none; cursor: pointer; display: inline-flex; align-items: center;
  gap: 10px; min-height: 44px; padding: 0 12px 0 14px;
  background: var(--bg-alt); border: 0;
  font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; font-weight: var(--fw-medium); color: var(--ink-black);
  transition: background-color var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard); }
.pt-chip__x { font-size: 11px; color: var(--graphite); }
.pt-chip:hover, .pt-chip:focus-visible { outline: none;
  background: var(--ink-black); color: var(--crisp-white); }
.pt-chip:hover .pt-chip__x, .pt-chip:focus-visible .pt-chip__x { color: var(--crisp-white); }

/* ---------- the stage is landscape where the card is two columns ----------
   The media column is a grid item and stretched to the panel, which the BODY
   makes tall — 810px at 1440x900 — so the stage came out 578x753 and portrait.
   `contain` alone would fix the cropping and leave the dots and the floor
   caption stranded 184px below the image they belong to, because both are
   positioned against the stage's bottom rather than the picture's.
   So the stage takes a landscape ratio of its own width and centres itself in
   the column. The leftover is the media column's own mist, which is the same
   tone as the frame's, so the column still reads as one panel with the image
   mounted in it — and the arrows, the caption and the dots all sit against the
   picture's edges again.
   Only where the card HAS two columns. At 900 and below it stacks, the media
   takes its own row, and the stage is already 343x240 — landscape, which is why
   the phone was reported as fine. */
@media (min-width: 901px) {
  .pt-stage { flex: 0 0 auto; aspect-ratio: 3 / 2; margin: auto 0; }
}

/* ---------- narrow desktop / landscape tablet, 901-1200 ----------
   At 1024 the sidebar left the list 598px for five columns: the unit cell was
   127px and the size cell 118, and every row wrapped to three lines. The band is
   above 900 so it keeps the sidebar - a stacked filter would waste the width -
   but the sidebar gives back 40px and the type column comes off, exactly as it
   does on phones and for the same reason: the run is already headed with it. */
@media (min-width: 901px) and (max-width: 1200px) {
  .pt-layout { grid-template-columns: 260px 1fr; gap: var(--space-6); }
  .pt-row { grid-template-columns: 1.7fr 1.4fr 1fr 24px; }
  .pt-cell--type { display: none; }
}

/* ---------- tablet and phone ---------- */
@media (max-width: 900px) {
  /* Stacked, but the BODY still scrolls and the panel does not. Letting the panel
     scroll was the obvious choice and it broke the close button: .pt-ov__close is
     absolutely positioned inside the panel, and an absolute child of a scroll
     container scrolls away with the content — at 390 the X was gone after ~98px
     and never came back. Media on top at its own height, body taking the rest. */
  .pt-ov__panel { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); overflow: hidden; }
  .pt-ov__media { min-height: 240px; }
  .pt-ov__body { overflow-y: auto; }
  /* A 300px sidebar at 900 leaves the list 560px, which the five columns cannot
     hold. The filter goes back above the list and stops sticking - a panel that
     deep pinned to the top of a short screen would cover the results it filters. */
  .pt-layout { grid-template-columns: 1fr; gap: var(--space-6); }
  /* THREE columns, named, not auto-fit. At 820 auto-fit gave four tracks: the
     three selects and Clear filled row 1, the range took the whole of row 2, and
     Clear was pushed alone onto row 3 beside 482px of empty mist — while the two
     number boxes, stretched across a full-width range, reached 333px each (371 at
     900) for a field that holds three digits. Fixed at three, the range takes two
     of them and Clear sits in the third, bottom-aligned by the align-items
     already here, and the boxes come back to 213. */
  .pt-filters { position: static; grid-template-columns: repeat(3, 1fr);
    align-items: end; }
  .pt-range { grid-column: 1 / 3; }
}
/* A thumb is already on the image, so the arrows come off and the stage is
   swiped. Gated on POINTER TYPE, not width — a touch tablet at 900 swipes too,
   and a narrow desktop window still has a mouse and keeps its arrows. */
@media (hover: none) {
  .pt-arrow { display: none; }
}

@media (max-width: 768px) {
  /* ---- the filter folds away ---- */
  .pt-filter-toggle { display: flex; align-items: center; justify-content: space-between;
    width: 100%; min-height: 48px; padding: 0 var(--space-4); cursor: pointer;
    background: var(--bg-alt); border: 0;
    font-family: var(--font-ui); font-size: 12px; letter-spacing: 0.16em;
    text-transform: uppercase; font-weight: var(--fw-medium); color: var(--ink-black); }
  .pt-filter-toggle__n { font-size: 11px; letter-spacing: 0.04em; color: var(--graphite); }
  /* The chevron turns rather than swapping glyphs, so the control reads as one
     thing in two states instead of two controls. */
  .pt-filter-toggle__n::after { content: "\25BE"; display: inline-block; margin-left: 10px;
    font-size: 13px; letter-spacing: 0; color: var(--ink-black);
    transition: transform var(--dur-fast) var(--ease-standard); }
  .pt-filter-toggle[aria-expanded="true"] .pt-filter-toggle__n::after { transform: rotate(180deg); }
  .pt-filters { display: none; }
  .pt-layout.is-open .pt-filters { display: grid; }
  /* ---- and what it set stays on screen while it is folded ----
     A filter you cannot see is a filter you forget you set. The count on the bar
     says how many; these say which, and each X drops one of them without the
     panel being opened at all. */
  .pt-chips { display: flex; flex-wrap: wrap; gap: 8px; }
  .pt-chips:empty { display: none; }
  /* The pills are a grid item of .pt-layout like the bar and the list, so the
     layout's own 48px row gap was what sat above and below them — a row of pills
     floating in the middle of the page, reading as belonging to neither. Tighten
     the whole column instead of fighting the gap with a negative margin: on a
     phone the bar, the panel, the pills and the list are one apparatus. */
  .pt-layout { row-gap: var(--space-3); }

  /* ---- the runs retract ----
     Phone only. On a 390px screen eleven rows is four and a half screens, and an
     agent who has found their villa still has to scroll the whole apartment run
     to reach the bottom of the page. Folding a run is how they stop having to. */
  .pt-group { padding: 12px var(--space-2); cursor: pointer; }
  .pt-group__chev { margin-left: auto; display: inline-flex; align-items: center;
    font-size: 13px; color: var(--white-a62); }
  .pt-group__chev::after { content: "\25BE"; display: inline-block;
    transition: transform var(--dur-base) var(--ease-standard); }
  .pt-grp.is-shut .pt-group__chev::after { transform: rotate(-90deg); }
  .pt-grp.is-shut .pt-rows { display: none; }
  /* A finger's target on the notice, and the band does not grow to hold it. */
  .pt-demo__x { width: 44px; height: 44px; margin: -9px -12px -9px auto; }
  /* ---- NO PHOTOGRAPH ON THE PHONE, at the user's instruction ----
     The page stays the white it always was, so there is no scrim to fight, no
     white type, and no card: this is the original composition exactly, and the
     only thing done to it is taking out enough vertical slack that it holds one
     screen. The picture is a desktop treatment.
     That also means the 1200px derivative is never fetched on a phone, which is
     160 KB a phone no longer downloads. */
  .pt-login { background-image: none;
    padding-top: var(--space-3);
    padding-bottom: calc(var(--space-3) + var(--sa-b)); }
  .pt-login::before { display: none; }
  .pt-login__card { background: none; border: 0; padding: 0; }
  .pt-login__apply { margin-top: var(--space-3); padding-top: 0; border-top: 0; }
  /* the phone's bar is 60, not 72 */
  .pt-form input { scroll-margin-top: calc(60px + var(--sa-t) + var(--space-2)); }

  /* ---- full bleed ----
     The tables run to the edges of the screen. Inset by the page's 20px gutter
     they sat as two floating cards in a white surround, and on a 390px phone
     that gutter is 10% of the width spent on framing a table that is already
     framed by its own hairline. Edge to edge, the ink head is a band across the
     screen the way a section is, and the rows get their gutter back INSIDE the
     box, so the type still lines up with the heading above.
     The side borders go with the inset — a hairline against the bezel is not a
     box, it is a line you cannot see the other side of. */
  .pt-grp { margin-left: -20px; margin-right: -20px;
    border-left: 0; border-right: 0; }
  .pt-group { padding-left: 20px; padding-right: 20px; }

  /* ---- the footer: TWO rows, four cells ----
     Three stacked lines plus a glyph beside them was 143px of phone for four
     short things. Two rows instead — the address beside the wordmark, the
     language and the mark on one line under them — and it is 114.
     display: contents on the side dissolves the wrapper so its three children
     become grid items of the footer itself, which is what lets the language
     switch sit in row two next to the mark while the mark stays outside the
     wrapper in the markup. Auto-placement then does the whole job in source
     order: wordmark, address, language, mark. */
  .pt-foot { display: grid; grid-template-columns: 1fr auto;
    align-items: center; gap: 10px var(--space-3); }
  .pt-foot__side { display: contents; }
  .pt-foot__help { justify-self: end; text-align: right; }
  /* The 10px of tap padding on AL/EN indented it off the left edge every other
     line in the column starts on. Pulled back, the column has ONE left edge. */
  .pt-foot .pt-lang { margin-left: -10px; }
  /* NO RING on the phone. At 44px around a 19px glyph the ring read as a button
     floating free of everything near it; the glyph alone sits in the bar instead
     of on top of it. The box stays 44px — that is the finger's target, not the
     mark's size — and the ring comes back on focus, where an outline is the
     whole point. */
  .pt-wa { width: 44px; height: 44px; justify-self: end; align-self: center;
    border: 0; background: none; }
  .pt-wa svg { width: 24px; height: 24px; }
  .pt-wa:hover { background: none; color: var(--crisp-white); }
  .pt-wa:focus-visible { background: var(--crisp-white); color: var(--ink-black); }

  /* ---- and the sign-out narrows ----
     121px in English against a 168px wordmark, in a 60px bar. The FONT is
     untouched at the user's instruction; it is .joy-btn's 26px of side padding
     that made the pill, and 16 is still a comfortable pill. 121 -> 101 in
     English, 77 -> 57 in Albanian. The height stays 44: that is the touch floor,
     not decoration. */
  .pt-bar .joy-btn { padding-left: 16px; padding-right: 16px; }

  .pt-bar { height: 60px; padding: 0 20px; padding-top: var(--sa-t); }
  .pt-stage { min-height: 240px; }
  .pt-bar__mark img { width: 168px; }   /* the phone figure, same as site.css */
  .pt-who { display: none; }          /* the name is not worth a line on a phone */
  /* "Agent portal" comes off too: at 390 it wrapped to two lines inside a 60px
     bar and squeezed the sign-out to a 35px sliver of itself. Once an agent is
     signed in, the bar does not need to tell them where they are. */
  .pt-bar__where { display: none; }
  .pt-bar__right { gap: var(--space-3); }
  /* 44px is the floor for a finger. Sign out measured 40 and the footer's language
     buttons 21 — both were comfortably under it. The language pair grows by
     padding, so the text stays the size it was and only the target changes. */
  /* 36, not 44. The pill sat 8px off the top and bottom of a 60px bar and read
     as filling it; at 36 it clears 12 each side and the bar has an edge again.
     Vertical padding has to go with the minimum — .joy-btn's own 13px top and
     bottom makes 38 on its own, so min-height alone would have changed nothing —
     and box-sizing pins the border inside the figure rather than adding 2 to it.
     The label is untouched at 12px, and 11px of ink still surrounds it.
     This is BELOW the 44px target this file uses everywhere else, at the user's
     instruction and knowingly: 44 is Apple's recommendation and WCAG 2.5.5 AAA,
     while the AA requirement (2.5.8) is 24 and 36 clears it by half again. The
     other phone targets — the group heads, the pills, the notice's X, the
     language switch — all stay at 44. */
  .pt-bar .joy-btn { flex: 0 0 auto; white-space: nowrap;
    box-sizing: border-box; min-height: 36px; padding-top: 0; padding-bottom: 0; }
  .pt-foot .pt-lang button { display: inline-flex; align-items: center;
    min-height: 44px; padding: 0 10px; }
  /* The range thumbs sit in a 26px band, which is a small thing to catch with a
     thumb. The band grows on touch; the track and the thumbs do not move, because
     the track is centred in it. */
  .pt-range__wrap { height: 44px; }
  .pt-range__track { top: 21px; }
  .pt-range__in { height: 44px; }
  .pt-main { padding: var(--space-6) 20px calc(var(--space-8) + var(--sa-b)); }
  .pt-filters { grid-template-columns: 1fr 1fr; padding: var(--space-4); }
  .pt-range { grid-column: 1 / -1; }
  /* PROJECT gets the full width. In a half-column at 390 the select is 130.8px
     with about 80.8px of text area, and both project names are far longer than
     that — "Joy Residences — The Villas" measures 168px and "The Apartments" 209,
     so a closed select read "Joy Residenc" whichever one was chosen. There is no
     ellipsis to fall back on: text-overflow computes to clip inside a select. The
     control has to be wide enough to say which project is selected, because
     saying so is the whole of its job.
     :first-child, NOT :first-of-type — two children are .pt-f (a label and the
     range's div), so :first-of-type would match both and stretch the range too. */
  .pt-filters > .pt-f:first-child { grid-column: 1 / -1; }
  /* The row stops being columns and becomes a block: five columns on a 390px
     screen gives every one of them about 70px, which no price fits. */
  /* 20px, the page's own gutter, so a full-bleed table's type sits on the same
     left margin as the heading and the hint above it. */
  .pt-row { grid-template-columns: 1fr auto; gap: var(--space-2) var(--space-3);
    padding: var(--space-4) 20px; }
  .pt-row__unit { grid-column: 1; }
  .pt-price { grid-column: 2; grid-row: 1; }
  /* The type is in the run's heading. (.pt-cell--deal was listed here and never
     existed - the row has no deal cell.) */
  .pt-cell--type { display: none; }
  .pt-size { grid-column: 1 / -1; }
  .pt-chev { display: none; }
  .pt-ov__body { padding: var(--space-5); }
}


/* ---------- SHORT PHONES: the login still has to be one screen ----------
   375x812 and 430x932 have room; 375x667 did not — 60 of bar, 544 of login and
   114 of footer is 718 against 667, so it was 51 over. Every number below is
   taken off the FORM's own rhythm rather than off the words: inputs and button
   46 instead of 52, the gaps and the label offsets in by a notch. Nothing is
   hidden at this size and nothing is smaller to read.
   46 is still clear of the 44px touch floor — that is the point of stopping
   there rather than at 40. */
@media (max-height: 750px) {
  .pt-login .pt-form { gap: var(--space-2); }
  .pt-login .pt-form input { height: 46px; }
  .pt-login .pt-form .joy-btn { height: 46px; margin-top: var(--space-2); }
  .pt-login .pt-form .pt-lbl { margin-bottom: 6px; }
  .pt-login__note { margin-bottom: var(--space-3); }
  .pt-login__apply { margin-top: var(--space-2); }
}

/* Shorter still — 320x568 is an iPhone SE 1, and after the band above it is
   394px of room against 487 of content. The note is the one element that can go:
   it describes what the portal is, which the heading directly above it already
   says, and the alternative is the scrolling the user ruled out. The heading
   drops a tier with it. Everything that has a JOB on this screen — both fields,
   the button, and the line telling you how to get access — stays. */
/* LANDSCAPE SCROLLS, at the user's instruction, and that is the right call.
   A two-column arrangement did fit 844x390 — words beside the form, the two
   fields on one row — but it made a second layout to maintain for a posture
   nobody signs in from twice, and it read as a different product from the same
   phone held upright. A landscape phone is 844-932 CSS pixels wide, which is to
   say indistinguishable from a small laptop window, and CLAUDE.md already
   settles that case: it takes the desktop composition. So it takes the desktop
   composition and scrolls.
   What scrolling needs is somewhere to land. The bar is sticky, so a field the
   browser scrolls into view on focus — which is what every mobile browser does
   when the keyboard comes up — would otherwise stop directly underneath it.
   scroll-margin-top reserves the bar's height plus a little air, so the field
   arrives clear of it with its label visible. */
.pt-form input { scroll-margin-top: calc(72px + var(--sa-t) + var(--space-2)); }

@media (max-width: 768px) and (max-height: 620px) {
  .pt-login__note { display: none; }
  .pt-login__title { font-size: var(--text-display-5-size); margin-bottom: var(--space-2); }
  .pt-login { padding-top: var(--space-2);
    padding-bottom: calc(var(--space-2) + var(--sa-b)); }
}

/* ---------- below the calibrated range ----------
   The phone scale is solved at 430x932 and 375x667, so 375 is this project's
   narrow anchor — and at 375 the bar holds the full wordmark in BOTH languages:
   20 + 168 + 33.6 + 121.36 + 20 = 362.96 against 375.
   At 320 Albanian still fits (measured on a clean load: mark 168, "DIL" 76.91,
   35.1px between them, document 320 wide, no overflow). English does not:
   "SIGN OUT" is 121.36 against "DIL"'s 76.91, and the bar would need 362.96 of a
   320px screen. Rather than scroll the whole PAGE sideways to hold a fixed width,
   flex-shrink is handed back below 375. It only fires on genuine overflow, so
   Albanian keeps its 168 at 320 and only English gives anything up.
   This block goes LAST, after the 768 block closes — an earlier version of it was
   opened in the middle of that block and swallowed everything below the wordmark
   rule, which took the phone's row layout and header with it. */
@media (max-width: 374px) {
  .pt-bar__mark { flex-shrink: 1; }
}
