/* ==========================================================================
   content-hub.css - the field-notes band on index.html.

   ADDITIVE. This sheet plus js/content-hub.js and the <section class="hub"> in
   the markup are one deletable unit. It loads AFTER css/insights.css, which
   supplies the note card itself: the band declares its own three-column shell
   and nothing else about how a card looks, so the card on the landing page and
   the card on insights.html cannot drift.

   WHY THE BAND IS HERE, between the constellation and the closer. The page
   already argues in order: who they are (the directory), what they add up to
   (the constellation), and then this - what they know. Intelligence is the last
   piece of evidence before the ask, and the ask immediately follows it. It is
   NOT above the constellation: the constellation is the directory's own
   abstraction and splitting the two breaks that argument.

   The one thing this costs, handled below: network-graph.css declares
   `.netsec + .closer .wrap{padding-top:var(--s14)}`, and the band now sits
   between them, so that adjacency no longer matches. The rule is re-made for
   `.hub + .closer` at the foot of this file rather than edited in the shared
   sheet.

   THE THIRD COLUMN. The reference layouts put a live trending rail here. This
   site cannot honestly show search volumes it does not measure, so the rail
   shows the one live thing it genuinely has: the working day, right now, across
   all ten cities, computed from the reader's own clock. It is the page's
   headline claim - one continuous day from Karachi to Auckland - stated as a
   fact that is true at the moment it is read rather than as a sentence in a
   footer.
   ========================================================================== */

/* WHITE, and the border is gone with the tint. The band sat on --offwhite
   directly below .netsec, which is also --offwhite - the two ran together, so a
   1px --line rule was drawn across the top to separate them. Flipping the band
   to --surface separates them with a tone change instead, which is the job a
   surface is for. */
.hub{background:var(--surface)}
.hub{padding:var(--sec) 0}
.hub .wrap{padding-top:0;padding-bottom:0}

/* --- head ---------------------------------------------------------------- */
/* CENTRED, to match the page rather than to decorate the band. Measured across
   index.html at 1440: .directory-head, the constellation head and .closer-box
   all compute text-align:center, and this was the only content section still
   ranged left - so left was the exception here, not the convention.

   Block and not flex. The head used to be a flex row because it held the market
   select and the way-out link on its right edge; with the select gone there is
   one action left, and a two-item space-between holding a heading block against
   a single button is what made the ranged-left version necessary. The button
   drops below the deck instead, which is also the stronger place for it - it
   reads as the section's next step rather than as a utility parked in the
   corner. Same shape .directory-head uses two sections above. */
.hub-head{display:block;text-align:center;margin-bottom:var(--s10)}
/* The head is two blocks: what this band is on the left, the way out of it on
   the right, sharing one baseline via align-items:flex-end above.

   A "Reading from" market select used to sit beside that link inside a
   .hub-head-acts flex wrapper, with a visually-hidden .hub-status announcing the
   reorder to screen readers. Both are gone with the control - see the note in
   index.html. The wrapper went too: two children is what justify-content
   :space-between already does, and a flex box around a single link is a
   container that exists only to be read in a stylesheet. */
.hub-head .eyebrow{margin-bottom:var(--s3)}
/* The measures stay - centring a heading is not licence to let it run the full
   1360px grid. auto margins rather than text-align alone, so the CAP is what
   centres the block and the ragged edge stays symmetrical. */
.hub-head h2{max-width:22ch;margin-inline:auto}
/* DELIBERATELY NOT text-wrap:balance, which is the site's default move on
   headings (.pcard-t, the display sizes) and is wrong here.

   The deck is two short declarative sentences. At this measure the first is
   554px and the box is 608, so left alone the break falls on the full stop and
   each line is one whole sentence:

     Published by the members themselves, from the markets they work in.
     No regional summary, no house byline.

   Balance rejects that split because it minimises the LONGEST line: it prefers
   451/404 to 554/301, and buys those evener lines by breaking mid-phrase, at
   "from the markets / they work in". Evenness is the right goal for a headline,
   where the words are one phrase. It is the wrong goal for a deck made of
   separate statements, where the sentence boundary is the natural break and
   throwing it away to save 100px of raggedness costs more than it saves.

   If the copy is ever rewritten past 554px the break moves and this reasoning
   should be re-checked; the measure is 52ch and the first sentence has about
   50px of headroom. */
