/* ==========================================================================
   Connect 2026 - Reset, Base Type & Layout Shell                      T-03
   --------------------------------------------------------------------------
   OWNER: Yash. Layout primitives only.

   This file provides the SKELETON every page sits in:
     - a minimal reset
     - base typography bound to the tokens
     - the container
     - the two-column grid (content + ad rail) that ~every screen uses
     - full-bleed band pattern
     - section rhythm

   It deliberately contains NO components. Cards, chips, pills, nav and
   footer live in their own files - see CONNECT2026.md.

   DEPENDS ON: tokens.css (must be loaded first).
   ========================================================================== */

/* ============================================================== RESET ==== */

*, *::before, *::after { box-sizing: border-box; }

/*
   hidden MEANS HIDDEN. The browser's own [hidden] rule is display:none at the
   lowest priority, so any component that sets its own display - .cn-btn is
   inline-flex - wins over it and the element shows anyway. That is how the
   comment thread offered "Load more comments" under "No comments yet": the
   button carried hidden from the tag and the JS, and the pill class drew it
   regardless. One rule, above every component, so the attribute can be
   trusted everywhere.
*/
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--cn-bg);
  color: var(--cn-ink-body);
  font-family: var(--cn-font);
  font-size: var(--cn-text-body);
  font-weight: var(--cn-fw-medium);
  line-height: var(--cn-lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; }
img, picture, video, canvas, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
table { border-collapse: collapse; }

/* Visible focus for keyboard users only. Do not remove - T-45 depends on it. */
:focus-visible {
  outline: 3px solid var(--cn-cyan);
  outline-offset: 2px;
  border-radius: var(--cn-radius-sm);
}

/* Screen-reader-only helper, for icon-only controls and skip links. */
.cn-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.cn-skip-link {
  position: absolute; left: var(--cn-space-4); top: -100px;
  z-index: var(--cn-z-modal);
  background: var(--cn-navy); color: var(--cn-ink-invert);
  padding: var(--cn-space-3) var(--cn-space-5);
  border-radius: var(--cn-radius-md);
  transition: top var(--cn-dur) var(--cn-ease);
}
.cn-skip-link:focus { top: var(--cn-space-4); }

/* ========================================================== BASE TYPE ==== */

.cn-h1      { font-size: var(--cn-text-h1);      font-weight: var(--cn-fw-semibold); line-height: var(--cn-lh-tight); color: var(--cn-ink); }
.cn-h2      { font-size: var(--cn-text-h2);      font-weight: var(--cn-fw-medium);   line-height: var(--cn-lh-tight); color: var(--cn-ink); }
.cn-h3      { font-size: var(--cn-text-h3);      font-weight: var(--cn-fw-semibold); line-height: var(--cn-lh-tight); color: var(--cn-ink); }

/* The dominant heading on every screen: "Trending this week", "Latest
   Insights", "Content Library", "Report Library". Cyan, medium, 24px. */
.cn-section-title {
  font-size: var(--cn-text-section);
  font-weight: var(--cn-fw-medium);
  line-height: var(--cn-lh-tight);
  color: var(--cn-cyan);
  margin-bottom: var(--cn-space-5);
}

.cn-card-title {
  font-size: var(--cn-text-card);
  font-weight: var(--cn-fw-semibold);
  line-height: var(--cn-lh-snug);
  color: var(--cn-ink);
}

/* Headline-only row title - the single most repeated text style in the
   design (listings, Related Articles, Most Read, Connect Plus grid).

   MEASURED from artboard p2 listing rows: 10.0pt Poppins-Medium x 2.4 = 24px,
   line-height 12pt/10pt = 1.2. It is Medium (500), NOT SemiBold, and 24px,
   NOT 18px - an earlier guess in T-03 had both wrong. */
.cn-headline {
  font-size: var(--cn-text-section);
  font-weight: var(--cn-fw-medium);
  line-height: 1.2;
  color: var(--cn-ink);
}

.cn-body     { font-size: var(--cn-text-body); line-height: var(--cn-lh-body); color: var(--cn-ink-body); }
.cn-body + .cn-body { margin-top: var(--cn-space-4); }

.cn-meta     { font-size: var(--cn-text-meta); color: var(--cn-ink-muted); }
.cn-author   { font-size: var(--cn-text-author); color: var(--cn-cyan); }
.cn-author em { font-style: italic; color: var(--cn-ink-muted); }
.cn-author strong { font-weight: var(--cn-fw-semibold); color: var(--cn-ink); }

