/* ==========================================================================
   tools.css — the free tools page.

   Loaded ONLY by tools.html, and it is half a sheet rather than a whole one
   because most of the page is not new. The chrome, the masthead and the closer
   all arrive from sheets that already shipped:

     base.css        tokens, .btn, .card, .tag, .closer, .eyebrow, the type scale
     insights.css    .ins .site-header (the light contained pill) and .ins-mast
     brief-form.css  .brief-row, .chips, .brief-err, .brief-sent and the footer
                     newsletter band
     footer.css      the closing block

   That is why <body> carries `ins` as well as `tools`. The insights pages
   already solved "a light page with no dark stage needs a header that is not
   index's translucent pill", and the answer is written down in insights.css in
   its own words. Borrowing the class is reuse; copying the twelve declarations
   into this file would be the third pill component in a project that already
   records having two as debt.

   What is genuinely new here, and all this file holds: the lead tool panel, the
   tool card, the three-step explainer and the FAQ list.
   ========================================================================== */


/* ==========================================================================
   01. Shared pieces of a tool
   Kicker, the "what you get" list and the run line appear on the lead panel and
   on every card, at two different sizes. Declared once here and sized at the
   two call sites, rather than as two near-identical components.
   ========================================================================== */

/* THE KICKER IS GONE, and so is the "Open now" pill. Each tool card was
   carrying seven distinct type treatments - pill, uppercase kicker, heading,
   deck, list, monospace run line, button - which is four more than the system
   allows in a single section and is the whole reason the page read as
   complicated. Both removals are the same edit twice: a label above a heading
   that the heading and its deck already explain, and a status on five of six
   cards where only three of them differ.

   Open is now the default state and says nothing. Only "In build" is marked,
   because only "In build" changes what happens when you click. */

/* .tl-get - REMOVED. The "what you get back" list survived on the lead cell
   after the plates took it off the cards, and went out with the lead cell: the
   daily brief is an ordinary card now and no card carries a list. Its three
   facts are in that card's deck. Nothing in tools.html matches .tl-get. */

/* .tl-run - REMOVED. The turnaround note under a card's action survived on two
   of the six, which made those two a different shape on a wall built entirely on
   the six being the same. Its facts are elsewhere and were not lost with it: the
   FAQ answers turnaround in both its copies, and the footer's own signup note
   still separates the daily brief from the monthly list.

   If a note ever comes back it comes back on ALL SIX or on none. One card
   carrying a line its neighbours do not is the shape problem this page has now
   removed three times - the kicker, the pill, and this. */

/* ==========================================================================
   02. The lead cell - REMOVED
   There is no lead cell. The wall is a uniform three-across grid and the daily
   brief is a .tl-card like the other five, so .tl-lead, .tl-lead-copy,
   .tl-lead-dek and .tl-lead-offer went out of this sheet whole rather than
   being left behind as rules nothing matches.

   What that component argued, kept because the next person to want a hero cell
   on this page should not have to rediscover it: it was the page's one tinted
   surface, and base.css's rule at .closer-box is that a second tinted block
   halves the weight of the first. One pale cell inside a wall of neutral ones
   is an accent; two are a pattern. If a lead cell ever comes back, it comes
   back as the only one.
   ========================================================================== */

/* ==========================================================================
   03. The wall
   ========================================================================== */

.tl-grid-sec .wrap{padding-bottom:var(--sec)}
.tl-grid-h{margin:0 0 var(--s6);max-width:24ch}

.tl-grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--s5);
}

/* SIX EQUAL CARDS, THREE ACROSS. This grid used to step down in three rows -
   one full-width lead, two at three columns, three at two - so that width
   carried rank and nothing had to announce it. It does not any more, at the
   client's direction: every cell is the same size, which is what the four tool
   directories this treatment was pegged against all do.

   WHAT THAT COSTS, recorded here because the grid can no longer say it: two of
   these six can be run today and four cannot, and an even wall shows four
   in-build cards at the weight of two live ones. Reading ORDER carries what
   width used to - the two open tools are first, the near-ready one third - and
   the pill and the action carry the rest.

   THREE TRACKS, NOT auto-fit, and that has not changed. auto-fit with a 300px
   minimum put four cards on one row and left the fifth alone on the next, which
   reads as a card that failed to load rather than as the end of a set. Six
   divides evenly by three, by two and by one, so every configuration below
   fills its last row and no card is ever orphaned. */
@media (min-width:1100px){
  .tl-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
}
/* One column below 640, and it went missing for a moment when the stepdown was
   torn out: the base two-track rule was left running at 390 and put two 165px
   cards side by side. Six divides by one as cleanly as by three. */
@media (max-width:640px){
  .tl-grid{grid-template-columns:1fr}
}

.tl-card{
  display:flex;flex-direction:column;gap:var(--s4);
  background-image:linear-gradient(0deg,
    rgb(var(--tool-rgb) / .045), rgb(var(--tool-rgb) / .045));
  /* `translate`, NOT `transform`, and it is not a style preference. The
     entrance below owns transform - it lands the cell from translateY(20px)
     scale(.985) to none - and a hover written in transform is the same property
     twice, at the same specificity, decided by which rule happens to sit lower
     in the file. Every hover on this wall uses the independent translate
     property instead, so the two compose: a card can be arriving and lifting at
     once, and neither rule has to out-specify the other to work.

     isolation:isolate so the two hover layers below can sit at z-index:-1 -
     behind the card's content, in front of the card's own ground - without
     escaping the card and landing behind the section. position:relative alone
     does not make a stacking context, and without one a z-index:-1 pseudo goes
     behind the card's own background and is simply never seen.

     THE DURATION IS A VARIABLE, NOT A NUMBER IN THIS SHORTHAND, and that is
     what makes in-fast/out-slow survive the cascade. The entrance rule further
     down re-declares `transition` on .is-in - it has to, it owns opacity and
     transform - and a shorthand replaces a shorthand, so a duration written
     here is thrown away the moment a cell arrives. A duration read from
     --tl-dur is not: the variable is set on the card, overridden on :hover, and
     whichever transition shorthand is winning picks up the current value. */
  isolation:isolate;
  transition:translate var(--tl-dur) var(--tl-ease);
}

/* ==========================================================================
   THE HOVER IS TWO OPACITY LAYERS AND A TRANSLATE, AND NOTHING ELSE
   It was a box-shadow and a background-image transition, and both of those are
   PAINT operations: a gradient that changes and a shadow that changes cannot be
   handed to the compositor, so every frame of a card lifting was the browser
   re-rasterising the whole card. Six of them under a moving cursor is where the
   roughness came from, and no amount of tuning the curve fixes a repaint.

   So both coloured states are pre-painted into layers that never change, and
   only their OPACITY moves - the one property besides transform the compositor
   can animate on its own thread:

     ::before   the deepened ground, over the card's resting tint
     ::after    the lifted shadow, in the card's own hue
     translate  the lift itself, already composited

   z-index:-1 on both, inside the isolate above: behind the card's content, in
   front of its ground, and unable to escape the card.

   IN FAST, OUT SLOW. 260ms arriving, 420 leaving. A hover that leaves at the
   speed it arrived feels snatched away, and letting it fall off slower than it
   came on is most of what reads as smooth. The base rule owns the leave and the
   :hover rule shortens it for the arrival - two durations, one curve.

   ONE CURVE THROUGHOUT: cubic-bezier(.22,.61,.36,1). Softer at the top than the
   .2,.7,.3,1 the specimens use, because a specimen is performing and a card is
   answering. Both durations and the curve are declared as properties on the
   cells so the six cards and the lead cannot drift apart.
   ========================================================================== */
.tl-card{
  --tl-in:260ms;
  --tl-out:420ms;
  --tl-dur:var(--tl-out);
  --tl-ease:cubic-bezier(.22,.61,.36,1);
}
/* The whole of in-fast/out-slow, in one declaration. At rest --tl-dur is the
   long one, so everything falls off at 420ms; under the cursor it is the short
   one, so everything arrives at 260. Nothing else has to know. */
.tl-card:hover,.tl-card:focus-within{--tl-dur:var(--tl-in)}

.tl-card::before,.tl-card::after{
  content:"";position:absolute;inset:0;
  border-radius:inherit;
  z-index:-1;
  opacity:0;
  pointer-events:none;
  transition:opacity var(--tl-dur) var(--tl-ease);
}
.tl-card:hover::before,.tl-card:hover::after,
.tl-card:focus-within::before,.tl-card:focus-within::after{opacity:1}

/* The deepened ground. 4% of the hue over the card's resting 4.5% lands where
   the old 8.5% rule landed, and gets there without repainting anything. */
.tl-card::before{background:rgb(var(--tool-rgb) / .04)}

/* THE LIFT, AND IT IS RANKED. Every card answers the cursor now - a wall where
   two of six respond reads as four broken cards - but they do not answer it
   equally, because the rank the widths carry has to survive the pointer. An
   open tool lifts 4px into a hue-tinted shadow; one you can only wait for lifts
   2 into half of it. The shadow is the card's own colour rather than black,
   which is what stops six lifting cards looking like six grey dialogs.

   The shadow is DECLARED on the ::after and never transitioned - the layer is
   painted once and faded in. That is the difference between a shadow that
   animates and a shadow that appears to.

   :focus-within, not only :hover. A keyboard user tabbing to "Tell me when it
   opens" gets the same card state a mouse gets, which is the one half of a
   hover treatment that is usually left out. */
.tl-card.is-live::after{box-shadow:0 12px 32px rgb(var(--tool-rgb) / .22)}
.tl-card.is-next::after{box-shadow:0 8px 20px rgb(var(--tool-rgb) / .14)}
.tl-card.is-live:hover,
.tl-card.is-live:focus-within{translate:0 -4px}
.tl-card.is-next:hover,
.tl-card.is-next:focus-within{translate:0 -2px}

/* The deepened ground is the ::before layer, declared with the hover block. */

/* The lead cell's name is set with the cards', not above them. It outranks
   them by a full row of width and by the only tinted ground on the page, and
   the system's rule is that hierarchy stated twice is hierarchy stated
   confusingly. Left to the UA default it came out SMALLER than the cards it
   leads, which is the same mistake in the other direction. */
.tl-card h3{margin:0;font-size:22px;line-height:29px;font-weight:500}
.tl-card-dek{
  margin:0;color:var(--gray);font-size:16px;line-height:25px;
}
/* THREE SIZES, which is the system's cap for a single section: 22 for the name,
   16 for everything you read, 14 for the note under the button. The list used
   to sit at 15 - a fourth size two pixels off the deck above it, which is drift
   rather than hierarchy. It is 16 now and the marker carries the difference. */

/* Pinned to the bottom so a row of cards of different lengths still has its
   actions on one line, and the BUTTON IS THE LAST THING IN IT on every card.
   The run line used to sit under its button, which was right when two of six
   cards carried one; once all six had the same button it stopped being a note
   and started being a spacer, floating two buttons 30 and 50px above the four
   beside them. Note first, button last, and every action on the wall lands the
   same 24px off the foot of its card. */
.tl-card-act{
  margin-top:auto;padding-top:var(--s2);
  display:flex;flex-direction:column;gap:var(--s3);align-items:flex-start;
}

/* THE ACTION NO LONGER CARRIES AVAILABILITY. This sheet used to argue the
   opposite - a text link on the waiting-list cards, because "coming soon"
   buttons sitting level with real ones would make most of the page's primary
   action lead to a form that cannot do anything yet. At the client's direction
   all six carry the same button now, and the argument is answered rather than
   ignored: the pill says "In build", the button says "Tell me when it opens",
   and picking one in the form prints a line saying the request joins a list.
   Three statements of the same fact, in words, where there used to be one in a
   shape.

   The deck stays a step quieter on the four, which is the last thing left that
   is not a word. */
