@import "tailwindcss";

/* Scan Blade root + Vue components for Tailwind class usage. */
@source "../views/**/*.blade.php";
@source "../js/**/*.vue";
/* …and the .js helpers that BUILD markup as strings. A map marker and a
   panorama marker are injected as HTML by utils/areaGuide/*.js, so their
   classes appear in no .vue file and were never generated: the marker discs
   only looked right because the same utilities happened to be used elsewhere,
   and `[animation-duration:9s]` on the arrows' orbit ring was missing outright
   (found 2026-09-18 while giving each marker kind its own colour). */
@source "../js/**/*.js";

/*
|--------------------------------------------------------------------------
| PropertyLab design tokens
|--------------------------------------------------------------------------
| Brand = royal blue (the "Lab" wordmark). Navy = the icon background /
| dark surfaces (sidebars, brand panels). These generate utilities like
| bg-brand-600, text-brand-700, bg-navy-900, ring-brand-500, etc.
*/
@theme {
    --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;

    --color-brand-50: #eef4ff;
    --color-brand-100: #d9e6ff;
    --color-brand-200: #bcd3ff;
    --color-brand-300: #8eb6ff;
    --color-brand-400: #5a8dff;
    --color-brand-500: #3366f6;
    --color-brand-600: #2563eb;
    --color-brand-700: #1d4ed8;
    --color-brand-800: #1e40af;
    --color-brand-900: #1e3a8a;

    --color-navy-600: #2d4368;
    --color-navy-700: #243656;
    --color-navy-800: #182944;
    --color-navy-900: #111e35;
    --color-navy-950: #0b1626;

    /* Ink & Indigo — Analyze Property semantic tokens (not in stock Tailwind).
       Indigo/emerald/amber/red come from stock Tailwind utilities; brand/navy are untouched. */
    --color-canvas: #f4f5f8;   /* cool page background — white cards are islands on it */
    --color-hairline: #e6e8ee; /* use sparingly; prefer shadow + radius over borders */
}

@layer base {
    html {
        font-family: var(--font-sans);
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
    }
}

/*
|--------------------------------------------------------------------------
| Animations
|--------------------------------------------------------------------------
*/
@keyframes pl-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes pl-fade-up {
    from { opacity: 0; transform: translateY(14px); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes pl-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-12px); }
}

@keyframes pl-pulse-ring {
    0% { transform: scale(0.95); opacity: 0.5; }
    50% { transform: scale(1.05); opacity: 0.2; }
    100% { transform: scale(0.95); opacity: 0.5; }
}

.animate-fade-in { animation: pl-fade-in 0.5s ease both; }
.animate-fade-up { animation: pl-fade-up 0.55s cubic-bezier(0.22, 1, 0.36, 1) both; }
.animate-float { animation: pl-float 7s ease-in-out infinite; }
.animate-pulse-ring { animation: pl-pulse-ring 4s ease-in-out infinite; }

/*
|--------------------------------------------------------------------------
| Consent "quest" (public consent link → questionnaire → done)
|--------------------------------------------------------------------------
| The customer's consent errand plays like a short game: six levels, a
| progress bar, a pop and confetti on every level cleared. Translate-only on
| the level card (never scale) — the signature canvas measures itself while
| it animates in, and a scaled rect would store a blurred signature.
| Everything here is switched off for prefers-reduced-motion (below).
*/
@keyframes quest-in-right {
    from { opacity: 0; transform: translateX(36px); }
    to { opacity: 1; transform: translateX(0); }
}
@keyframes quest-in-left {
    from { opacity: 0; transform: translateX(-36px); }
    to { opacity: 1; transform: translateX(0); }
}
@keyframes quest-pop {
    0% { opacity: 0; transform: scale(0.3); }
    60% { opacity: 1; transform: scale(1.15); }
    100% { opacity: 1; transform: scale(1); }
}
@keyframes quest-bob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-5px); }
}
@keyframes quest-wiggle {
    0%, 100% { transform: rotate(0deg); }
    25% { transform: rotate(-12deg); }
    75% { transform: rotate(12deg); }
}
@keyframes quest-shimmer {
    from { background-position: -200% 0; }
    to { background-position: 200% 0; }
}
@keyframes quest-scan {
    0% { top: 4%; }
    50% { top: 92%; }
    100% { top: 4%; }
}
/* Vertical only: the toast is centred by its -translate-x-1/2 class (Tailwind v4 writes
   the `translate` property, which composes with this transform), so it stays centred
   when reduced motion turns the animation off. */
