/* CustomerNode Insights — the /insights/ hub.
   Premium light editorial layout: a featured lead plus an image-forward grid,
   built around the brand indigo system (see brand/AGENTS.md). Cover art is the
   article header captured for each LinkedIn piece (static/img/articles/).
   Everything is namespaced under .cn-insight* / .cn-cover*. No inline styles:
   PUBLIC_CSP is style-src 'self', so all styling lives here. */

.cn-insights-hub {
    --cni-indigo: #6366f1;
    --cni-indigo-deep: #4f46e5;
    --cni-indigo-soft: #818cf8;
    --cni-ink: #2c2a4d;
    --cni-ink-soft: #4a4866;
    --cni-muted: #6f6d8a;
    --cni-border: #e4e2f2;
    --cni-border-strong: #cfcceb;
    --cni-card: #ffffff;
    --cni-lilac: #edeafe;
    --cni-cover-bg: #f1f1f9;
    /* Post accent — the OTHER END OF THE BRAND'S OWN GRADIENT, not a new hue.
       brand/AGENTS.md defines the journey thread as cyan #4eacf1 -> indigo
       #4f46e5; an Article takes the indigo end, a Post takes the cyan. Same
       hue (h=205) as the brand cyan, darkened to 4.99:1 on the white card
       because #4eacf1 itself is only 2.47:1 and fails AA as text. */
    --cni-post-accent: #0f73bd;
    --cni-post-tile: #e3f2fd;
    --cni-title: #0f172a;
    --cni-page: #f5f5fb;
    --cni-frost: rgba(251, 251, 254, 0.92);
    --cni-cover-sheen: rgba(44, 42, 77, 0.06);
    --cni-card-shadow: 0 1px 2px rgba(28, 26, 54, 0.045), 0 14px 34px -16px rgba(28, 26, 54, 0.12);
    --cni-card-shadow-hover: 0 2px 6px rgba(28, 26, 54, 0.05), 0 30px 60px -22px rgba(79, 70, 229, 0.28);
    --cni-scrim: rgba(28, 26, 54, 0.55);
    --cni-field-bottom: #eef0f6;

    position: relative;
    isolation: isolate;
    /* NOT overflow:hidden — that would trap the sticky header. */
    /* The section box starts at y=0 (behind the fixed 60px nav). Pad the top by
       the nav height so the sticky header's natural position == its sticky top
       (60px): the header sits flush under the nav with no gap and no clamp, so
       the grid can't slide up underneath it. */
    padding: 60px 24px 120px;
    /* Follows the site theme -- see the dark branch below. */
    background: var(--cni-page);
}

/* Soft indigo aurora behind the top of the page — the same treatment /pricing/
   uses (see .pricing-v2::before in index_v6.css), built from the shared brand
   tokens so it stays subtle and on-brand in both themes. Sits above the paper,
   below the content (.cn-insights-inner is z-index 1). */
.cn-insights-hub::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    /* Kept OFF the top-left, where the eyebrow + headline live — a glow there
       fought the purple header. Instead a soft cool wash anchored to the right
       and a faint one lower-down, so it's ambient depth, not competition. */
    background:
        radial-gradient(60% 50% at 104% -8%, color-mix(in srgb, var(--blurple) 12%, transparent) 0%, transparent 60%),
        radial-gradient(48% 44% at 88% 40%, color-mix(in srgb, var(--indigo) 6%, transparent) 0%, transparent 68%);
}

/* ---- Dark theme -------------------------------------------------------
   The site renders its palette server-side into :root and flags the document
   with html[data-theme="dark"] (see templates/style-variables.css). The hub
   keeps its own editorial palette rather than borrowing the app tokens
   wholesale, so dark mode is one re-point of the --cni-* set: every rule below
   already reads a token, and nothing else changes. */
html[data-theme="dark"] .cn-insights-hub {
    --cni-indigo: #818cf8;
    --cni-indigo-deep: #a5b4fc;   /* links must READ as links on black */
    --cni-indigo-soft: #6366f1;
    --cni-ink: #e8eaf3;
    --cni-ink-soft: #c3c6d6;
    --cni-muted: #9aa0b8;
    --cni-border: #1e2233;
    --cni-border-strong: #333b55;
    --cni-card: #0c0f1a;
    --cni-lilac: rgba(129, 140, 248, 0.18);
    --cni-cover-bg: #11141f;
    /* On black the brand cyan needs no darkening: #4eacf1 verbatim, 7.73:1. */
    --cni-post-accent: #4eacf1;
    --cni-post-tile: rgba(78, 172, 241, 0.18);
    --cni-title: #f4f6fb;
    --cni-page: var(--main-bg-color);
    --cni-frost: rgba(10, 12, 20, 0.86);
    /* Light's sheen darkens the bottom of a cover; on black it has to lift. */
    --cni-cover-sheen: rgba(0, 0, 0, 0.28);
    --cni-card-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 16px 36px -16px rgba(0, 0, 0, 0.62);
    --cni-card-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.5), 0 30px 60px -20px rgba(0, 0, 0, 0.72);
    --cni-scrim: rgba(0, 0, 0, 0.72);
    --cni-field-bottom: #1a1f2e;
}

/* position:sticky is confined by any ancestor with overflow != visible. The
   shared .body-wrap clips overflow site-wide; relax it on THIS page only (scoped
   by the insights-hub-page body class) so the header can stick. */
body.insights-hub-page .body-wrap,
body.insights-reader-page .body-wrap {
    overflow: visible;
}

.cn-insights-inner {
    position: relative;
    z-index: 1;
    /* Matched to the featured card's width so the whole hub aligns to one measure
       (was 1180 -> 1040 -> 900). At 900 the grid falls to 2-up, which is the
       contained, un-sprawled feel the wider versions were missing. */
    max-width: 900px;
    margin: 0 auto;
}