.cn-label {
  font-size: var(--cn-text-label);
  letter-spacing: var(--cn-tracking-label);
  text-transform: uppercase;
  color: var(--cn-ink-muted);
}

/* ========================================================== CONTAINER ==== */

/*
   --cn-container (1256px) is the CONTENT box measured on the artboards, not
   the outer box. box-sizing is border-box globally, so max-width must include
   the padding or the content ends up 48px narrower than designed - which
   cascades into the rail grid and makes the content column 696px instead of
   743px on every page.
*/
.cn-container {
  width: 100%;
  max-width: calc(var(--cn-container) + var(--cn-space-6) * 2);
  margin-inline: auto;
  padding-inline: var(--cn-space-6);
}

/*
   THE SIDE MARGIN IS A PROPORTION, NOT A PADDING.

   Measured on p2: the artboard is 1428.7px wide and content runs 86.4 ->
   1341.9, so the margin is 86.4px - 6.05% of the page. Add this container's
   own 24px padding and the OUTER box is 62.4 -> 1365.9, which is 91.24% of
   the page.

   Only the 1304px cap expressed that, so the proportion held at exactly one
   width. Below it the container simply filled the viewport and the margin
   collapsed to the 24px padding: 1.9% at 1280, against the 6.05% the artboard
   draws. That is why a smaller laptop looked cramped to the edges while the
   same page looked right on a larger screen.

   min() keeps whichever is smaller, so this is the proportion up to the point
   the cap takes over, and the cap above it.

   FROM 768 UP ONLY. On a phone the artboard's 6% would be 23px where the
   mobile rules deliberately use 16, and holding a percentage there would take
   50px off an already narrow column.
*/
@media (min-width: 768px) {
  .cn-container {
    width: min(91.24vw, calc(var(--cn-container) + var(--cn-space-6) * 2));
  }
}

/* Full-bleed coloured band that keeps its inner content on the grid.
   Used by the grey "Latest Videos & Audio" band and the lime newsletter band. */
.cn-band { width: 100%; }
.cn-band--grey { background: var(--cn-band-grey); }
.cn-band--navy { background: var(--cn-navy); color: var(--cn-ink-invert); }
.cn-band > .cn-container { padding-block: var(--cn-space-20); }

/*
   Bands that supply their own height - the navy nav bar is the case that
   raised this. .cn-band gives a section band 80px of breathing room, which is
   right for content bands and wrong for chrome, where .cn-nav already sets
   --cn-nav-h.

   Added because the alternative people reach for is an empty wrapper <div> to
   break the direct-child selector. That works and is invisible to the next
   reader, and it breaks silently the moment this rule changes.
*/
.cn-band--flush > .cn-container { padding-block: 0; }

/* =============================================================== GRID ==== */
/*
   The core layout. Measured from artboard p2/p3:
     content 743px | gutter 60px | rail 452px   (= 1256px container)

   The rail is a FIXED width, not fractional, because ad creative ships at
   fixed pixel dimensions - a fluid rail would letterbox every banner.
   minmax(0, 1fr) on the main column prevents long unbreakable strings
   (URLs, long titles) from blowing the grid out.
*/
/*
   THE RAIL YIELDS, THE CONTENT COLUMN DOES NOT.

   --cn-col-rail is the DESIGNED width, not a guaranteed one. The rail takes
   it whenever the container can spare it, and gives back whatever the content
   column needs below its floor:

       rail = clamp(300, container - <content column> - <gutter>, --cn-col-rail)

   where <content column> is the artboard's own width for that variant -
   --cn-col-main (743) for a listing, --cn-col-main-read (775) for the reader
   and Home. Those tokens stop being just "what the board draws" and become
   "what the column is never given less than".

   WHY THIS IS NOT A MEDIA QUERY. The rail used to be a flat 452px from 1280
   up, which is where every "3 cards became 2" report came from. Run it:

       viewport  container   content = container - 60 - 452
         1280      1120         608     3-up needs 730  ->  2 cards
         1366      1198         686                     ->  2 cards
         1429      1256         744                     ->  3 cards

   So the artboard's two-column split does not physically fit until 1429, and
   a 150px-wide band of laptops - the common ones - got the desktop layout in
   a container that could not hold it. The grid was doing the right thing with
   the wrong space.

   Moving the breakpoint to 1429 was the other option and it is worse: it
   throws the rail away on every 1280 and 1366 screen to protect a number.
   Letting the rail flex keeps the designed page on those screens instead -

       1280   rail 317 | content 743   3 cards
       1366   rail 396 | content 743   3 cards
       1429   rail 452 | content 744   3 cards   <- artboard, exact
       1920   rail 602 | content 996   3 cards

   and it is the SAME rule .cn-grid already follows: size from the space you
   actually have, not from a guess about the viewport. There is no new
   breakpoint literal here and no new width - 1429 comes out exact rather than
   being aimed at, because 743 + 60 + 452 IS 1255.

   AN EARLIER PASS FLOORED THE COLUMN AT 730 - 3 x 230 + 2 x 20, the plain
   3-up - and two grids still broke at 1280, because they do not use the plain
   gap:

       .cn-grid--3.cn-grid--divided   3 x 230 + 2 x 24 = 742   -> 2/1
       .cn-grid--4.cn-grid--equal     4 x 170 + 3 x 20 = 740   -> 3/3/2

   Chasing that with a bigger number would have been the same mistake twice.
   The column's floor is not any one grid's arithmetic - it is the width the
   BOARD gives the column, because every grid on the page was sized to fit
   there in the first place. Hold 743/775 and the whole set fits by
   construction.

   300px is the floor - the standard IAB skyscraper - and the rail reaches it
   at about 1247, comfortably below the 1279 stack. Ad creative is placed by
   aspect-ratio (see ad.css), so a narrower rail scales the unit rather than
   cropping it.
*/
.cn-grid-rail {
  display: grid;
  grid-template-columns:
    minmax(0, 1fr)
    clamp(300px, calc(100% - var(--cn-col-main) - var(--cn-gutter)), var(--cn-col-rail));
  column-gap: var(--cn-gutter);
  align-items: start;
}