@keyframes quest-toast {
    0% { opacity: 0; transform: translateY(-24px) scale(0.9); }
    12% { opacity: 1; transform: translateY(0) scale(1.04); }
    18% { transform: translateY(0) scale(1); }
    85% { opacity: 1; transform: translateY(0) scale(1); }
    100% { opacity: 0; transform: translateY(-16px) scale(0.96); }
}
@keyframes quest-glow {
    0%, 100% { box-shadow: 0 0 0 0 rgb(16 185 129 / 0.45); }
    50% { box-shadow: 0 0 0 10px rgb(16 185 129 / 0); }
}
/* The backdrop photo's slow push-in — a still that moves like a film shot. */
@keyframes quest-kenburns {
    from { transform: scale(1); }
    to { transform: scale(1.12) translateY(-1.5%); }
}
@keyframes quest-rise {
    from { opacity: 0; transform: translateY(18px); }
    to { opacity: 1; transform: translateY(0); }
}

.animate-quest-in-right { animation: quest-in-right 0.45s cubic-bezier(0.22, 1, 0.36, 1) both; }
.animate-quest-in-left { animation: quest-in-left 0.45s cubic-bezier(0.22, 1, 0.36, 1) both; }
.animate-quest-pop { animation: quest-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
.animate-quest-bob { animation: quest-bob 2.4s ease-in-out infinite; }
.animate-quest-wiggle { animation: quest-wiggle 1.2s ease-in-out infinite; }
.animate-quest-shimmer {
    background-image: linear-gradient(110deg, transparent 25%, rgb(255 255 255 / 0.45) 50%, transparent 75%);
    background-size: 200% 100%;
    animation: quest-shimmer 2.2s linear infinite;
}
.animate-quest-scan { animation: quest-scan 1.6s ease-in-out infinite; }
.animate-quest-toast { animation: quest-toast 2.2s cubic-bezier(0.22, 1, 0.36, 1) both; }
.animate-quest-glow { animation: quest-glow 1.8s ease-out infinite; }
.animate-quest-rise { animation: quest-rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; }
.animate-quest-kenburns { animation: quest-kenburns 40s ease-in-out infinite alternate; }

@media (prefers-reduced-motion: reduce) {
    [class*="animate-quest-"] {
        animation: none !important;
    }
}

/*
|--------------------------------------------------------------------------
| Radar search motif (Analyze Property)
|--------------------------------------------------------------------------
| Ported from petav2 index.blade.php L36-66, blue→indigo for cohesion.
| Used by the searched-location pin and the comparison maps.
*/
@keyframes radar-ping {
    0%   { transform: scale(0.6); opacity: 0.7; }
    100% { transform: scale(2.4); opacity: 0; }
}
@keyframes radar-rotate {
    to { transform: rotate(360deg); }
}
.animate-radar-ping  { animation: radar-ping 1.5s ease-out infinite; }
.animate-radar-sweep { animation: radar-rotate 3s linear infinite; }

/* Spinner for an in-progress step (Analyze loading overlay). */
@keyframes ai-spin { to { transform: rotate(360deg); } }

/*
|--------------------------------------------------------------------------
| Sleek scrollbar
|--------------------------------------------------------------------------
| A slim, rounded, low-contrast scrollbar for dark surfaces (e.g. the
| sidebar nav). The track stays transparent and the thumb brightens on
| hover. Apply with the `scrollbar-sleek` class on any scroll container.
*/
.scrollbar-sleek {
    scrollbar-width: thin;
    scrollbar-color: rgba(148, 163, 184, 0.25) transparent;
}
.scrollbar-sleek::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}
.scrollbar-sleek::-webkit-scrollbar-track {
    background: transparent;
}
.scrollbar-sleek::-webkit-scrollbar-thumb {
    background-color: rgba(148, 163, 184, 0.25);
    border-radius: 9999px;
}
.scrollbar-sleek::-webkit-scrollbar-thumb:hover {
    background-color: rgba(148, 163, 184, 0.45);
}