/* ---- Compact masthead: full-width sticky bar under the nav ----
   Two states, one bar. At the top of the page it is a transparent, full-size
   masthead; once scrolled (hub.js toggles .is-stuck) it collapses into a thin
   frosted utility strip -- smaller title, smaller pills -- so the reading
   surface below owns the screen. Every property that moves is transitioned on
   the same curve and duration (--cn-hub-anim) so the collapse reads as one
   motion instead of several. */
.cn-hub-head {
    /* Bar height, and the ONLY thing that drives vertical compression. The
       bottom margin is its exact complement: height + margin-bottom is a
       constant (--cn-hub-slot), so collapsing the bar never changes the
       document's flow height and the card grid below cannot lurch upward the
       instant the header sticks. */
    /* rem, not px, so the bar scales with the type it contains (everything
       inside it is rem) instead of clipping it. 2.9rem == the previous 58px.
       Honest scope note, measured: this does NOT deliver WCAG 1.4.4 text-only
       zoom today, because landing_base.css pins `html { font-size: 20px }`
       absolutely -- with the root hard-pinned, a browser default-font-size
       change moves neither the px nor the rem value. Unpinning that root is a
       site-wide typography change, not one this file can make. These stay rem
       so the bar is already correct on the day it is unpinned. */
    --cn-hub-h: 2.9rem;
    --cn-hub-slot: 4.4rem;
    --cn-hub-anim: 280ms cubic-bezier(0.22, 1, 0.36, 1);

    position: sticky;
    top: 60px;              /* flush against the 60px fixed nav */
    z-index: 5;             /* above the cards, below the fixed site nav */
    /* Negative side margins cancel the section's 24px padding so the frosted
       bar spans the full viewport width (no 100vw -> no scrollbar overflow). */
    margin: 0 -24px;
    margin-bottom: calc(var(--cn-hub-slot) - var(--cn-hub-h));
    /* Explicit height, not min-height: the margin above compensates against the
       *variable*, so a bar that grew past it would shove the grid -- exactly
       what this pair exists to prevent. Nothing in the bar can wrap (see
       flex-wrap below), so the height is always honoured. */
    height: var(--cn-hub-h);
    display: flex;
    align-items: center;
    /* 24px side padding here (not on the inner) so the inner's container box is
       identical to the grid's .cn-insights-inner -> content aligns with cards. */
    padding: 0 24px;
    /* Transparent at the top; the frosted cover fades in once scrolled so cards
       scroll cleanly underneath. */
    background: transparent;
    transition: height var(--cn-hub-anim), margin-bottom var(--cn-hub-anim),
                background 200ms ease, box-shadow 200ms ease,
                -webkit-backdrop-filter 200ms ease, backdrop-filter 200ms ease;
}

.cn-hub-head.is-stuck {
    /* Leaves ~4px of slack around the collapsed pills (the bordered --site
       variant is the tallest), which is what keeps a focus ring from painting
       above the bar and under the fixed nav. */
    --cn-hub-h: 2.1rem;
    background: var(--cni-frost);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    box-shadow: 0 8px 22px -20px rgba(44, 42, 77, 0.5);
}

/* Content stays aligned to the grid container inside the full-width bar. */
.cn-hub-head-inner {
    width: 100%;
    max-width: 1180px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Never wrap: a second row would break the fixed-height contract above and
       make the collapse shove the grid. The title ellipsises instead. */
    flex-wrap: nowrap;
    gap: 24px;
    transition: gap var(--cn-hub-anim);
}

.cn-hub-head.is-stuck .cn-hub-head-inner { gap: 16px; }

.cn-hub-title {
    /* Deliberately smaller than the Knowledge Center / Trust Center page titles (.faqx-h1 / .tc-h1). */
    font-size: 1.2rem;
    line-height: 1.15;
    letter-spacing: -0.02em;
    margin: 0;
    font-weight: 700;
    color: var(--cni-title);
    white-space: nowrap;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: font-size var(--cn-hub-anim), letter-spacing var(--cn-hub-anim);
}

.cn-hub-head.is-stuck .cn-hub-title {
    font-size: 0.92rem;
    letter-spacing: -0.01em;
}

.cn-hub-actions {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    flex-shrink: 0;
    gap: 12px;
    transition: gap var(--cn-hub-anim);
}

.cn-hub-head.is-stuck .cn-hub-actions { gap: 8px; }

/* `a.` specificity so the global `a:not(.button)` underline never applies. */
a.cn-hub-cta {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 0.76rem;
    font-weight: 600;
    text-decoration: none;
    border-radius: 11px;
    padding: 8px 15px;
    white-space: nowrap;
    transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease, color 160ms ease,
                font-size var(--cn-hub-anim), padding var(--cn-hub-anim),
                border-radius var(--cn-hub-anim), gap var(--cn-hub-anim);
}

.cn-hub-head.is-stuck a.cn-hub-cta {
    font-size: 0.75rem;
    padding: 5px 11px;
    border-radius: 8px;
    gap: 5px;
}

/* Sized in em off the pill's own font-size, so the icon rides the SAME
   transition as the label instead of a second one of its own. (Sizing it in px
   per state desynchronises: Chromium interpolates a px `width` but snaps a
   `height` that came from the SVG's presentation attribute.) */
a.cn-hub-cta svg {
    flex-shrink: 0;
    width: 0.95em;
    height: 0.95em;
}