.hub-deck{
  margin:var(--s4) auto 0;max-width:52ch;
  color:var(--gray);font-size:17px;line-height:26px;
}
.hub-head .btn{margin-top:var(--s6)}
/* The way out is .btn.btn-fill.btn-primary from base.css - the shared button,
   not a local one. There is no .hub-all rule here any more and there should not
   be one again.

   What was here was a hand-drawn text link: colour var(--purple), 16px/500, a
   2px bottom pad and an inset box-shadow underline on hover. Every one of those
   values already existed as .btn-ghost in base.css, down to an identical hover
   line - so the band was carrying a second copy of a component the system
   already shipped, free to drift the moment either was touched. Deleted rather
   than re-pointed: the fix for a duplicate is one definition, not two that
   agree today. */

/* --- the three columns ---------------------------------------------------
   Measured, not chosen. The rail needs 300 to hold "New Zealand" beside a time
   without wrapping; the list needs about 360 before its two-line titles start
   breaking after one word; the lead takes what is left and stops being a lead
   below about 420. Those three floors add up to 1080 plus two 40px gutters,
   which is the width the first collapse happens at. */
.hub-grid{
  display:grid;
  grid-template-columns:minmax(0,1.25fr) minmax(0,1fr) 320px;
  gap:var(--s10);align-items:start;
}
/* Set by content-hub.js when every dated signal has passed and the rail has
   removed itself. Two columns, and the lead keeps its lead. */
.hub-grid.is-no-rail{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr)}

@media (max-width:1180px){
  /* The rail drops under the two content columns first. It is the supporting
     act of the three and it is the one that reads fine at full width. */
  .hub-grid{grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);}
  .hub-rail{grid-column:1 / -1}
  /* Full width, so the five signals lay out across the track instead of
     stacking down a column that is no longer narrow. The rule that removes the
     last row's border is switched off here - in a grid, "last child" is one
     cell of the bottom row, not the whole row - and every cell takes a top
     border instead, which is the one edge that is correct in both layouts. */
  .hub-rail-list{
    display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));
    gap:0 var(--s8);
  }
  /* .hub-watch's own overrides for this layout are NOT here - they sit after the
     base rule further down the sheet, because they lost to it from up here. See
     the note beside them. */
}
@media (max-width:820px){
  .hub-grid{grid-template-columns:1fr;gap:var(--s10)}
}

/* --- column 1: the lead --------------------------------------------------
   The note card from insights.css at its wide crop. No second template. */

/* THE ONE CARD THAT KEEPS ITS FILL. insights.css dropped .pcard's --haze on
   2026-09-02 because it was costing 48px of measure to draw a surface three
   steps off white - see the long note there. That argument does not hold in
   this band. Everywhere else the card is one of a grid of identical cards, and
   what separates them is the gutter. Here it is the ONLY card, standing beside
   a column of text rows and a filled rail, and the fill is what says "this one
   first". Unfilled it flattens into a third column of type and the band loses
   its focal point.

   Reinstated in full - fill, radius, clip, hover and lift - because the lift
   only reads as a response when there is a box to lift. The media radius is
   reset to 0: the card clips it again here, exactly as it did before. */
.hub-lead .pcard{
  background:var(--haze);border-radius:var(--r-lg);overflow:hidden;
  transition:background-color 200ms ease-in-out,box-shadow 200ms ease-in-out,
             transform 200ms cubic-bezier(.2,.7,.3,1);
}
.hub-lead .pcard:hover,.hub-lead .pcard:focus-visible{
  background:var(--mist);box-shadow:var(--e-high);transform:translateY(-2px);
}
.hub-lead .pcard-media{border-radius:0}

