/* PlaneSales Shared Styles
 * Bootstrap 5.2 customizations and shared component styles
 */

/* ==========================================================================
   Design tokens — single source of truth
   --------------------------------------------------------------------------
   [Requirement("RFT-PSG-2026-0625")] — token unification (finding D2).

   This shared sheet NO LONGER declares a :root palette. The canonical palette,
   the single spacing scale (--sp-*) and the single radius scale (--r-*) live in
   PlaneSales.Web.Public/wwwroot/css/tokens.css, which the public site loads
   AFTER this sheet. Removing the old, competing :root (navy #00144C / cyan
   #7DD2F7) leaves the public site rendering from the canonical palette only.

   Seller and Admin (MudBlazor) load planesales.css but NOT tokens.css, so every
   rule below resolves its tokens with an explicit fallback equal to the token's
   historical value. That keeps the Seller/Admin portals visually identical while
   the public site picks up the canonical values from tokens.css. The old legacy
   token values, preserved as those fallbacks:
     --ps-primary #00144C · --ps-gray-100 #f6f9fb · --ps-gray-400 #ced4da
     --ps-gray-500 #adb5bd · --ps-border-radius 0.375rem (→ canonical --r-sm 6px)
     --ps-shadow 0 0.5rem 1rem rgba(0,0,0,.15)
   (--ps-font-family is NO LONGER in that list: RFT-PSG-2026-0727 gives it a real
   value in the :root block below, so it is a defined token, not a fallback.)
     --ps-transition-fast 150ms · --ps-transition-normal 300ms
   The remaining legacy tokens (--ps-secondary, --ps-accent, --ps-btn-*,
   --ps-background-*, --ps-spacing-*, --ps-border-radius-lg, etc.) had no
   consumers and are dropped.

   !! NEVER let an asterisk be immediately followed by a slash in here. !!
   [RFT-PSG-2026-0727] This list previously used a slash to separate the token
   prefixes, so `--ps-btn-` + asterisk + slash formed a comment terminator and
   CLOSED THIS COMMENT EARLY. Everything after it became stray
   CSS, and the parser's error recovery then swallowed tokens up to and including
   the NEXT `{ ... }` block. That block was the `body { font-family: ... }` rule
   below — so the shared body font rule was silently DEAD for the entire life of
   this file, in every app that loads it. Verified in a real browser: parsing the
   pre-fix file left `body` absent from cssRules (first surviving rule was
   `.listing-card`) and computed body font "Times New Roman". Separate a `*` and a
   `/` with a comma or a space, always.
   ========================================================================== */

/* ==========================================================================
   Typeface — self-hosted Inter, the ONE platform font
   --------------------------------------------------------------------------
   [Requirement("RFT-PSG-2026-0727")] — typography consolidation.

   Head Warden ruling 2026-09-06 (GitHub issue #240 item 9): Inter is the brand
   face, platform-wide. This sheet is the SINGLE declaration site — it is loaded
   by all three web apps (Public, Seller, Admin) as
   _content/PlaneSales.UI.Shared/css/planesales.css, so one copy of the woff2
   serves every portal and there is no per-app font drift to keep in sync.

   These replace THREE competing stacks that were live simultaneously before
   this change:
     · Public          — self-hosted Roboto (RFT-PSG-2026-0626)
     · Seller / Admin  — a Google Fonts <link> their own CSP already blocked,
                          so MudBlazor fell through Roboto → Helvetica → ARIAL
     · everything else — the OS system stack, because --ps-font-family was
                          consumed below but never actually defined anywhere

   Mechanics copied verbatim from the proven RFT-PSG-2026-0626 Roboto setup:
   Google's own Inter VARIABLE build (v4.001), one woff2 per subset spanning
   weights 100–900, so every weight the old stacks asked for (300/400/500/700)
   resolves from a single file. `latin` = the common case (preloaded in the
   public App.razor); `latin-ext` = accented Latin. font-display:swap avoids
   invisible-text FOIT. The unicode-ranges are Google's, unchanged.

   NO third-party font fetch: there is no googleapis/gstatic <link> or @import
   anywhere in the tree, and every app's CSP is font-src 'self' data: to match.
   Do NOT reintroduce one — it would be blocked at runtime, not merely slow.

   The url() is relative to THIS file, i.e. _content/PlaneSales.UI.Shared/fonts/.
   ========================================================================== */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/inter-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/inter-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --------------------------------------------------------------------------
   [RFT-PSG-2026-0727] The one platform font token.

   --ps-font-family was consumed by the `body` rule below (and nothing else
   defined it), so its fallback stack always won and shared components rendered
   in the OS system font — the likely cause of the reported "headline and
   content are different fonts" defect on the FAQ page, where a shared component
   sat next to public body copy in a different face. It now has a real value,
   and it is declared here so that ALL THREE web apps get it from one place.

   Declared on `html`, NOT on `:root`, deliberately. RFT-PSG-2026-0625 removed
   this sheet's competing :root palette and pinned that with a test
   (TokenUnificationRft0625Tests.PlaneSalesCss_ShouldNotDeclare_ARootBlock)
   that forbids any root-pseudo-class block in this file at all — the assertion
   is a regex over the source, so even a mention in a comment trips it.
   `html` selects the same element, and since
   nothing else in the tree declares --ps-font-family the lower specificity
   costs us nothing.

   The Bootstrap bridge (--bs-font-sans-serif / --bs-body-font-family) canNOT
   live here for that same reason: it must out-specify bootstrap-reboot's own
   `:root` declaration. It lives in the public tokens.css instead — which is
   both the canonical token home and Bootstrap's only consumer, since Seller and
   Admin load no Bootstrap at all.
   -------------------------------------------------------------------------- */
