/* ============================================================================
   GjejPronën — shared app shell: design tokens, web fonts, unified header, buttons.
   Class names are `gp-` prefixed so they never collide with a page's own CSS.
   Load this <link> on any page; include nav.html / footer.html to get the chrome.

   2026 REDESIGN — warm, premium "earthy green" system.
   Only design values changed; every class name + selector kept identical so
   all existing pages (and their JS) keep working untouched.
   ========================================================================== */
/* Fonts load from ONE <link> per page head (the union URL) - the @import here
   was a SECOND, render-blocking fetch of overlapping faces, serialized behind
   this stylesheet. Spectral roman 500 was requested by nothing. */

:root {
    /* ===== CANONICAL palette — SINGLE SOURCE OF TRUTH. =====
       LIMESTONE & PATINA. Chosen 2026-09-02 from four independent directions
       scored by three judges - a buyer, an accessibility reviewer and a
       cartographer - and the only one all three put first.

       The brief was Besnik's: "the colours do not give that impression."
       The diagnosis was that EVERY surface was tinted mint (var(--gp-bg), Tailwind
       teal-50) and the two brand colours were stock Tailwind ramp stops. A
       coloured wash over an entire product is the loudest template signal there
       is, and in this category restraint IS the premium signal: the expensive
       sites are the ones where the page has no opinion and the content has all
       of it.

       So: the ground is limestone plaster, warm and near-chromaless, not a
       colour. Cards separate by shadow and rule rather than by fill. The brand
       green is copper patina - the verdigris on a church dome - which is greener
       and greyer than a Tailwind teal and dark enough to typeset. The metal is
       brass, and it is reserved for price and prestige, nothing else.

       ONE HARD RULE, from the cartographer: terracotta (--gp-terracotta) is an
       EDITORIAL colour only. Against a Kosovo aerial it measures 1.19:1 on an
       actual terracotta roof, so it must never carry status or sit on the map.
       ===== */
    --gp-primary:       #0B5D4E;   /* kit teal - brand text, links on light, 7.81:1 with white */
    --gp-primary-dark:  #08483C;   /* primary hover               */
    --gp-primary-light: #0E8F63;   /* borders/icons on light, 4.10:1 on white */
    --gp-accent:        #007A55;   /* links, 5.36:1 white / 4.95:1 ground */
    --gp-accent-light:  #00E08A;   /* kit mint - accent ON DARK only, 7.86:1 on --gp-navy */
    /* Accent for text/borders sitting on dark surfaces. --gp-accent (#0369a1) was
       chosen for white backgrounds; on the map's slate/teal panels it measures
       2.47:1 - failing on every price it coloured. This one passes. */
    --gp-accent-on-dark: var(--gp-accent-light);
    --gp-navy:          #0C332F;   /* kit deep - header/footer/headings */
    --gp-navy-deep:     #07211E;
    --gp-amber:         #8A6520;   /* premium gold (investor)     */
    /* 2026 creative layer (design E1-E9) */
    --gp-mint:          #00E08A;   /* kit mint - FILL only, dark text on it; 1.75:1 as text on light */
    --gp-mint-bright:   #00E08A;   /* accent-word fallback on dark */
    --gp-gold:          #D9AE63;   /* price/premium accent on dark */
    --gp-reserved:      #A8761C;   /* reserved status (amber fam.) */
    /* Neutrals */
    --gp-ink:           #0C1F1C;   /* near-black headings, 15.78:1 */
    --gp-text:          #3A4A45;   /* body text, 8.63:1           */
    --gp-muted:         #52625C;   /* secondary text, 5.95:1      */
    --gp-muted-2:       #5E6E68;   /* tertiary / placeholder (WCAG AA: 4.8:1 on white, 4.55:1 on mint bg; was var(--gp-muted-2) = 2.56:1, failed) */
    --gp-on-dark:       #CFE9DE;   /* text on dark surfaces, 13.14:1 on --gp-navy-deep */
    --gp-line:          #D9E3DE;   /* borders                     */
    --gp-line-2:        #7C938A;
    --gp-tint:          #D6F5E6;   /* mint chip/fill, teal text on it 6.71:1 */
    --gp-tint-2:        #EAFBF3;   /* kit light */
    --gp-bg:            #F5F7F6;   /* near-neutral ground - brand colour lives in actions and marks, never as a wash */
    --gp-bg-alt:        #E8EEEB;
    --gp-card:          #FCFDFD;   /* card / surface              */
    /* Status (semantic — keep distinct from brand) */
    --gp-success:       #0F7A55;
    --gp-warning:       #8A6520;
    --gp-danger:        #A82A22;
    /* rgb companions for rgba() overlays */
    /* Materials the direction adds. Terracotta is EDITORIAL ONLY - see the rule
       at the top: on an aerial photograph of Kosovo it lands on actual roof
       tiles at 1.19:1 and disappears. */
    --gp-terracotta:    #9E5236;
    --gp-walnut:        #3A2E24;
    --gp-glass:         #0C332F;   /* dark panel over the map */
    /* Status, on a light ground and on a dark one. Kept as a value ladder so the
       three states stay separable in greyscale and for deuteranopia. */
    --gp-avail-on-dark:    #7FE0C4;
    --gp-reserved-on-dark: #E8B65C;
    --gp-sold-on-dark:     #A79E90;
    --gp-sold:             #5E594F;
    --gp-select:           #6FC0F5;   /* selection, never a status colour */
    --gp-terracotta-rgb:   158, 82, 54;
    --gp-primary-rgb:   11, 93, 78;
    --gp-navy-rgb:      12, 51, 47;
    --gp-accent-rgb:    0, 122, 85;
    /* The kit's own look for an action: mint fill, deep text. Mint cannot carry
       white text (1.75:1) or be text on a light ground (1.75:1), so it gets its
       own pair instead of being forced into --gp-primary, which is also used as
       a text colour in 340 places. */
    --gp-action:        #00E08A;   /* button fill                 */
    --gp-action-hover:  #00C97A;   /* 6.30:1 with --gp-on-action  */
    --gp-on-action:     #0C332F;   /* 7.86:1 on --gp-action       */
    --gp-action-rgb:    0, 224, 138;
    --gp-lime:          #D7F25C;   /* kit highlight - FILL only, 10.94:1 with --gp-on-action */
    --gp-on-dark-soft:  #7FD3B4;   /* secondary text on dark, 9.54:1 on --gp-navy-deep */
    --gp-amber-rgb:     138, 101, 32;
    /* legacy aliases — kept so existing rules keep resolving to one source */
    --gp-blue:      var(--gp-primary);
    --gp-blue-600:  var(--gp-primary-dark);
    --gp-blue-300:  var(--gp-accent);
    --gp-green:     var(--gp-primary);
    --gp-orange:    var(--gp-accent);
    /* Shape & depth */
    /* FOUR STEPS, NOT TWENTY-TWO. Counted across the shared stylesheet and every
       public template: 398 border-radius declarations in 22 distinct values, and
       254 of them sat in SEVEN values inside a six-pixel band - 8, 9, 10, 11, 12,
       13, 14. Nobody can see 8 against 9; what they see is that five stacked
       white boxes on one page have five different corner softnesses and nothing
       looks deliberate. These two tokens already existed and were used by almost
       nothing. The tiny 2-4px radii on 4px-tall tracks and bars are geometric
       rather than stylistic and are deliberately left alone. */
    /* ── TYPE SCALE ───────────────────────────────────────────────────────
       Counted across the shared stylesheet and every public template: 853
       font-size declarations in 79 distinct values. The clustering was the
       whole problem - 243 of them sat in SEVEN values inside a 1.3px band
       (0.78 / 0.80 / 0.82 / 0.83 / 0.84 / 0.85 / 0.86rem), and another 152 in
       nine values inside 2.2px. Nobody can see 13.1px against 13.6px; what
       they see is that the same kind of label is a slightly different size on
       every screen, and that nothing was decided.

       Nine steps, anchored on the values that were already the majority in
       each band, so most declarations do not move at all and the ones that do
       move by well under a pixel. The exception is the bottom: sizes from
       0.55rem (8.8px) up now start at 11.5px, which is a readability gain on
       badges and micro-labels rather than a cosmetic one.

       Anything at or above 3.2rem is a decorative glyph - a facade monogram, a
       404 numeral, an empty-state icon - not text, and is deliberately left
       literal. So are `em` sizes, which are parent-relative by intent. */
    --fs-2xs:  0.72rem;   /* 11.5px  badges, chips, micro-labels      */
    --fs-xs:   0.82rem;   /* 13.1px  secondary text, meta rows        */
    --fs-sm:   0.9rem;    /* 14.4px  body in dense UI                 */
    --fs-base: 1rem;      /* 16px    body                             */
    --fs-md:   1.15rem;   /* 18.4px  lead paragraphs, card titles     */
    --fs-lg:   1.4rem;    /* 22.4px  h3                               */
    --fs-xl:   1.8rem;    /* 28.8px  h2                               */
    --fs-2xl:  2.4rem;    /* 38.4px  h1 on inner pages                */
    --fs-3xl:  3rem;      /* 48px    hero h1                          */

    --gp-radius:      12px;   /* controls, chips, ordinary boxes */
    --gp-radius-lg:   18px;   /* panels, cards, sheets           */
    --gp-radius-pill: 999px;  /* anything whose radius is "half its height" */
    /* Warm, not teal. A shadow carries the colour of the ground it falls on, and
       a green-cast one on limestone reads as a bruise rather than as depth. */
    --gp-shadow:    0 8px 24px rgba(58, 46, 36, 0.10);
    --gp-shadow-lg: 0 20px 50px rgba(58, 46, 36, 0.16);
    /* Type */
    --gp-font:         'Plus Jakarta Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
    --gp-font-display: 'Spectral', Georgia, 'Times New Roman', serif;
}