/*
   Reader pages use a WIDER content column and a TIGHTER gutter than listings -
   771/32 rather than 743/60, both totalling 1256. Measured on p3/p5/p9.
   The action-pill row is exactly 771px, so it only fits on one line here.
*/
.cn-grid-rail--reader {
  grid-template-columns:
    minmax(0, 1fr)
    clamp(300px, calc(100% - var(--cn-col-main-read) - var(--cn-gutter-read)), var(--cn-col-rail));
  column-gap: var(--cn-gutter-read);
}

/*
   THE READER FILLS THE CONTAINER, exactly as a listing does.

   There is no width cap here on purpose, and three attempts at one are why.

   At the artboard width this question does not exist: 775 + 29 + 452 = 1256 is
   the container, so p3, p5 and p9 draw a reader running edge to edge. Every
   cap only ever changed what happens ABOVE 1429, and every one of them broke
   the alignment the boards do show.

     capping the prose      the title, pills, comments and gate ran to x=1151
                            while the paragraphs stopped at 888 - the article
                            ragged down its right edge
     capping the grid       tabs kept the full container while the content
     and centring it        below shifted right: 118px out at 1700, 206 at 1920
     capping the container  the nav has its own container in the header and
                            did not narrow with it, so the menu ran 1726 wide
                            from x=97 above content 1304 wide from x=308

   All three chased the same theory - that long lines need protecting - and
   all three cost the thing that is actually drawn: one left edge and one right
   edge, nav, tabs, content and rail parallel down the page. The board is the
   authority, and the board fills the container.

   So the reader inherits the base rule above: content takes the space, the
   rail scales with --cn-col-rail, and the two edges stay parallel with the
   menu at every width. A line does run long on a very wide monitor. That is
   the trade, taken deliberately, and it is reversible in one place if the
   client would rather have the margin than the alignment.
*/

/*
   Home reverses it: tall ad rail on the LEFT.

   It also uses the TIGHT gutter, not the 60px listing one. Measured on p1:
   the rail ends at x=224.75pt and the content starts at x=236.4pt, so the gap
   is 11.65pt = 28px, and the content column is 774 rather than 744. Inheriting
   the 60px gutter cost 30px of content width, which was enough to drop the
   experts grid from four columns to three.
*/
.cn-grid-rail--left {
  grid-template-columns:
    clamp(300px, calc(100% - var(--cn-col-main-read) - var(--cn-gutter-read)), var(--cn-col-rail))
    minmax(0, 1fr);
  column-gap: var(--cn-gutter-read);
}

/* Single-column pages (Membership intro, Events intro). */
.cn-grid-full { display: block; }