html {
    --ps-font-family: 'Inter', system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}

/* ==========================================================================
   Base Styles
   ========================================================================== */

/* [RFT-PSG-2026-0727] font-family ONLY, deliberately.

   This rule was DEAD until the comment-terminator fix above, so its former
   `font-size: var(--ps-font-size-base, 1rem)` and
   `line-height: var(--ps-line-height-base, 1.5)` declarations had never once
   applied — and neither token is defined anywhere in the tree, so both would
   have resolved to their hard-coded fallbacks. Reviving them verbatim was a
   measured REGRESSION: in a real browser it moved Seller and Admin body text
   from 14px/20.02px to 16px/24px, i.e. a platform-wide type-size jump. This
   order is a like-for-like typeface swap (font-family only), so the two size
   declarations are dropped rather than resurrected. Public was unaffected either
   way — its app.css sets 14px/1.6 on html,body and loads after this sheet.

   If a shared base size/line-height is wanted, that is a deliberate design
   decision for its own order, not a side effect of a font change. */
body {
    font-family: var(--ps-font-family, 'Inter', system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif);
}

/* ==========================================================================
   Component: Cards
   ========================================================================== */

.listing-card,
.dealer-card {
    transition: transform var(--ps-transition-fast, 150ms ease-in-out), box-shadow var(--ps-transition-fast, 150ms ease-in-out);
}

.listing-card:hover,
.dealer-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--ps-shadow, 0 0.5rem 1rem rgba(0, 0, 0, 0.15));
}

.listing-card .card-img-wrapper img,
.dealer-card .card-img-wrapper img {
    transition: transform var(--ps-transition-normal, 300ms ease-in-out);
}

.listing-card:hover .card-img-wrapper img,
.dealer-card:hover .card-img-wrapper img {
    transform: scale(1.05);
}

/* Featured badge positioning */
.listing-card .badge,
.dealer-card .badge {
    z-index: 1;
}

/* ==========================================================================
   Component: Gallery
   ========================================================================== */

.listing-gallery .main-image-container {
    cursor: pointer;
}

.listing-gallery .thumbnail-container button {
    transition: opacity var(--ps-transition-fast, 150ms ease-in-out);
}

.listing-gallery .thumbnail-container button:hover {
    opacity: 0.8;
}

/* Thumbnail strip */
.gallery-thumbs {
    display: flex;
    flex-direction: row;
    gap: 8px;
    overflow-x: auto;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 2px;
}
.gallery-thumbs::-webkit-scrollbar { display: none; }

.gallery-thumb {
    flex: 0 0 auto;
    width: 150px;
    height: 100px;
    border: 2px solid transparent;
    border-radius: 4px;
    overflow: hidden;
    padding: 0;
    cursor: pointer;
    background: #f8f9fa;
    transition: border-color 0.15s;
}
.gallery-thumb img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}
/* --brand-blue is defined in the canonical tokens.css. The listing gallery is a
   public-only surface (rendered solely by Web.Public listing/property Details),
   so the token always resolves there; no Seller/Admin fallback is required, and
   BUG-0519 pins this to the bare brand token (not a fallback). */
.gallery-thumb-active {
    border-color: var(--brand-blue);
}
.gallery-thumb:hover:not(.gallery-thumb-active) {
    border-color: #adb5bd;
}

/* Carousel wrapper */
.gallery-thumbs-wrap {
    position: relative;
    display: flex;
    align-items: center;
    gap: 4px;
}
.gallery-thumbs-wrap .gallery-thumbs {
    flex: 1;
    overflow-x: hidden;
}

/* Static row (≤5 images, desktop) */
.gallery-thumbs-wrap.gallery-thumbs-static .gallery-thumbs {
    overflow-x: visible;
    flex-wrap: nowrap;
}