/* ── Unified header — cream, glassy, pill CTA ───────────────────────────── */
/* ── The content grid ──────────────────────────────────────────────────────
   Measured at 1440px, the site had SEVEN content widths - 1000, 1080, 1100,
   1180, 1200, 1280, 1300 - and a header with none at all. The footer is 1180 on
   every page, so 1180 was already the de-facto grid; the two elements present on
   every single screen just never agreed with each other. The nav's content
   started 36px from the edge and the footer's 130px in: 94px apart, on every
   page, above and below everything a visitor reads. */
:root { --gp-wrap: 1180px; --gp-gutter: 24px; }
.gp-wrap { max-width: var(--gp-wrap); margin-left: auto; margin-right: auto; padding-left: var(--gp-gutter); padding-right: var(--gp-gutter); }

.gp-nav {
    position: sticky; top: 0; z-index: 1000;
    background: rgba(245, 247, 246, 0.86);   /* --gp-bg at 86%, literal for the blur */
    -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    /* The BAR stays full-bleed - it is sticky and blurred, so it has to span the
       viewport - while its CONTENT lines up with the footer's. Done with padding
       rather than a wrapper element because the mobile drawer depends on
       .gp-nav-links wrapping inside this exact flex container (flex-basis:100%),
       and a wrapper would break it. The percentage resolves against this block's
       own containing box, which excludes the scrollbar - so it matches the
       footer's centring exactly rather than approximately, as 100vw would. */
    padding: 0 max(var(--gp-gutter), calc((100% - var(--gp-wrap)) / 2)); height: 68px;
    display: flex; align-items: center; justify-content: space-between;
    font-family: var(--gp-font);
    border-bottom: 1px solid var(--gp-line);
}
.gp-brand { color: var(--gp-navy); font-weight: 800; font-size: var(--fs-lg); text-decoration: none; letter-spacing: -0.01em; white-space: nowrap; font-family: var(--gp-font-display); }
.gp-brand { display: inline-flex; align-items: center; color: var(--gp-primary); }
.gp-brand span { color: inherit; }   /* one colour, as in the kit lockup - the mark carries the mint */
.gp-brand-mark { width: 26px; height: 30px; margin-right: 9px; flex: none; display: block; }
.gp-nav-links { display: flex; align-items: center; gap: 26px; }
.gp-nav-links a {
    color: var(--gp-text); font-size: var(--fs-sm); font-weight: 500; text-decoration: none; white-space: nowrap;
    display: inline-flex; align-items: center; gap: 6px; transition: color 0.15s;
}
.gp-nav-links a:hover { color: var(--gp-navy); }
.gp-nav-links a.is-accent { color: var(--gp-blue-300); font-weight: 600; }
.gp-nav-links a.is-invest { color: var(--gp-amber); font-weight: 600; }
/* Current-page marker — distinct colour + weight + a subtle underline. */
.gp-nav-links a.is-current {
    color: var(--gp-navy); font-weight: 700;
    text-decoration: underline; text-decoration-color: var(--gp-blue-300);
    text-decoration-thickness: 2px; text-underline-offset: 6px;
}
.gp-btn-nav {
    background: var(--gp-action); color: var(--gp-on-action) !important;
    padding: 10px 20px; border-radius: var(--gp-radius-pill); font-weight: 600;
}
.gp-btn-nav:hover { background: var(--gp-action-hover); }

