/* FLoCK subdomain (flock.aiquniq.com)
 *
 * The offer form is rendered three times â€” once per tier tab â€” so it cannot
 * carry the ids the original single-instance page used. wireframes/landing.css
 * still styles it via #offerForm / #offerSubmitBtn; these rules re-apply the
 * same declarations against the class hooks used by flock/partials/form.blade.php.
 *
 * Load AFTER wireframes/landing.css. */

.flock-offer-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
    transition: opacity 0.5s ease;
}

.flock-offer-form.fade-out {
    opacity: 0;
}

.offer-submit-btn {
    padding: 11px 32px;
    background: #fff;
    color: #000;
    border: none;
    font-size: 0.9rem;
    font-weight: 600;
    border-radius: 2px;
    cursor: pointer;
    letter-spacing: 0.04em;
    transition: background 0.25s ease, opacity 0.25s ease;
}

.offer-submit-btn:hover {
    background: #d4d4d4;
}

.offer-submit-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}


/* ── Hero parallax ───────────────────────────────────────────────────────────
 *
 * The hero clip drifts as the page scrolls, the same as the still images the
 * rest of the site parallaxes from js/public/addons/motion.js. It is done in CSS
 * here because the JS approach cannot be smooth: the page scrolls on the
 * compositor thread while motion.js computes the offset on the main thread from
 * a scroll event, so the transform always lands a frame or more after the scroll
 * it answers. Against a full-viewport clip that is invisible, but as soon as the
 * banner's bottom edge and the section below it are on screen there is a
 * stationary reference to compare against and the lag reads as judder — worse
 * here than it ever was for a still, because the moving picture gives the eye
 * far more to lock onto. A scroll-driven animation runs on the compositor and
 * cannot fall behind.
 *
 * motion.js skips any .banner containing a <video> so the two never both drive
 * it. Everything else on the site keeps the JS parallax unchanged.
 *
 * The numbers reproduce motion.js exactly. Its offset is -rect.top * 0.35, and
 * over a view() timeline's cover range rect.top runs from +100vh (the banner
 * about to enter) to -100vh (fully past), so the offset runs -35vh → +35vh,
 * linearly in both cases.
 *
 * The @supports guard is load-bearing, not decoration: a browser that does not
 * know animation-timeline still honours `animation: … both`, and with no
 * duration that would snap the clip to the `to` keyframe — parked 35vh down,
 * permanently. Inside the guard, unsupporting browsers get no parallax at all,
 * which is the right fallback. */
@supports (animation-timeline: view()) {
    .banner {
        view-timeline-name: --flock-hero;
    }

    .banner-video {
        animation: flock-hero-parallax linear both;
        /* Named rather than a bare view(), so the timeline is unambiguously the
           banner's own box and never the video's transformed one. Each tab panel
           has its own .banner, and the lookup walks up from the video, so each
           hero resolves to its own. */
        animation-timeline: --flock-hero;
        animation-range: cover 0% cover 100%;
    }

    @keyframes flock-hero-parallax {
        from { transform: translate3d(0, -35vh, 0); }
        to   { transform: translate3d(0,  35vh, 0); }
    }

    /* motion.js clears its transforms under reduced motion; match that here.
       flock-loader.js already declines to load the clip at all in this case, so
       what stays still is the poster. */
    @media (prefers-reduced-motion: reduce) {
        .banner-video {
            animation: none;
        }
    }
}

/* Tier tabs sit directly under the pricing card, so drop the extra top padding
 * the standalone landing page needed. */
.tab-content .offer-form-section {
    padding-top: 60px;
}

/* Light/dark submit button inside the scroll-reactive form section.
 *
 * The tab that reproduces the full FLoCK page renders its form with
 * id="offer-form", which picks up the whole lightâ†’dark block in
 * pages/software/flock/main.css for free. The one thing that block cannot
 * reach is the submit button: it targets #offer-form #offerSubmitBtn, and the
 * shared partial has no ids. Mirror those four declarations onto the class. */
#offer-form .offer-submit-btn {
    transition: color 0.9s ease, background-color 0.9s ease, border-color 0.9s ease;
    background-color: #111111;
    color: #ffffff;
}

#offer-form .offer-submit-btn:hover {
    background-color: #333333;
}

#offer-form.is-dark .offer-submit-btn {
    background-color: #ffffff;
    color: #000000;
}

#offer-form.is-dark .offer-submit-btn:hover {
    background-color: #d4d4d4;
}