/*
   Generic n-up card grids. 3-up gap is the measured 20px.

   INTRINSIC, NOT BREAKPOINT-DRIVEN. Each grid responds to the width of ITS
   OWN CONTAINER, not to the viewport. That matters here: when the rail stacks
   at tablet the content column jumps from 743px to full width, and a viewport
   breakpoint cannot know that - it would show FEWER columns when there is
   MORE room.

   The pattern:
     minmax( max( floor, (100% - gaps) / N ), 1fr )

   The max() has two jobs. (100% - gaps)/N caps the grid at N columns however
   wide the container gets. The pixel floor forces a wrap to fewer columns
   once each card would be too narrow to read. So a 3-up is 3 across in the
   743px column, 2 across around 500px, and 1 below ~250px - with no media
   query anywhere.
*/
.cn-grid {
  display: grid;
  gap: var(--cn-grid-gap, var(--cn-space-5));
  align-items: start;
}

.cn-grid--2 { grid-template-columns: repeat(auto-fit, minmax(max(var(--cn-grid-floor, 280px), (100% - 1 * var(--cn-grid-gap, var(--cn-space-5))) / 2), 1fr)); }
.cn-grid--3 { grid-template-columns: repeat(auto-fit, minmax(max(var(--cn-grid-floor, 230px), (100% - 2 * var(--cn-grid-gap, var(--cn-space-5))) / 3), 1fr)); }
/*
   4-up floor is 170, not 180. The narrowest column this grid has to survive is
   Home's 775px content column: (775 - 3x20) / 4 = 178.75px per card. A 180px
   floor is too tall to fit and silently drops it to three columns, which is
   exactly what happened to Connect Industry Experts. The floor also has to
   clear the --media gap: (1256 - 3x30) / 4 = 291.5, which is comfortable.
*/
.cn-grid--4 { grid-template-columns: repeat(auto-fit, minmax(max(var(--cn-grid-floor, 170px), (100% - 3 * var(--cn-grid-gap, var(--cn-space-5))) / 4), 1fr)); }
.cn-grid--5 { grid-template-columns: repeat(auto-fit, minmax(max(var(--cn-grid-floor, 150px), (100% - 4 * var(--cn-grid-gap, var(--cn-space-5))) / 5), 1fr)); }
/* Six event tiles in ONE row at the readers' foot (p3 draws the row of small
   tiles; asked for six of them, 17 Sep). (1256 - 5x20) / 6 = 192px each at
   1440; the 150 floor lets 1280's 1120 column keep all six, and below that
   auto-fit wraps them 3 / 3 rather than leaving a lone tile. */
.cn-grid--6 { grid-template-columns: repeat(auto-fit, minmax(max(var(--cn-grid-floor, 150px), (100% - 5 * var(--cn-grid-gap, var(--cn-space-5))) / 6), 1fr)); }

/*
   Home "Latest Insights": three headline columns separated by vertical rules.

   Sets --cn-grid-gap rather than column-gap directly. The track sizing above
   subtracts the gap to work out the column cap, so overriding column-gap on
   its own puts the two out of step - the calc assumes 20px while the browser
   lays out 24px, and a 3-up silently collapses to 2 columns. Always override
   the variable, never the property.
*/
/*
   Two grids whose tile size is FIXED by the artboard, so the gap takes up the
   slack instead of the card. Both set --cn-grid-gap, never column-gap - see
   the note on .cn-grid--divided below for why that distinction bites.

     --covers  research report row. Cover is 228 wide at the A-paper ratio
               (228/322.5 = 0.707 = 1/sq2), so it cannot flex; 3 x 228 plus
               2 x 45 fills the 775 content column exactly.
     --media   4-up video/audio row across the full 1256 container.
               4 x 292 plus 3 x 30 = 1256.

   Measured on p1: covers at x=567.2 / 840.7 / 1114.2, media at x=85.7 /
   407.7 / 729.8 / 1050.4. Both were rendering at the generic 20px gap, which
   made the covers 16px too wide and the media cards 7px too wide.
*/
/*
   The floor MUST come down with the gap. A wider gap leaves less for each
   track, so the generic 230px 3-up floor stops fitting: (775 - 2x45) / 3 =
   228.3, which is under 230, and auto-fit silently drops to TWO 365px
   columns. That is the same trap the --4 floor note describes, reached from
   the other direction - it is the GAP that moved, not the container.
   190px keeps three covers across until the rail stacks.
*/
.cn-grid--covers { --cn-grid-gap: var(--cn-gap-covers); --cn-grid-floor: 190px; }

/*
   Equal-height tracks. .cn-grid is align-items:start so a card is only as
   tall as its own content, which is right for cards that sit on the page -
   but wrong for a grid of BOXES, where a two-line job title makes one tile
   24px taller than the one beside it. Home's expert grid came out
   223/223/223/199/199/223/199/199.

   Stretch the track and let the card fill it, rather than truncating the
   role, which is real information.
*/
.cn-grid--equal { align-items: stretch; }
.cn-grid--equal > * { height: 100%; }
.cn-grid--media  { --cn-grid-gap: var(--cn-gap-media); }