/* Carousel chevron buttons */
.gallery-thumb-chevron {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 100px;
    background: rgba(255,255,255,0.9);
    border: 1px solid #dee2e6;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.15s;
    padding: 0;
}
.gallery-thumb-chevron:hover { background: #f8f9fa; }
.gallery-thumb-chevron:disabled { opacity: 0.35; cursor: default; }

@media (max-width: 767px) {
    .gallery-thumbs-wrap .gallery-thumbs,
    .gallery-thumbs-wrap.gallery-thumbs-static .gallery-thumbs {
        overflow-x: auto;
        flex-wrap: nowrap;
    }
    .gallery-thumb-chevron {
        display: none;
    }
    .gallery-thumb {
        width: 100px;
        height: 67px;
    }
    .gallery-thumb img {
        width: 100%;
        height: 100%;
    }
}

/* ==========================================================================
   Component: Search
   ========================================================================== */

.search-box .input-group {
    transition: box-shadow var(--ps-transition-fast, 150ms ease-in-out);
}

.search-box .input-group:focus-within {
    box-shadow: 0 0 0 0.25rem rgba(0, 20, 76, 0.25);
}

/* ==========================================================================
   Component: Data Grid
   ========================================================================== */

.data-grid th.sortable button {
    color: inherit;
}

.data-grid th.sortable button:hover {
    color: var(--ps-primary, #00144C);
}

.data-grid tbody tr.cursor-pointer {
    cursor: pointer;
}

.data-grid tbody tr.cursor-pointer:hover {
    background-color: var(--ps-gray-100, #f6f9fb);
}

/* ==========================================================================
   Component: Side Navigation
   ========================================================================== */

.side-nav {
    min-height: 100vh;
}

.side-nav .nav-link {
    padding: 0.75rem 1rem;
    color: inherit;
    /* Canonical radius scale (--r-sm = 6px). Fallback 6px keeps Seller/Admin,
       which don't load tokens.css, at the historical 0.375rem (= 6px). */
    border-radius: var(--r-sm, 6px);
    margin: 0.125rem 0.5rem;
}

.side-nav .nav-link:hover {
    background-color: rgba(0, 0, 0, 0.05);
}

.side-nav .nav-link.active {
    background-color: var(--ps-primary, #00144C);
    color: white;
}

.side-nav.bg-dark .nav-link:hover {
    background-color: rgba(255, 255, 255, 0.1);
}

/* Collapse chevron rotation */
.side-nav .nav-link[aria-expanded="true"] .bi-chevron-down {
    transform: rotate(180deg);
}

.side-nav .bi-chevron-down {
    transition: transform var(--ps-transition-fast, 150ms ease-in-out);
}

/* ==========================================================================
   Component: Toast Notifications
   ========================================================================== */

.toast-container {
    pointer-events: none;
}

.toast-container .toast {
    pointer-events: auto;
    animation: slideIn 0.3s ease-out;
}

@keyframes slideIn {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* ==========================================================================
   Component: Loading Spinner
   ========================================================================== */

.loading-spinner {
    display: flex;
    align-items: center;
    justify-content: center;
}

.spinner-lg {
    width: 3rem;
    height: 3rem;
}

/* ==========================================================================
   Component: Pagination
   ========================================================================== */

.pagination .page-link {
    min-width: 2.5rem;
    text-align: center;
}

/* ==========================================================================
   Component: Forms
   ========================================================================== */

.form-control:focus,
.form-select:focus {
    border-color: var(--ps-primary, #00144C);
    box-shadow: 0 0 0 0.25rem rgba(0, 20, 76, 0.25);
}

/* Upload area styling */
.upload-area {
    transition: background-color var(--ps-transition-fast, 150ms ease-in-out), border-color var(--ps-transition-fast, 150ms ease-in-out);
}

.upload-area.border-primary {
    background-color: rgba(0, 20, 76, 0.05);
}

/* Dashed border for upload */
.border-dashed {
    border-style: dashed !important;
}

/* ==========================================================================
   Component: Modals
   ========================================================================== */

.modal.show {
    display: block;
}

.modal-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
}

/* ==========================================================================
   Utility Classes
   ========================================================================== */

/* Cursor utilities */
.cursor-pointer {
    cursor: pointer;
}

.cursor-not-allowed {
    cursor: not-allowed;
}

/* Text truncation */
.text-truncate-2 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.text-truncate-3 {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Aspect ratios */
.aspect-16-9 {
    aspect-ratio: 16 / 9;
}

.aspect-4-3 {
    aspect-ratio: 4 / 3;
}

.aspect-1-1 {
    aspect-ratio: 1 / 1;
}

/* Object fit */
.object-cover {
    object-fit: cover;
}

.object-contain {
    object-fit: contain;
}

/* ==========================================================================
   Component: Image Fallback
   ========================================================================== */

.img-fallback {
    background: url('/_content/PlaneSales.UI.Shared/images/placeholder-aircraft.svg') center / contain no-repeat #f8f9fa;
}

.img-fallback > img,
.img-fallback > .mud-image {
    display: block;
}

/* ==========================================================================
   Component: PlaceholderImage
   ========================================================================== */

.ps-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* Placeholder tokens formerly derived from the removed :root grays. Fallbacks
       preserve the historical values (bg #f6f9fb, fg #ced4da, radius 6px); the
       .dark override below still supplies its own bg/fg/label at runtime. */
    background-color: var(--ps-placeholder-bg, #f6f9fb);
    color: var(--ps-placeholder-fg, #ced4da);
    border-radius: var(--ps-placeholder-radius, var(--r-sm, 6px));
    overflow: hidden;
}

.ps-placeholder svg {
    width: 40%;
    height: 40%;
    max-width: 80px;
}

.ps-placeholder__label {
    font-size: 0.75rem;
    color: var(--ps-placeholder-label-color, #adb5bd);
    margin-top: 0.5rem;
}

/* Size variants */
.ps-placeholder--thumbnail {
    width: 100%;
    aspect-ratio: 1 / 1;
    max-width: 200px;
    max-height: 200px;
}
.ps-placeholder--medium {
    width: 100%;
    aspect-ratio: 4 / 3;
    max-width: 800px;
    max-height: 600px;
}
.ps-placeholder--full {
    width: 100%;
    aspect-ratio: 4 / 3;
    max-width: 1600px;
    max-height: 1200px;
}
.ps-placeholder--avatar-small {
    width: 100%;
    aspect-ratio: 1 / 1;
    max-width: 100px;
    max-height: 100px;
    border-radius: 50%;
}
.ps-placeholder--avatar-large {
    width: 100%;
    aspect-ratio: 1 / 1;
    max-width: 400px;
    max-height: 400px;
    border-radius: 50%;
}

/* Dark mode override */
.dark .ps-placeholder,
[data-theme="dark"] .ps-placeholder {
    --ps-placeholder-bg: #1e2533;
    --ps-placeholder-fg: #3d4a66;
    --ps-placeholder-label-color: #5a6a8a;
}

/* ==========================================================================
   Responsive Adjustments
   ========================================================================== */

@media (max-width: 767.98px) {
    .listing-card .card-img-wrapper,
    .dealer-card .card-img-wrapper {
        height: 180px !important;
    }

    .side-nav {
        min-height: auto;
    }
}

/* ==========================================================================
   Rich-text wrap guard — [BUG-PSG-2026-0788] (supersedes the BUG-PSG-2026-0695 rule)
   --------------------------------------------------------------------------
   Rich text pasted from Word/Docs, saved from Quill, or migrated from legacy
   content often carries &nbsp; between EVERY word (197 on listing 103, 36 on
   a FAQ answer, 0 ordinary spaces). A non-breaking space removes the line-break
   opportunity, so the paragraph becomes one unbreakable run that overflows its
   container: /content/faq scrolled sideways, listing descriptions clipped.

   `overflow-wrap: anywhere` restores a break opportunity only where a run would
   otherwise overflow — normally-spaced prose is unaffected — and, unlike
   `break-word`, it also lowers the min-content width, so flex items (the FAQ
   accordion button) can shrink instead of widening the page.

   WHY HERE: BUG-0695 put this rule in PageBlockRenderer.razor.css. That is a
   SCOPED (CSS-isolated) sheet: its selector compiles to
   `.page-block-richtext[b-xxxx]`, which never matched — Faq.razor lives in
   another project, and PageBlockRenderer builds its rich-text divs with
   RenderTreeBuilder.OpenElement, which carries no scope attribute. This sheet is
   global and loaded by all three portals (Public, Seller, Admin), so one rule
   reaches every sink. Render-path guard over rewriting stored content, per the
   Head Warden ruling of 2026-08-26.

   Surfaces: .page-block-richtext  CMS blocks (PageBlockRenderer), FAQ answers,
                                   DynamicPage content-only fallback, Admin preview
             .detail-prose         Public aircraft/part/property descriptions
             .article-content      Public news article body
             .listing-description  Seller listing Details + Create preview
             .faq-question         Public FAQ accordion questions
   ========================================================================== */

.page-block-richtext,
.detail-prose,
.article-content,
.listing-description,
.faq-question {
    overflow-wrap: anywhere;
}

/* ==========================================================================
   Print Styles
   ========================================================================== */

@media print {
    .no-print,
    .toast-container,
    .modal {
        display: none !important;
    }

    .listing-card,
    .dealer-card {
        break-inside: avoid;
    }
}