.tl-card.is-next .tl-card-dek{color:var(--ash)}

/* One action, one size, one place. .tl-card-act is already pinned to the foot
   of the card, so with six identical buttons every row now lands its action on
   one line without anything having to be told to. */
.tl-card-act .btn{width:auto}


/* ==========================================================================
   03b. The plates and the specimens
   ALL SIX CARDS CARRY A PICTURE NOW, and three of them did not before. Half a
   wall of drawings beside half a wall of paragraphs is what made this section
   read as text-heavy - not the word count. Measured before changing anything:
   the audit card ran 520px tall and its specimen was 110px of that, 21%. The
   reference this treatment was pegged to runs its artwork at about 60% of card
   height, and its DECKS ARE LONGER than the ones here were. Ratio, not words.

   The fix is the plate. A specimen used to be an inline diagram sharing a text
   column with a deck above it and a list below; it is now the thing the card is
   built around - a fixed-height white zone at the top, with the title and a
   single line of deck underneath. That reorganisation, plus deleting the
   three-item "what you get" list from every card but the lead, is the whole
   change. It lands the plate at roughly half of card height.

   INPUTS AND OUTPUTS ARE NOT THE SAME PICTURE, and this is the rule that let
   the three in-build cards get artwork at all. The old rule here - a tool that
   has not been built has no output, and drawing one would fabricate the exact
   proof the page is offering to go and find - still stands, unchanged. What is
   new is the distinction beside it: the three built tools draw what they SEND
   BACK, and the three in-build tools draw what they POINT AT. A shelf of
   outlets, a network of who follows whom, a list of what gets checked. An input
   claims nothing about a result, so none of it fabricates anything.

   WHY NOT ICONS, still. A magnifier beside "Website Technical Audit" says
   nothing the two words do not, and six of them would be a row of small grey
   shapes competing with the only purple on the page. Every name on this wall
   already says what its tool does; icons work where a name does not. Note that
   the strongest of the four directories this was pegged against uses no icons
   either - it uses full-bleed plates, which is what these are.

   DRAWN, NOT DELIVERED. Divs, tokens and one inline SVG: no image request, no
   new asset family beside the relief maps and the city photographs, nothing to
   decode before the card has a height, and it all works from file://.

   NOTHING IN THEM IS A NUMBER. Every bar is redacted, no brand is named, no
   outlet is named, no box is ticked, and the only words are "You", "A reader"
   and "A crawler". The widths live in nth-child rules below, so the markup
   holds no figures either and nobody can mistake one for data by reading the
   source.
   ========================================================================== */

/* ==========================================================================
   03z. The wall arrives
   THE INITIAL STATE IS ONLY EVER SET BY SCRIPT, which is the whole architecture
   and the same one the specimens already use. .js-enter is added to the grid by
   tools.js; without it - script blocked, script errored, script still parsing -
   every cell is simply on screen. Nothing on this page is ever hidden behind
   something that might not run. The class is added before first paint for the
   same reason the specimens' is: a card that appears and then hides has
   flickered, and a flicker is worse than no animation.

   ONE SECTION MOVES, NOT ALL OF THEM. The masthead, the three-step explainer,
   the FAQ and the closer are all still. A page that animates every section on
   scroll has no accents, only a habit - and the wall is what the reader came to
   this page to choose from, so the wall is what earns the entrance.

   THE ROWS ARRIVE IN RANK ORDER, which is the same order everything else on
   this wall is in. The stagger is per cell in document order and capped, so the
   lead lands, then the two ranking tools, then the three in build - and the
   reader's eye is walked down the hierarchy once rather than being handed six
   things at once. 70ms apart: fast enough that six cells read as one gesture,
   slow enough that the order is legible.

   AND THEN THE SPECIMEN PLAYS. The card's own arrival and the drawing inside it
   moving are two beats, not one - tools.js waits out the card before starting
   the picture, so the drawing performs to a card that has already settled.
   ========================================================================== */

/* The heading is armed by the same script and lands one step ahead of the lead,
   so the section names itself and then fills. It rises without the scale: a
   line of type that grows into place reads as a zoom, and there is nothing to
   zoom into. */
.tl-grid-h.is-arming{opacity:0;transform:translateY(12px)}
.tl-grid-h.is-arming.is-in{
  opacity:1;transform:none;
  transition:opacity 480ms cubic-bezier(.2,.7,.3,1),
             transform 480ms cubic-bezier(.2,.7,.3,1);
}

.tl-grid.js-enter > .tl-card{
  opacity:0;
  transform:translateY(20px) scale(.985);
}
/* translate is named here as well as opacity and transform, and it has to be:
   this shorthand REPLACES the one on .tl-card, so a cell that has arrived would
   otherwise have no transition on its lift at all and would snap. It reads the
   same --tl-dur the resting rule does, so the hover stays in-fast/out-slow. */
.tl-grid.js-enter > .is-in{
  opacity:1;
  transform:none;
  transition:opacity 560ms cubic-bezier(.2,.7,.3,1),
             transform 560ms cubic-bezier(.2,.7,.3,1),
             translate var(--tl-dur) var(--tl-ease);
}
/* The entrance owns `transform` outright; every hover on this wall is written
   in the independent `translate` property, so the two compose instead of
   competing and a card can be arriving and lifting at the same time. See the
   note at .tl-card's transition. */

/* Both locks, as everywhere else on this page: the script refuses to add
   .js-enter under a reduced-motion request, and this holds if a future script
   forgets to ask. */
@media (prefers-reduced-motion:reduce){
  .tl-grid.js-enter > .tl-card,
  .tl-grid-h.is-arming{opacity:1;transform:none}
  .tl-card,.tl-plate,
  .tl-card::before,.tl-card::after{transition:none !important}
  .tl-card:hover,.tl-card:focus-within,
  .tl-card.is-live:hover,.tl-card.is-live:focus-within,
  .tl-card.is-next:hover,.tl-card.is-next:focus-within{translate:none}
}


/* ==========================================================================
   03a. One hue per tool
   THE WALL WAS TOO PLAIN, and the reason was that six drawings shared one
   scrubbed plum. What it got is not a new palette: base.css already carries six
   discipline hues, this wall already carries six tools, and each tool takes the
   hue of the DISCIPLINE IT SERVES rather than one dealt off the top of the set.

     Daily Sector Brief      Media & activation      it monitors the media
     Website Technical Audit Digital & performance   exactly what it audits
     AI Visibility Check     Brand & campaigns       whether your brand is named
     Share of Voice Snapshot PR & public affairs     it counts press
     Media Landscape Map     Content & production    programmes and platforms
     Launch Readiness Check  B2B & market entry      it is a market entry check

   THE ONE FORCED FIT is the landscape map. The other five map to their
   discipline without argument; that one is genuinely a PR instrument and PR was
   already spent on the share-of-voice card. Content & production is defensible -
   the tool maps programmes and platforms, which is where content lands - but it
   is the pairing to revisit if the roster ever changes, and it is the reason
   this list is written out rather than left to the class names.

   base.css states the governing rule for these: they mean a discipline, and a
   second use has to earn the rhyme rather than borrow the palette. That is what
   the mapping above is for. network-graph.css states the other half of it - one
   colour cannot mean two things ON ONE PAGE - and no page shows both the
   constellation and this wall.

   ONE CUSTOM PROPERTY, SET ONCE PER CARD, read by every rule below. Adding a
   seventh tool is a class on the card and nothing else.
   ========================================================================== */

.tl-card{--tool:var(--d-brand);--tool-rgb:var(--d-brand-rgb)}
.hue-media  {--tool:var(--d-media);  --tool-rgb:var(--d-media-rgb)}
.hue-digital{--tool:var(--d-digital);--tool-rgb:var(--d-digital-rgb)}
.hue-brand  {--tool:var(--d-brand);  --tool-rgb:var(--d-brand-rgb)}
.hue-pr     {--tool:var(--d-pr);     --tool-rgb:var(--d-pr-rgb)}
.hue-content{--tool:var(--d-content);--tool-rgb:var(--d-content-rgb)}
.hue-b2b    {--tool:var(--d-b2b);    --tool-rgb:var(--d-b2b-rgb)}

/* WHAT THE HUE IS ALLOWED TO TOUCH, and this is the line that keeps the wall
   from turning into a chart. A redacted bar stands for text that has been taken
   out. Colour it and it stops reading as a redaction and starts reading as a
   series - which on a page selling findings it has not made yet is the one
   thing none of these drawings may do.

   So: the hue takes the PLATE and the one element in each drawing that is a
   thing rather than a measurement - the tile that is you, the outlet the rest
   follow, the boxes a checklist is made of. The rank strip is the strictest of
   the six and gets the least, because its bars ARE lengths and a coloured length
   is a claim. Nothing here carries a number in any case; this is the second lock
   on the same door.

   base.css's own rule that the accent is interactive-and-emphasis only is not
   bent either: none of these are purple, none of them are clickable, and the one
   purple element on the wall that IS clickable stays the only purple that is. */
.tl-plate{
  /* A wash over white rather than an alpha on the card's ground: a plate is the
     raised thing and has to stay lighter than what it sits on, and 7% of a hue
     composited onto --haze would come out darker than the card. Two layers, one
     colour, no color-mix, works everywhere the rest of this sheet does. */
/* THE PLATE HAS NO GROUND, and losing it is the second time this page has made
   the same edit. The lead cell's own note two sections up: a pale panel holding
   a white card on a white page is three grounds stacked to say one thing. That
   was true of a box inside the lead, and it was just as true of six boxes
   inside six cards - a tinted card holding a tinted plate holding, on two of
   them, tinted panes is three grounds deep to show one drawing.

   So .tl-plate keeps everything that is structural and nothing that is a
   surface. It is a FRAME now, not a plate: a fixed height so neighbouring
   titles align, a reserved band so the pill never lands on the drawing, and
   overflow so the two oversized specimens crop. No fill, no radius, no shadow.
   The name is left alone because renaming a class across two files and a brief
   entry to record a change of role is churn, not clarity - this comment does it.

   AND THE SIDE PADDING WENT WITH IT. A frame with no fill that still inset its
   drawing by 16px was holding the artwork 40px from the card edge while the
   title sat at 24 - an invisible box you could still see the shape of. At zero
   the drawing aligns with the type it belongs to. */
  background:none;
}

/* THE PLATE. White on --haze and on --pale alike, no keyline, which is the same
   way .tl-pane already separates from its ground and the site's
   standing answer to "how does a box exist without a line".

   The height is FIXED rather than min, and that is the point of it: two cards
   side by side must put their titles on one line, and a plate sized to its own
   specimen would leave the audit's title 30px below the visibility check's. The
   three heights are one per row of the wall, which is the same rank the widths
   already carry.

   NOT A TOKEN. 240/200/140 are the only raw lengths in this sheet, and they are
   deliberately not spacing values - a plate is a picture frame, sized to the
   drawing in it, and rounding it to the --s scale would size the artwork to a
   rhythm meant for gaps between text. Documented here rather than quietly
   introduced. */
.tl-plate{
  width:100%;
  height:200px;
  padding:0;
  display:flex;align-items:center;
  /* What makes the bleed a bleed - and now the only reason this element boxes
     anything at all. */
  overflow:hidden;
}