.hub-lead .pcard-t{font-size:24px;line-height:32px}
.hub-lead .pcard-dek{font-size:16px;line-height:25px;-webkit-line-clamp:4}
.hub-lead .pcard-body{padding:var(--s7)}

/* --- column 2: the recent list ------------------------------------------- */
.hub-list{display:flex;flex-direction:column}

.hub-row{
  display:grid;grid-template-columns:minmax(0,1fr) 96px;
  gap:var(--s5);align-items:start;
  padding:var(--s5) 0;
  border-top:1px solid var(--line);
  text-decoration:none;
  /* base.css balances bare `a` and text-wrap inherits, so a row-sized link was
     balancing its byline and date along with its title. .hub-row-t declares
     balance for itself and keeps it. See the long note beside .pcard in
     insights.css. */
  text-wrap:pretty;
}
/* The first row's rule is the one that would sit directly under the section
   head, where it reads as a divider the section did not ask for. */
.hub-row:first-child{border-top:0;padding-top:0}

.hub-row-k{
  display:block;margin-bottom:var(--s2);
  color:var(--ac,var(--purple));font-size:11px;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;
}
/* TWO LINES, CLAMPED AND RESERVED, exactly as .pcard-t is on the card - and
   for a sharper reason here, because these four rows are a COLUMN and their
   heights add up.

   Left free they ran 117, 190, 164 and 216px, so the middle column was 687
   against a 629 lead, and at 1280 - where the titles wrap further - 791 against
   592. That is the balance rule below broken by 56px, and it is broken by the
   LIST, not by the rail: the rail is a fixed 698 at every width.

   Uniform rows fix both complaints at once. The four rows stop being four
   different sizes, and the column stops overrunning the two beside it.

   The reserve matters more than the clamp. Without min-height a one-line title
   leaves its row short and the ragged internal rhythm survives even though the
   column total is fine.

   THE RESERVE IS ON THE COLUMN, NOT ON THE TITLE. It sat on .hub-row-t until
   2026-09-03, and that put the spare line INSIDE the title box: a one-line
   headline - "The SaaS Source - August 2026" is one at 1440 and 1600, and wraps
   only from 1280 down - left 26px of nothing between itself and its own byline, so "Custom Media ·
   31 Aug 2026" floated free of the thing it credits. The eyebrow sits 8px off
   its title; the byline sat 34px off. Same measurement, four times the gap, and
   the reader reads it as a credit belonging to nothing.

   Moving the same 122px to the text column keeps every number the rule below
   was tuned against - rows still 142/163/164/163, list still 632 - and puts the
   slack after the byline instead of before it, where it is one row's slightly
   deeper approach to a divider rather than a hole in the middle of a name. */
.hub-row-t{
  display:block;color:var(--black);font-size:18px;font-weight:500;line-height:26px;
  text-wrap:balance;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  transition:color 180ms ease-in-out;
}
/* 27 eyebrow + 8 + 52 (2 x 26px title) + 8 + 27 byline. Measured, not derived:
   change the type here and re-measure rather than trusting the sum. */
.hub-row-txt{display:block;min-height:122px}
/* THE HOVER HAD TO BE FELT, and --plum could not do it.

   The rest state is --black. Measured in Lab, black to --plum is dL* 20 and
   dE 48 - a real number that reads, at 18px/500 on a dark title, as "slightly
   less black". --purple-deep is dL* 37 and dE 118, two and a half times the
   shift, and base.css names it "focus and hover" for exactly this.

   NOT BLUE, though it was asked for and the palette does contain blues. Every
   one of them already means something: --d-digital #2F6BE3 IS the discipline
   colour for Digital & performance, and this row prints a discipline in its own
   eyebrow. A blue title over a "B2B & market entry" eyebrow would have the card
   making two contradictory claims at once. --globe-apac is the map's. There is
   no unclaimed blue here, and the hover does not need one.

   THE UNDERLINE IS THE OTHER HALF, and probably the more important one. Colour
   alone asks the reader to remember what the title looked like a moment ago;
   an underline appearing is a change they can see without a reference. It also
   says "link", which is the actual message - the only other cue this row had
   was a 96px thumbnail scaling 6%, easy to miss beside a two-line headline.

   Offset 3px so it clears the descenders on "gy", "p" and "y", which a
   default-position underline cuts straight through. */