/* â”€â”€ Floating "Contact us" button (GROW / ENTERPRISE) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
 *
 * z-index 900 is deliberate. It must sit above page content but BELOW the
 * mobile-nav overlay (999), the nav (1000), the cookie banner (1999) and the
 * reCAPTCHA badge (9999) â€” otherwise it floats over the open mobile menu.
 *
 * Hidden state mirrors .grecaptcha-badge in elements/form.css: opacity +
 * visibility + pointer-events, so it is neither clickable nor tab-focusable
 * until flock/partials/contact-fab adds .is-visible â€” which it does from the
 * first pixel of scroll, and removes again at the top and over the form. */
.flock-contact-fab {
    position: fixed;
    right: 24px;
    bottom: 28px;
    z-index: 900;
    padding: 13px 26px;
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: 999px;
    background: rgba(15, 15, 15, 0.85);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: #ffffff;
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(8px);
    transition: opacity 0.35s ease, visibility 0.35s ease,
                transform 0.35s ease, background 0.25s ease,
                box-shadow 0.25s ease;
}

.flock-contact-fab.is-visible {
    opacity: 0.92;              /* near-solid, but still lets the page through */
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0);
}

.flock-contact-fab.is-visible:hover,
.flock-contact-fab.is-visible:focus-visible {
    opacity: 1;
    background: rgba(8, 8, 8, 0.95);
    box-shadow: 0 8px 26px rgba(0, 0, 0, 0.45);
}

@media (max-width: 768px) {
    .flock-contact-fab {
        right: 14px;
        bottom: 20px;
        padding: 12px 22px;
        font-size: 0.9rem;
    }
}

/* ── Canvas stages ───────────────────────────────────────────────────────────
 *
 * Ported from the catalog canvas in the FLoCK app, driven by
 * js/public/addons/flock-canvas.js. The app ships light and dark variants; this
 * page is always dark, so every `.dark X` rule from the source is collapsed into
 * its base selector here.
 *
 * Three stages use this: GROW's catalog canvas and ENTERPRISE's scope and
 * watcher canvases. None of them carry console chrome — where TEAM has a fake
 * terminal window on a wave panel, these are the canvas alone on a black stage.
 *
 * The selector is chained on purpose. It replaced #docker-placeholder, which at
 * 0-1-0-0 beat everything; a bare .docker--stage is 0-0-1-0, which merely TIES
 * .docker and would then be decided by source order. main.css:522 sets
 * `.docker { padding: 40px }` inside @media (max-width: 1000px) and would take
 * the full-bleed stage back on a phone. .docker.docker--stage is 0-0-2-0 and
 * wins outright, so this sheet no longer depends on being linked last. */

.docker.docker--stage {
    background: #0b0b0d;
    padding: 0;              /* main.css:380 has 60px 5% — this one is full bleed */
    align-items: stretch;    /* the canvas fills the column rather than centring in it */
    /* With the console gone there is no fixed-height element left in this
       column, so the stage would take its height from the copy beside it and the
       canvas scale would drift with the translation. 500px clears the tallest
       per-mode reserve (`reserveH` in the JS — 490 for the catalog mode's random
       worst case; the two enterprise modes are fixed trees and reserve less),
       which is what lets the canvas sit at 1:1 on a desktop viewport. */
    min-height: 500px;
}