/* THE STATUS BAND IS GONE WITH THE PILL IT WAS RESERVED FOR. Every card plate
   used to hold 44px empty at the top so an absolutely-positioned "In build"
   could never land on a drawing - a cost paid on all six so that four could
   carry a pill. The pill went at the client's direction, and the band went with
   it: the frame is padding-free again and every drawing is 44px bigger than it
   was, which is most of what "improve the illustration" turned out to mean.

   195, and it is the only raw length left in this sheet. A plate is a picture
   frame, sized to the drawing in it, and rounding it to the --s scale would
   size artwork to a rhythm meant for gaps between text. */
.tl-card .tl-plate{height:195px;padding:0}

/* THE FIXED HEIGHT IS FOR NEIGHBOURS, and below 640 there are none: the wall is
   one column, every card is full width, and no two titles share a line to be
   aligned on. So the frame stops being a frame and gives each drawing its own
   height. Holding 180px here instead cropped the six-row mail and the rank
   strip, which is the frame winning an argument it only exists to lose. */
@media (max-width:640px){
  .tl-plate{height:auto}
}

/* width:100% is load-bearing, not tidiness. A grid whose second track is 1fr has
   no content width of its own, and inside a flex column that does not stretch
   its children the rank strip collapsed to the width of its label column and
   nothing else. */
.tl-spec{
  display:grid;gap:var(--s2);width:100%;
  margin:var(--s1) 0 var(--s2);
}
/* Inside a plate the margins go: the plate's own padding is the frame now. */
.tl-plate .tl-spec{margin:0;flex:0 0 auto}

/* THE SPECIMENS BREATHE UP TO THE PLATE. Every one of them was drawn to sit in
   a text column between a deck and a list, so dropped into a 160px frame they
   filled about two thirds of it and the plate read as a picture with a margin
   round it rather than as a picture. The row gaps open up inside a plate and
   nowhere else - the drawings themselves are untouched, which is why none of
   the nth-child widths above had to move. */
/* 16, down from 20: the strip has seven children now rather than six - the
   engines row joined it - and six gaps at 20 overran the frame. */
.tl-plate .tl-spec-rank{gap:var(--s4)}
/* 20, not 24: an item is two lines now, so five of them at the old gap stood
   197px in a 195px frame and lost a pixel off the top and bottom row. */
.tl-plate .tl-spec-mail{gap:var(--s5)}
.tl-plate .tl-spec-checks{gap:var(--s7)}
/* THE PANES LOST THEIR GROUND TOO, and they were the last box inside a box on
   this wall - literally two filled rectangles sitting in a card. Every other
   drawing here is marks on the card's own surface now, and these were the one
   that still looked like an inset panel.

   The argument survives the fill, which is why it could go. The panes were never
   comparing two CONTAINERS, they were comparing how much is in each - four lines
   against one - and two columns of marks under two captions say that as plainly
   as two boxes did. What is lost is the "same page twice" reading that the
   matched rectangles carried; the matched column widths and the two captions
   carry it instead, and the animation still opens on both columns identical
   before it takes the copy away, which was always the part doing the work.

   THE CAPTION STOPPED BEING PINNED. margin-top:auto held it at the foot of the
   pane, which was right when the pane was a box being filled and wrong the
   moment the box went: it left the label stranded 80px under the lines it
   labels. It sits directly under them now and the whole group centres in the
   frame. The captions still land on one line - the two panes hold four lines
   and a caption each, identical structure and so identical height - which is
   the one alignment in this pair that has to hold, because the panes are being
   compared. It now holds without being forced to.

   The lines are a little taller and further apart than they are in the other
   specimens. Four thin lines and a label, unboxed, are the smallest drawing on
   this wall, and at the row-two frame's size they read as a caption with some
   dust above it rather than as a page. */
.tl-plate .tl-spec-panes{height:100%}
.tl-plate .tl-spec-panes .tl-pane{justify-content:center;gap:var(--s3)}
.tl-plate .tl-spec-panes .tl-line{height:10px}
/* The masthead-and-nav band at the top of a page, and the one mark the crawler
   pane keeps in full. Wider and heavier than a line of copy, with a gap under
   it, so two columns of marks read as two PAGES rather than as two paragraphs.
   It is also the honest part: a crawler that runs no script gets the header and
   the navigation and then nothing, which is what the right column now shows. */
.tl-head{
  display:block;height:14px;width:100%;
  border-radius:var(--r-sm);
  background:rgb(var(--tool-rgb) / .3);
  margin-bottom:var(--s2);
}
.tl-plate .tl-spec-panes .tl-pane-cap{margin-top:var(--s2);padding-top:0}
.tl-plate .tl-spec-panes .tl-pane{min-height:0;background:none;padding:0}

/* THE BLEED, and it is opt-in per specimen rather than global. An oversized
   drawing cropped by the plate's right edge reads as a fragment of something
   longer, which is the borrowed move; a drawing that fits reads as a diagram.
   Two specimens take it and three do not:

     rank    yes - and here the crop IS the reading. The bars belonging to the
             brands ahead of you run off the edge and yours is the one that fits.
     mail    yes - a morning does not end at the sixth item.
     panes   NO  - cropping takes away the crawler's pane, which is the argument.
     tiles   NO  - a cropped shelf reads as a missing outlet.
     checks  NO  - same.  */
.tl-plate .tl-spec-rank{width:124%}
/* 112 rather than the rank strip's 124. At 124 four of the six mail lines were
   long enough to reach the edge and the column read as one repeated shape; at
   112 two run off and four end where they end, which is what a list of items of
   different lengths is supposed to look like. */
.tl-plate .tl-spec-mail{width:112%}
/* The redacted bars. rgb(var(--plum-rgb) / a) is the site's own scrubbed-plum
   idiom rather than a grey: on --haze and on --pale alike a neutral grey went
   muddy, and these have to read as "text removed" on both. Fully round at 8px
   tall, which --r-pill already gives. */
.tl-bar,.tl-val,.tl-line{
  display:block;height:8px;border-radius:var(--r-pill);
  background:rgb(var(--plum-rgb) / .12);
  /* Every specimen animation is a scaleX, so the origin is the one property
     they all depend on. From the centre a bar grows in both directions, which
     reads as a pulse rather than as a measurement being taken. */
  transform-origin:left center;
}
.tl-val{background:rgb(var(--plum-rgb) / .22)}

/* --- the rank strip, on the AI visibility card --- */
/* The cap mattered when this sat in the lead cell's 1.1fr column and could have
   run to a newspaper measure. Inside a row-two card it never reaches 34rem, and
   the cap is kept only so the strip does not stretch if the card is ever
   promoted back to the lead. */
.tl-spec-rank{max-width:34rem}
.tl-rank-row{
  display:grid;grid-template-columns:8rem minmax(0,1fr);
  gap:var(--s4);align-items:center;
}
/* The four surfaces the check queries, as four marks and no names. They sit
   above the strip on their own row, at the width of the strip's label column,
   so the drawing reads as "these four were asked, and this came back". No logo
   and no initial: naming them would put four companies' marks on an AMIN page
   to illustrate a tool that has not run yet. */
.tl-engines{
  display:flex;gap:var(--s2);
  margin-bottom:var(--s2);
}
/* SQUARES, NOT DASHES. Four pills 9px tall read as four stray marks at the top
   of the strip; four rounded squares read as four things that were asked. Same
   restraint - no logo, no initial, no fourth colour. */
.tl-engines span{
  display:block;width:20px;height:20px;
  border-radius:6px;
  background:rgb(var(--tool-rgb) / .26);
}
.tl-engines span:nth-child(2){background:rgb(var(--tool-rgb) / .36)}
.tl-engines span:nth-child(3){background:rgb(var(--tool-rgb) / .2)}
.tl-engines span:nth-child(4){background:rgb(var(--tool-rgb) / .32)}
/* It is a row, not a rank row, so it must not take the grid's row sizing. */
.tl-spec-rank{grid-auto-rows:min-content}

/* THE NAMES TAKE THE HUE, THE COUNTS DO NOT, and that split is the reason this
   strip can carry any colour at all. The left column stands for brand NAMES -
   its widths vary because names are different lengths, which is not a quantity
   and cannot be misread as one. The right column stands for how often each was
   named, which is exactly a quantity, so it stays scrubbed plum. Same drawing,
   and the only half a reader could mistake for data is the half left grey. */
.tl-plate .tl-spec-rank .tl-bar{background:rgb(var(--tool-rgb) / .24)}

/* Descending, because the point of the picture is that the last row is you.
   Five plausible steps and then a stub - not a smooth curve, which would read
   as a chart of something. */
.tl-rank-row:nth-child(1) .tl-bar{width:70%}
.tl-rank-row:nth-child(2) .tl-bar{width:55%}
.tl-rank-row:nth-child(3) .tl-bar{width:82%}
.tl-rank-row:nth-child(4) .tl-bar{width:48%}
.tl-rank-row:nth-child(5) .tl-bar{width:64%}
.tl-rank-row:nth-child(1) .tl-val{width:100%}
.tl-rank-row:nth-child(2) .tl-val{width:78%}
.tl-rank-row:nth-child(3) .tl-val{width:55%}
.tl-rank-row:nth-child(4) .tl-val{width:40%}
.tl-rank-row:nth-child(5) .tl-val{width:30%}
/* The one row that is not redacted, and the only place on this page the accent
   is used for emphasis rather than for something you can click. base.css allows
   exactly that and nothing wider. */
/* THE STRICTEST OF THE SIX. Every bar in this strip is a length, and a coloured
   length is a claim about a number, so the redacted rows stay scrubbed plum and
   the hue touches exactly two things: the word "You" and the stub beside it.
   Both were already purple, and Brand & campaigns IS the brand purple, so on
   this card the hue changes nothing - which is the correct amount for the one
   drawing on the wall that could be mistaken for a chart. */
.tl-rank-row.is-you b{
  color:var(--tool);font-size:14px;line-height:21px;font-weight:600;
}
.tl-rank-row.is-you .tl-val{width:12%;background:var(--tool)}

@media (max-width:520px){
  .tl-rank-row{grid-template-columns:5rem minmax(0,1fr);gap:var(--s3)}
}

/* --- the two panes, on the audit card --- */
.tl-spec-panes{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s3)}
/* White on --haze, no keyline. 1.05:1 is the separation .tag already relies on
   against a card, and it is the site's standing answer to "how does a box exist
   without a line". */
/* flex, not grid. align-content:start put the lines at the top correctly and
   then ignored margin-top:auto on the caption, so the two captions floated at
   whatever height their own pane's content left them - one at the foot of the
   box and the other halfway up it, which is the one alignment in this pair that
   has to hold: the panes are being compared. */
.tl-pane{
  background:var(--white);border-radius:var(--r-sm);
  padding:var(--s3);
  display:flex;flex-direction:column;gap:6px;
  min-height:88px;
}
/* Counted inside .tl-copy, never against the pane: the header is a sibling of
   that wrapper, so it cannot shift these by one the way it did the first time
   it was added. */
.tl-copy{display:flex;flex-direction:column;gap:var(--s3)}
.tl-copy .tl-line:nth-child(1){width:100%}
.tl-copy .tl-line:nth-child(2){width:86%}
.tl-copy .tl-line:nth-child(3){width:94%}
.tl-copy .tl-line:nth-child(4){width:62%}
/* The whole argument of the card, in one difference: same box, almost nothing
   in it. The single line is short on purpose - a crawler that runs no script
   usually gets the nav and the footer, not nothing at all, and an empty pane
   would be overstating it. */