/* :focus-visible rides with :hover, the pairing .pcard uses in insights.css.
   base.css draws a focus ring on the row either way, so this is not the
   accessible minimum - it is the keyboard user getting the same read of WHICH
   title they are on that the mouse user gets, rather than a ring around a row
   whose text has not moved. */
.hub-row:hover .hub-row-t,
.hub-row:focus-visible .hub-row-t{color:var(--blue-deep)}
/* The underline is .u-sweep's, wrapped around the title text in
   js/content-hub.js - the same background-gradient rule the footer links and
   .link use, so every underline on the site enters from the left and leaves to
   the right. It replaced a static text-decoration here, which appeared all at
   once and was the one underline treatment on the site that could not move. */
.hub-row:hover .u-sweep,
.hub-row:focus-visible .u-sweep{
  background-size:100% var(--u-h);
  background-position:0 100%;
}
.hub-row-m{display:block;margin-top:var(--s2);color:var(--ash);font-size:13px}

/* The thumbnail is the same untreated photograph as the card, at a size where
   the city is a texture rather than a picture. It is square because at 96px a
   16:10 crop is an indistinct stripe.

   The ground comes from insights.css, which is loaded first and already names
   .hub-row-media in that rule. Only the size, the crop and the hover belong
   here. It used to be the duotone that came from there; that was removed on
   2026-08-31 and the note in insights.css records what went with it. */
.hub-row-media{
  position:relative;overflow:hidden;
  width:96px;aspect-ratio:1;border-radius:var(--r-md);
}
/* Same --pz/--ptx frame vars the insights cards use - see the note in
   css/insights.css. This thumbnail is 1:1 off a 16/10 file, so cover already
   crops the sides here; the vars only decide which sides. */
.hub-row-media img{
  width:100%;height:100%;object-fit:cover;display:block;
  transform:scale(var(--pz,1)) translate(var(--ptx,0%),var(--pty,0%));
  transition:transform 600ms cubic-bezier(.2,.7,.3,1);
}
.hub-row:hover .hub-row-media img{
  transform:scale(calc(var(--pz,1) * 1.06)) translate(var(--ptx,0%),var(--pty,0%));
}

@media (max-width:560px){
  .hub-row{grid-template-columns:minmax(0,1fr) 72px;gap:var(--s4)}
  .hub-row-media{width:72px}
}

/* --- column 3: the watchlist --------------------------------------------
   Replaced a live clock rail. That rail proved the network's GEOGRAPHY inside a
   band whose job is to prove its EXPERTISE, which is why it went; the clock
   itself was good and belongs beside the statbar, where "one continuous day
   from Karachi to Auckland" is the claim being made.
   ------------------------------------------------------------------------ */
/* --haze, the same fill .pcard uses, now that the band beneath is white. This
   also settles an inconsistency that predates the flip: the lead card was
   already --haze while the rail beside it was --white, so the two panels in one
   three-column band sat on different surfaces for no stated reason. They agree
   now, and the keyline goes - a filled panel on a white ground does not need an
   outline as well, and .pcard has never had one. */
.hub-rail{
  background:var(--haze);border:0;border-radius:var(--r-lg);
  padding:var(--s6);
}
.hub-rail-head{
  display:flex;align-items:baseline;gap:var(--s3);
  padding-bottom:var(--s5);border-bottom:1px solid var(--line);
}
.hub-rail-head h3{font-size:17px;font-weight:500;line-height:24px}
.hub-rail-n{margin-left:auto;color:var(--ash);font-size:12px;white-space:nowrap}

/* The pulsing green dot went with the clock. It said "this is changing while
   you look at it", which was true of a wall clock and is not true of a dated
   watchlist - keeping it would have been a live-data signal over content that
   changes when somebody edits a file. */