.cn-grid--divided { --cn-grid-gap: var(--cn-space-6); }
/*
   STRETCH, so the dividers are one height and the closing rule is one line.

   The grid inherits align-items:start, which is right almost everywhere and
   wrong here: with four cells of four different heights the vertical rules
   below ended at four different depths and the hairline under each cell
   stepped down the row. p16 draws the four Related Webinars columns with
   full-height dividers and a single straight line closing them, which is what
   stretch gives - every cell as tall as the tallest, so both the border-left
   and the border-bottom land where the board puts them.
*/
.cn-grid--divided { align-items: stretch; }

.cn-grid--divided > * + * {
  /* --border-mid, not --border: the hairline between two rows of text at
     #d8dade was invisible on a white page (reported on Related Webinars). */
  border-left: 1px solid var(--cn-border-mid);
  padding-left: var(--cn-space-6);
}

/*
   AND THE CELLS START LEVEL. .cn-headline-row is built for a vertical list -
   20px of top padding, and none on :first-child so the first row sits tight
   under its heading. In a row of columns that exception applies to the LEFT
   column only, so column one sat 20px above its three neighbours. Measured on
   the webinar reader and reported as "related webinars content display is
   wrong". Every column starts level; the heading above owns the gap.
*/
/*
   NO RULE UNDER THE COLUMNS EITHER. .cn-headline-row closes itself with a
   hairline because in a vertical list that is what separates it from the next
   row. In a row of columns the separator is the VERTICAL rule beside it, and
   p16 draws no horizontal line under the four Related Webinars columns at all
   - rendered at 6x, the block ends with the last line of text.

   Making the cells equal height had turned four ragged hairlines into one
   straight one, which looked deliberate and was still a line the design does
   not have.
*/
.cn-grid--divided > .cn-headline-row {
  padding-top: 0;
  border-bottom: 0;
}


/*
   One-line intro under a section heading. p1 uses it once, under Connect
   Industry Experts at y=2415: 16.7px in the muted grey, sitting between the
   24px heading and the content below it.
*/
.cn-section-sub {
  margin: calc(var(--cn-space-4) * -1) 0 var(--cn-space-6);
  font-size: var(--cn-text-meta);
  font-weight: var(--cn-fw-medium);
  color: var(--cn-ink-muted);
}

/* ====================================================== TAP TARGETS === */
/*
   THE 44px FLOOR IS ABOUT THE INPUT DEVICE, NOT THE SCREEN WIDTH.

   Every component raised its controls to 44px inside `max-width: 767px`, and
   button.css stated the consequence outright: "buttons keep their 34px height
   at tablet". 768px is iPad portrait. Measured on the Article reader at 768,
   seven controls came back under the floor - the back link at 21px, all five
   action pills at 36px, and a primary button at 34px - on a device that has
   no pointer at all.

   `pointer: coarse` asks the right question: is the PRIMARY input a finger?
   That is true on phones and tablets at any width, and false for a mouse user
   who has dragged their desktop window down to 700px - who does not need, and
   should not get, chunky controls.

   Kept as one block rather than repeated per component. It is a single
   accessibility floor that cuts across the library, so "where is the tap
   target rule?" should have one answer.

   The width-based rules in the component files still stand. They do more than
   height - padding, font size, layout - and they are correct for narrow
   screens whatever the input device. This only guarantees the floor.
*/
@media (pointer: coarse) {
  /*
     Pagination was the one control this block missed. Verified on a touch
     viewport: every other control cleared 44px and the pager sat at 34,
     which is the smallest target on the page and also the one that moves
     you between pages.
  */
  /*
     TWO COMPONENTS SET THEIR OWN FLOOR INSTEAD, and it is not a style choice.

     shell.css is the 4th stylesheet loaded; footer.css is 14th and
     listing.css 21st. Both set min-height on these same selectors at equal
     specificity, so a rule written here LOSES the cascade to them - silently.
     Pagination stayed 34px and footer links 24px on a touch device even
     though this block named them.

     So the pagination and footer floors live at the end of their own files,
     after the rules they have to beat:
         components/listing.css   .cn-pagination__page / __step
         components/footer.css    .cn-footer__links a / __social a / __back
  */
  .cn-action,
  .cn-btn,
  .cn-btn--sm,
  .cn-tabs__tab,
  .cn-tabs__link,
  .cn-chip,
  .cn-back-link,
  .cn-fce__name,
  .cn-speaker-bio__link {
    min-height: 44px;
  }

  /* Text-only controls need the hit area to grow around the text, not the
     text to move. */
  .cn-back-link,
  .cn-tabs__link,
  .cn-fce__name,
  .cn-speaker-bio__link {
    display: inline-flex;
    align-items: center;
  }
}