.tl-pane.is-thin .tl-copy .tl-line:nth-child(1){width:44%}
/* The other three exist in the markup and are gone at rest. This IS the finished
   state: with no script or with reduced motion the pane shows one short line,
   which is the whole point being made. The animation below starts them visible
   and takes them away. */
.tl-pane.is-thin .tl-copy .tl-line:nth-child(n+2){opacity:0;transform:scaleX(.15)}
.tl-pane-cap{
  margin-top:auto;padding-top:var(--s2);
  color:var(--ash);font-size:12px;line-height:18px;
}

/* --- the morning mail, on the lead cell --- */
.tl-spec-mail{gap:var(--s3)}
.tl-mail-row{
  display:grid;grid-template-columns:3.4rem minmax(0,1fr);
  gap:var(--s3);align-items:center;
}
/* Only the headline of each item bleeds; the source line under it is short by
   nature and a cropped byline reads as a mistake rather than as a fragment. */
.tl-plate .tl-spec-mail .tl-src{width:44%!important}
/* Tabular figures so three times sit on one vertical, which is the only reason
   the column reads as a timeline rather than as three loose labels. */
.tl-mail-row b{
  color:var(--ash);font-size:13px;line-height:20px;font-weight:500;
  font-variant-numeric:tabular-nums;
}
/* Six widths for six rows, and no two adjacent ones close enough to read as a
   pattern. A descending set would have drawn a chart of something; a morning's
   items are not ranked, they are just the morning. */
/* An item is a headline and the outlet under it, which is what the card
   promises arrives - "sourced to the outlet and the hour". The source line is
   shorter, thinner and quieter than the headline above it, the way a byline is.
   The hours stay --ash: they are the only literal thing in the drawing and the
   only thing a reader will try to actually read. */
.tl-item{display:grid;gap:5px;min-width:0}
.tl-src{height:5px;opacity:.62}

/* The lines here stand for headlines, not for amounts, so they take the hue. */
.tl-plate .tl-spec-mail .tl-line{background:rgb(var(--tool-rgb) / .3)}
.tl-mail-row:nth-child(1) .tl-line{width:100%}
.tl-mail-row:nth-child(2) .tl-line{width:72%}
.tl-mail-row:nth-child(3) .tl-line{width:88%}
.tl-mail-row:nth-child(4) .tl-line{width:64%}
.tl-mail-row:nth-child(5) .tl-line{width:94%}
.tl-mail-row:nth-child(6) .tl-line{width:78%}


/* --- the release, on the pickup card --- INPUT, not output.
   The release you would hand over, not the pickup it came back with. Drawing a
   map of outlets that carried it would be drawing a result for a tool that has
   produced none.

   One page WITH STRUCTURE, which is what keeps it from being the audit card's
   two abstract ones: a dateline above a headline that is heavier than anything
   under it, then a body. Three weights in one drawing, and no word in any of
   them. */
/* 16, not 12: the body is five lines and a headline in a 195px frame, and
   at the tighter gap the page sat in the middle of it with air above and
   below rather than reading as a full sheet. */
.tl-spec-doc{gap:var(--s4);align-content:center}
.tl-dateline{
  display:block;height:6px;width:32%;
  border-radius:var(--r-pill);
  background:rgb(var(--tool-rgb) / .45);
}
.tl-headline{
  display:block;height:14px;width:86%;
  border-radius:var(--r-sm);
  background:rgb(var(--tool-rgb) / .34);
  margin-bottom:var(--s2);
}
.tl-spec-doc .tl-line{background:rgb(var(--tool-rgb) / .22)}
.tl-spec-doc .tl-line:nth-of-type(1){width:100%}
.tl-spec-doc .tl-line:nth-of-type(2){width:94%}
.tl-spec-doc .tl-line:nth-of-type(3){width:98%}
.tl-spec-doc .tl-line:nth-of-type(4){width:88%}
.tl-spec-doc .tl-line:nth-of-type(5){width:56%}

/* --- the shortlist, on the creator card --- INPUT, not output.
   Four creators as four marks and a name line each. NO COUNTS ANYWHERE: a
   follower number is exactly the finding this tool has not produced yet, and it
   is the number a reader would most readily believe, which is what makes
   inventing one worse here than anywhere else on the wall.

   The face is a circle and everything else on this page is a rounded rectangle.
   That is the point - a person is not a publication, and the shape says so
   before the card does. */
.tl-spec-people{gap:var(--s6);align-content:center}
.tl-person{
  display:grid;grid-template-columns:26px minmax(0,1fr);
  gap:var(--s3);align-items:center;
}
.tl-face{
  display:block;width:26px;height:26px;
  border-radius:50%;
  background:rgb(var(--tool-rgb) / .3);
}
.tl-person .tl-line{background:rgb(var(--tool-rgb) / .26)}
.tl-person:nth-child(1) .tl-line{width:74%}
.tl-person:nth-child(2) .tl-line{width:58%}
.tl-person:nth-child(3) .tl-line{width:82%}
.tl-person:nth-child(4) .tl-line{width:64%}
/* The one already working with a rival, marked the way the shelf marks the tile
   that is you: emphasis, not interaction. */
.tl-person.is-you .tl-face{background:var(--tool)}
.tl-person.is-you .tl-line{background:rgb(var(--tool-rgb) / .44)}

/* --- the shelf, on the share of voice card --- INPUT, not output.
   Four outlets and nothing counted. The tiles are uneven in height on purpose:
   a row of identical rectangles reads as a chart with the numbers rubbed out,
   and four different heights read as four different publications. The tinted
   one is you, and it is neither the tallest nor the first. */
.tl-spec-tiles{grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--s3);align-items:end}
.tl-tile{
  background:rgb(var(--tool-rgb) / .1);
  border-radius:var(--r-sm);
  padding:var(--s3);
  display:flex;flex-direction:column;gap:5px;
}
.tl-tile:nth-child(1){height:140px}
.tl-tile:nth-child(2){height:185px}
.tl-tile:nth-child(3){height:116px}
.tl-tile:nth-child(4){height:162px}
.tl-tile .tl-line{height:6px;background:rgb(var(--tool-rgb) / .28)}
/* The masthead across the top of each tile. Four plain rounded rectangles read
   as a bar chart with the numbers rubbed off, which is the one thing this
   drawing must not be; a heavier bar at the head of each makes them
   publications. It sits at the TOP because that is where a masthead is, which
   is why the tile is a column with its copy pushed to the foot. */
.tl-mast{
  display:block;height:8px;width:70%;
  border-radius:var(--r-pill);
  background:rgb(var(--tool-rgb) / .45);
  margin-bottom:auto;
}
.tl-tile.is-you .tl-mast{background:rgb(var(--tool-rgb) / .7)}
.tl-tile .tl-line:nth-child(2){width:64%}
/* The one tinted cell, and the accent is doing the same job it does on the rank
   strip's last row: emphasis, not interaction, which base.css allows and
   nothing wider. */
.tl-tile.is-you{background:rgb(var(--tool-rgb) / .32)}
.tl-tile.is-you .tl-line{background:rgb(var(--tool-rgb) / .5)}

/* --- the network, on the media landscape card --- INPUT, not output.
   currentColor rather than a fill on every node, so the whole drawing takes one
   colour from one declaration and the hub can override it. Unlabelled: a named
   node would be an editorial claim about an outlet that has not agreed to be on
   this page. */
.tl-spec-nodes{
  display:block;width:100%;height:100%;margin:0 auto;
  color:rgb(var(--tool-rgb) / .3);
}
.tl-spec-nodes .tl-nodes circle{fill:rgb(var(--tool-rgb) / .34)}
/* The one outlet the rest follow, and the only element in this drawing at full
   strength. A node is a thing, not a quantity, so it may carry the hue solid. */
.tl-spec-nodes .tl-nodes circle.is-hub{fill:var(--tool)}
/* The reach of the outlet the rest follow. A ring is a radius, not a number,
   which is the whole reason it is allowed on a card for a tool that has
   produced no findings yet. */
.tl-spec-nodes .tl-halo{
  fill:rgb(var(--tool-rgb) / .1);
  stroke:rgb(var(--tool-rgb) / .28);
  stroke-width:1.5;
  stroke-dasharray:3 5;
}

/* --- the checklist, on the launch readiness card --- INPUT, not output.
   THE BOXES ARE EMPTY AND STAY EMPTY. A tick would be a finding on a tool that
   has produced none. An outline is a question, which is what this tool is. */
/* Two groups, and the gap between them is a section break. The card names three
   things - regulation, platform mix, the local calendar - and one run of
   identical rows drew a list rather than a check with parts to it. */
.tl-spec-checks{gap:var(--s8)}
.tl-check-grp{display:grid;gap:var(--s5)}
.tl-check-row{
  display:grid;grid-template-columns:18px minmax(0,1fr);
  gap:var(--s3);align-items:center;
}
/* 18, up from 14. The boxes are the subject of this drawing rather than a
   marker beside it, and at 14 in a frame this tall they read as bullet points. */
.tl-box{
  display:block;width:18px;height:18px;
  border-radius:5px;
  box-shadow:inset 0 0 0 1.5px rgb(var(--tool-rgb) / .5);
}
/* A checklist line is a question waiting to be asked, not a measurement. */
.tl-spec-checks .tl-line{background:rgb(var(--tool-rgb) / .27)}
.tl-check-grp:nth-child(1) .tl-check-row:nth-child(1) .tl-line{width:88%}
.tl-check-grp:nth-child(1) .tl-check-row:nth-child(2) .tl-line{width:62%}
.tl-check-grp:nth-child(2) .tl-check-row:nth-child(1) .tl-line{width:78%}
.tl-check-grp:nth-child(2) .tl-check-row:nth-child(2) .tl-line{width:54%}
.tl-check-grp:nth-child(2) .tl-check-row:nth-child(3) .tl-line{width:70%}


/* --- the two scripts, on the local-language card --- INPUT, not output.
   THE PAGE, IN TWO SCRIPTS. Every other drawing on this wall shows a thing the
   tool is pointed at - a release, a shelf, a checklist. This one shows the
   market itself: the same day's coverage, part of it in a script an
   English-only monitor reads and the rest in one it does not. It draws no gap,
   counts nothing and names no language, which keeps it on the right side of the
   rule the other seven follow.

   AND IT IS NOT THE AUDIT'S TWO PANES. That drawing is two boxes side by side
   because it compares two RENDERINGS of one page. This is one column, top to
   bottom, because it is one body of coverage - the second half is not a second
   version of the first, it is the part that carries on after your tooling stops
   reading. Stacked says "continues"; side by side would have said "versus".

   The rhythm is the whole of it. Continuous bars above, segmented blocks below,
   and nothing else changes - not the colour, not the weight. Two writing
   systems have different texture on a page at any size, which is a fact about
   type rather than a claim about anybody's coverage. */
.tl-spec-scripts{gap:var(--s4);align-content:center}

.tl-script{display:grid;gap:9px}
.tl-script .tl-line{background:rgb(var(--tool-rgb) / .24)}
.tl-script:nth-of-type(1) .tl-line:nth-child(1){width:97%}
.tl-script:nth-of-type(1) .tl-line:nth-child(2){width:86%}
.tl-script:nth-of-type(1) .tl-line:nth-child(3){width:58%}

/* The seam, and the one element here the hue is allowed to own outright. It is
   a THING - the point where one script gives way to another - rather than a
   measurement, which is the test this sheet sets at the hue block above. The
   two rules either side of it are hairlines so it reads as a fold in one page
   rather than as a border between two panels. */