a.cn-hub-cta--linkedin {
    color: #ffffff;
    background: linear-gradient(120deg, #4f46e5, #6366f1);
    box-shadow: 0 14px 30px -16px rgba(79, 70, 229, 0.6);
}

/* The secondary pills (`--site`, and the dev-only `--drafts`) render on the
   PUBLISHED hub too, where insights_drafts.css is not loaded -- so their colour
   has to live here or they inherit the site's body text and fall under 4.5:1 on
   the themed background. */
a.cn-hub-cta--site {
    color: var(--cni-ink-soft);
}

a.cn-hub-cta--site:hover,
a.cn-hub-cta--site:focus-visible {
    color: var(--cni-indigo-deep);
}

/* Quieter lift once collapsed: the strip should read as chrome, not a CTA band. */
.cn-hub-head.is-stuck a.cn-hub-cta--linkedin {
    box-shadow: 0 8px 16px -12px rgba(79, 70, 229, 0.55);
}

a.cn-hub-cta--linkedin:hover,
a.cn-hub-cta--linkedin:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 20px 38px -16px rgba(79, 70, 229, 0.7);
    color: #ffffff;
    outline: none;
}

.cn-hub-head.is-stuck a.cn-hub-cta--linkedin:hover,
.cn-hub-head.is-stuck a.cn-hub-cta--linkedin:focus-visible {
    box-shadow: 0 14px 24px -14px rgba(79, 70, 229, 0.65);
}

/* Keyboard focus has to be visible in BOTH states -- the collapsed pill's
   shadow alone is too quiet to read as focus, and it is the only affordance
   left once the label goes icon-only on phones. Scoped to `.cn-hub-head` so it
   out-specifies the `outline: none` rules above AND the one insights_drafts.css
   adds for `a.cn-hub-cta--site` on the drafts hub (that file loads after this
   one, so plain source order would lose). */
.cn-hub-head a.cn-hub-cta:focus-visible {
    outline: 2px solid var(--cni-indigo-deep);
    outline-offset: 3px;
}

/* Collapsed, the pill has ~2px of slack in the bar, so anything that grows or
   lifts it paints above the bar -- under the fixed site nav, which sits at a far
   higher z-index, i.e. an invisible top edge on the focus ring. In the collapsed
   strip the ring hugs the pill and the hover/focus lift is dropped: it is chrome
   there, not a CTA band, so it has nothing to lift off. */
.cn-hub-head.is-stuck a.cn-hub-cta:focus-visible {
    outline-offset: 0;
}

.cn-hub-head.is-stuck a.cn-hub-cta:hover,
.cn-hub-head.is-stuck a.cn-hub-cta:focus-visible {
    transform: none;
}

/* (Narrow-viewport sizing lives in the Responsive section at the bottom of this
   file, so it lands after the shared 640px block in the cascade.) */

/* ---- Shared entry shell: clean card, matching the index .article-card ---- */
.cn-insight-entry {
    background: var(--cni-card);
    border: 1px solid var(--cni-border);
    border-radius: 16px;
    overflow: hidden;
    scroll-margin-top: 110px;
    box-shadow: var(--cni-card-shadow);
    transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}

.cn-insight-entry:hover,
.cn-insight-entry:focus-within {
    transform: translateY(-4px);
    border-color: var(--cni-border-strong);
    box-shadow: var(--cni-card-shadow-hover);
}

/* ---- Cover art ---- */
.cn-cover {
    display: block;
    position: relative;
    /* The card is a flex column; the cover must keep its aspect box and never
       be squeezed by a long commentary above it. */
    flex: 0 0 auto;
    aspect-ratio: 16 / 9;
    background: var(--cni-cover-bg);
    overflow: hidden;
    text-decoration: none;
}

.cn-cover-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    display: block;
    transition: transform 420ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.cn-cover-img--top { object-position: top center; }
.cn-cover-img--center { object-position: center; }

.cn-insight-entry:hover .cn-cover-img,
.cn-insight-entry:focus-within .cn-cover-img {
    transform: scale(1.03);
}

/* Subtle top sheen on every cover so light and dark sources read as one set. */
.cn-cover::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0) 62%, var(--cni-cover-sheen) 100%);
    pointer-events: none;
}

/* Generated cover for pieces without a designed hero. */
.cn-cover--gen {
    background:
        radial-gradient(120% 120% at 18% 12%, rgba(129, 140, 248, 0.55), rgba(129, 140, 248, 0) 60%),
        linear-gradient(135deg, #4f46e5 0%, #6366f1 48%, #312e81 100%);
    display: flex;
    align-items: center;
    justify-content: center;
}

.cn-cover-num {
    font-size: clamp(4rem, 12vw, 7rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.04em;
    color: rgba(255, 255, 255, 0.16);
    user-select: none;
}

.cn-cover--gen::before {
    content: "Insights";
    position: absolute;
    bottom: 18px;
    left: 22px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.72);
}

/* ---- Post anatomy: the card reads as the LinkedIn post it is -------------
   Order is actor -> commentary -> media -> link unit -> footer, matching the
   feed. Two consequences the layout depends on:
     1. The commentary sits ABOVE the cover, so it is reserved a fixed 3-line
        box. Without that the covers land at a different y in every column and
        the row stops reading as a set.
     2. The cover is edge-to-edge (no card padding around it), so every child
        that is NOT the cover carries its own horizontal padding. */

.cn-post-actor {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 16px 10px;
}

/* Holds the format glyph. Themed tokens, unlike the fixed pale chip this
   needed when it held the logo: that mark is a dark-navy handshake on
   transparent and would have vanished on a dark card, whereas a stroked glyph
   just follows currentColor. */
.cn-actor-avatar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 8px;
    border-radius: 9px;
    background: var(--cni-lilac);
    color: var(--cni-indigo-deep);
    overflow: hidden;
}