.catalog-canvas {
    display: flex;
    flex: 1;
    /* Both zeroed: the cards box inside is a fixed 736px at full size, and a
       flex item's default min-width/min-height of auto would let it push the
       stage wider than its own box rather than being clipped and scaled. */
    min-height: 0;
    min-width: 0;
    transition: opacity 0.45s ease;
    /* This is an app UI, not the page — do not inherit main.css's Arial. */
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.catalog-canvas.is-out {
    opacity: 0;
}

/* The app scrolls this and floors it at 420px tall. Here it is a fixed frame
   that never scrolls — flock-canvas.js scales the card layer to fit instead. */
.catalog-canvas__viewport {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    flex: 1;
    min-height: 0;
    min-width: 0;
    width: 100%;
    background-image: radial-gradient(circle, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
    background-size: 22px 22px;
}

.catalog-canvas__cards {
    position: relative;
    flex: 0 0 auto;   /* wider than the viewport — must not be flex-shrunk */
    transform-origin: center;
    transition: transform 0.5s ease;
}

.catalog-canvas__links {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    overflow: visible;
}

.catalog-canvas__links path {
    fill: none;
    stroke: rgb(100 116 139);
    stroke-width: 1.5;
}

.node-card {
    position: absolute;
    top: 0;
    left: 0;
    width: 212px;
    box-sizing: border-box;
    background: rgb(31 41 55);
    border: 1px solid rgb(55 65 81);
    border-radius: 0.6rem;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
    z-index: 1;
    /* Not in the app, where left/top are set once per morph. Here the transition
       IS the animation: every create and every reparent reflows the tree and the
       cards glide to their new slots. Keep in sync with GLIDE_MS in the JS. */
    transition: left 0.45s cubic-bezier(0.16, 1, 0.3, 1),
                top 0.45s cubic-bezier(0.16, 1, 0.3, 1),
                opacity 0.3s ease,
                transform 0.25s ease,
                box-shadow 0.25s ease;
}

.node-card.is-root {
    border-color: rgb(59 130 246);
    border-width: 2px;
}

.node-card.drop-into {
    outline: 2px solid rgb(59 130 246);
    outline-offset: 2px;
}

/* Pop-in start state, cleared once the card has been positioned.
 *
 * transition:none is load-bearing. layout() forces a reflow before it writes
 * left/top, so without it the browser has a before-change style at the wrapper's
 * top-left corner and animates the new card all the way across the canvas to its
 * slot. Suppressed, that first write is a placement; dropping the class then
 * restores the normal transition and the card fades in where it belongs. */
.node-card.is-new {
    opacity: 0;
    transform: scale(0.96);
    transition: none;
}

/* Stands in for the app's .is-dragging (opacity .4), which only reads correctly
   while the browser paints a native drag image alongside it. Nothing paints one
   here — the card itself is what travels — so it lifts rather than fades. */
.node-card.is-lifted {
    transform: scale(1.04);
    box-shadow: 0 10px 24px rgba(0, 0, 0, 0.45);
    z-index: 6;
}

.node-card__head {
    display: flex;
    align-items: stretch;
    gap: 0.15rem;
    border-bottom: 1px solid rgb(55 65 81);
}

/* Every GROW card has a body under the head — chips or the "No products" line —
   but the watcher canvas has title-only cards, where the rule above would draw a
   divider with nothing beneath it. */
.node-card__head:last-child {
    border-bottom: none;
}

.node-card__grip {
    display: flex;
    align-items: center;
    padding: 0.5rem 0.35rem;
    color: rgb(148 163 184);
    user-select: none;
}

.node-card__title {
    flex: 1;
    min-width: 0;
    text-align: left;
    font-weight: 600;
    font-size: 0.85rem;
    line-height: 1.2;
    word-break: break-word;
    padding: 0.5rem 0.55rem 0.5rem 0.1rem;
    color: rgb(226 232 240);
}

.node-card__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    padding: 0.45rem 0.5rem;
}

.node-card__chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.72rem;
    padding: 0.12rem 0.4rem;
    border-radius: 0.3rem;
    max-width: 100%;
    background: rgba(59, 130, 246, 0.12);
    color: rgb(147 197 253);
    border: 1px solid rgba(59, 130, 246, 0.3);
}

.node-card__chip-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.node-card__empty {
    font-size: 0.72rem;
    color: rgb(148 163 184);
    padding: 0 0.5rem 0.35rem;
}

/* ── ENTERPRISE canvas modes ─────────────────────────────────────────────────
 *
 * Everything above is shared with GROW's catalog canvas. What follows is only
 * used by the two ENTERPRISE stages — the scope canvas (build → per-partner
 * mask → lifecycle ripple) and the watcher canvas (flag → propagate → notify).
 * Both are driven by addons/flock-canvas.js; the geometry is the same, only the
 * chrome and the states differ. */

/* Both enterprise modes hang a rail off the canvas. .catalog-canvas is a row
   flex container by default, so without this the rail would land BESIDE the
   tree instead of above or below it. */
.catalog-canvas[data-canvas-mode="scope"],
.catalog-canvas[data-canvas-mode="watcher"] {
    flex-direction: column;
}

/* ── Rail ──
 *
 * Scope's partner picker and watcher's history log. A flex sibling of the
 * viewport, never a child of .catalog-canvas__cards — that layer is
 * transform: scale()d, so chrome placed inside it would shrink along with the
 * tree and sit at its top-left corner. Out here it stays at 1:1 and its height
 * comes off viewport.clientHeight, which fit() already measures against. */
.canvas-rail {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.55rem 0.75rem;
    font-size: 0.72rem;
    color: rgb(148 163 184);
    /* The tree scales to fit but the rail does not, so on a narrow stage it has
       to scroll rather than wrap — a second row would eat the tree's height. */
    white-space: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
}

.canvas-rail::-webkit-scrollbar {
    display: none;
}

.canvas-rail--owners { border-bottom: 1px solid rgb(55 65 81); }
.canvas-rail--events { border-top: 1px solid rgb(55 65 81); }