[x-cloak] { display: none !important; }

/*
 * "Found it" — the moment the library assistant points at a resource.
 *
 * The job is ATTENTION, and attention is comparative: a ring drawn on one card
 * competes with forty other cards drawn exactly as loudly. So the effect works
 * on the whole grid, not on one element. Everything except the found card
 * recedes, and the found card gains a light that travels its edge. That reads in
 * peripheral vision, which is the actual test — the member is looking at the
 * chat panel when this fires, not at the grid.
 *
 * All of it is compositor-friendly: opacity, filter and a custom property. No
 * layout property is animated, so a grid of forty cards does not reflow while
 * the member is reading.
 */

/* The travelling light needs an ANGLE it can animate, and a raw custom property
   is not interpolatable — `--angle: 0deg` to `360deg` would jump, not sweep.
   Registering its type is what makes the sweep possible in pure CSS. Where
   @property is unsupported the ring simply renders static, which still reads as
   a highlight rather than as nothing. */
@property --ai-found-angle {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
}

/* ── the grid recedes ──────────────────────────────────────────────────── */
.ai-learning-spotlight > li:not(.ai-learning-found) {
    /* Blur AND desaturate, not just dim. Dimming alone still leaves forty
       legible titles competing for the eye; taking the edges and the colour out
       of them is what makes one card the only thing in focus. */
    filter: blur(2px) saturate(0.45);
    opacity: 0.4;
    transition: filter 420ms cubic-bezier(0.22, 1, 0.36, 1),
                opacity 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Returning is slower than leaving. A snap back is startling; a settle reads as
   the page relaxing once you have seen what you were shown.

   THE HOVER PROPERTIES ARE LISTED HERE, not left to the card's Tailwind
   `transition` utility, and that is not belt-and-braces. This rule is UNLAYERED
   while Tailwind's utilities sit in a layer, so it does not merely add to the
   card's transition — it REPLACES it. Before the hover physics landed
   (2026-09-02) that meant the card's own `hover:border` and `hover:shadow`
   snapped instead of easing, on a rule written for the spotlight and never
   suspected. Anything a card animates on hover has to be named in this list. */
.ai-learning-grid > li {
    transition: filter 620ms cubic-bezier(0.22, 1, 0.36, 1),
                opacity 620ms cubic-bezier(0.22, 1, 0.36, 1),
                transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
                box-shadow 300ms cubic-bezier(0.22, 1, 0.36, 1),
                border-color 300ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* ── the found card ────────────────────────────────────────────────────── */
.ai-learning-found {
    position: relative;
    z-index: 1; /* above its dimmed neighbours, and above their blur */
    animation: ai-found-lift 2600ms cubic-bezier(0.22, 1, 0.36, 1) 1;
}

/* The light itself: a conic gradient painted only in a 2px band around the
   card. The band is cut with a two-layer mask composited to exclude — the
   inside is masked away, leaving the border. Rotating the ELEMENT would not
   work: the mask turns with it and a rotated rounded rectangle stops matching
   the card, which is why the gradient's start angle sweeps instead. */
.ai-learning-found::after {
    content: '';
    position: absolute;
    inset: -2px;
    border-radius: 1.1rem;
    padding: 2px;
    background: conic-gradient(
        from var(--ai-found-angle),
        transparent 0deg 200deg,
        var(--color-brand-300) 260deg,
        #ffffff 292deg,
        var(--color-brand-500) 320deg,
        transparent 360deg
    );
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    mask-composite: exclude;
    pointer-events: none;
    animation: ai-found-sweep 1300ms linear 2,
               ai-found-fade 2600ms cubic-bezier(0.22, 1, 0.36, 1) 1;
}

/* A soft brand halo underneath, so the card reads as lit rather than outlined.
   Separate element from the ring because the two have different jobs and
   different timings — the halo arrives once and decays, the light goes round. */
.ai-learning-found::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 1rem;
    box-shadow: 0 0 0 0 rgb(37 99 235 / 0.45);
    pointer-events: none;
    animation: ai-found-halo 2600ms cubic-bezier(0.22, 1, 0.36, 1) 1;
}

@keyframes ai-found-sweep {
    to { --ai-found-angle: 360deg; }
}

@keyframes ai-found-fade {
    0% { opacity: 0; }
    10% { opacity: 1; }
    82% { opacity: 1; }
    100% { opacity: 0; }
}

@keyframes ai-found-halo {
    0% { box-shadow: 0 0 0 0 rgb(37 99 235 / 0.5), 0 0 0 0 rgb(37 99 235 / 0); }
    18% { box-shadow: 0 0 24px 4px rgb(37 99 235 / 0.28), 0 0 0 0 rgb(37 99 235 / 0); }
    70% { box-shadow: 0 0 18px 2px rgb(37 99 235 / 0.12), 0 0 0 0 rgb(37 99 235 / 0); }
    100% { box-shadow: 0 0 0 0 rgb(37 99 235 / 0), 0 0 0 0 rgb(37 99 235 / 0); }
}

/* Barely a lift. Enough to read as "this one came forward", small enough that
   the card never covers its neighbours or drags the grid's rhythm out of true. */
@keyframes ai-found-lift {
    0% { transform: scale(1); }
    14% { transform: scale(1.022); }
    100% { transform: scale(1); }
}

/*
 * Reduced motion keeps the SPOTLIGHT and drops the movement. The recede is a
 * state change rather than a journey, so it is still legible without animating,
 * and the found card keeps a static ring. Removing the whole effect here would
 * leave the assistant pointing at nothing for the members who most need to be
 * told where to look.
 */
@media (prefers-reduced-motion: reduce) {
    .ai-learning-spotlight > li:not(.ai-learning-found),
    .ai-learning-grid > li {
        transition: none;
    }

    .ai-learning-found {
        animation: none;
    }

    .ai-learning-found::after {
        animation: none;
        background: var(--color-brand-500);
    }

    .ai-learning-found::before {
        animation: none;
        box-shadow: 0 0 0 4px rgb(37 99 235 / 0.18);
    }
}

/*
|--------------------------------------------------------------------------
| Print — "Download PDF" is the browser's own print-to-PDF
|--------------------------------------------------------------------------
| The Wealth Planning result page is a REPORT, and members asked to keep a
| copy. Every figure on it is computed in the browser (the controller only
| persists the plan's state blob), so a server-rendered PDF would have to
| duplicate the whole projection engine — the one thing the module's handbook
| forbids. Printing the page the member is looking at is therefore not the
| cheap option, it is the only one that cannot disagree with the screen.
|
| It is also better output than a canvas-scraping library would give: text
| stays selectable and the file stays small. html2canvas was ruled out for a
| harder reason — Tailwind v4 emits `oklch()` colours, which it cannot parse.
|
| These rules are global because the chrome they hide (sidebar, sticky header)
| belongs to AppShell, and no page should print its own navigation.
*/
@media print {
    /* App chrome and anything interactive is not part of a document. */
    .print-hide,
    .print-hide * {
        display: none !important;
    }

    /* Screen-only helpers that must appear ON PAPER (cover block, CTA). */
    .print-only {
        display: block !important;
    }

    /* The sidebar is gone, so its gutter must go with it. */
    .app-shell-content {
        padding-left: 0 !important;
    }

    /* Backgrounds and brand colours are meaningful here (green = rent covers
       the loan), so keep them rather than letting the browser drop them. */
    * {
        -webkit-print-color-adjust: exact !important;
        print-color-adjust: exact !important;
    }

    html,
    body {
        background: #fff !important;
    }

    /* A report is read section by section; let each start cleanly and never
       split a property card or a stat across two sheets. */
    .print-section {
        break-inside: avoid;
        page-break-inside: avoid;
    }

    .print-break-before {
        break-before: page;
        page-break-before: always;
    }

    /* Nothing sticky or scrolling survives on paper. */
    .sticky,
    .fixed {
        position: static !important;
    }

    [class*="overflow-"] {
        overflow: visible !important;
    }

    /* Shadows print as grey mud on most drivers. */
    * {
        box-shadow: none !important;
    }

    @page {
        margin: 14mm 12mm;
    }
}

/* The print-only blocks must not show on screen. `.print-only` is flipped to
   visible inside the print block above. */
.print-only {
    display: none;
}