.cn-actor-glyph {
    width: 100%;
    height: 100%;
    display: block;
}

/* A Post is the other kind of thing in the series, so it takes the other end of
   the brand gradient. Measured: 4.99:1 light / 7.73:1 dark against the card. */
.cn-insight-card--post .cn-actor-avatar {
    background: var(--cni-post-tile);
    color: var(--cni-post-accent);
}

.cn-insight-card--post .cn-actor-sub {
    color: var(--cni-post-accent);
}

/* Only used if the logo variant is uncommented (see insights/index.html). */
.cn-actor-avatar img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.cn-actor-id {
    display: flex;
    flex-direction: column;
    min-width: 0;      /* let the three lines ellipsise instead of widening the card */
    gap: 1px;
}

.cn-actor-name,
.cn-actor-sub,
.cn-actor-stamp {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Currently unrendered: the actor name is commented out in both card templates
   (see the note in insights/index.html). Kept so restoring it is one uncomment
   — do NOT sweep this as dead CSS. */
.cn-actor-name {
    font-size: 0.84rem;
    font-weight: 700;
    line-height: 1.25;
    color: var(--cni-ink);
}

/* The eyebrow. Promoted from quiet muted text once the actor name above it was
   removed: without it the two remaining meta lines were the same size and
   colour, so the block had no focal point. Says the same thing as the glyph
   beside it, in words. */
.cn-actor-sub {
    font-size: 0.58rem;
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--cni-indigo-deep);
}

.cn-actor-stamp {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 0.66rem;
    line-height: 1.25;
    color: var(--cni-muted);
}

.cn-actor-globe { flex-shrink: 0; }

/* No deck under any title now (removed 2026-07-23), so a Post's commentary
   truncates to the SAME length as an Article's account comment — the shared
   3-line clamp below. Both formats are account comment -> cover -> title, so
   their covers align across every row, mixed or not. */

.cn-post-commentary {
    /* Three lines tall whatever the copy does, so the covers line up across a
       row of same-format cards. Longer hooks clamp; shorter ones leave the box. */
    --cn-commentary-lines: 3;
    --cn-commentary-lh: 1.5;

    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: var(--cn-commentary-lines);
    line-clamp: var(--cn-commentary-lines);
    overflow: hidden;
    /* Reserve exactly three lines so short hooks still push the cover down to
       the same y as their neighbours; the clamp handles the long ones. */
    min-height: calc(var(--cn-commentary-lines) * var(--cn-commentary-lh) * 1em);
    /* The gap to the cover MUST be margin, not padding. `overflow: hidden`
       clips at the padding box, so bottom padding is a window the clamped
       fourth line shows through. */
    padding: 0 16px;
    margin: 0 0 12px;
    /* Sits between the deck (0.7rem) and the title (0.94rem). At 0.83rem it was
       within 2px of the title and read as a second headline competing with it;
       on LinkedIn the post copy is plainly smaller than the title it sits above.
       The three-line reserve above is in `em`, so it follows this automatically. */
    font-size: 0.8rem;
    line-height: var(--cn-commentary-lh);
    color: var(--cni-ink-soft);
}

/* The title unit under the media. The old LinkedIn-style tinted band + hairline
   made a clunky two-tone card; a clean single surface reads more premium and
   matches the featured card (which already dropped the tint). */
.cn-post-unit {
    display: block;
    padding: 14px 18px 18px;
    /* Last element in the card, so it takes the slack: a one-line title and a
       three-line title still bottom out on the same edge across a grid row. */
    flex: 1 1 auto;
}

/* The deck under the title in the link unit — an Article's subtitle. Not
   --cni-muted, the usual quiet-text token: --cni-muted is tuned against the
   WHITE card and measures only 4.42:1 on this tinted band, which fails AA.
   --cni-ink-soft is 7.76:1 here and still reads quieter than the title.

   .cn-post-unit-source is the retired domain line, kept alongside so the
   commented-out markup in both card templates still resolves if restored. */
.cn-post-unit-deck,
.cn-post-unit-source {
    display: block;
    font-size: 0.7rem;
    line-height: 1.45;
    color: var(--cni-ink-soft);
}

.cn-insight-card:hover .cn-insight-entry-title,
.cn-insight-card:focus-within .cn-insight-entry-title {
    color: var(--cni-indigo-deep);
}

/* ---- Meta / badges ---- */
.cn-insight-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 9px;
    margin-bottom: 12px;
}

.cn-insight-badge {
    font-size: 0.57rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--cni-indigo-deep);
    background: var(--cni-lilac);
    border-radius: 999px;
    padding: 4px 9px;
    line-height: 1;
}

.cn-insight-date {
    font-size: 0.68rem;
    color: var(--cni-muted);
    font-weight: 500;
}

/* ---- Titles / copy ----
   The display face (Space Grotesk) at a medium weight, matching the masthead +
   featured title — the old body-font bold read generic. Deeper --cni-title ink
   (not the muted --cni-ink) for authority + contrast. */
.cn-insight-entry-title {
    font-family: var(--brand-font);
    color: var(--cni-title);
    font-weight: 500;
    letter-spacing: -0.02em;
    margin: 0 0 8px;
    transition: color 160ms ease;
}

.cn-post-unit .cn-insight-entry-title { margin: 0 0 3px; }

/* ---- Grid ---- */
.cn-insights-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(324px, 1fr));
    gap: 22px;
}

.cn-insight-card {
    display: flex;
    flex-direction: column;
    position: relative;
    cursor: pointer;
}

/* Stretched link covering the whole card (crawlable on-domain href; hub.js
   intercepts the click to open the read-where modal). */