.tl-seam{display:flex;align-items:center;gap:var(--s3);margin:var(--s1) 0}
.tl-seam::before,
.tl-seam::after{
  content:"";flex:1;height:1px;
  background:rgb(var(--tool-rgb) / .28);
}
.tl-seam-tag{
  display:block;flex:none;
  width:22px;height:8px;border-radius:var(--r-pill);
  background:var(--tool);
}

/* The other script. Rows of blocks rather than one bar, because that is what
   changes to the eye when a writing system changes - the run breaks up. Widths
   vary per block and per row so no two rows read as the same sentence, and
   nothing in here is a character in any language: it is the texture of type,
   not type. */
.tl-glyphs{display:flex;align-items:center;gap:5px}
.tl-glyphs i{
  display:block;height:8px;border-radius:2px;
  background:rgb(var(--tool-rgb) / .3);
}
/* FOUR ROWS TO THE FIRST BLOCK'S THREE, and the block runs wider. The point the
   card makes is that most of what is written is in the half you are not
   reading, so the drawing has to be bottom-heavy - a symmetrical split would
   have drawn "some of each", which is a different and untrue claim. */
.tl-script.is-other{gap:11px}
.tl-script.is-other .tl-glyphs:nth-child(1) i:nth-child(1){width:16px}
.tl-script.is-other .tl-glyphs:nth-child(1) i:nth-child(2){width:34px}
.tl-script.is-other .tl-glyphs:nth-child(1) i:nth-child(3){width:22px}
.tl-script.is-other .tl-glyphs:nth-child(1) i:nth-child(4){width:41px}
.tl-script.is-other .tl-glyphs:nth-child(1) i:nth-child(5){width:19px}
.tl-script.is-other .tl-glyphs:nth-child(2) i:nth-child(1){width:29px}
.tl-script.is-other .tl-glyphs:nth-child(2) i:nth-child(2){width:18px}
.tl-script.is-other .tl-glyphs:nth-child(2) i:nth-child(3){width:44px}
.tl-script.is-other .tl-glyphs:nth-child(2) i:nth-child(4){width:25px}
.tl-script.is-other .tl-glyphs:nth-child(3) i:nth-child(1){width:37px}
.tl-script.is-other .tl-glyphs:nth-child(3) i:nth-child(2){width:21px}
.tl-script.is-other .tl-glyphs:nth-child(3) i:nth-child(3){width:31px}
.tl-script.is-other .tl-glyphs:nth-child(3) i:nth-child(4){width:17px}
.tl-script.is-other .tl-glyphs:nth-child(3) i:nth-child(5){width:26px}
.tl-script.is-other .tl-glyphs:nth-child(4) i:nth-child(1){width:24px}
.tl-script.is-other .tl-glyphs:nth-child(4) i:nth-child(2){width:39px}
.tl-script.is-other .tl-glyphs:nth-child(4) i:nth-child(3){width:20px}


/* ==========================================================================
   03c. The specimens play once
   THE DEFAULT STATE IS THE FINISHED STATE, and that is the whole architecture
   here. Nothing on this page is hidden waiting for JavaScript to reveal it:
   every specimen is complete in CSS, and tools.js adds .is-playing to run an
   animation that starts from the OPENING state and lands back on the default.
   A blocked script, a failed observer or a reduced-motion request all leave the
   finished picture rather than an empty box - which is the site's own standing
   rule, degrade rather than break, applied to motion.

   ONCE, AND ONLY THESE THREE. The class is added on first intersection and the
   element is unobserved immediately. Nothing loops, and no section on this page
   has an entrance animation - a page where everything moves on scroll has no
   accents, and these three are the accents.

   AND THE HOVER IN 03d DOES NOT REPLAY THEM. This note used to read "nothing
   replays on hover", and that half of it is still true: an entrance is a
   performance a drawing gives once, unasked; a hover is an answer to a cursor
   that is already there. Different registers, and built out of different
   properties on purpose - see 03d.
   The specimens move because the movement IS the finding: bars arriving in
   descending order until yours, a page having its copy taken away, a morning's
   items landing in the order they broke.
   ========================================================================== */

@keyframes tl-draw{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes tl-vanish{from{opacity:1;transform:scaleX(1)} to{opacity:0;transform:scaleX(.15)}}
@keyframes tl-shrink{from{transform:scaleX(2.27)} to{transform:scaleX(1)}}
@keyframes tl-land{from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:none}}
@keyframes tl-fade{from{opacity:0} to{opacity:1}}

/* --- the rank strip draws down to you --- */
.tl-spec-rank.is-playing .tl-bar,
.tl-spec-rank.is-playing .tl-val{
  animation:tl-draw 560ms cubic-bezier(.2,.7,.3,1) both;
}
/* 90ms apart. Fast enough that the six read as one gesture, slow enough that
   the order is legible - and the order is the argument. */
.tl-spec-rank.is-playing .tl-rank-row:nth-child(1) span{animation-delay:0ms}
.tl-spec-rank.is-playing .tl-rank-row:nth-child(2) span{animation-delay:90ms}
.tl-spec-rank.is-playing .tl-rank-row:nth-child(3) span{animation-delay:180ms}
.tl-spec-rank.is-playing .tl-rank-row:nth-child(4) span{animation-delay:270ms}
.tl-spec-rank.is-playing .tl-rank-row:nth-child(5) span{animation-delay:360ms}
/* A beat, then you. 200ms of nothing after the fifth bar is what turns a
   sequence into a punchline. */
.tl-spec-rank.is-playing .tl-rank-row.is-you .tl-val{animation-delay:560ms}
.tl-spec-rank.is-playing .tl-rank-row.is-you b{animation:tl-fade 300ms ease-out 560ms both}

/* --- the page has its copy taken away --- */
/* Half a second of the two panes being identical before anything moves. Without
   the hold the reader never sees the "before", and the animation is only an
   argument if they do. */
.tl-spec-panes.is-playing .tl-pane.is-thin .tl-copy .tl-line:nth-child(1){
  animation:tl-shrink 500ms cubic-bezier(.4,0,.6,1) 500ms both;
}
.tl-spec-panes.is-playing .tl-pane.is-thin .tl-copy .tl-line:nth-child(n+2){
  animation:tl-vanish 460ms cubic-bezier(.4,0,.6,1) both;
}
.tl-spec-panes.is-playing .tl-pane.is-thin .tl-copy .tl-line:nth-child(2){animation-delay:500ms}
.tl-spec-panes.is-playing .tl-pane.is-thin .tl-copy .tl-line:nth-child(3){animation-delay:620ms}
.tl-spec-panes.is-playing .tl-pane.is-thin .tl-copy .tl-line:nth-child(4){animation-delay:740ms}

/* --- the morning lands --- */
.tl-spec-mail.is-playing .tl-mail-row{
  animation:tl-land 400ms cubic-bezier(.2,.7,.3,1) both;
}
/* 110ms rather than 130 now there are six of them: the same 130 across six rows
   ran to 650ms and the last item landed after the eye had left. */
.tl-spec-mail.is-playing .tl-mail-row:nth-child(2){animation-delay:110ms}
.tl-spec-mail.is-playing .tl-mail-row:nth-child(3){animation-delay:220ms}
.tl-spec-mail.is-playing .tl-mail-row:nth-child(4){animation-delay:330ms}
.tl-spec-mail.is-playing .tl-mail-row:nth-child(5){animation-delay:440ms}
.tl-spec-mail.is-playing .tl-mail-row:nth-child(6){animation-delay:550ms}

/* tools.js already refuses to add the class under a reduced-motion request.
   This is the second lock, and the project keeps both on purpose: the CSS one
   holds if a future script forgets to ask. */
@media (prefers-reduced-motion:reduce){
  .tl-spec.is-playing *{animation:none !important}
}


/* ==========================================================================
   03d. The drawings answer the cursor
   THE CARD LIFTS, AND THE DRAWING DOES ONE THING MORE. Eight of the nine cards
   on this wall are for tools you cannot run yet, so the only thing the cursor
   had to answer with was a 2px lift and a tint - and a wall of nine cards that
   all answer identically is a wall that answers nothing. The drawing is the one
   part of a card that is different from every other card, so the drawing is
   where the difference belongs.

   ONE GESTURE EACH, AND IT IS THE CARD'S OWN ARGUMENT, NOT A FLOURISH. The two
   scripts pull apart at the seam. The reader's page and the crawler's page stop
   being level with each other. The row that is you steps out of the ranking.
   Each is the sentence the deck underneath is already making, said once more in
   four pixels. Where a drawing had no such sentence to make it got a stagger and
   nothing else - inventing a claim for a tool that has produced no findings is
   the failure this whole sheet is written against.

   ONE SWITCH, AND EVERY DISTANCE AND DELAY BELOW IS A MULTIPLE OF IT.
   --tl-on is 0 at rest and 1 under the cursor. This is the same trick --tl-dur
   already plays two sections up - set on the card, flipped on :hover, read by
   everything downstream - and it buys three things at once:

     - no rule has to be written twice, once for :hover and once for
       :focus-within. The switch carries the selector, so keyboard focus gets
       every one of these for free rather than as an afterthought.
     - the stagger EXISTS ON THE WAY IN AND VANISHES ON THE WAY OUT, because a
       delay of `--tl-on * i * step` is zero the moment the cursor leaves. A
       staggered leave reads as nine parts letting go one at a time, which is
       hesitation. They arrive in order and they leave together.
     - reduced motion is one line at the foot of this section rather than
       nineteen, because zeroing the switch zeroes the whole system.

   COMPOSITED, LIKE THE CARD. Only `translate` and `scale` move here - never
   width, height, margin or a background. Same rule the hover block in 03 was
   rewritten to obey, and it matters more here rather than less: a cursor
   crossing this wall passes over nine drawings holding about sixty parts
   between them, and any one of those parts animating a paint property would
   re-rasterise its whole card on every frame of the lift.

   AND THEY ARE THE INDEPENDENT PROPERTIES, NOT `transform`. The entrance
   animations in 03c own `transform` on several of these same elements. A hover
   written in `transform` would be the same property twice, and a card hovered
   while it was still playing would drop one of the two. `translate` and `scale`
   compose with `transform` instead of replacing it, so a drawing can be
   arriving and answering at once - which is exactly what happens to the top row
   of cards on a fast scroll.

   THREE TO EIGHT PIXELS, AND THE CEILING IS NOT TASTE. .tl-plate is 195px with
   `overflow:hidden`, and two specimens are deliberately oversized inside it -
   the rank strip at 124% and the mail at 112%. Every distance below was measured
   against the frame with the cursor down: nothing here crops, and the tallest
   share-of-voice column clears the top edge by a single pixel.
   ========================================================================== */

.tl-card{--tl-on:0}
.tl-card:hover,.tl-card:focus-within{--tl-on:1}

/* Everything that moves, and nothing else. Listed rather than matched with a
   wildcard so that adding a tenth drawing is a deliberate act: a new specimen
   gets no hover until someone has decided what its one gesture is.

   --tl-i is a part's index within its own drawing and --tl-step is that
   drawing's beat. Both default to zero here, so a part with no stagger declared
   simply moves with the rest of its drawing. */
.tl-spec-mail .tl-mail-row,
.tl-spec-panes .tl-pane > *,
.tl-spec-rank .tl-rank-row,
.tl-spec-doc .tl-dateline,
.tl-spec-doc .tl-headline,
.tl-spec-doc > .tl-line,
.tl-spec-tiles .tl-tile,
.tl-spec-nodes .tl-halo,
.tl-spec-nodes .tl-nodes .is-hub,
.tl-spec-people .tl-face,
.tl-spec-checks .tl-box,
.tl-spec-scripts .tl-script,
.tl-spec-scripts .tl-seam-tag{
  --tl-i:0;
  --tl-step:0ms;
  transition:translate var(--tl-dur) var(--tl-ease),
             scale var(--tl-dur) var(--tl-ease);
  transition-delay:calc(var(--tl-on) * var(--tl-i) * var(--tl-step));
}