.canvas-rail__label {
    flex: 0 0 auto;
    margin-right: 0.15rem;
    font-size: 0.63rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgb(100 116 139);
}

.canvas-rail__pill {
    flex: 0 0 auto;
    padding: 0.18rem 0.5rem;
    border: 1px solid rgb(55 65 81);
    border-radius: 0.3rem;
    color: rgb(148 163 184);
    transition: background 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

.canvas-rail__pill.is-active {
    background: rgba(59, 130, 246, 0.16);
    border-color: rgba(59, 130, 246, 0.45);
    color: rgb(147 197 253);
}

.canvas-rail__event {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding-right: 0.5rem;
    opacity: 0;
    transform: translateX(-6px);
    transition: opacity 0.4s ease, transform 0.4s ease;
}

.canvas-rail__event.is-in {
    opacity: 1;
    transform: none;
}

.canvas-rail__event-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: rgb(251 146 60);
}

.canvas-rail__event-time { color: rgb(100 116 139); }
.canvas-rail__event-text { color: rgb(203 213 225); }

/* ── Card additions ── */

.node-card__meta {
    padding: 0.3rem 0.55rem 0;
    font-size: 0.68rem;
    line-height: 1.25;
    text-align: left;
    color: rgb(148 163 184);
}

/* Sits in the head row. Kept small enough not to set the head's height —
   the title's own line-height stays the tallest thing in there — so a card
   gaining one never re-bands the level it sits in. */
.node-card__live {
    flex: 0 0 auto;
    align-self: center;
    width: 6px;
    height: 6px;
    margin-right: 0.5rem;
    border-radius: 50%;
    background: rgb(52 211 153);
    animation: flock-live-pulse 2s ease-in-out infinite;
}

@keyframes flock-live-pulse {
    0%, 100% { opacity: 1;   box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.5); }
    50%      { opacity: 0.5; box-shadow: 0 0 0 5px rgba(52, 211, 153, 0); }
}

/* In the card's flow, so it changes the card's height and therefore the level
   band — flock-canvas.js calls layout() after setting one. */
.node-card__badge {
    margin-top: 0.15rem;
    padding: 0.28rem 0.55rem;
    border-top: 1px solid rgb(55 65 81);
    font-size: 0.66rem;
    letter-spacing: 0.04em;
    text-align: left;
    color: rgb(148 163 184);
}

.node-card__badge[data-badge-state="risk"]     { color: rgb(252 165 165); background: rgba(248, 113, 113, 0.1); }
.node-card__badge[data-badge-state="ok"]       { color: rgb(110 231 183); background: rgba(52, 211, 153, 0.1); }
.node-card__badge[data-badge-state="flagged"]  { color: rgb(252 165 165); background: rgba(248, 113, 113, 0.14); font-weight: 600; }
.node-card__badge[data-badge-state="affected"] { color: rgb(253 186 116); background: rgba(251, 146, 60, 0.1); }

/* Absolutely positioned, unlike a badge: it lands on top of the card rather
   than in its flow, so the watcher cycle can notify five cards in a row without
   re-banding the tree between each one. */
.node-card__notify {
    position: absolute;
    top: -9px;
    right: -8px;
    z-index: 2;
    padding: 0.1rem 0.4rem;
    border: 1px solid rgba(52, 211, 153, 0.45);
    border-radius: 0.3rem;
    background: rgb(6 40 32);
    font-size: 0.64rem;
    color: rgb(110 231 183);
    transition: opacity 0.3s ease, transform 0.3s ease;
}

/* Pre-transition state, committed and then dropped — same trick as .is-new. */
.node-card__notify.is-landing {
    opacity: 0;
    transform: translateY(-6px) scale(0.9);
    transition: none;
}