a.cn-card-link {
    position: absolute;
    inset: 0;
    z-index: 2;
    border-radius: inherit;
    text-decoration: none;
    font-size: 0;
    cursor: pointer;
}

/* Negative offset, deliberately. The link is inset:0 inside a card that clips
   (.cn-insight-entry has overflow:hidden for the cover's rounded corners), so a
   POSITIVE offset paints the whole ring outside the padding box and it is
   clipped away — keyboard users got no focus indicator at all, only the
   :focus-within lift, which is not one. */
a.cn-card-link:focus-visible {
    outline: 2px solid var(--cni-indigo);
    outline-offset: -3px;
}

.cn-insight-card .cn-insight-entry-title {
    font-size: 1.1rem;
    line-height: 1.24;
}

/* ---- Back link ---- */
.cn-insights-hub-back {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 56px;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--cni-indigo-deep);
    text-decoration: none;
}

.cn-insights-hub-back svg { transition: transform 180ms ease; }

.cn-insights-hub-back:hover,
.cn-insights-hub-back:focus-visible {
    color: var(--cni-indigo);
    outline: none;
}

.cn-insights-hub-back:hover svg,
.cn-insights-hub-back:focus-visible svg {
    transform: translateX(-3px);
}

/* ---- "Read where?" modal ---- */
.cn-readmodal {
    position: fixed;
    inset: 0;
    z-index: 1000;                 /* above the site nav (z-index 10) */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    opacity: 0;
    transition: opacity 180ms ease;
}

.cn-readmodal.is-open { opacity: 1; }
.cn-readmodal[hidden] { display: none; }

.cn-readmodal-backdrop {
    position: absolute;
    inset: 0;
    background: var(--cni-scrim);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
}

.cn-readmodal-dialog {
    position: relative;
    width: min(440px, 100%);
    background: var(--cni-card);
    border-radius: 22px;
    padding: 34px 32px 30px;
    box-shadow: 0 40px 90px -30px rgba(28, 26, 54, 0.7);
    transform: translateY(8px) scale(0.98);
    transition: transform 200ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.cn-readmodal.is-open .cn-readmodal-dialog { transform: none; }

/* Cover art mirrored from the clicked card. Bleeds to the dialog's top edges
   (negative margins cancel the dialog padding) and shares the card's 16:9 crop
   and object-position variants, so the modal reads as the same piece. */
.cn-readmodal-cover {
    display: block;
    position: relative;
    aspect-ratio: 16 / 9;
    width: calc(100% + 64px);
    margin: -34px -32px 22px;
    border-radius: 22px 22px 0 0;
    overflow: hidden;
    background: var(--cni-cover-bg);
}

.cn-readmodal-cover-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    display: block;
}

.cn-readmodal-cover-img--top { object-position: top center; }
.cn-readmodal-cover-img--center { object-position: center; }

/* Numbered-gradient fallback for pieces without a designed hero — matches
   the card's .cn-cover--gen so covered and uncovered pieces stay a set. */
.cn-readmodal-cover--gen {
    background:
        radial-gradient(120% 120% at 18% 12%, rgba(129, 140, 248, 0.55), rgba(129, 140, 248, 0) 60%),
        linear-gradient(135deg, #4f46e5 0%, #6366f1 48%, #312e81 100%);
    display: flex;
    align-items: center;
    justify-content: center;
}

.cn-readmodal-cover-num {
    font-size: clamp(3.4rem, 10vw, 5.6rem);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.04em;
    color: rgba(255, 255, 255, 0.18);
    user-select: none;
}

.cn-readmodal-x {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 2;                    /* stay above the cover image */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 0;
    border-radius: 50%;
    background: var(--cni-field-bottom);
    color: var(--cni-muted);
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(28, 26, 54, 0.22);
    transition: background 140ms ease, color 140ms ease;
}

.cn-readmodal-x:hover { background: var(--cni-lilac); color: var(--cni-indigo-deep); }

.cn-readmodal-eyebrow {
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--cni-indigo-deep);
    margin: 0 0 8px;
}

.cn-readmodal-title {
    font-size: 1.12rem;
    line-height: 1.3;
    font-weight: 700;
    color: var(--cni-ink);
    margin: 0 0 22px;
    padding-right: 28px;
}

a.cn-readmodal-cta {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    width: 100%;
    font-size: 0.92rem;
    font-weight: 700;
    text-decoration: none;
    border-radius: 12px;
    padding: 14px 20px;
    transition: transform 150ms ease, box-shadow 150ms ease, border-color 150ms ease, color 150ms ease;
}

a.cn-readmodal-cta + a.cn-readmodal-cta { margin-top: 12px; }

a.cn-readmodal-cta--linkedin {
    color: #ffffff;
    background: linear-gradient(120deg, #4f46e5, #6366f1);
    box-shadow: 0 16px 34px -16px rgba(79, 70, 229, 0.6);
}

a.cn-readmodal-cta--linkedin:hover,
a.cn-readmodal-cta--linkedin:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 22px 44px -16px rgba(79, 70, 229, 0.7);
    color: #ffffff;
    outline: none;
}

a.cn-readmodal-cta--site {
    color: var(--cni-indigo-deep);
    border: 1px solid var(--cni-border-strong);
    background: var(--cni-card);
}

a.cn-readmodal-cta--site:hover,
a.cn-readmodal-cta--site:focus-visible {
    border-color: var(--cni-indigo);
    color: var(--cni-indigo);
    outline: none;
}

body.cn-readmodal-open { overflow: hidden; }