/* ============================================================ SECTIONS === */

.cn-section        { margin-block: var(--cn-space-20); }
.cn-section--tight { margin-block: var(--cn-space-12); }

/*
   THE CYAN RULE ABOVE THE EVENTS STRIP.

   p3 and p9 both draw a full-container rule immediately above "Events you may
   be interested in":

       y=1213.7  x=36.0  523.3pt (1256px)  rgb(28,183,252)
       y=1230.9  the heading

   Same accent as the one on the reader body's top edge, and the same 2px - it
   is the design's one rule colour, not a second grey hairline. 17.2pt between
   rule and heading is the 40px step.

   A MODIFIER, because .cn-section is on thirteen blocks across the listings,
   Magazine and Membership, and only the readers' events strip is ruled.

   Credit where due: this came out of Sachin's In-depth branch, which drew it
   as a page-level div. The observation was right and both readers were missing
   it - article.jsp included, which is ours.
*/
.cn-section--ruled {
  border-top: 2px solid var(--cn-cyan);
  padding-top: var(--cn-space-10);
}
.cn-section:first-child { margin-top: var(--cn-space-12); }

.cn-rule { height: 1px; background: var(--cn-border); border: 0; margin-block: var(--cn-space-6); }

/* Sticky rail. Offset by nav height so it never slides under the nav bar.
   Only sticks when the rail is genuinely shorter than the content column. */
.cn-sticky {
  position: sticky;
  top: calc(var(--cn-nav-h) + var(--cn-space-5));
  z-index: var(--cn-z-sticky);
}

/* ======================================================== PLACEHOLDERS === */
/*
   TEMPORARY. These render the artboards' lime "Advert space" and black
   "stock image" blocks so pages can be reviewed before real assets and the
   ad component (T-10) exist.

   Every one of these must be gone before ship.
   Check with:  grep -rn "cn-ph-" src/main/webapp/WEB-INF/views/connect2026/
*/
.cn-ph {
  display: grid;
  place-items: center;
  text-align: center;
  font-weight: var(--cn-fw-semibold);
  font-size: var(--cn-text-card);
  color: var(--cn-ink-invert);
  padding: var(--cn-space-6);
}
.cn-ph--ad  { background: var(--cn-ph-ad); color: var(--cn-ink-invert); }
.cn-ph--img { background: var(--cn-ph-img); }
.cn-ph--grey{ background: var(--cn-panel-grey); }
.cn-ph--blue{ background: var(--cn-panel-blue); color: var(--cn-cyan); }

/* Common placeholder heights, measured off the artboards. */
.cn-ph--rail-tall { min-height: 1358px; }  /* 565.7pt skyscraper */
.cn-ph--rail-half { min-height: 679px;  }  /* 282.9pt half advert */
.cn-ph--hero      { aspect-ratio: 16 / 9; } /* 309.8 x 174.4pt     */

/* ========================================================= RESPONSIVE ==== */
/*
   THE T-40 SPEC - see CONNECT2026.md section 4.7.

   Note how LITTLE is left here. Type and spacing scale continuously via
   clamp() in tokens.css, and the card grids size themselves from their own
   container. So the only things that need a breakpoint are genuine STRUCTURAL
   changes - the rail stacking and the two disclosure behaviours.

     tablet  max-width: 1279px   rail drops below the content column
     portrait 768-863px          card counts pinned so rows stay full
     mobile  max-width:  767px   single column, denser rhythm

   BEFORE it stacks, the rail FLEXES - see the long note on .cn-grid-rail.
   That is deliberately not a breakpoint: 1280-1429 is a real band of laptops
   where the artboard's 743|60|452 split does not fit, and the fix is for the
   rail to give width back rather than for a media query to guess. The three
   rules below run only once the rail has nothing left to give.

   Use only those two literals. Custom properties cannot be used in media
   query conditions, so consistency is by discipline.

   No mobile artboards were supplied. These decisions are ours, reasoned
   rather than authored, and need designer sign-off.
*/