/* Hamburger — hidden on desktop, shown ≤860px */
.gp-nav-burger {
    display: none; align-items: center; justify-content: center;
    /* 44x44 is the minimum reliable touch target — this is the most-tapped
       control on the site and it only ever shows on phones. */
    width: 44px; height: 44px; margin-left: auto; padding: 0;
    background: rgba(var(--gp-navy-rgb), 0.08); border: none; border-radius: var(--gp-radius);
    color: var(--gp-navy); font-size: var(--fs-md); cursor: pointer; transition: background 0.15s;
}
.gp-nav-burger:hover { background: rgba(var(--gp-navy-rgb), 0.16); }

/* Unread-lead count on the "Paneli" nav link (property-owner inbox cue). */
.gp-nav-badge {
    display: inline-block; background: var(--gp-danger); color: #fff;
    font-size: var(--fs-2xs); font-weight: 700; line-height: 1;
    padding: 2px 6px; border-radius: var(--gp-radius); vertical-align: middle; margin-left: 2px;
}

/* Anchored scrolls (post-submit flash, #inquiry-form) must clear the sticky
   header - without this the very message the buyer needs lands hidden under it. */
[id] { scroll-margin-top: 84px; }

@media (max-width: 860px) {
    /* iOS Safari zooms the whole page in when a focused field is under 16px — and it
       does not zoom back out afterwards, leaving the user stranded mid-form. 16px is
       the threshold, not a preference. */
    /* !important on purpose: inline-styled fields (apartment inquiry) and
       class-styled selects (browse filters) silently outranked the bare rule -
       every one of them re-armed the zoom trap. Nothing styles a mobile field
       ABOVE 16px, so nothing is clamped down. */
    input:not([type=range]):not([type=checkbox]):not([type=radio]),
    select, textarea { font-size: 16px !important; }

    /* On a phone the gutter narrows for both, together. */
    :root { --gp-gutter: 16px; }
    .gp-nav { padding: 8px var(--gp-gutter); height: auto; flex-wrap: wrap; gap: 6px; }
    .gp-nav-burger { display: inline-flex; }
    .gp-nav-links {
        display: none; flex-basis: 100%; flex-direction: column;
        align-items: stretch; gap: 0; padding: 4px 0 8px;
    }
    .gp-nav-links.is-open { display: flex; }
    .gp-nav-links a {
        font-size: var(--fs-sm); padding: 12px 10px; border-radius: var(--gp-radius);
        border-bottom: 1px solid var(--gp-line);
    }
    .gp-nav-links a:last-child { border-bottom: none; }
    .gp-btn-nav { text-align: center; justify-content: center; margin-top: 6px; }
}