.hub-rail-list{margin-top:var(--s2)}

/* --s2, not --s4, and this is the second half of the balance fix below.

   With the list clamped to uniform rows the two content columns land at 629 and
   632, and the rail was still running to 698 - the tallest of the three and the
   only one whose full height is VISIBLE, because it is the only one inside a
   filled box. A ragged bottom edge on the middle column is invisible; 66px of
   grey overhanging both neighbours is not, and that is what reads as "the
   watchlist is too long" even while the rule below says the band is fine.

   Halving the row padding takes 16px off each of four rows and lands the rail
   at 650, within 21px of both columns beside it. Nothing is removed - all four
   markets stay - and the rows do not read as cramped, because each one already
   carries its own internal spacing between the market line, the sentence and
   the credit. The outer padding was doing less work than its value suggested.

   The stacked layout below 1180 keeps --s5: there the rail is full width, its
   height matches nothing, and the rows want the air. */
.hub-watch{
  display:block;padding:var(--s2) 0;
  border-bottom:1px solid var(--line);
  text-decoration:none;
}
.hub-watch:last-child{border-bottom:0;padding-bottom:0}

/* THE STACKED RAIL'S OWN ROWS, and they live here rather than up in the
   @media (max-width:1180px) block with the rest of that layout FOR A REASON
   THAT COST A REAL BUG.

   They were up there, and they never applied. Media queries carry no extra
   specificity, so at equal weight the LATER rule in the sheet wins - and the
   base .hub-watch above is later. The result, at every width below 1180:

     border-bottom:0 lost, border-top:1px applied  ->  doubled hairlines
                                                       between every row
     padding:var(--s5) lost                        ->  rows kept the
                                                       three-column padding

   Neither was visible in review because both failures looked plausible. It
   surfaced only when the base padding was retuned for the balance fix and the
   stacked rail silently came with it.

   Below 1180 the rail is a full-width grid, so "last child" is one cell of the
   bottom row rather than the whole row - which is why the border moves to the
   top edge, the one that is correct in both layouts. */
@media (max-width:1180px){
  .hub-watch{
    border-bottom:0;border-top:1px solid var(--line);
    padding:var(--s5) 0;
  }
  .hub-watch:last-child{padding-bottom:var(--s5)}
}

/* The market takes its own ramp colour, so the rail reads west to east in the
   same sequence as the map, the cards and the list beside it. */
.hub-watch-top{display:flex;align-items:baseline;gap:var(--s3);margin-bottom:var(--s2)}
.hub-watch-m{
  color:var(--ac,var(--purple));font-size:11px;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;
}
/* Tabular, and ranged right: five dates in a column are a schedule, and a
   schedule that does not line up reads as a list of unrelated facts. */
.hub-watch-w{
  margin-left:auto;color:var(--ash);font-family:var(--mono);font-size:11px;
  letter-spacing:.04em;text-transform:uppercase;font-variant-numeric:tabular-nums;
  white-space:nowrap;
}

/* 14/21, a step below the list column beside it. The signals are the shortest
   text in the band and the most numerous; at the list's own size four of them
   read as a fifth, sixth, seventh and eighth headline competing with the four
   real ones. A step down makes the rail supporting material, which is what it
   is. */
.hub-watch-what{
  display:block;color:var(--slate);font-size:14px;line-height:21px;
  text-wrap:pretty;
}
.hub-watch.is-linked:hover .hub-watch-what,
.hub-watch.is-linked:focus-visible .hub-watch-what{color:var(--blue-deep)}

.hub-watch-by{
  display:block;margin-top:var(--s2);
  color:var(--ash);font-size:12px;line-height:19px;
}
/* The link affordance lives here rather than on the whole row, because half
   these rows are not links. A row with no note still earns its place - it says
   the market is being watched and nothing is written yet - and it must not look
   clickable while it says so. */