.node-card__chip {
    transition: opacity 0.3s ease, transform 0.3s ease,
                background 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

.node-card__chip.is-arriving {
    opacity: 0;
    transform: scale(0.9);
    transition: none;
}

.node-card__chip.is-replaced {
    /* Its own row. A replaced chip carries the superseding code alongside the
       old one, which is roughly twice the width — sharing a row would ellipsize
       the very SKU the beat is about. */
    flex: 1 0 100%;
    background: rgba(251, 146, 60, 0.12);
    border-color: rgba(251, 146, 60, 0.35);
    color: rgb(253 186 116);
}

.node-card__chip.is-replaced .node-card__chip-label {
    text-decoration: line-through;
    opacity: 0.7;
}

.node-card__chip-tag {
    flex: 0 0 auto;
    font-size: 0.66rem;
    color: rgb(253 186 116);
}

/* ── Card states ──
 *
 * Ordered after .is-root deliberately. Both are 0-0-2-0, so on the root card —
 * which the watcher cycle flags — the later rule is the one that wins. */

.node-card.is-risk     { border-color: rgb(248 113 113); }
.node-card.is-ok       { border-color: rgba(52, 211, 153, 0.55); }
.node-card.is-affected { border-color: rgb(251 146 60); }

.node-card.is-flagged {
    border-color: rgb(248 113 113);
    border-width: 2px;
    background: rgb(43 26 30);
}

/* The partner mask. The card keeps its frame and its place — the catalog is one
   catalog, not a different one per audience — and only its contents go dark.
 *
 * Deliberately NOT `opacity` on the card itself. Opacity is inherited by every
 * descendant and cannot be undone by one of them, so the lock would fade along
 * with the contents it is there to explain. Ghosting the children individually
 * leaves the lock at full strength. */
.node-card.is-masked {
    background: rgb(22 27 36);
    border-color: rgb(45 53 68);
}

.node-card__title,
.node-card__meta,
.node-card__chips,
.node-card__empty {
    transition: opacity 0.3s ease;
}

.node-card.is-masked .node-card__title,
.node-card.is-masked .node-card__meta,
.node-card.is-masked .node-card__chips,
.node-card.is-masked .node-card__empty,
.node-card.is-masked .node-card__grip {
    opacity: 0.16;
}

/* A pseudo-element rather than a real node: it only exists while the card is
   masked, so an unmasked card carries no extra markup and no extra height. */
.node-card.is-masked .node-card__head::after {
    content: '\1F512';
    align-self: center;
    padding-right: 0.5rem;
    font-size: 0.7rem;
    line-height: 1;
}

/* ── Link states ──
 *
 * These only animate because drawLinks() reconciles persistent <path> elements
 * instead of re-serialising the SVG. If it ever goes back to innerHTML, every
 * path becomes a new element with no before-value and all of this snaps. */
.catalog-canvas__links path {
    transition: stroke-opacity 0.35s ease, stroke 0.35s ease;
}

.catalog-canvas__links path[data-edge="dim"] {
    stroke-opacity: 0.16;
}

.catalog-canvas__links path[data-edge="alert"] {
    stroke: rgb(251 146 60);
    stroke-dasharray: 6 5;
    animation: flock-edge-march 1.1s linear infinite;
}

@keyframes flock-edge-march {
    to { stroke-dashoffset: -11; }   /* one full 6 + 5 dash period */
}

/* Cross-branch dependency edges. drawLinks() sets pathLength="1" on these, so
   the dash units below are fractions of the whole route and the draw-on works
   without measuring it. */
.catalog-canvas__links path[data-edge-kind="dep"] {
    stroke: rgb(251 146 60);
    stroke-dasharray: 1;
    stroke-dashoffset: 1;          /* one dash the length of the path, pushed off it */
    transition: stroke-dashoffset 0.55s ease;
}

.catalog-canvas__links path[data-edge-kind="dep"][data-edge-phase="drawn"] {
    stroke-dashoffset: 0;
}

/* Swapped in once the line is fully drawn at both ends, so the change from one
   long dash to many short ones is invisible. */
.catalog-canvas__links path[data-edge-kind="dep"][data-edge-phase="settled"] {
    stroke-dasharray: 0.022 0.018;
    stroke-dashoffset: 0;
    transition: none;
}

@media (max-width: 1000px) {
    /* The column is width-limited on a phone regardless, so the stage only needs
       to be tall enough not to letterbox the scaled-down tree. */
    .docker.docker--stage {
        min-height: 380px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .flock-contact-fab,
    .flock-contact-fab.is-visible {
        transform: none;
    }

    .flock-contact-fab {
        transition: opacity 0.2s ease, visibility 0.2s ease;
    }

    /* flock-canvas.js also skips the loop entirely and builds the tree in one
       pass — this stops the cards gliding into place on that single build. */
    .catalog-canvas,
    .catalog-canvas__cards,
    .node-card {
        transition: none;
    }

    /* Same for the enterprise chrome. Every mode's reduced branch renders its
       final state directly, so these only need to stop moving — except the two
       that default to a hidden start state, which have to be forced open since
       nothing will animate them there. */
    .node-card__live,
    .node-card__notify,
    .node-card__chip,
    .canvas-rail__pill,
    .canvas-rail__event,
    .catalog-canvas__links path {
        animation: none;
        transition: none;
    }

    .canvas-rail__event {
        opacity: 1;
        transform: none;
    }

    .catalog-canvas__links path[data-edge-kind="dep"] {
        stroke-dashoffset: 0;
    }
}