/* ── Button system ──────────────────────────────────────────────────────── */
.gp-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    font-family: var(--gp-font); font-weight: 700; font-size: var(--fs-sm);
    border-radius: var(--gp-radius-pill); padding: 12px 24px; border: 1px solid transparent;
    cursor: pointer; text-decoration: none; transition: all 0.15s; white-space: nowrap;
}
.gp-btn-primary   { background: var(--gp-action); color: var(--gp-on-action); box-shadow: 0 8px 20px rgba(var(--gp-action-rgb), 0.28); }
.gp-btn-primary:hover { background: var(--gp-action-hover); transform: translateY(-1px); }
.gp-btn-secondary { background: #fff; color: var(--gp-navy); border-color: var(--gp-line); }
.gp-btn-secondary:hover { border-color: var(--gp-navy); }
.gp-btn-ghost     { background: transparent; color: var(--gp-navy); }
.gp-btn-ghost:hover { background: rgba(var(--gp-primary-rgb), 0.08); }
.gp-btn-accent    { background: var(--gp-blue-300); color: var(--gp-bg); }
.gp-btn-accent:hover { background: var(--gp-accent); transform: translateY(-1px); }
.gp-btn-lg { padding: 15px 34px; font-size: var(--fs-base); }      /* hero / CTA size */
.gp-btn-ghost-light { background: rgba(255,255,255,0.10); border-color: rgba(255,255,255,0.25); color: #fff; }
.gp-btn-ghost-light:hover { background: rgba(255,255,255,0.18); }
/* The solid primary FOR A DARK GROUND. for_developers had built this locally,
   with its own radius and padding, because the system had no variant for it -
   which is how a design system grows a sixth button rather than a fifth. */
.gp-btn-light { background: #fff; color: var(--gp-navy-deep); }
.gp-btn-light:hover { background: var(--gp-bg); transform: translateY(-1px); }

/* Contact action buttons — full-width CTAs in project/apartment contact sidebars.
   Shared by projects/detail + projects/apartment_detail (were duplicated per-page). */
.contact-actions { display: flex; flex-direction: column; gap: 8px; }
.contact-btn {
    padding: 12px 18px; border-radius: var(--gp-radius); text-decoration: none;
    font-weight: 700; font-size: var(--fs-sm); text-align: center;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    border: none; cursor: pointer;
}
.contact-btn-primary { background: linear-gradient(135deg, var(--gp-primary), var(--gp-navy)); color: #fff; }
.contact-btn-secondary { background: var(--gp-bg-alt); color: var(--gp-ink); }
.contact-btn-call { background: #d1fae5; color: #065f46; }
.contact-btn-whatsapp { background: #25d366; color: #fff; }
.contact-btn-whatsapp:hover { background: #1da851; color: #fff; }

/* Verified-developer badge — trust signal on listings + detail pages. */
.gp-verified {
    display: inline-flex; align-items: center; gap: 4px;
    background: var(--gp-tint); color: var(--gp-primary-dark);
    padding: 2px 8px; border-radius: var(--gp-radius-lg);
    font-size: var(--fs-2xs); font-weight: 800;
    text-transform: uppercase; letter-spacing: 0.03em; vertical-align: middle;
}

/* ── Card system — one canonical surface; pages add content classes + optional
   .gp-card-hover. Replaces the per-page .prop-card/.path-card/.feat-card/.fav-card
   duplicates (same look). ───────────────────────────────────────────────────── */
.gp-card {
    background: #fff; border: 1px solid var(--gp-bg-alt); border-radius: var(--gp-radius-lg);
    overflow: hidden; text-decoration: none; color: inherit; display: block;
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.gp-card-col { display: flex; flex-direction: column; }   /* equal-height grid cards */
.gp-card img { transition: transform 0.4s ease; }
.gp-card-hover:hover { transform: translateY(-4px); box-shadow: var(--gp-shadow-lg); }
.gp-card-hover:hover img { transform: scale(1.06); }

/* ── Construction status ───────────────────────────────────────────────────
   The same building's status was two different badges: warm amber on cream on
   the home page, and BLUE on white on /projects - blue being this site's link
   and accent colour, which made "under construction" borrow accent semantics it
   should not have. Amber for in-progress is also the language already used by
   the 3D floor lids (green free / amber reserved / slate sold) and by the demo
   chip, so it is the one that was already consistent with everything else.
   Pill-shaped, like every other status mark on the site. */
.gp-cs {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 5px 12px; border-radius: var(--gp-radius-pill);
    font-size: var(--fs-2xs); font-weight: 700; white-space: nowrap;
}
.gp-cs-build   { background: #F2E4D3; color: #8A4B22; }   /* 6.1:1 */
.gp-cs-done    { background: var(--gp-tint); color: var(--gp-primary-dark); }
.gp-cs-planned { background: rgba(255,255,255,0.95); color: var(--gp-navy); }

/* ── The empty state ───────────────────────────────────────────────────────
   Lifted out of index.html, where it was the only good one on the site. /browse
   had its own `.empty`: a 3rem glyph coloured --gp-on-dark - a token for text ON
   a dark ground, so on the white results column it was very nearly invisible -
   one 0.9rem sentence and a text link. That block is not a corner case: with no
   approved listings it is the WHOLE of /browse, which is where the home page's
   Kerko button sends every first-time visitor. */
.gp-empty {
    background: #fff; border: 1px solid var(--gp-bg-alt); border-radius: var(--gp-radius-lg);
    padding: 50px 30px; text-align: center;
}
.gp-empty .es-icon { font-size: var(--fs-2xl); color: var(--gp-muted-2); margin-bottom: 14px; }
.gp-empty h3 {
    font-family: var(--gp-font-display); color: var(--gp-navy);
    margin: 0 0 6px; font-size: var(--fs-md);
}
.gp-empty p { color: var(--gp-muted); margin: 0 auto 20px; max-width: 46ch; font-size: var(--fs-sm); }
.gp-empty-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
@media (max-width: 600px) {
    .gp-empty { padding: 36px 20px; }
    /* Full-width buttons: two half-width ones on a 375px screen are 44px tall and
       about 150px wide, which is where mis-taps live. */
    .gp-empty-actions { flex-direction: column; }
    .gp-empty-actions > * { width: 100%; }
}

/* ── The save heart on a result card ───────────────────────────────────────
   /favorites has always said "Kliko [heart] ne cdo prone ose banese per ta
   ruajtur ketu" and "Te ruajtura" has always been in the nav, while the only
   heart on the site was on the detail page - so a shortlist could only be built
   by opening every flat and pressing back. The wrapper exists because the
   button must be a SIBLING of the card's <a>, never a child of it. */
.card-wrap { position: relative; display: flex; }
.card-wrap > .gp-card { flex: 1; min-width: 0; }
.save-btn {
    position: absolute; top: 8px; right: 8px; z-index: 3;
    /* 44x44, the touch minimum. The existing remove control on /favorites is
       30px, which is the same button and a third too small. */
    width: 44px; height: 44px;
    display: grid; place-items: center; padding: 0;
    border: 0; border-radius: 50%; cursor: pointer;
    background: rgba(255,255,255,0.93); backdrop-filter: blur(4px);
    box-shadow: 0 2px 10px rgba(0,0,0,0.18);
    color: var(--gp-muted); font-size: var(--fs-base); line-height: 1;
    transition: transform 0.15s ease, color 0.15s ease, background 0.15s ease;
    -webkit-tap-highlight-color: transparent;
}
.save-btn:hover     { background: #fff; color: #E11D48; }
.save-btn.is-saved  { color: #E11D48; }        /* 4.8:1 on the white pill */
.save-btn:active    { transform: scale(0.9); }
.save-btn:focus-visible { outline: 3px solid var(--gp-accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .save-btn { transition: none; } }

/* How old the listing is. Muted and last, because it qualifies the price
   rather than competing with it. */
.prop-age {
    display: flex; align-items: center; gap: 5px; margin-top: 8px;
    font-size: var(--fs-2xs); color: var(--gp-muted-2);
}

/* ── Shared footer — deep green ─────────────────────────────────────────── */
/* Same gutter as the nav. It was 36px here and 36px there, which looked the
   same until the nav gained a max-width and the two fell 12px apart below the
   grid width. One token now. */
.gp-footer { background: var(--gp-navy-deep); color: var(--gp-on-dark); font-family: var(--gp-font); padding: 56px var(--gp-gutter) 0; }
.gp-footer-inner {
    max-width: var(--gp-wrap); margin: 0 auto; display: grid;
    grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 36px;
}
.gp-footer-col h4 { color: var(--gp-bg); font-size: var(--fs-sm); font-weight: 700; margin: 0 0 14px; }
.gp-footer-col a { display: block; color: var(--gp-on-dark-soft); text-decoration: none; font-size: var(--fs-xs); margin-bottom: 9px; transition: color 0.15s; }
/* Comfortable tap target for the footer links on touch (they were ~17px tall). */
@media (pointer: coarse), (max-width: 860px) {
    .gp-footer-col a { padding: 6px 0; margin-bottom: 4px; }
}
.gp-footer-col a:hover { color: var(--gp-mint-bright, var(--gp-mint-bright)); }
.gp-footer-logo { font-size: var(--fs-lg); font-weight: 800; color: var(--gp-bg); text-decoration: none; font-family: var(--gp-font-display); }
.gp-footer-logo span { color: var(--gp-mint); }
.gp-footer-brand p { font-size: var(--fs-xs); line-height: 1.65; margin: 12px 0 0; max-width: 300px; }
.gp-footer-contact { margin-top: 16px; display: flex; flex-direction: column; gap: 6px; }
.gp-footer-contact a, .gp-footer-contact span {
    display: inline-flex; align-items: center; gap: 8px;
    color: #A9BAB0; font-size: var(--fs-xs); text-decoration: none; margin: 0;
}
.gp-footer-contact a:hover { color: var(--gp-mint-bright, var(--gp-mint-bright)); }
.gp-footer-contact i { color: var(--gp-mint-bright, var(--gp-mint-bright)); width: 16px; text-align: center; }
.gp-footer-bottom {
    max-width: var(--gp-wrap); margin: 40px auto 0; padding: 22px 0; text-align: center;
    border-top: 1px solid rgba(255,255,255,0.08); font-size: var(--fs-xs); color: #A9BAB0;
}
@media (max-width: 760px) {
    .gp-footer-inner { grid-template-columns: 1fr 1fr; gap: 26px; }
    .gp-footer-brand { grid-column: 1 / -1; }
}

/* ── Accessibility ──────────────────────────────────────────────────────── */
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible,
select:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
    outline: 3px solid var(--gp-blue-300);
    outline-offset: 2px;
    border-radius: 4px;
}
.gp-nav-links a:focus-visible, .gp-brand:focus-visible,
.gp-footer-col a:focus-visible, .gp-footer-logo:focus-visible { outline-color: var(--gp-navy); }

.gp-sr-only {
    position: absolute !important; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0);
    white-space: nowrap; border: 0;
}

.gp-skip {
    position: absolute; left: 50%; top: -60px; transform: translateX(-50%);
    z-index: 2000; background: var(--gp-navy); color: #fff;
    padding: 10px 20px; border-radius: 0 0 10px 10px;
    font-family: var(--gp-font); font-weight: 700; text-decoration: none;
    transition: top 0.18s ease;
}
.gp-skip:focus { top: 0; outline: 3px solid #fff; outline-offset: 2px; }

/* ── Global modern-polish layer (lifts every page that loads this) ──────── */
body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}
html { scroll-behavior: smooth; }

::selection { background: var(--gp-navy); color: var(--gp-bg); }

/* Thin, modern scrollbar — warm */
* { scrollbar-width: thin; scrollbar-color: var(--gp-on-dark) transparent; }
*::-webkit-scrollbar { width: 11px; height: 11px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: var(--gp-on-dark); border-radius: var(--gp-radius);
    border: 3px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: #A39C8C; background-clip: content-box; }

/* Button press feedback */
.gp-btn:active, .gp-btn-nav:active { transform: translateY(0) scale(0.97); }
.gp-btn-nav { transition: background 0.15s, transform 0.12s; }

/* Entrance animation — pure CSS, runs once on load. Always ends fully visible
   (animation-fill 'both' → opacity:1), so it can NEVER leave content hidden,
   even with no JS. prefers-reduced-motion (below) collapses it to an instant show. */
.gp-reveal { animation: gpFadeUp 0.7s cubic-bezier(.22,.61,.36,1) both; }
@keyframes gpFadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }

/* Form controls — soft green focus halo site-wide */
input:not([type=range]):not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):focus,
select:focus, textarea:focus {
    border-color: var(--gp-blue) !important;
    box-shadow: 0 0 0 3px rgba(var(--gp-primary-rgb), 0.16);
}
input:not([type=range]):not([type=checkbox]):not([type=radio]),
select, textarea { transition: border-color 0.15s ease, box-shadow 0.15s ease; }

/* Reusable card-hover lift (opt-in via .gp-lift) */
.gp-lift { transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; }
.gp-lift:hover { transform: translateY(-4px); box-shadow: var(--gp-shadow-lg); }

/* Honor the OS "reduce motion" setting. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================================================
   2026 creative layer (design plan E1-E9): generative facade, live-pulse
   tokens, accent-word gradient, price language, 3D seal, view transitions.
   Colors reference the :root tokens above - no new scattered hex.
   ========================================================================== */

/* ── E2: accent word — serif italic with a mint→gold gradient fill.
   Base rule IS the fallback (solid mint on dark); browsers that support
   background-clip:text swap in the gradient. Callers must not re-set `color`. */
.gp-accent-word { font-style: italic; color: var(--gp-mint-bright); }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
    .gp-accent-word {
        background: linear-gradient(100deg, var(--gp-mint), var(--gp-lime));
        -webkit-background-clip: text; background-clip: text;
        -webkit-text-fill-color: transparent; color: transparent;
    }
}

/* ── E3: "nga" prefix for starting prices ── */
.gp-nga {
    font-size: var(--fs-2xs); font-weight: 700; letter-spacing: 0.07em;
    text-transform: uppercase; color: var(--gp-muted); margin-right: 5px;
}

/* ── E7: live pulse dot — 7px mint dot + 2.4s expanding ring ── */
.gp-live-dot {
    position: relative; display: inline-block; flex-shrink: 0;
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--gp-mint); vertical-align: middle;
}
.gp-live-dot::after {
    content: ''; position: absolute; inset: -4px; border-radius: 50%;
    border: 1.5px solid var(--gp-mint); opacity: 0;
    animation: gpPulseRing 2.4s cubic-bezier(0.2, 0.6, 0.4, 1) infinite;
}
@keyframes gpPulseRing {
    0%   { transform: scale(0.45); opacity: 0.9; }
    65%  { transform: scale(1.35); opacity: 0; }
    100% { transform: scale(1.35); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .gp-live-dot::after { animation: none; transform: none; opacity: 0.35; }
}

/* ── E7: segmented availability bar — mint/amber/gray via custom props.
   Usage: <div class="gp-availbar" style="--avail:62%; --reserved:15%"></div>
   (--sold is implicit: whatever remains after avail+reserved.) ── */
.gp-availbar {
    --avail: 0%; --reserved: 0%; --sold: 0%;
    display: block; height: 4px; border-radius: 2px; overflow: hidden;
    background: linear-gradient(90deg,
        var(--gp-mint) 0 var(--avail),
        var(--gp-reserved) var(--avail) calc(var(--avail) + var(--reserved)),
        var(--gp-muted-2) calc(var(--avail) + var(--reserved)) 100%);
}

/* ── E1: generative facade — blueprint cover for photo-less projects.
   Structure comes from partials/facade.html; parent must be positioned. ── */
.gp-facade {
    position: absolute; inset: 0; overflow: hidden; pointer-events: none;
    background:
        repeating-linear-gradient(0deg,  rgba(153,246,228,0.07) 0, rgba(153,246,228,0.07) 1px, transparent 1px, transparent 24px),
        repeating-linear-gradient(90deg, rgba(153,246,228,0.07) 0, rgba(153,246,228,0.07) 1px, transparent 1px, transparent 24px),
        linear-gradient(135deg, var(--gp-navy-deep) 0%, var(--gp-navy) 55%, var(--gp-primary) 130%);
}
/* bottom offset keeps the ground row of windows clear of the micro-line */
.gp-facade-bldg { position: absolute; left: 0; bottom: 26px; width: 100%; height: calc(86% - 26px); }
.gp-facade-mono {
    position: absolute; right: 8px; bottom: -0.18em;
    font-family: var(--gp-font-display); font-weight: 700; font-size: 5.6rem;
    line-height: 1; color: rgba(255,255,255,0.14); user-select: none;
}
.gp-facade-line {
    position: absolute; left: 14px; bottom: 11px; white-space: nowrap;
    font-family: var(--gp-font); font-size: var(--fs-2xs); font-weight: 700;
    letter-spacing: 0.16em; text-transform: uppercase; color: rgba(255,255,255,0.85);
    text-shadow: 0 1px 6px rgba(11,59,56,0.9);
}
.gp-facade-badge {
    position: absolute; top: 10px; right: 12px;
    display: inline-flex; align-items: center; gap: 8px; padding: 4px 11px;
    border-radius: var(--gp-radius-pill); background: rgba(11,59,56,0.72);
    border: 1px solid rgba(153,246,228,0.35); color: var(--gp-line-2);
    font-family: var(--gp-font); font-size: var(--fs-2xs); font-weight: 700;
    -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.gp-facade-hero .gp-facade-bldg  { bottom: 36px; height: calc(88% - 36px); }
.gp-facade-hero .gp-facade-mono  { font-size: 10rem; right: 20px; }
.gp-facade-hero .gp-facade-line  { font-size: var(--fs-xs); left: 22px; bottom: 16px; }
.gp-facade-hero .gp-facade-badge { top: 16px; right: 18px; font-size: var(--fs-xs); padding: 6px 14px; }

/* ── E8: ◈ — the 3D seal. Prefix for true 3D-interactive entries only.
   Default gold (dark surfaces); add .gp-3d-mark--navy on light surfaces. ── */
.gp-3d-mark::before {
    content: '◈'; display: inline-block; margin-right: 7px;
    color: var(--gp-gold); font-style: normal; transform: translateY(-1px);
}
.gp-3d-mark--navy::before { color: var(--gp-navy); }

/* ── E9: cross-document view transitions (progressive; no-op if unsupported).
   Card covers + detail heroes share per-project view-transition-name values
   set inline (proj-<id>) so card→detail morphs in supporting browsers. ── */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.18s; }
@media (prefers-reduced-motion: reduce) {
    ::view-transition-old(root), ::view-transition-new(root),
    ::view-transition-group(*), ::view-transition-image-pair(*),
    ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* ── Image lightbox (gallery-lightbox.js) ─────────────────────────────────── */
#gp-lightbox {
    position: fixed; inset: 0; z-index: 2000; display: none;
    align-items: center; justify-content: center;
    background: rgba(6, 12, 11, 0.93); backdrop-filter: blur(3px);
}
#gp-lightbox.is-open { display: flex; }
#gp-lightbox .gplb-img {
    max-width: 92vw; max-height: 86vh; object-fit: contain;
    border-radius: var(--gp-radius); box-shadow: 0 16px 56px rgba(0, 0, 0, 0.55);
}
#gp-lightbox .gplb-x {
    position: absolute; top: 14px; right: 16px;
    width: 44px; height: 44px; border-radius: 50%;
    background: rgba(255, 255, 255, 0.12); color: #fff; border: none;
    font-size: var(--fs-xl); line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
#gp-lightbox .gplb-x:hover { background: rgba(255, 255, 255, 0.22); }
#gp-lightbox .gplb-nav {
    position: absolute; top: 50%; transform: translateY(-50%);
    width: 48px; height: 64px; border: none; cursor: pointer;
    background: rgba(255, 255, 255, 0.10); color: #fff; font-size: var(--fs-lg);
    border-radius: var(--gp-radius); display: flex; align-items: center; justify-content: center;
}
#gp-lightbox .gplb-nav:hover { background: rgba(255, 255, 255, 0.2); }
#gp-lightbox .gplb-prev { left: 14px; }
#gp-lightbox .gplb-next { right: 14px; }
#gp-lightbox .gplb-count {
    position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%);
    color: #fff; font: 600 0.85rem 'Plus Jakarta Sans', system-ui, sans-serif;
    background: rgba(0, 0, 0, 0.4); padding: 5px 12px; border-radius: var(--gp-radius-pill);
}
@media (max-width: 600px) {
    #gp-lightbox .gplb-nav { width: 40px; height: 56px; }
}

/* The complete plan, including its selection, shares one zoom/pan transform. */
#gp-lightbox.is-open { display: grid; grid-template-rows: auto minmax(0,1fr) auto; align-items: stretch; justify-content: stretch; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }
#gp-lightbox .gplb-header { display:flex; align-items:center; gap:16px; padding:12px 16px; color:#fff; z-index:1; }
#gp-lightbox h2 { margin:0; flex:1; font-size:var(--fs-base); line-height:1.4; color:#fff; }
#gp-lightbox .gplb-x { position:static; flex-shrink:0; }
#gp-lightbox .gplb-viewport { display:flex; align-items:center; justify-content:center; overflow:hidden; min-height:0; touch-action:none; }
#gp-lightbox .gplb-scene { position:relative; flex-shrink:0; line-height:0; transform-origin:center; cursor:grab; }
#gp-lightbox .gplb-scene:active { cursor:grabbing; }
#gp-lightbox .gplb-img { display:block; width:auto; height:auto; max-width:calc(100vw - 32px); max-height:calc(100dvh - 240px); user-select:none; }
#gp-lightbox .cj-plan-selection { position:absolute; inset:0; width:100%; height:100%; pointer-events:none; }
#gp-lightbox .cj-plan-selection polygon { fill:rgba(34,211,238,.28); stroke:#0891b2; stroke-width:.65; vector-effect:non-scaling-stroke; }
#gp-lightbox .gplb-footer { z-index:1; color:#fff; background:#102d28; padding:10px 16px 14px; }
#gp-lightbox .gplb-caption, #gp-lightbox [role=status] { margin:0 auto 8px; max-width:900px; font-size:var(--fs-sm); line-height:1.5; text-align:center; }
#gp-lightbox [role=status]:empty, #gp-lightbox .gplb-caption:empty { display:none; }
#gp-lightbox .gplb-tools { display:flex; flex-wrap:wrap; gap:8px; justify-content:center; align-items:center; }
#gp-lightbox .gplb-tools button { min-width:44px; min-height:44px; border:1px solid #91bdb2; border-radius:var(--gp-radius); padding:8px 12px; color:#fff; background:#214e43; font:600 var(--fs-sm)/1.3 var(--gp-font); cursor:pointer; }
#gp-lightbox button[hidden] { display:none!important; }
#gp-lightbox button:disabled { opacity:.5; cursor:default; }
#gp-lightbox .gplb-zoom-value { min-width:42px; text-align:center; font-size:var(--fs-xs); font-variant-numeric:tabular-nums; }
#gp-lightbox button:focus-visible { outline:3px solid #a5f3fc!important; outline-offset:3px; }
@media (max-height:500px) { #gp-lightbox .gplb-header { padding:4px 12px; } #gp-lightbox .gplb-footer { padding:6px 12px; } #gp-lightbox .gplb-caption { font-size:var(--fs-xs); margin-bottom:4px; } #gp-lightbox .gplb-img { max-height:calc(100dvh - 165px); } }

/* ── Upload preview (upload-preview.js) ────────────────────────────────────── */
.gp-up-preview { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
.gp-up-cell {
    position: relative; width: 104px; height: 78px; border-radius: var(--gp-radius);
    overflow: hidden; border: 1px solid var(--gp-line, #d9e1e0);
    background: var(--gp-card, #f4f7f6); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.gp-up-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gp-up-meta {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: rgba(0, 0, 0, 0.55); color: #fff; font-size: var(--fs-2xs);
    padding: 2px 6px; text-align: right; letter-spacing: 0.2px;
}
.gp-up-cover {
    position: absolute; top: 4px; left: 4px; font-size: var(--fs-2xs); font-weight: 700;
    background: var(--gp-primary, var(--gp-primary)); color: #fff; padding: 2px 7px;
    border-radius: 4px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.gp-up-rm {
    position: absolute; top: 4px; right: 4px; width: 22px; height: 22px;
    border: none; border-radius: 50%; background: rgba(0, 0, 0, 0.6); color: #fff;
    cursor: pointer; font-size: var(--fs-sm); line-height: 1;
    display: flex; align-items: center; justify-content: center; padding: 0;
    transition: background 0.12s;
}
.gp-up-rm:hover { background: #dc2626; }
.gp-up-dropzone.is-dragover {
    border-color: var(--gp-primary, var(--gp-primary)) !important;
    background: rgba(15, 118, 110, 0.06) !important;
}
/* Existing-gallery management (project-gallery-admin.js): promote-to-cover button */
.gp-gm-cover {
    position: absolute; bottom: 4px; right: 4px; width: 24px; height: 24px;
    border: none; border-radius: 50%; background: rgba(15, 118, 110, 0.92); color: #fff;
    cursor: pointer; font-size: var(--fs-2xs); line-height: 1; padding: 0;
    display: flex; align-items: center; justify-content: center; transition: background 0.12s;
}
.gp-gm-cover:hover { background: var(--gp-gold, #c0894a); }
.gp-gm-cover:disabled, .gp-up-rm:disabled { opacity: 0.5; cursor: default; }

/* Screen-reader-only text: present for assistive tech, invisible on screen.
   Defined here (app-shell.css loads on every page) so it never depends on Bootstrap. */
.gp-sr-only {
    position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Visible keyboard focus (WCAG 2.4.7). Many components set `outline:none`, which
   leaves keyboard users with no focus indicator. :focus-visible only fires for
   keyboard/AT navigation - never on mouse click - so this restores a clear ring
   without changing the mouse experience. !important overrides the scattered
   outline:none rules; border-color focus styles still layer underneath. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible,
[tabindex]:focus-visible, [role="button"]:focus-visible, [role="tab"]:focus-visible {
    outline: 2px solid var(--gp-primary, var(--gp-primary)) !important;
    outline-offset: 2px;
    border-radius: 3px;
}

/* ── Demo badge ─────────────────────────────────────────────────────────
   A showcase building carries a demonstration sale mix, which is honest as a
   demonstration and dishonest as a listing. Wherever such a project appears in
   public, it says so - amber rather than brand colour, because this is a
   caution, not a feature. */
.gp-demo {
    display: inline-flex; align-items: center; gap: 5px; vertical-align: middle;
    padding: 2px 8px; border-radius: var(--gp-radius-pill);
    font-size: var(--fs-2xs); font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
    background: rgba(245, 158, 11, 0.16); color: #b45309;
    border: 1px solid rgba(245, 158, 11, 0.45);
    white-space: nowrap;
}
.gp-demo::before { content: '●'; font-size: 0.5em; line-height: 1; }
/* On the dark map chrome the amber has to lift off the glass instead of sinking. */
.gp-demo.on-dark { background: rgba(245, 158, 11, 0.20); color: #fcd34d;
    border-color: rgba(245, 158, 11, 0.5); }
/* The note lives in the project hero, which is dark, so it carries its own dark
   amber ground and light text - inheriting --gp-ink put near-black on near-black
   and the warning was invisible, which is worse than not writing it. */
.gp-demo-note {
    margin: 10px 0 0; padding: 10px 12px; border-radius: var(--gp-radius);
    background: rgba(120, 53, 15, 0.45); border: 1px solid rgba(245, 158, 11, 0.45);
    color: #fde68a; font-size: var(--fs-xs); line-height: 1.55;
}
.gp-demo-note .gp-demo { margin-right: 6px; }