/* ---- Responsive ---- */
@media (max-width: 640px) {
    .cn-insights-hub { padding: 60px 16px 80px; }   /* clear the fixed nav */
    /* Longhands only: the base rule's margin-bottom/height pair is a calc that
       must survive (see --cn-hub-slot). Narrower slot to match the tighter page. */
    .cn-hub-head {
        --cn-hub-slot: 4.2rem;
        margin-left: -16px;
        margin-right: -16px;
        padding-left: 16px;
        padding-right: 16px;
    }
    .cn-hub-title { font-size: 1.12rem; }
    .cn-insights-grid { grid-template-columns: 1fr; gap: 22px; }
    /* One card per row on a phone, so there is nothing to align against and a
       short hook should not sit in a reserved box — but the CLAMP stays. A
       Post's preview is the opening of a real post body; unclamped it runs ~9
       lines here and pushes the cover off the screen. */
    .cn-post-commentary {
        min-height: 0;
    }
}

/* Dev-only chrome in the bar (the Drafts pill on the published hub, the
   live-reload pill on the drafts hub) sheds its label first: it must never be
   what squeezes the title into an ellipsis on a real reader's screen. */
@media (max-width: 760px) {
    .cn-hub-head .cn-hub-cta--drafts .cn-hub-cta-label,
    .cn-hub-head .cn-draft-sync-label {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* Phone: the bar stays a single row in BOTH states (title + pill, title
   ellipsises if it must) so the height stays fixed and compensated, exactly
   like desktop -- a wrapped second row would make the collapse change the
   document's flow height and shove the grid upward mid-scroll. */
@media (max-width: 560px) {
    .cn-hub-head {
        --cn-hub-h: 2.5rem;
        --cn-hub-slot: 3.8rem;
    }
    .cn-hub-head.is-stuck { --cn-hub-h: 1.9rem; }
    .cn-hub-head-inner { gap: 12px; }
    /* Needs the .is-stuck compound to beat the base collapsed rule -- source
       order alone loses to specificity. */
    .cn-hub-head.is-stuck .cn-hub-head-inner { gap: 8px; }
    /* Icon-only pills: at this width the labels would eat the title. Clipped,
       not removed, so the link keeps its accessible name. */
    .cn-hub-head .cn-hub-cta-label {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    /* Square-ish pills once icon-only, kept ~35px+ so they stay thumb-sized. */
    a.cn-hub-cta { padding: 12px 14px; }
    .cn-hub-head.is-stuck a.cn-hub-cta { padding: 9px 11px; }
}

@media (prefers-reduced-motion: reduce) {
    .cn-insight-entry,
    .cn-cover-img,
    .cn-insights-hub-back svg,
    .cn-hub-head,
    .cn-hub-head-inner,
    .cn-hub-actions,
    .cn-hub-title,
    a.cn-hub-cta,
    /* The read-where modal too: it opens on a card click, so a reader who asked
       for no motion still got a 200ms slide+scale on the dialog and a lift on
       each CTA. */
    .cn-readmodal-dialog,
    a.cn-readmodal-cta {
        transition: none;
    }
    /* `transition: none` alone is not enough: it removes the TWEEN, not the
       displacement. Every rule that moves something on hover/focus needs its
       transform explicitly reset, or the element still jumps to the new
       position instantly — which is motion, just abrupt motion. Keyboard focus
       counts too, so every :hover here has its :focus-visible twin. */
    .cn-insight-entry:hover,
    .cn-insight-entry:focus-within { transform: none; }
    .cn-insight-entry:hover .cn-cover-img,
    .cn-insight-entry:focus-within .cn-cover-img { transform: none; }
    a.cn-hub-cta:hover,
    a.cn-hub-cta:focus-visible { transform: none; }
    a.cn-readmodal-cta:hover,
    a.cn-readmodal-cta:focus-visible { transform: none; }
    .cn-insights-hub-back:hover svg,
    .cn-insights-hub-back:focus-visible svg { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   EDITORIAL MASTHEAD + 3D TILT — 2026-07-23
   Keeps the LinkedIn-post card grid above (it mirrors the real LinkedIn feed so
   the hub doubles as a review surface). Swaps the old sticky .cn-hub-head bar for
   a non-sticky editorial masthead, and gives the cards a cursor-follow 3D tilt +
   a 3D staggered entrance. Public root font-size is 20px (1rem = 20px).
   ═══════════════════════════════════════════════════════════════════════════ */

.cn-insights-hub { --ins-ease: cubic-bezier(0.22, 1, 0.36, 1); }

/* ---- Masthead (not sticky) ---- */
.cn-ins-mast {
    /* Deliberate air above the eyebrow so it reads as an editorial opening, not
       a headline crammed under the fixed nav. */
    padding-top: 34px;
    margin-bottom: 40px;
    animation: cnInsUp 0.6s var(--ins-ease) both;
}
.cn-ins-eyebrow {
    display: inline-block;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--cni-indigo-deep);
}
.cn-ins-mast-title {
    font-family: var(--brand-font);
    font-weight: 500;
    /* More commanding than the earlier 2.2rem cap — the masthead should have real
       presence, closer to the confident hero the dark treatment gets for free. */
    font-size: clamp(1.9rem, 4.2vw, 2.7rem);
    line-height: 1.04;
    letter-spacing: -0.03em;
    color: var(--cni-title);
    margin: 0.85rem 0 0;
    max-width: 20ch;
}
.cn-ins-mast-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 1.4rem;
}
/* Calm flat chip — no gradient fill, no coloured glow (the old Follow pill's
   problem). A hairline that warms to indigo on hover. */
a.cn-ins-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 40px;                /* uniform with the search field + toggle */
    box-sizing: border-box;
    font-size: 0.72rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--cni-ink-soft);
    background: transparent;
    border: 1px solid var(--cni-border-strong);
    border-radius: 999px;
    padding: 0 16px;
    white-space: nowrap;
    transition: border-color 180ms var(--ins-ease), color 180ms var(--ins-ease);
}
a.cn-ins-chip svg { flex-shrink: 0; }
a.cn-ins-chip:hover,
a.cn-ins-chip:focus-visible { border-color: var(--cni-indigo); color: var(--cni-indigo-deep); outline: none; }
a.cn-ins-chip:focus-visible { outline: 2px solid var(--cni-indigo-deep); outline-offset: 3px; }
a.cn-ins-chip--drafts { color: var(--cni-muted); }

@keyframes cnInsUp {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ---- 3D tilt: cards rotate toward the cursor over their own centre ----
   Each card carries its own perspective() so it tilts around itself (the classic
   tilt-card feel), independent of its neighbours. hub.js sets --rx/--ry/--ty/--sc
   via the CSSOM (allowed under PUBLIC_CSP style-src 'self'); the values default to
   a flat resting state, so with JS off the cards are simply static. */
/* No hover tilt (removed 2026-07-23, per feedback: too movey). Cards get a
   simple fade-rise on load and the clean lift on :hover (.cn-insight-entry:hover). */
.cn-insights-grid[data-tilt-grid] .cn-insight-entry {
    animation: cnCardIn 0.5s var(--ins-ease) backwards;
}
.cn-insights-grid[data-tilt-grid] .cn-insight-entry:nth-child(1) { animation-delay: 0.02s; }
.cn-insights-grid[data-tilt-grid] .cn-insight-entry:nth-child(2) { animation-delay: 0.07s; }
.cn-insights-grid[data-tilt-grid] .cn-insight-entry:nth-child(3) { animation-delay: 0.12s; }
.cn-insights-grid[data-tilt-grid] .cn-insight-entry:nth-child(4) { animation-delay: 0.17s; }
.cn-insights-grid[data-tilt-grid] .cn-insight-entry:nth-child(5) { animation-delay: 0.22s; }
.cn-insights-grid[data-tilt-grid] .cn-insight-entry:nth-child(6) { animation-delay: 0.27s; }
.cn-insights-grid[data-tilt-grid] .cn-insight-entry:nth-child(n+7) { animation-delay: 0.32s; }

@keyframes cnCardIn {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ---- Responsive ---- */
@media (max-width: 640px) {
    .cn-ins-mast { padding-top: 24px; margin-bottom: 28px; }
    .cn-ins-mast-title { font-size: clamp(1.5rem, 7vw, 1.9rem); }
}

@media (prefers-reduced-motion: reduce) {
    .cn-ins-mast { animation: none; }
    .cn-insights-grid[data-tilt-grid] .cn-insight-entry { animation: none; }
}

/* ---- Featured lead: the first card, promoted to a full-width horizontal card.
   Pure CSS over the existing LinkedIn-post anatomy (cover + actor + commentary +
   unit) — no bespoke markup. Cover on the left, the post content centred on the
   right. It still tilts + opens the read modal like every other card. ---- */
.cn-insights-grid .cn-insight-feature {
    grid-column: 1 / -1;
    /* Spans the full (now 900px) content width, so its edges match the grid. */
    width: 100%;
    display: grid;
    /* Stacked, not side-by-side. The covers are 1.91:1 (the 1200x628 OG shape),
       and no side-by-side split of a 900px card can show that uncropped next to
       readable text — the old layout squeezed the art into a 1.26:1 box and
       object-fit:cover ate 34% of every featured image. Full width on top lets
       the artwork run at its own ratio, and the headline gets the whole measure
       underneath. */
    grid-template-columns: minmax(0, 1fr);
    /* Editorial order on the hero: quiet meta -> the TITLE as the headline ->
       the hook as a standfirst under it. (The grid cards keep the LinkedIn order;
       the hero leads with its headline, which is what makes it read as a hero.) */
    grid-template-areas:
        "cover"
        "actor"
        "unit"
        "commentary";
    grid-template-rows: auto auto auto auto;
    align-content: start;
}
/* Quiet, uncluttered meta — the orphaned avatar tile is dropped on the hero, so
   the format + date read as a clean kicker above the headline. */
.cn-insight-feature .cn-actor-avatar { display: none; }
.cn-insight-feature .cn-post-actor { grid-area: actor; padding: 26px 40px 0; }
.cn-insight-feature .cn-actor-id { flex-direction: row; align-items: baseline; gap: 10px; }
.cn-insight-feature .cn-actor-stamp { font-size: 0.7rem; }

/* 1.91:1 is the source ratio of the cover art (1200x628 / 2400x1256), so the
   image lands with no crop at all. */
.cn-insight-feature .cn-cover { grid-area: cover; aspect-ratio: 1.91 / 1; height: auto; min-height: 0; }

/* The TITLE is the headline of the hero, right under the meta. */
.cn-insight-feature .cn-post-unit {
    grid-area: unit;
    background: transparent;
    border-top: 0;
    padding: 12px 40px 0;
    flex: none;
}
.cn-insight-feature .cn-insight-entry-title {
    font-size: clamp(1.55rem, 2.5vw, 1.95rem);
    line-height: 1.16;
    letter-spacing: -0.025em;
    margin: 0;
}

/* The hook as a standfirst BELOW the headline. Shown in FULL — the featured is
   the hero and has room, so it drops the grid cards' 3-line clamp (which was
   truncating mid-sentence and leaving a stray "…" before the last word). */
.cn-insight-feature .cn-post-commentary {
    grid-area: commentary;
    display: block;
    -webkit-line-clamp: unset;
    overflow: visible;
    padding: 12px 40px 30px;
    margin: 0;
    min-height: 0;
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--cni-ink-soft);
}

/* Stack the featured on narrow screens (cover on top), like a full-width post. */
@media (max-width: 720px) {
    /* Same stacked order as the desktop hero (the featured card is stacked at
       every width now), just tighter padding. */
    .cn-insights-grid .cn-insight-feature {
        grid-template-columns: 1fr;
        grid-template-areas: "cover" "actor" "unit" "commentary";
        align-content: start;
    }
    .cn-insight-feature .cn-post-actor { padding: 14px 16px 10px; }
    .cn-insight-feature .cn-post-commentary { padding: 0 16px; }
    .cn-insight-feature .cn-post-unit { padding: 12px 16px 16px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   STICKY TOPNAV + MODE TOGGLE + SEARCH — 2026-07-23
   ═══════════════════════════════════════════════════════════════════════════ */

/* ---- Published/Drafts segmented toggle (dev only) ----
   A pill track with the current mode as a raised pill, the other as a link. */
.cn-ins-modetoggle {
    display: inline-flex;
    align-items: stretch;
    gap: 2px;
    height: 40px;                /* uniform with the search field + Follow chip */
    box-sizing: border-box;
    padding: 3px;
    border-radius: 999px;
    background: var(--cni-cover-bg);
    border: 1px solid var(--cni-border);
}
.cn-ins-modetoggle-opt {
    display: inline-flex;
    align-items: center;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1;
    padding: 0 15px;
    border-radius: 999px;
    color: var(--cni-muted);
    text-decoration: none;
    white-space: nowrap;
    transition: color 180ms var(--ins-ease), background 180ms var(--ins-ease), box-shadow 180ms var(--ins-ease);
}
.cn-ins-modetoggle-opt.is-active {
    background: var(--cni-card);
    color: var(--cni-indigo-deep);
    box-shadow: 0 1px 2px rgba(28, 26, 54, 0.09);
}
/* `a.` specificity so the site-wide link underline never applies (same trick as
   a.cn-ins-chip). The .cn-ins-modetoggle-opt rule alone loses to `a:not(.button)`. */
a.cn-ins-modetoggle-opt { cursor: pointer; text-decoration: none; }
a.cn-ins-modetoggle-opt:hover,
a.cn-ins-modetoggle-opt:focus-visible { color: var(--cni-ink); outline: none; text-decoration: none; }
a.cn-ins-modetoggle-opt:focus-visible { outline: 2px solid var(--cni-indigo-deep); outline-offset: 2px; }

/* ---- Compressed sticky topnav ----
   Fixed so it never occupies flow (no layout shift); hidden above the fold and
   slides in on scroll (hub.js toggles .is-stuck once the masthead scrolls past). */
.cn-ins-stickybar {
    position: fixed;
    top: 60px;              /* flush under the 60px fixed site nav */
    left: 0;
    right: 0;
    z-index: 5;             /* above cards, below the site nav */
    background: var(--cni-frost);
    -webkit-backdrop-filter: blur(12px) saturate(1.3);
    backdrop-filter: blur(12px) saturate(1.3);
    border-bottom: 1px solid var(--cni-border);
    transform: translateY(-101%);
    opacity: 0;
    pointer-events: none;
    transition: transform 320ms var(--ins-ease), opacity 240ms var(--ins-ease);
}
.cn-ins-stickybar.is-stuck {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
}
.cn-ins-stickybar-inner {
    max-width: 900px;
    margin: 0 auto;
    padding: 0 24px;
    height: 56px;
    display: flex;
    align-items: center;
    gap: 16px;
}
.cn-ins-stickybar-brand {
    flex-shrink: 0;
    font-family: var(--brand-font);
    font-weight: 500;
    font-size: 0.95rem;
    letter-spacing: -0.01em;
    color: var(--cni-title);
    white-space: nowrap;
}

/* ---- Search field ---- */
.cn-ins-search {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 9px;
    height: 40px;
    padding: 0 14px;
    border-radius: 999px;
    background: var(--cni-card);
    border: 1px solid var(--cni-border-strong);
    transition: border-color 180ms var(--ins-ease), box-shadow 180ms var(--ins-ease);
}
.cn-ins-search:focus-within {
    border-color: var(--cni-indigo);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--cni-indigo) 16%, transparent);
}
.cn-ins-search-icon { flex-shrink: 0; color: var(--cni-muted); }
.cn-ins-search-input {
    flex: 1;
    min-width: 0;
    border: 0;
    background: transparent;
    outline: none;
    font-family: var(--font-main, 'Satoshi', sans-serif);
    font-size: 0.82rem;
    color: var(--cni-ink);
}
.cn-ins-search-input::placeholder { color: var(--cni-muted); }
/* Kill the native search clear/decoration for a clean field. */
.cn-ins-search-input::-webkit-search-decoration,
.cn-ins-search-input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Empty state when a search matches nothing. */
.cn-ins-search-empty {
    grid-column: 1 / -1;
    padding: 40px 4px;
    text-align: center;
    color: var(--cni-muted);
    font-size: 0.9rem;
}

@media (max-width: 640px) {
    .cn-ins-stickybar-inner { padding: 0 16px; gap: 10px; height: 52px; }
    /* The brand name yields first on a phone so the search keeps room. */
    .cn-ins-stickybar-brand { display: none; }
}

/* The masthead search is a fixed-ish field in the action row (unlike the sticky
   bar's, which fills the space between the brand and the Follow button). */
.cn-ins-search--mast { flex: 0 1 300px; }
@media (max-width: 640px) {
    .cn-ins-search--mast { flex: 1 1 100%; order: -1; }   /* full-width, first, on a phone */
}