/* --- 1. the brief: the morning lands again, in a lower register -------------
   The entrance drops each row 6px over 400ms, 110ms apart. This is 3px over the
   card's own 260, 40ms apart - a third of the distance and a third of the
   interval, which is what keeps it an echo of the entrance rather than a second
   performance of it. It is also the only tool on this wall you can actually
   run, so it is the only card whose gesture repeats its entrance at all: the
   other eight answer with something their entrance never said. */
.tl-spec-mail .tl-mail-row{--tl-step:40ms;translate:0 calc(var(--tl-on) * -3px)}
.tl-spec-mail .tl-mail-row:nth-child(2){--tl-i:1}
.tl-spec-mail .tl-mail-row:nth-child(3){--tl-i:2}
.tl-spec-mail .tl-mail-row:nth-child(4){--tl-i:3}
.tl-spec-mail .tl-mail-row:nth-child(5){--tl-i:4}
.tl-spec-mail .tl-mail-row:nth-child(6){--tl-i:5}

/* --- 2. the audit: the two pages stop being level ---------------------------
   The entrance takes the copy off the crawler's pane. The hover does not touch
   the copy at all - it puts the two panes on different planes, 6px apart, which
   is the same finding stated as position instead of as content. Together rather
   than staggered: the whole point is that they were level a moment ago. */
/* THE PANE'S CONTENTS, NOT THE PANE. Inside the frame .tl-pane is a full-height
   invisible column - it fills the plate top to bottom and centres a head, four
   lines and a caption inside itself. Translating the column moved the drawing
   correctly and clipped a box nobody can see, which is fine until the day
   somebody gives .tl-pane a ground back and the clip becomes visible. Moving
   what is actually drawn is the same picture and cannot rot that way. */
.tl-spec-panes .tl-pane > *{translate:0 calc(var(--tl-on) * -4px)}
.tl-spec-panes .tl-pane.is-thin > *{translate:0 calc(var(--tl-on) * 2px)}

/* --- 3. the visibility check: the row that is you steps out -----------------
   Five bars and then you, and the entrance already holds a 200ms beat before
   the last one arrives. So the hover moves ONLY that last one. 5px of
   separation from a stack it is otherwise flush with reads as "this row is the
   subject", which is the card's whole sentence. Nothing scales: these bars are
   lengths, and a length that grows under a cursor is a claim this tool has not
   made yet. */
.tl-spec-rank .tl-rank-row.is-you{translate:0 calc(var(--tl-on) * -5px)}

/* --- 4. the pickup check: the release presents its head ---------------------
   Dateline and headline up 4, body down 2. A release is read from the top, and
   what this tool is pointed at is whether the top of it travelled. */
.tl-spec-doc .tl-dateline,
.tl-spec-doc .tl-headline{translate:0 calc(var(--tl-on) * -4px)}
.tl-spec-doc > .tl-line{translate:0 calc(var(--tl-on) * 2px)}

/* --- 5. the share of voice: the shelf settles, and yours goes further -------
   Bottom-aligned columns, so up is the only direction that means anything here.
   4px each at 50ms apart, and 8 for yours. THE TALLEST COLUMN IS 185 IN A 195
   FRAME, which leaves it 5px of headroom - it takes the 4 and clears the top
   edge by one. The 8 belongs to .is-you deliberately: it is 116px tall and has
   the room to spare. */
.tl-spec-tiles .tl-tile{--tl-step:50ms;translate:0 calc(var(--tl-on) * -4px)}
.tl-spec-tiles .tl-tile:nth-child(2){--tl-i:1}
.tl-spec-tiles .tl-tile:nth-child(3){--tl-i:2}
.tl-spec-tiles .tl-tile:nth-child(4){--tl-i:3}
.tl-spec-tiles .tl-tile.is-you{translate:0 calc(var(--tl-on) * -8px)}

/* --- 6. the landscape map: the reach opens ----------------------------------
   The halo is already described in this sheet as a radius rather than a number,
   which is the only reason it is allowed to move at all: a ring widening is not
   a figure going up. The hub takes a smaller share of the growth so the ring is
   clearly the thing that opened and the node is clearly what it opened around.

   transform-box:fill-box, and without it neither of these is centred on itself.
   An SVG child's default transform-origin is the origin of the VIEWBOX, so a
   scale on the hub would have thrown it down and right across the whole drawing
   instead of swelling it in place. */
.tl-spec-nodes .tl-halo,
.tl-spec-nodes .tl-nodes .is-hub{
  transform-box:fill-box;transform-origin:center;
}
.tl-spec-nodes .tl-halo{scale:calc(1 + var(--tl-on) * .14)}
.tl-spec-nodes .tl-nodes .is-hub{scale:calc(1 + var(--tl-on) * .1)}

/* --- 7. the shortlist: the faces come up, one at a time ---------------------
   Scale rather than lift, and that is what keeps this card distinct from the
   four above it that all move on the y axis. The one already working with a
   competitor comes up further, because it is the one the shortlist is for. */
.tl-spec-people .tl-face{--tl-step:45ms;scale:calc(1 + var(--tl-on) * .12)}
.tl-spec-people .tl-person:nth-child(2) .tl-face{--tl-i:1}
.tl-spec-people .tl-person:nth-child(3) .tl-face{--tl-i:2}
.tl-spec-people .tl-person:nth-child(4) .tl-face{--tl-i:3}
.tl-spec-people .tl-person.is-you .tl-face{scale:calc(1 + var(--tl-on) * .2)}

/* --- 8. the readiness check: the list is run down ---------------------------
   Five boxes swelling in order, 50ms apart, across the group break so the two
   groups read as one pass rather than two. THEY STAY EMPTY: a tick would be a
   finding, and this sheet has refused to draw one twice already. Something
   moving down a checklist is not the same claim as something having been
   checked off it. */
.tl-spec-checks .tl-box{--tl-step:50ms;scale:calc(1 + var(--tl-on) * .16)}
.tl-spec-checks .tl-check-grp:nth-child(1) .tl-check-row:nth-child(2) .tl-box{--tl-i:1}
.tl-spec-checks .tl-check-grp:nth-child(2) .tl-check-row:nth-child(1) .tl-box{--tl-i:2}
.tl-spec-checks .tl-check-grp:nth-child(2) .tl-check-row:nth-child(2) .tl-box{--tl-i:3}
.tl-spec-checks .tl-check-grp:nth-child(2) .tl-check-row:nth-child(3) .tl-box{--tl-i:4}

/* --- 9. the local-language gap: the seam opens ------------------------------
   The best of the nine, because it is the only one where the gesture IS what
   the card is called. The two halves separate by 7px and the tag between them
   widens by 45% - the gap the tool is named for, opening while you look at it.

   scaleX on the tag rather than a width, for the reason at the top of this
   section: a width is layout, a scale is the compositor. The 1 on the y axis is
   explicit so that an 8px pill does not also become an 11px one. */
.tl-spec-scripts .tl-script{translate:0 calc(var(--tl-on) * -4px)}
.tl-spec-scripts .tl-script.is-other{translate:0 calc(var(--tl-on) * 3px)}
.tl-spec-scripts .tl-seam-tag{scale:calc(1 + var(--tl-on) * .45) 1}

/* ONE LINE, AND IT STOPS ALL NINE. Every distance and every delay above is a
   multiple of --tl-on, so pinning the switch at zero is the whole opt-out - the
   drawings stay exactly where CSS already had them, which is the finished
   state. The transition goes too, in case a distance is ever added here that
   forgets to read the switch. */
@media (prefers-reduced-motion:reduce){
  .tl-card,.tl-card:hover,.tl-card:focus-within{--tl-on:0}
  .tl-spec *{transition:none !important}
}


/* ==========================================================================
   03e. The routing answer
   IT NO LONGER HAS A USER ON THIS PAGE, and it stays anyway. .tl-route was
   built for the request form's own answer - pick a market, get the member who
   works it - and that form was removed on 2026-08-28. daily-brief.html and
   tech-audit.html both draw the same block in their closers and both load this
   sheet WHOLE to get it, saying so at their <link>. Deleting it here would
   unstyle two working pages.

   THE RULES BELONG HERE RATHER THAN IN EITHER OF THOSE SHEETS for the reason
   both of them state: the answer to "who runs this" is one component on every
   page or it is three answers that drift, which docs/brief.md records happening
   once already between .fbar-sel and .svc-select. This sheet is the shared one
   because it is the one both already load.

   Filled white on the closer's --mist box, no keyline, which is the same way
   every other raised thing on this site separates from its ground.
   ========================================================================== */

.tl-route{
  display:flex;align-items:center;gap:var(--s4);
  padding:var(--s4);
  background:var(--white);border-radius:var(--r-md);
  text-align:left;
}
.tl-route-t{margin:0;color:var(--gray);font-size:15px;line-height:23px}
.tl-route-id{display:grid;gap:2px;min-width:0}
.tl-route-id b{color:var(--plum);font-size:15px;line-height:22px;font-weight:600}
.tl-route-id span{color:var(--gray);font-size:14px;line-height:21px}
/* Tabular, because it ticks. Proportional figures make the whole line twitch
   sideways every time a minute rolls over. */
.tl-clock{font-variant-numeric:tabular-nums}

/* It arrives rather than appearing, for the same 180ms the buttons use. Guarded
   twice, like the specimens. */
.tl-route.is-in{animation:tl-land 320ms cubic-bezier(.2,.7,.3,1) both}
@media (prefers-reduced-motion:reduce){
  .tl-route.is-in{animation:none}
}

/* ==========================================================================
   04. How a free tool works here
   Three steps, and the section exists to answer one question the page cannot
   leave open: why is this free. The third step is the answer - a named person
   in the market sends it back - and it is also the whole difference between
   this and the scanner that mails you a PDF.
   ========================================================================== */

/* tools.html surface run: mast - grid - how - faq - closer. Alternating, so the
   two border-tops that used to sit on .tl-how and .tl-faq are gone: both were
   drawing a line between sections that share a tone, which is the collision the
   surface rule exists to prevent. */
.tl-how{padding:var(--sec) 0;background:var(--surface-alt)}
.tl-how .wrap{padding-top:0;padding-bottom:0}
.tl-how-h{margin:0 0 var(--s10);max-width:20ch}


/* --- the rail ------------------------------------------------------------
   ONE TRACK, NOT A BORDER PER COLUMN. A border on each .tl-step would break at
   every gap and read as three underlines rather than one line the three steps
   sit on. The track is a pseudo-element on the list, full width, and the nodes
   are positioned onto it.

   THE NODE IS 34px AND CARRIES THE STEP NUMBER, so the track sits at 17px -
   the node's own centre line, minus nothing, because a 1px rule crossing a
   34px disc is centred at 17 either way. 34 and 17 are off the spacing scale
   on purpose: they are the diameter of one component and half of it, which is
   optical alignment rather than spacing, and the rule for a value like that is
   to declare it where it is used instead of promoting it to a token nothing
   else will ever want.

   THE DISC IS OPAQUE, which is the whole reason the rail can run behind it
   rather than being cut into segments. --pale is a solid colour, so the line
   enters the circle and leaves it and is hidden in between, which is what a
   node on a timeline looks like. Nothing masks anything. */