.hub-watch-go{
  display:block;margin-top:2px;
  color:var(--purple);font-weight:500;
  transition:color 180ms ease-in-out;
}
.hub-watch.is-linked:hover .hub-watch-go,
.hub-watch.is-linked:focus-visible .hub-watch-go{color:var(--blue-deep)}

/* The underline rides on "Read the note", not on .hub-watch-what above it.
   The sentence is the row's content and it runs to two lines at 14/21; the
   go line is the affordance, one line, and the only part of the row that
   claims to be clickable. Sweeping the sentence would underline the thing
   the reader is reading, which is the treatment the notes column uses
   BECAUSE there the sentence IS the link. Here it is not.

   Nothing is added for the unlinked rows on purpose. Half the rail is a
   <div>, and .is-linked gates every rule here so a row with no note stays
   inert under the cursor - which is the whole reason the affordance was put
   on the go line rather than the row in the first place. */
.hub-watch.is-linked:hover .u-sweep,
.hub-watch.is-linked:focus-visible .u-sweep{
  background-size:100% var(--u-h);background-position:0 100%;
}

/* THE BALANCE RULE, and it is a rule rather than a pixel budget on purpose.

   The previous note here recorded that four signals "lands the rail at roughly
   660 and the three columns finish together". Measured at 1440 the columns were
   lead 604, list 683, rail 777 - so the budget had gone stale and nothing in the
   file could tell. Absolute targets rot the moment the copy changes.

   The rule instead: NO COLUMN MAY END MORE THAN ONE ROW-HEIGHT ABOVE THE
   TALLEST. A row is ~120-170px here, so the band reads as three columns that
   stop at slightly different points rather than one column left hanging. It
   survives a copy edit, because it is measured against the band's own rows.

   RE-MEASURED 2026-09-02, after the members' own blog posts joined the band and
   made the row titles much longer than the notes' had been. The state it found:

     1600   lead 629   list 687   rail 698   worst gap  68   INSIDE
     1280   lead 592   list 791   rail 698   worst gap 199   over by 56
     1200   lead 567   list 817   rail 698   worst gap 250   over by 107

   The previous note predicted the failure exactly: the LIST grows at the narrow
   end, not the rail. Its four rows were running 117, 190, 164 and 216px because
   nothing clamped .hub-row-t, so a long syndicated headline took three or four
   lines and the middle column outgrew both neighbours.

   Two changes, both above, and each fixes a different complaint:

     .hub-row-t clamped and reserved at two lines - rows become 142/163/164/163
     and the list lands at 632 at EVERY width, against a 629 lead.

     .hub-watch padding --s4 -> --s2 - the rail comes down from 698 to 650. It
     was the tallest column and the only one whose height is visible, being the
     only one in a filled box, which is why it read as too long while the rule
     still called the band balanced.

   After both:

     1600   lead 629   list 632   rail 650   worst gap  12   INSIDE
     1366   lead 619   list 633   rail 650   worst gap  23   INSIDE
     1280   lead 592   list 632   rail 650   worst gap  50   INSIDE
     1200   lead 567   list 632   rail 650   worst gap  75   INSIDE
     1180-  stacked - the rail goes grid-column 1/-1 and the rule does not apply

   The lead is now the column that shrinks at the narrow end; it is a fixed-ratio
   image over a clamped dek, so there is little left to give. If the band ever
   misses again, look there first.

   Re-probe before changing anything here; do not trust this paragraph's numbers
   either. */

/* --- the adjacency the band displaced -----------------------------------
   `.hub + .closer .wrap{padding-top:var(--s14)}` stood here, re-making the top
   padding network-graph.css gave the closer when the constellation was its
   neighbour. Both rules are gone: .closer now carries symmetric --sec-lg
   padding of its own and is indifferent to what precedes it, so deleting this
   band no longer breaks the spacing of the section after it. */

@media (prefers-reduced-motion: reduce){
  .hub-row:hover .hub-row-media img{transform:scale(var(--pz,1)) translate(var(--ptx,0%),var(--pty,0%))}
}