/* ------------------------------------------------------------ TABLET ---- */
@media (max-width: 1279px) {

  /* Rail moves below the content column, full width. */
  .cn-grid-rail,
  .cn-grid-rail--left {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--cn-space-12);
  }

  /* The left-rail variant reorders so the advert is not the first thing on
     the page once stacked. */
  .cn-grid-rail--left > :first-child { order: 2; }

  /* Sticky only means anything beside a scrolling column. */
  .cn-sticky { position: static; }

  .cn-ph--rail-tall { min-height: 400px; }
  .cn-ph--rail-half { min-height: 300px; }

  /*
     THE TWO WIDE GAPS ARE SLACK ONCE THE RAIL STACKS.

     --covers (45) and --media (30) were measured against the 1256 container,
     where the tile size is fixed by the board and the gap takes up the rest.
     Stacked, the container is smaller and the gap is just width the cards are
     not getting - and worse, it makes each grid drop a column at a DIFFERENT
     point:

         .cn-grid--4          4 x 170 + 3 x 20 = 740   ->  crosses at 863
         .cn-grid--4.--media  4 x 170 + 3 x 30 = 770   ->  crosses at 896
         .cn-grid--3          3 x 230 + 2 x 20 = 730   ->  crosses at 853
         .cn-grid--3.--covers 3 x 190 + 2 x 45 = 660   ->  crosses at 776

     That spread is why --media sat at 3/1 for a 33px band above everything
     else. Normalising the gaps collapses the four crossings onto two, so the
     one portrait block below covers the whole set instead of each variant
     needing its own bound.
  */
  .cn-grid--covers,
  .cn-grid--media { --cn-grid-gap: var(--cn-space-5); }
}