.tl-steps{
  position:relative;
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s10);
  margin:0;padding:0;list-style:none;
}
/* FULL WIDTH, EDGE TO EDGE. Two other lengths were tried and both were worse:
   ending the track on node 03 left the third column with no line over it, and
   a full track with the fill stopping on node 03 left a grey remainder over
   that same column, so the section read as two-thirds done after the sweep had
   finished. One line, drawn all the way, is the version where nothing looks
   unreached - and the run past 03 reads as the track carrying on rather than
   as a gap, BECAUSE IT IS PLUM. The fault was never the length. It was that
   part of the line was a different colour from the rest. */
.tl-steps::before,
.tl-steps::after{
  content:"";position:absolute;left:0;right:0;top:17px;height:1px;
}
/* THE TRACK ENDS ON THE LAST NODE. It used to run the full width with the plum
   fill stopping at node 03, which left a grey remainder - and that remainder
   ran directly OVER the third column, so once the sweep had finished the
   section still read as two-thirds done. The unhighlighted third column was
   the bug; the leftover line was only how it showed.

.tl-steps::before{background:var(--line)}
/* The fill. It rests at 1 and tools.js arms it to 0, then releases it once the
   rail is on screen, west to east - the same direction the map ramp, the button
   wipe and the footer rule all run, which base.css states at .btn-fill in those
   words. With no script or with reduced motion it is simply at 1 from the
   start, so the rail is drawn either way and only the drawing of it is the
   animation.

   LINEAR, and it is the one easing on this site that is not the house curve.
   Everything else here is something answering a cursor, which should arrive
   fast and settle. This is a line being drawn, and a pen does not accelerate.
   It also has to be linear for the reason below: a constant speed is what makes
   "the node lands when the line reaches it" a multiplication rather than a
   guess.

   900ms across the full width. At 720 the sweep was over before it read as a
   sweep; at 1200 the reader is waiting for a line. */
/* The fill covers the track completely - same box, 0 to 1 - so --line is only
   ever seen while the sweep is running. Nothing rests grey. */
.tl-steps::after{
  background:var(--plum);
  transform:scaleX(1);transform-origin:left center;
}
.tl-steps.is-arming::after{transform:scaleX(0)}
.tl-steps.is-playing::after{
  transform:scaleX(1);
  transition:transform 900ms linear;
}

/* --- WHERE EACH STEP SITS ON THE LINE ------------------------------------
   --t is the node's position as a fraction of the rail, and every delay in this
   section is that fraction times the sweep. It is what synchronises the whole
   thing: the node lands as the line reaches it and its copy follows one beat
   later, so the sweep is not decoration running alongside the content, it is
   what delivers the content.

   --t IS EACH NODE'S CENTRE AS A FRACTION OF THE FULL ROW, because that is
   what the sweep now crosses. Node 03 turns at .699 and the line carries on
   past it for the last three tenths, which is the track running on beyond the
   last step rather than the sequence continuing.

   The values are declared here rather than measured in JavaScript on purpose.
   Measuring would mean a layout read, a resize listener and a number that has
   to be written back into a style attribute, to correct an error nobody can
   see. */
.tl-step:nth-child(1){--t:.013}
.tl-step:nth-child(2){--t:.356}
.tl-step:nth-child(3){--t:.699}
.tl-step{--sweep:900ms}

/* 34 for the node plus 20 of air. Written as a calc against the node's own
   diameter rather than as 54, so moving the node resizes the clearance with it
   and the two cannot drift apart. */
.tl-step{
  position:relative;
  /* ABOVE THE RAIL, and this is the one line that makes the opaque disc work.
     A parent's ::before and ::after paint around its children - ::before below
     them, ::after ABOVE - so the plum fill was drawing straight across the
     three discs and through the numerals in them. z-index on the step lifts the
     whole column over both pseudo-elements, and the line then enters the circle
     and leaves it, which is what a node on a timeline looks like. */
  z-index:1;
  display:flex;flex-direction:column;align-items:flex-start;gap:var(--s3);
  padding-top:calc(34px + var(--s5));
}

/* --- the node ---
   Pale ground, plum numeral: 11.5:1, the same pair brief-form.css measured for
   its chips. Tabular figures so 01, 02 and 03 are the same width and the three
   discs hold identical optical weight - at this size a narrow 1 against a wide
   3 is visible as the circles looking uneven, which they are not. */
.tl-step-node{
  position:absolute;left:0;top:0;
  width:34px;height:34px;border-radius:50%;
  display:grid;place-items:center;
  background:var(--pale);
  color:var(--plum);font-size:13px;line-height:1;font-weight:600;
  font-variant-numeric:tabular-nums;letter-spacing:.02em;
  /* No transform in here any more: the node turns, it does not move or grow,
     in any state. Both properties that DO change are colour, and 240ms is slow
     enough that the turn is visible on a 34px disc without lagging the line
     front that triggers it. */
  transition:background-color 240ms ease-in-out,
             color 240ms ease-in-out,
             box-shadow 240ms ease-in-out;
}
/* THE NODES DO NOT POP IN. All three are on the rail from the first frame, in
   a neutral state, and each one TURNS as the line reaches it: --mist and --ash
   before, --pale and --plum after. That is a truer picture of what the section
   is saying - the three steps all exist, and the line is what moves through
   them - and it is the mechanic the reference this was drawn from uses.

   Popping them in was the wrong read: it said the steps themselves appear one
   by one, which is not what happens when you request a tool.

   The delay is the node's own position along the sweep, so it turns at the
   moment the line front passes it rather than on a hand-picked number that
   drifts out of step the first time the duration changes. */
.tl-steps.is-arming .tl-step-node{background:var(--mist);color:var(--ash)}
.tl-steps.is-playing .tl-step-node{
  background:var(--pale);color:var(--plum);
  transition-delay:calc(var(--t) * var(--sweep));
}

/* --- the arrival ---
   A ring that expands out of the node and dies, at the instant the line reaches
   it. NOT a new idea: it is network-motion.css's mo-halo on the constellation
   hubs, at its own values - the same gesture marking the same kind of thing, a
   point on a structure announcing itself. It matters more now that the node
   turns rather than appears: a colour change on a 34px disc is quiet, and the
   ring is what makes the moment the line arrives legible.

   Lavender rather than the accent, because at 2x the node's size a saturated
   ring is a second object rather than an echo of the first.

   ::after on an absolutely positioned parent, so it is out of the node's grid
   and does not fight the numeral in the centre cell. */
.tl-step-node::after{
  content:"";position:absolute;inset:-4px;
  border-radius:50%;border:2px solid var(--lavender);
  opacity:0;pointer-events:none;
}
.tl-steps.is-playing .tl-step-node::after{
  animation:tl-halo 1200ms ease-out both;
  animation-delay:calc(var(--t) * var(--sweep));
}
@keyframes tl-halo{
  from{transform:scale(.6);opacity:0}
  25% {opacity:.8}
  to  {transform:scale(2.1);opacity:0}
}

.tl-step h3{margin:0;font-size:19px;line-height:27px;font-weight:500;transition:color 200ms ease-in-out}
.tl-step p{margin:0;color:var(--gray);font-size:16px;line-height:25px;max-width:38ch}

/* --- the copy follows its own node ---
   One beat after the node lands, which is what makes the sweep read as
   delivering the three steps rather than running behind them.

   THIS IS THE ONLY TEXT ON THE PAGE THAT ENTERS, and it is allowed to because
   it is not an entrance for its own sake: the order the three arrive in IS the
   thing the section is explaining. No other section here animates on scroll -
   that is what keeps this one an accent rather than a habit.

   The colour transition is kept undelayed alongside the entrance so hovering a
   step still moves its heading at the usual 200ms instead of waiting out a
   delay meant for the arrival. */
.tl-steps.is-arming .tl-step h3,
.tl-steps.is-arming .tl-step p,
.tl-steps.is-arming .tl-step-meta{opacity:0;transform:translateY(8px)}
.tl-steps.is-playing .tl-step h3,
.tl-steps.is-playing .tl-step p,
.tl-steps.is-playing .tl-step-meta{
  opacity:1;transform:none;
  transition:opacity 420ms ease-out calc(var(--t) * var(--sweep) + 110ms),
             transform 420ms cubic-bezier(.2,.7,.3,1) calc(var(--t) * var(--sweep) + 110ms),
             color 200ms ease-in-out;
}
/* Pushed to the foot so the three sit on one line whatever the copy above them
   does. It is the same margin-top:auto the tool cards use for their action. */
.tl-step-meta{
  margin-top:auto;padding-top:var(--s3);
  color:var(--ash);font-size:14px;line-height:21px;
  transition:color 200ms ease-in-out;
}

/* --- hover ---
   The step is not a control and is not focusable: nothing here does anything
   when you click it, and making text tabbable to look interactive is a promise
   the section cannot keep. What it does have is a hover that helps you READ -
   it picks one of the three out of the row, which is exactly what you want
   while you are comparing them. The other two are left alone; dimming them
   would cost the reader the thing they were comparing against.

   Three things move, and all three are the same state said once each on the
   node, the heading and the tag - not three separate ideas:

     the disc fills           - the point on the rail you are at
     a pale ring opens        - the same halo the sweep uses, held still
     the heading and tag darken

   AND THERE IS NO GROUND BEHIND THE COPY. A --pale panel used to fade in under
   the three lines to bind them into one block. It was removed at the client's
   direction and the call is right for a reason worth keeping: this section sits
   on --surface-alt, and a tinted panel appearing on a tinted band is a THIRD
   ground stacked to say one thing - the same fault .tl-plate's own note records
   losing on the cards. It also drew a box where the layout has none: three
   columns threaded on one rail are held together by the rail, and a panel round
   one of them cut it out of the row it is a step in. The node and the type
   carry the state now, which is all a step that is not a link should claim.

   NOTHING MOVES. No lift, no scale, no shift. The three columns sit on a
   shared rail: anything that translates takes its node off the line it is
   threaded on, or takes the copy out of alignment with the other two. Colour
   and a ground carry the whole state and cost no layout. */

@media (hover:hover){
  /* The disc fills rather than grows. At 34px a scale-up shoulders the rail
     and the copy under it; the colour carries the state on its own and costs
     no layout. --purple-deep, NOT --purple: white on #904FFF is 4.42:1 and
     fails for a 13px numeral, where #7A00DF clears it at 7.26 - the same call
     brief-form.css records for its selected chips.

     The ring is box-shadow, not the ::after - that pseudo-element is the
     sweep's halo and borrowing it would mean the two states fighting over one
     property. A spread shadow also costs no layout at all, where inset:-4px
     on a second element would.

     LAVENDER, not --pale. Pale was the first call and it was wrong: the ring
     sits on --surface-alt, which is already near-white, so a pale ring on it
     was a gesture nobody could see. Lavender is the halo the sweep already
     uses on these same discs, and around a filled --purple-deep node it reads
     as an echo of the disc rather than as a second circle. 4px, not 5 - at 5
     the ring is as thick as the numeral is tall. */
  .tl-step:hover .tl-step-node{
    background:var(--purple-deep);color:var(--white);
    box-shadow:0 0 0 4px var(--lavender);
    /* LOAD-BEARING. The sweep sets transition-delay on this same element -
       .509 x 900ms on the middle node - and a delay is not cleared by a later
       rule that does not mention it. Without this line the disc waits up to
       half a second after the cursor arrives before it fills, which measures
       as correct and feels broken. A hover answers now. */
    transition-delay:0s;
  }
  .tl-step:hover h3{color:var(--plum)}
  /* --ash to --plum: the tag is the one line on the step that is a fact about
     the step rather than a description of it, so it is the one that should
     come up with the node. */
  .tl-step:hover .tl-step-meta{color:var(--plum)}
}