/* -------------------------------------------------- TABLET PORTRAIT ---- */
/*
   THE ONLY RUNG WHERE THE INTRINSIC LADDER LEAVES A RAGGED ROW.

   Once the rail stacks, the card grids get the whole container - and between
   768 and 863 that container is 653-739px, which is wide enough for three
   small cards but not four. auto-fit then picks a count that does not divide
   the number of cards the design puts there:

       .cn-grid--divided   n=3   ->  2 / 1     a lone headline, and the
                                               vertical rule beside it has
                                               nothing left to divide
       .cn-grid--covers    n=3   ->  3         only because the stacked block
                                               above already gave it back the
                                               45px gap; at the measured gap it
                                               missed 3-up by TWO pixels.
       .cn-grid--4 equal   n=8   ->  3 / 3 / 2
       .cn-grid--4 media   n=4   ->  3 / 1

   These counts are fixed by the design, not by data, so the fix is to pin the
   column count to something that divides them rather than to keep pushing the
   floors around - which is what produced the 2px miss above.

   BOTH EDGES ARE DERIVED, not chosen. 768 is where the mobile single column
   ends. 863 is where 4-up starts fitting on its own (4 x 170 + 3 x 20 = 740,
   and 0.9124 x 863 - 48 = 739), so from 864 the ladder is clean without help
   and this block must not reach it.
*/
/*
   863.9 AND NOT 863 - THE FRACTION IS LOAD-BEARING.

   4-up starts fitting at 0.9124V - 48 >= 740, i.e. V >= 863.44, so 864 is the
   first integer viewport that does not need this block and 863 is the last
   that does. But a viewport is not always an integer: at devicePixelRatio
   1.25 a nominal 863 measures 863.2, and at browser zoom it can land anywhere
   in between. `max-width: 863px` then matches NEITHER this block nor the
   intrinsic 4-up, and the experts grid falls back to 3/3/2 in a one-pixel
   dead zone.

   Ending at 863.9 makes the two bands OVERLAP rather than abut. Anything the
   overlap catches is forced to 2-up where 4-up would also have worked - four
   full rows either way, never a ragged one. Erring into the safe layout is
   the point.
*/
@media (min-width: 768px) and (max-width: 863.9px) {

  /*
     A 3-up STAYS a 3-up. Every grid on this ladder carries either 3 or 6
     cards, both of which divide by three and neither of which divides by two:
     the listing rows are 3, Home's insights and covers are 3, Home's event
     grid is 6. Letting auto-fit pick 2 gives 2/1 and 2/2/2 respectively, and
     the 2/1 is the exact shape the client reported - a row of three with the
     third pushed down and a hole beside it.

     Three narrow columns still read as the three-column block the board
     draws. Two plus a widow does not.
  */
  .cn-grid--3,
  .cn-grid--divided,
  .cn-grid--covers { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* n is 4 or 8; 2 divides both, 3 divides neither. */
  .cn-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------------------------------------------------ MOBILE ---- */
@media (max-width: 767px) {

  /* Must sit on a selector - a bare custom property inside a media query is
     invalid and gets dropped silently. */
  :root { --cn-grid-gap: var(--cn-space-4); }

  .cn-container { padding-inline: var(--cn-space-4); }

  /* Vertical rules read wrong stacked - they would sit above and below. */
  .cn-grid--divided { grid-template-columns: minmax(0, 1fr); }
  .cn-grid--divided > * + * { border-left: 0; padding-left: 0; }

  /*
     COVERS STAY 2-UP. cover.css has said so since T-40 ("one per row wastes
     most of the screen and pushes the rest of the page below the fold") and
     pointed here for the rule - which was never written, so a 343px column
     drew each A4 cover 485px tall, one under the other. Home's reports row,
     the magazine archive and the Research library all take this.
  */
  .cn-grid--covers { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------------------------------------- VERY SMALL SCREENS --- */
/*
   Below ~360px - small phones, folded devices, watch browsers. Nothing here
   is a layout change, only breathing room: the container padding halves so
   content is not squeezed into a ribbon.
*/
@media (max-width: 359px) {
  .cn-container { padding-inline: var(--cn-space-3); }
  .cn-grid { gap: var(--cn-space-3); }
}

/* ========================================================== UTILITIES ==== */
/*
   A deliberately SHORT list. These exist so page files never need an inline
   style for simple vertical rhythm. If you find yourself wanting a utility
   that isn't here, you probably want a component instead - ask first.
*/
.cn-mt-2  { margin-top: var(--cn-space-2); }
.cn-mt-4  { margin-top: var(--cn-space-4); }
.cn-mt-5  { margin-top: var(--cn-space-5); }
.cn-mt-6  { margin-top: var(--cn-space-6); }
.cn-mt-8  { margin-top: var(--cn-space-8); }
.cn-mt-10 { margin-top: var(--cn-space-10); }   /* p1: five section gaps measure 40-44 */
.cn-mt-12 { margin-top: var(--cn-space-12); }
.cn-mt-20 { margin-top: var(--cn-space-20); }

/*
   Centres the block's inline content. Earns a utility because three boards
   draw the same centred CTA and none of them is a component of its own:

     p1  "View all of our upcoming events"  centre 397.7 vs column 397.65
     p3  same button                        centre 297.6 vs column 297.7
     p9  same button                        centre 297.6 vs column 297.7

   All three are the 136.1pt pill under the events strip.
*/
.cn-center { text-align: center; }

.cn-list-plain { list-style: none; }

/* Inline text link. The design underlines links inside body copy only. */
.cn-link { color: var(--cn-cyan); text-decoration: underline; }
.cn-link:hover { color: var(--cn-fam-cyan-ink); }

/* Muted inverse text, for use on the navy band. */
.cn-on-navy { color: var(--cn-ink-invert); }
.cn-on-navy--muted { color: var(--cn-ink-invert); opacity: .75; }

/* Scaffold-only: footer placeholder padding. Removed by T-08. */
.cn-footer-stub { padding-block: var(--cn-space-10); }

/* ------------------------------------------------- first block of a page -- */
/*
   THE AIR BETWEEN THE NAV BAR AND THE FIRST HEADING.

   Every Content Library screen gets this from .cn-section-title--library,
   which opens them. The screens that do NOT open with the library tabs -
   Home, Connect Magazine, and the other top-level sections - had nothing, so
   their first heading sat hard against the nav bar.

   51px, and it is not a guess: measured on three artboards, all agreeing.

     p1  Home             nav bar bottom y=111, "Trending this week" y=132
     p4  News             nav bar bottom y=111, "Content Library"    y=132
     p10 Connect Magazine nav bar bottom y=107, "Latest Magazine"    y=128

   Same value and same mobile step as .cn-section-title--library, so the two
   ways into a page cannot drift apart.
*/
.cn-page-start { margin-top: 51px; }

@media (max-width: 767px) {
  .cn-page-start { margin-top: var(--cn-space-8); }
}

/* ============================================ ALT TEXT WHILE LOADING === */
/*
   Every picture on the site is lazy and carries an alt. While one loads
   (or before it scrolls into view) the browser draws that alt in the box in
   the page's ink - "Connect Magazine June Edition 2026 cover" flashing
   through a cover on a slow connection, the tail of it peeking out under a
   half-painted image. Transparent ink on the image elements keeps the alt
   for screen readers and shows the box's own background until the picture
   lands. Broken images still remove themselves where the tags do that.
*/
.cn-cover__art,
.cn-card__image,
.cn-reader__hero-img,
.cn-event-tile__banner,
.cn-event-thumb__img,
.cn-ad__img,
.cn-video__poster,
.cn-person__avatar {
  color: transparent;
  font-size: 0;
}