/* --- stacked, the rail turns ---
   A horizontal rule over one column is not a timeline, it is a line above a
   paragraph. Below 900 the rail stands up: the track runs down the left, the
   nodes ride it, and the copy is indented past them. Same three elements, same
   two pseudo-elements, rotated - no second markup and nothing hidden. */
@media (max-width:900px){
  .tl-steps{grid-template-columns:1fr;gap:var(--s8)}
  .tl-steps::before,
  .tl-steps::after{
    left:17px;right:auto;top:0;bottom:0;
    width:1px;height:auto;
  }
  /* Full length when it stands up, and --t goes back to rough thirds. The
     horizontal values are exact because three equal columns are a fixed
     geometry; stacked, each step is as tall as its own copy, so the nodes are
     wherever the paragraphs put them. The sweep is approximate here and cannot
     be otherwise without measuring - and at this width the rail is taller than
     the viewport, so no reader sees both ends of it at once to compare. */
  /* Same full length, turned. --t goes to rough thirds: the horizontal values
     are exact because three equal columns are a fixed geometry, but stacked
     each step is as tall as its own copy, so the nodes are wherever the
     paragraphs put them. Approximate here and cannot be otherwise without
     measuring - and at this width the rail is taller than the viewport, so
     nobody sees both ends of it at once to compare. */
  .tl-step:nth-child(1){--t:0}
  .tl-step:nth-child(2){--t:.4}
  .tl-step:nth-child(3){--t:.75}
  .tl-steps::after{transform:scaleY(1);transform-origin:top center}
  .tl-steps.is-arming::after{transform:scaleY(0)}
  .tl-steps.is-playing::after{transform:scaleY(1)}
  /* Same pair as the horizontal case, turned: the copy clears the node's
     diameter plus the same 20 of air, and the rail runs down the node's centre
     line rather than its edge. */
  .tl-step{padding-top:0;padding-left:calc(34px + var(--s5))}
  .tl-step-node{top:0}
  .tl-step p{max-width:56ch}
  .tl-step-meta{margin-top:var(--s1);padding-top:0}
}

@media (prefers-reduced-motion:reduce){
  .tl-steps.is-arming::after{transform:scaleX(1)}
  .tl-steps.is-arming .tl-step-node{background:var(--pale);color:var(--plum)}
  .tl-steps.is-arming .tl-step h3,
  .tl-steps.is-arming .tl-step p,
  .tl-steps.is-arming .tl-step-meta{opacity:1;transform:none}
  .tl-steps.is-playing .tl-step-node::after{animation:none}
  .tl-steps::after,.tl-step-node,.tl-step h3,.tl-step p,
  .tl-step-meta{transition:none}
}

/* The honesty line that used to close this section - no card, no trial, no
   sequence - is gone from here. It was the first FAQ answer two sections
   further down, in almost the same words, and the FAQ is where a reader looks
   for it. Said once. */




/* ==========================================================================
   05. Questions, as an accordion
   A real FAQ block, visible, and the same five questions are written into the
   FAQPage graph in the head of tools.html. THE RULE: neither is edited without
   the other. Structured data that describes an answer the page does not show is
   the one schema failure that is worth a manual penalty - which is also the
   first reason this is <details> rather than a scripted accordion. A closed
   <details> still has its answer in the DOM.

   NO GROUND, and that is a deliberate departure from the reference this was
   drawn against, which puts the whole set on a grey card. This page already
   spends its surfaces - the pale lead cell, the card token on the wall, the
   mist closer box - and a fourth block of tint would undo the reduction the
   page was cut down for two passes ago. The hairlines are the container. They
   are also the same hairlines .tl-how uses above, so two sections that are
   simply next to each other are divided the way the rest of the site divides
   them.

   CENTRED, heading and column both, and the text inside stays left. A centred
   paragraph is a readability failure at any width; a centred COLUMN is
   composition. It also turns the page to face the reader for the last two
   sections - the closer directly below is centred too - after four sections
   that run left.
   ========================================================================== */
.tl-faq{padding:var(--sec) 0;background:var(--surface)}
.tl-faq .wrap{padding-top:0;padding-bottom:0}

/* THE CLOSER WAS ARRIVING ON HALF THE AIR EVERY OTHER BOUNDARY ON THIS PAGE
   GETS. base.css sets .closer .wrap's top padding to 0 on the stated principle
   that the section above supplies the single gap - which is right, and which
   means the section above has to actually supply it. This one was supplying 48
   where the wall-to-how boundary is 104 and the how-to-questions boundary is 96.

   48 + 48 = 96, so the last boundary on the page is the same pair as the two
   before it and the closer stops reading as though it had slid up into the
   questions. Written as `.tl-faq + .closer` rather than by changing the closer,
   which is content-hub.css's own idiom for the same problem on index.html
   (`.hub + .closer .wrap{padding-top:var(--s14)}`) - the adjacent-sibling form
   keeps the value with the section that caused it and leaves the shared
   component alone.

   It matters more here than it would elsewhere because this section ends on a
   RULE. Everywhere else on this site the thing above the closer is a card or a
   button; a hairline 48px from the top edge of a filled box is two horizontal
   edges reading as one cramped pair. */
/* `.tl-faq + .closer .wrap{padding-top:var(--s12)}` - retired with the rest of
   the adjacent-sibling chain; .closer carries its own --sec-lg now. */
.tl-faq-h{margin:0 auto var(--s8);max-width:24ch;text-align:center}

.tl-faq-list{
  max-width:68ch;margin-inline:auto;
  /* Scoped here rather than on :root. It is what lets block-size animate to
     `auto` on open, and it inherits, so the five rows below are its whole
     reach - no other page and no other block on this one is affected. */
  interpolate-size:allow-keywords;
}
.tl-faq-item{border-top:1px solid var(--line)}
.tl-faq-item:last-child{border-bottom:1px solid var(--line)}

/* --- the row --- */
.tl-faq-q{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s5);
  padding:var(--s5) 0;
  cursor:pointer;
  /* Both, because the two engines hide the default triangle differently and
     neither alone covers the pair. */
  list-style:none;
  transition:color 180ms ease-in-out;
}
.tl-faq-q::-webkit-details-marker{display:none}
/* 20px top and bottom on a 26px line is a 66px row: comfortably past the 44px
   the rest of this site holds itself to, and the whole row is the target rather
   than the words in it. */
.tl-faq-q h3{margin:0;font-size:18px;line-height:26px;font-weight:600}
.tl-faq-q:hover h3{color:var(--plum)}
.tl-faq-item[open] > .tl-faq-q h3{color:var(--plum)}

/* --- the sign ---
   Two rules and a rotation rather than a glyph or an SVG: + and - are the same
   two strokes with one of them turned off, so the state change is a transform
   the compositor can take and there is no second icon to load or align. --ash,
   not the accent: it marks a control, it is not the point of the row. */
.tl-faq-sign{position:relative;flex:none;width:14px;height:14px}
.tl-faq-sign::before,
.tl-faq-sign::after{
  content:"";position:absolute;left:0;right:0;top:50%;
  height:1.5px;margin-top:-.75px;border-radius:1px;
  background:var(--ash);
  transition:transform 240ms cubic-bezier(.2,.7,.3,1);
}
.tl-faq-sign::after{transform:rotate(90deg)}
.tl-faq-item[open] .tl-faq-sign::after{transform:rotate(90deg) scaleX(0)}
.tl-faq-q:hover .tl-faq-sign::before,
.tl-faq-q:hover .tl-faq-sign::after{background:var(--plum)}

/* --- the answer --- */
.tl-faq-a{
  margin:0 0 var(--s6);max-width:62ch;
  color:var(--gray);font-size:16px;line-height:25px;
}

/* Progressive, and correct when it does nothing. ::details-content is recent;
   where it is not supported the panel snaps open, which is what a <details> has
   always done and is not a broken state. allow-discrete on content-visibility is
   what keeps the answer rendered for the length of the close, instead of it
   vanishing on frame one and animating an empty box. */
.tl-faq-item::details-content{
  block-size:0;overflow:hidden;
  transition:block-size 280ms cubic-bezier(.2,.7,.3,1),
             content-visibility 280ms allow-discrete;
}
.tl-faq-item[open]::details-content{block-size:auto}

/* Both locks again, the same pair the specimens keep. */
@media (prefers-reduced-motion:reduce){
  .tl-faq-item::details-content{transition:none}
  .tl-faq-sign::before,.tl-faq-sign::after{transition:none}
  .tl-faq-q{transition:none}
}


/* ==========================================================================
   06. The close - WHAT WAS THE REQUEST FORM
   This section styled #request: a select, the picked-tool line, and the paired
   market/email row. The form was removed from tools.html on 2026-08-28 (client
   direction; docs/brief.md carries the decision) and the rules went with it -
   a select with no select and a grid with no rows are dead weight in a sheet
   this page loads on every visit.

   The closer that replaced it needs no rules of its own. It is .closer /
   .closer-box from base.css with a button and a note in it, which is the same
   shape index.html's closer uses. .tl-req survives as the section's hook only
   because section 07 and the page's own margins reference it.

   Where a tool request goes now: brief.html, carrying ?tool= in the href. There
   is no form on this page to style.

   .tl-select SURVIVES THE CUT and is below, for the same reason .tl-route does
   at 03e: ai-visibility.html, daily-brief.html and tech-audit.html each have a
   market field wearing it, and each loads this sheet whole to get it. It has no
   user on tools.html any more. That is not a reason to delete it; it is a
   reason not to move it somewhere the three pages would have to follow.
   ========================================================================== */

/* The select brief-form.css has no component for, so it takes that sheet's own
   text-field values rather than a new look: same padding, same 1px --line edge,
   same --r-sm corner, same 16px floor that stops iOS zooming the viewport on
   focus. The caret is drawn in the background because a native select arrow
   renders differently on every platform. */
.tl-select{
  width:100%;
  padding:var(--s3) var(--s4);
  padding-right:var(--s10);
  border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--white);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='%23333' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3 5.5 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right var(--s4) center;
  background-size:14px 14px;
  color:var(--slate);font-family:var(--font);font-size:16px;line-height:24px;
  appearance:none;-webkit-appearance:none;
  cursor:pointer;
}
.tl-select:focus-visible{
  outline:var(--focus-w) solid var(--focus-c);outline-offset:var(--focus-in);border-color:var(--focus-c);
}

/* THE FOOTNOTE MOVED TO base.css AS .closer-note. It was .tl-req-note here for
   one day. index.html's closer grew the same element when its form went to
   contact.html, and a footnote that exists under two closers is a component of
   the closer rather than of this page - so it sits beside .closer, where the
   size and the lift of that closer's button already sit. Nothing about the
   values changed in the move; the reasoning for each is written there. */

/* ==========================================================================
   07. Reduced motion
   The page's only transitions are the card lift and the button wipe, both of
   which base.css already covers in its own block. This is here for the one
   thing that is local: the smooth scroll to the form when a tool is picked.
   tools.js reads the same query, so a visitor who has asked for less motion
   gets an instant jump rather than a 900px glide.
   ========================================================================== */
/* The card hover is pinned off in its own block, up with the layers that make
   it - this one used to also carry `.tl-card.is-live:hover{transform:none}`,
   which stopped matching anything the day the lift moved to `translate`. */
