/**
 * TiorAI tool card — the canonical shared card.
 *
 * ONE implementation, consumed by every surface that opts in through
 * tiorai_uses_shared_tool_card() (inc/tool-card.php):
 *
 *                       this file
 *                           |
 *              +------------+------------+
 *              |                         |
 *          /tools/            /tool-category/{term}/
 *
 * Scope is the `tr-tools-cards` BODY class, added by the same predicate that
 * routes the markup, so the stylesheet cannot reach a surface that is not
 * rendering this card. It replaces the `.tr-tools` page-wrapper scope the card
 * used while it existed only on `/tools/`: that wrapper is emitted by the child
 * archive template, and the taxonomy archive is the parent's template, which the
 * child does not own. A body class is the one scope both surfaces can carry
 * without a parent edit, and it is a single class, so every selector below keeps
 * the specificity it had.
 *
 * The grid selector is `:is(.tools-grid, .tr-archive__grid, .tr-tools__grid)` for the same reason:
 * `/tools/` names its results grid `tr-archive__grid` and also uses it for the
 * skeleton, the taxonomy archive names its two grids `tools-grid`, and `:is()`
 * takes the specificity of its most specific argument — one class — so the pair
 * costs nothing.
 *
 * Loads after the parent's tool-card.css / archive-layout.css / states.css
 * (declared as dependencies), so equal-specificity overrides win without
 * !important or selector inflation.
 *
 * Tokens only. The custom properties below are the card's own and use the
 * --tiorai- prefix required by CLAUDE.md 9.1; everything else resolves from
 * theme-unified.css so light and dark both follow the design system.
 */

/* ==========================================================================
   Card tokens
   Declared on the body scope rather than on a page wrapper, so the card carries
   its own measures onto any surface that adopts it. `.tr-tools` inherits them
   like everything else inside the document, which is why tools-archive.css no
   longer declares them.
   ========================================================================== */

.tr-tools-cards {
	/* The measure a page of these cards is laid out on. Shared, because the
	   column count the grid resolves is a function of it — see the gutter rule
	   in the taxonomy section below. */
	--tiorai-tools-measure: 1280px;
	--tiorai-tools-gutter: clamp(1rem, 4vw, 2rem);

	--tiorai-tools-ease: cubic-bezier(0.22, 1, 0.36, 1);

	/* Ring painted with the theme's focus token, not --color-primary. Brand
	   cyan measures 2.46:1 on white, under SC 1.4.11's 3:1 for a non-text
	   indicator; --tiorai-focus-ring-color is #00829e in light (4.48:1) and
	   flips back to brand cyan in dark, where cyan is 7.77:1. */
	--tiorai-tools-ring: 0 0 0 2px var(--surface-base), 0 0 0 4px var(--tiorai-focus-ring-color);

	/* Link text needs 4.5:1, which neither brand cyan (2.49:1) nor the focus
	   colour (4.48:1) reaches on white. #00758f is the next step down the same
	   cyan ramp and measures 5.39:1. In dark, brand cyan is already 7.77:1 on
	   #0B0C10, so the token points back at it rather than inventing a second
	   dark value. */
	--tiorai-tools-link: #00758f;

	/* The floating description panel's edge. Named here because the panel is
	   part of the card system even though it is appended to the body. */
	--tiorai-tools-box-border: var(--border-default);

	/*
	 * The card's one internal measure.
	 *
	 * The screenshot inset, the body's horizontal padding and the footer's seam
	 * all resolve from this, so every edge inside the card sits on the same
	 * vertical line and the picture and the text share a column. C-02 ran the
	 * media and the body on --space-4 by coincidence rather than by contract;
	 * naming it is what makes "increase the padding" a one-value change instead
	 * of four rules drifting apart.
	 */
	--tiorai-tools-card-inset: var(--space-5);

	/*
	 * The screenshot's own corner.
	 *
	 * It was --radius-lg, which resolves to 12px here — the SAME radius as the
	 * card around it. Two identical corner radii, one inset inside the other, is
	 * the whole reason the media read as a second card sitting inside the first.
	 * 4px keeps the picture from having hard corners against a rounded card
	 * without competing with the card's own shape: it reads as a plate, not a
	 * panel.
	 */
	--tiorai-tools-shot-radius: var(--radius-base);

	/* The screenshot frame. Screenshots are third-party website captures that
	   span the full brightness range, so the box behind one has to be a real
	   surface rather than the card colour: a near-white capture on a white card
	   has no edge at all. The hairline is painted with an inset shadow, not a
	   border, so it cannot change the box the aspect-ratio computes from. */
	--tiorai-tools-shot-bg: var(--color-bg-tertiary);
	--tiorai-tools-shot-edge: rgba(15, 23, 42, 0.08);

	/* The rule between the description and the footer. Lighter than
	   --border-default, which is the weight used for a box edge: this one
	   separates two parts of the same object, so it should read as a seam and
	   not as another boundary. */
	--tiorai-tools-sep: rgba(15, 23, 42, 0.09);

	/* The pricing chip sits on a third-party screenshot. Its background is one
	   of the A-09a badge tints, which are opaque in both themes, so the LABEL's
	   contrast is fixed regardless of the image. Its own outline is not: a cream
	   chip on a white landing-page capture has no edge. These two paint that
	   edge, and they are the only reason the shared badge is touched here. */
	--tiorai-tools-chip-ring: rgba(15, 23, 42, 0.10);
	--tiorai-tools-chip-shadow: 0 1px 2px rgba(15, 23, 42, 0.09);
}

[data-theme="dark"] .tr-tools-cards {
	--tiorai-tools-link: var(--color-primary);

	--tiorai-tools-box-border: rgba(255, 255, 255, 0.14);

	/* --color-bg-tertiary is #334155 in dark, a mid slate that reads as a filled
	   panel rather than a recess, and the hairline has to be light-on-dark. */
	--tiorai-tools-shot-bg: rgba(255, 255, 255, 0.06);
	--tiorai-tools-shot-edge: rgba(255, 255, 255, 0.12);

	--tiorai-tools-sep: rgba(255, 255, 255, 0.09);

	/* The dark chip tints are near-black, so on a bright screenshot the ring has
	   to be the light side of the pair for the edge to exist at all. */
	--tiorai-tools-chip-ring: rgba(255, 255, 255, 0.16);
	--tiorai-tools-chip-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* ==========================================================================
   Results grid
   auto-fill replaces the parent's four hard-coded column counts, so the track
   width stays inside a readable range at every viewport instead of only at the
   four the breakpoints happened to name.
   ========================================================================== */

.tr-tools-cards :is(.tools-grid, .tr-archive__grid, .tr-tools__grid) {
	display: grid;
	/* 230px is chosen, not rounded: at 250px a 900px tablet fell one pixel short
	   of a third track and dropped to two 382px cards, which is wider than the
	   desktop card. 230px holds four tracks on desktop and three on tablet. */
	grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
	gap: var(--space-6) var(--space-5);
	margin-bottom: var(--space-8);

	/* `start` sized every card to its own content, so a two-line tool name made
	   one card 22px taller than its neighbours and its footer sat 22px lower
	   than theirs across the row. Stretch equalises the row; .card-base is
	   already `height:100%` with `flex:1 1 auto` on the body, so the slack lands
	   above the footer and the footers line up. */
	align-items: stretch;
}

/*
 * List view is two cards per row on desktop, not one full-width row per tool.
 *
 * One row per tool put a 220px screenshot and ~60 characters of text on a
 * 1216px line and pushed the metadata to the far right edge, so the eye had to
 * cross 700px of nothing to connect a tool to its category. Two cards halve the
 * line and double the density without changing what a row contains.
 *
 * 440px is the measured floor, not a round number: a list card is a
 * ~34%-wide screenshot beside its text, so below 440 the text column falls under
 * ~230px — narrower than a GRID track at the same viewport, which makes the
 * horizontal layout strictly worse than the vertical one it is competing with.
 * auto-fill then resolves the column count per width instead of naming it:
 *
 *   1440 / 1280 → 1216px of content → 2 tracks (3 would need 1352)
 *   1024        →  960px            → 2 tracks at 472
 *    900        →  836px            → 1 track  (2 would need 896)
 *    768 -  360 →  706 - 331px      → 1 track
 *
 * So it falls to one column at 900, before the cards become cramped, without a
 * breakpoint having to know that.
 */
.tr-tools-cards :is(.tools-grid, .tr-archive__grid, .tr-tools__grid).tools-grid--list {
	/*
	 * `min(440px, 100%)`, not a bare 440px. A minmax floor is a hard floor: below
	 * 440px of container the single track stayed 440px wide and the cards hung
	 * out of the page — measured at 430, 390 and 360. Capping the floor at the
	 * container itself makes the last step down to one full-width column.
	 */
	grid-template-columns: repeat(auto-fill, minmax(min(440px, 100%), 1fr));
	gap: var(--space-4);
}

/* ==========================================================================
   Tool card
   Composition over the shared .card-base scaffold: surface, hover, stretched
   title link and image-failure fallback all still come from cards.css.
   ========================================================================== */

/*
 * The card is its own container.
 *
 * The footer's one-line layout works down to about 220px of card and is cramped
 * below it, but card width and viewport width are not the same axis and on this
 * archive they are not even ordered the same way: the grid puts four 167px
 * tracks on a 768px tablet and two 192px tracks on a 430px phone, so the
 * *tablet* card is the narrower of the two. The footer's breakpoint was keyed to
 * the viewport at 480px, which meant the widest phone card stacked and the
 * narrowest tablet card did not — the one that actually needed it.
 *
 * Keyed to the card instead, every track width gets the right answer without the
 * rule having to know which breakpoint produced it. `inline-size` contains the
 * inline axis only; the card is already `position: relative`, so the stretched
 * title anchor and the media's badge row still resolve against the same boxes.
 */
.tr-tools-cards .tr-tools-card {
	container-type: inline-size;
	container-name: tr-tools-card;
}

.tr-tools-cards .tr-tools-card {
	border-radius: var(--radius-default);
	overflow: hidden;

	/* No transform in the list. The card no longer moves on hover — with four
	   tracks per row a 2px lift under the pointer reads as the grid twitching,
	   and the feedback is carried by the border, the shadow and the 1.02 on the
	   screenshot instead. */
	transition:
		border-color 0.18s var(--tiorai-tools-ease),
		box-shadow 0.18s var(--tiorai-tools-ease);
}

/*
 * --shadow-sm, not --shadow-md. Twelve cards in view means twelve potential
 * shadows, and the pointer is only ever on one of them: the state has to be
 * legible on the card under the cursor, not visible from across the grid. The
 * border step carries the state; the shadow only lifts it off the page enough
 * to read as "this one".
 */
.tr-tools-cards .tr-tools-card:hover {
	transform: none;
	border-color: var(--color-border-hover);
	box-shadow: var(--shadow-sm);
}

/*
 * cards.css groups `:focus-within` with `:hover` and gives both
 * translateY(-4px), a brand-cyan border and --shadow-lg. The child only ever
 * overrode the `:hover` half, so tabbing to a card still jumped it four pixels
 * and painted a cyan edge underneath the focus ring — two competing focus
 * affordances on one element. The focus state is the ring below and nothing
 * else; this rule exists to cancel the inherited one.
 */
.tr-tools-cards .tr-tools-card:focus-within {
	transform: none;
	border-color: var(--color-border-hover);
	box-shadow: var(--shadow-sm);
}

/* The whole card is one link, so the ring belongs on the card, not on the
   text node inside it. `:has()` takes the specificity of its argument, so this
   outranks the :focus-within rule above and replaces its shadow. */
.tr-tools-cards .tr-tools-card:has(.card-base__title-link:focus-visible) {
	box-shadow: var(--tiorai-tools-ring);
}

.tr-tools-cards .tr-tools-card .card-base__title-link:focus-visible {
	outline: none;
}

/* ── Media ─────────────────────────────────────────────────────────────────
   Two changes, one problem. The screenshot was 16/9 and full-bleed to the card
   edge, so it was 161px of a 321px card — half the card, before any of the text
   had been read — and on a light capture (a white SaaS landing page, which most
   of these are) it had no visible edge against the white card at all.

   An inset picture with a shorter ratio fixes both: it is an object sitting on
   the card rather than the top half of it, and the inset is
   --tiorai-tools-card-inset, the same value the body's padding and the footer's
   seam resolve from, so the picture and the text share one measure.

   Grid variant only. .card-base--list sets `aspect-ratio: auto` on a 200px
   flex-basis media, and forcing a ratio on both variants made the list card's
   media compute its width from the stretched row height, overflowing the track
   and painting over the title. */
.tr-tools-cards .card-base--grid.tr-tools-card .card-base__media {
	/*
	 * 11/5, not 2/1. The body below it grew by a whole description line, a
	 * larger name and 4px of inset on each side, and the screenshot is the one
	 * element that can pay for that without losing information: the crop comes
	 * entirely off the bottom (object-position 50% 0), which on a landing-page
	 * capture is the footer. Measured at a 289px card: 124.5px -> 113.2px, and
	 * the picture's share of the card drops 45.2% -> 36%. Still a screenshot you
	 * can recognise a product from, no longer the thing you read first.
	 */
	aspect-ratio: 11 / 5;
	margin: var(--tiorai-tools-card-inset) var(--tiorai-tools-card-inset) 0;
	border-radius: var(--tiorai-tools-shot-radius);
	background: var(--tiorai-tools-shot-bg);
	box-shadow: inset 0 0 0 1px var(--tiorai-tools-shot-edge);
}

/*
 * ── List card ─────────────────────────────────────────────────────────────
 *
 * cards.css lays this variant out as `flex-direction: row`, and card-base emits
 * media, body and footer as three siblings — so the FOOTER became a third
 * column pinned to the far right of the row. That is what put the category and
 * the pricing label 700px away from the tool they describe, with dead space
 * between.
 *
 * Two columns and two rows fixes the composition without touching the markup:
 * the screenshot spans both rows on the left, and the text column holds the
 * body above the footer, exactly as it does in the grid card. Same slots, same
 * order, one layout rotated.
 *
 *   ┌────────┬──────────────────┐
 *   │        │ title            │
 *   │ media  │ description      │
 *   │        ├──────────────────┤
 *   │        │ category      →  │
 *   └────────┴──────────────────┘
 *
 * The media column is a percentage with both ends pinned rather than a fixed
 * 220px, because the same card renders at 600px (two-up desktop), 836px (one-up
 * tablet) and 331px (phone). A container query cannot do this — `@container`
 * styles descendants, never the container element itself, and the card IS the
 * container. clamp() reads: 34% of the card, never below 110px, never above
 * 230px. Measured: 204px at a 600px card, 160 at 472, 230 at 836, 112 at 331.
 */
.tr-tools-cards .card-base--list.tr-tools-card {
	display: grid;
	grid-template-columns: clamp(110px, 34%, 230px) minmax(0, 1fr);
	grid-template-rows: 1fr auto;
	grid-template-areas:
		"media body"
		"media footer";
}

.tr-tools-cards .card-base--list.tr-tools-card .card-base__media {
	grid-area: media;
	align-self: start;
	border-radius: var(--tiorai-tools-shot-radius);

	/*
	 * cards.css sets `aspect-ratio: auto` here and `16 / 9` again below 640px,
	 * so without this the media stretched to whatever height the row came out at
	 * — 200x173, near square — and `cover` answered by scaling a 1366x800
	 * screenshot 1.5x and cropping both sides off it. Every list row was showing
	 * the middle third of a website.
	 */
	aspect-ratio: 16 / 9;
	margin: var(--tiorai-tools-card-inset) 0 var(--tiorai-tools-card-inset) var(--tiorai-tools-card-inset);
	background: var(--tiorai-tools-shot-bg);
	box-shadow: inset 0 0 0 1px var(--tiorai-tools-shot-edge);
}

.tr-tools-cards .card-base--list.tr-tools-card .card-base__body {
	grid-area: body;
	padding: var(--tiorai-tools-card-inset) var(--tiorai-tools-card-inset) var(--space-4);
}

.tr-tools-cards .card-base--list.tr-tools-card .card-base__footer {
	grid-area: footer;
}

/*
 * Two lines here, three in the grid.
 *
 * Not a different treatment — same size, leading, colour and ellipsis — but the
 * two layouts have different shapes to fill. The grid card is a column and the
 * third line costs it 21px it can absorb; the list card is a row whose height
 * is already set by the text beside a fixed-ratio thumbnail, so a third line
 * pushes the text past the picture and opens a hole under it. Measured at a
 * 600px card: 2 lines leaves 6px between the media block and the footer, 3
 * leaves 48px of nothing.
 */
/* `.card-base--list.tr-tools-card`, not `.card-base--list`, so this outranks
   the three-line grid default by specificity rather than by source order —
   the body rules it has to beat are declared further down the file. */
.tr-tools-cards .card-base--list.tr-tools-card .card-base__excerpt {
	-webkit-line-clamp: 2;
	min-height: calc(2 * 1.5 * var(--font-size-sm));
}

/*
 * Every screenshot in the library is 1366x800, so a 2/1 window crops 14.6% of
 * the height. Centred, that came off the top and the bottom equally and took
 * the site's own header with it. Anchored to the top the whole crop comes off
 * the footer, which is the part of a landing-page capture that identifies
 * nothing.
 */
.tr-tools-cards .tr-tools-card .card-base__media-img {
	object-position: 50% 0;
	transition: transform 0.35s var(--tiorai-tools-ease);
}

/*
 * TiorAI.MediaFallback hides a failed image with the `hidden` ATTRIBUTE and
 * reveals the initials avatar beside it. `hidden` is a UA-stylesheet
 * `display: none`, and cards.css sets `.card-base__media-img { display: block }`
 * — any author declaration beats the UA one, so the hidden image kept its full
 * 127px box. The media box then held a zero-content image *and* the avatar, and
 * the avatar the box was supposed to centre ended up wedged in the corner.
 * Measured: img height 127.5 with hidden="true".
 *
 * Scoped to this archive rather than fixed in cards.css, which is a Main Theme
 * file and out of scope here. Worth raising there — every card in the theme
 * inherits the same behaviour.
 */
.tr-tools-cards .tr-tools-card .card-base__media-img[hidden] {
	display: none;
}

/*
 * The other half of the same defect, found when list view stopped being 300px
 * tall. card-base emits the initials avatar beside every real image, carrying
 * the `hidden` ATTRIBUTE, for TiorAI.MediaFallback to reveal on a 404. But
 * cards.css sets `.card-base__avatar { display: flex }`, and an author
 * declaration beats the UA stylesheet's `[hidden] { display: none }` — so the
 * "hidden" fallback was in flow on every card that HAS an image, sitting 56px
 * below the picture. In the tall grid card it was clipped by the media box's
 * own `overflow: hidden` and invisible; in a 142px list card it hangs 35.9px
 * past the bottom of the card.
 *
 * Nothing about the fallback changes: MediaFallback reveals it by REMOVING the
 * attribute, so the moment it is needed this rule stops matching. Same scope
 * and same reasoning as the rule above; worth raising in cards.css, which is a
 * Main Theme file and out of scope here.
 */
.tr-tools-cards .tr-tools-card .card-base__avatar[hidden] {
	display: none;
}

/* cards.css scales the image to 1.05 on hover, which at this size visibly
   re-crops the picture. 1.02 is enough to register as a response. */
.tr-tools-cards .tr-tools-card:hover .card-base__media-img,
.tr-tools-cards .tr-tools-card:focus-within .card-base__media-img {
	transform: scale(1.02);
}

/*
 * The media box holds one of three things and only the first is a screenshot.
 * `cover` is right for a 1366x800 capture and wrong for a square brand logo,
 * which it crops to a strip of one letter. The card declares which it rendered
 * (see template-parts/shared/tool-card.php) so the logo case can be fitted instead of cropped.
 */
.tr-tools-cards .tr-tools-card[data-tiorai-media="logo"] .card-base__media-img {
	object-fit: contain;
	padding: var(--space-4);
}

.tr-tools-cards .tr-tools-card[data-tiorai-media="logo"] .card-base__media {
	background: var(--surface-base);
}

/* ── Pricing overlay ───────────────────────────────────────────────────────
   One label, top corner of the screenshot, identical box in every pricing
   state. It is the shared `.badge` component in card-base's own media slot —
   cards.css already positions, caps and ellipses that row, and A-09a already
   made every tint opaque precisely because it paints over a tool screenshot.
   Four declarations are added on top of that and nothing else:

     1. TOP-END, not top-start. cards.css anchors the row to the inline start,
        which on a landing-page capture is the site's own logo — the one thing in
        a screenshot that identifies the tool. The end corner holds navigation
        and a sign-up button, which identify nothing.
     2. 8px from the media edge instead of 12. The media box is already inset
        16px from the card, so 12 put the chip 28px in and floating; 8 reads as
        sitting on the picture.
     3. A hairline ring and a 1px shadow. The chip's TEXT contrast is fixed by
        the opaque tint, but its own outline is not — a cream chip on a white
        SaaS landing page has no edge at all. This is the whole reason the shared
        badge is touched here.
     4. Tighter horizontal padding, because at this size the default 12px made a
        two-word tier ("Custom Pricing") wide enough to read as a button.
   ========================================================================== */

.tr-tools-cards .tr-tools-card .card-base__media .card-base__badges {
	top: var(--space-2);
	inset-inline-start: auto;
	inset-inline-end: var(--space-2);
	max-inline-size: calc(100% - var(--space-2) * 2);
}

/*
 * Keyed to the pricing class, not to `.badge` inside the media, so the chip
 * keeps its treatment in the one case card-base renders badges in the BODY
 * instead: a tool with no screenshot, no logo and no initials to fall back on.
 */
.tr-tools-cards .tr-tools-card .tr-tools-card__price {
	/*
	 * The box tightens; the text does not. 12px is where A-09b deliberately put
	 * the shared badge for legibility and dropping back to 10px would undo an
	 * accessibility fix to win a visual argument. The block padding is the part
	 * that was making the chip loud: 4px top and bottom gave it a 24.8px box,
	 * taller than the 22px category link at the other end of the card. 2px
	 * brings it to 20.8px, so it reads as a tag on the picture rather than a
	 * control sitting on it.
	 */
	padding-block: 2px;
	padding-inline: var(--space-2);
	letter-spacing: 0;
	box-shadow: 0 0 0 1px var(--tiorai-tools-chip-ring), var(--tiorai-tools-chip-shadow);
}

/* ── Body ────────────────────────────────────────────────────────────────── */

/*
 * The gap IS the title-to-description spacing: the body has exactly two
 * children. 12px rather than 8px, because at 18px the name now needs air under
 * it to read as its own rank rather than as the first line of the paragraph.
 *
 * Top padding matches the side inset; bottom does not. The gap under the
 * picture has to be at least as large as the gutter beside the text, or the
 * title reads as crowded against the image instead of starting a new block —
 * measured at 16px it was visibly tighter than the 20px it sat between. The
 * bottom is 16px because the seam below it contributes its own space, and
 * making that gap 20px as well pushed the footer away from the paragraph it
 * belongs to.
 */
.tr-tools-cards .tr-tools-card .card-base__body {
	gap: var(--space-3);
	padding: var(--tiorai-tools-card-inset) var(--tiorai-tools-card-inset) var(--space-4);
}

/*
 * The name is the card.
 *
 * 17px/600 was a bespoke value invented because the scale "had no step between
 * 16 and 18" — but the shared card component already answers this: DESIGN.md
 * specifies `.card-base__title` at 18px/700, and --font-size-lg is that 18px.
 * The bespoke token is gone and the title is back on the scale.
 *
 * 18px/700 against a 14px/400 description is a 1.29 size ratio and a 300-unit
 * weight step, clear of the 1.25 the type scale needs for a step to read as a
 * step. DESIGN.md's own Flat Scale Rule says this system carries hierarchy in
 * weight rather than size, which is why the weight moves with the size.
 */
.tr-tools-cards .tr-tools-card .card-base__title {
	margin: 0;
	font-size: var(--font-size-lg);
	font-weight: var(--font-weight-bold);
	line-height: 1.28;
	letter-spacing: -0.02em;
	color: var(--color-text-primary);
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

/*
 * Three lines, not two. A two-line clamp at this width stops mid-clause on
 * essentially every entry — measured, 12 of 12 cards on the first page were
 * clipped — so the card was showing that a description exists without showing
 * what the tool does. The third line is the one that usually completes the
 * sentence.
 *
 * min-height reserves all three whether or not they are used, so a short
 * description cannot pull its footer up the card and break the row.
 *
 * 1.5 rather than 1.55: three lines multiply the leading, and the tighter
 * figure buys back 2px per card without the paragraph reading as set solid.
 */
.tr-tools-cards .tr-tools-card .card-base__excerpt {
	margin: 0;
	font-size: var(--font-size-sm);
	line-height: 1.5;
	min-height: calc(3 * 1.5 * var(--font-size-sm));
	color: var(--color-text-tertiary);
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

/* ── Separator + footer ──────────────────────────────────────────────────────
   The footer stopped being a metadata strip and became a navigation row:
   primary category on one side, a direction cue on the other, with a seam above
   it. The seam is inset to the body's own 16px measure rather than run to the
   card edge, so it lines up with the screenshot frame and the text and reads as
   one column of content divided, not as the card being cut in half.
   ========================================================================== */

/*
 * The seam runs on the same 20px measure as the picture above it and the text
 * beside it, so it reads as a rule drawn across one column rather than as the
 * card being cut in half.
 *
 * Space above it comes from the body's own bottom padding (14px); space below
 * is this rule's 20px, which is also the card's last measure — the footer's
 * bottom padding — so the row sits centred in its own band and the inset closes
 * on all four sides at the same 20px the seam is drawn to.
 */
.tr-tools-cards .tr-tools-card .card-base__footer {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	margin-inline: var(--tiorai-tools-card-inset);
	padding: var(--space-5) 0 var(--tiorai-tools-card-inset);
	border-top: 1px solid var(--tiorai-tools-sep);
}

/*
 * Quiet at rest — no underline, so the footer row reads as plain meta text
 * rather than as a second link competing with the card title. The link
 * affordance is carried on interaction instead: colour plus an underline on
 * hover/focus, so it is still discoverable as a link and focus stays visible.
 *
 * `flex: 0 1 auto` with `min-width: 0`: the category is the only thing in the
 * row that can give up space, and it gives it up by ellipsing rather than by
 * pushing the arrow past the card edge.
 */
.tr-tools-cards .tr-tools-card__cat {
	flex: 0 1 auto;
	min-width: 0;
	font-size: var(--font-size-xs);
	font-weight: var(--font-weight-medium);
	color: var(--color-text-tertiary);
	text-decoration: none;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	transition: color 0.15s var(--tiorai-tools-ease);
}

.tr-tools-cards .tr-tools-card__cat:hover,
.tr-tools-cards .tr-tools-card__cat:focus-visible {
	color: var(--tiorai-tools-link);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
}

/*
 * The direction cue. `margin-inline-start: auto` rather than
 * `justify-content: space-between` on the row, so it stays pinned to the end
 * edge even on the cards that have no category at all.
 *
 * Not a button, not a link, no `.card-base__action` — it sits under the
 * stretched title anchor, so a click on it is a click on the card. It answers
 * the pointer together with the rest of the card (2px of travel, no colour
 * change), which is what makes it read as part of one surface rather than as a
 * control of its own.
 */
.tr-tools-cards .tr-tools-card__go {
	flex: 0 0 auto;
	display: inline-flex;
	margin-inline-start: auto;
	color: var(--color-text-tertiary);
	transition:
		transform 0.18s var(--tiorai-tools-ease),
		color 0.18s var(--tiorai-tools-ease);
}

.tr-tools-cards .tr-tools-card:hover .tr-tools-card__go,
.tr-tools-cards .tr-tools-card:focus-within .tr-tools-card__go {
	transform: translateX(2px);
	color: var(--color-text-primary);
}

/* ── Full description on pointer / keyboard ─────────────────────────────────
   One floating panel for the whole grid, built and moved by
   TiorAI.ToolCard — not one hidden copy of the text per card. The card's
   own <p> already holds the complete sentence and the clamp is presentation
   only, so a screen reader reads it in full without this and a crawler indexes
   it without this. This is a sighted-pointer/keyboard convenience and nothing
   else, which is why it is `aria-hidden` and `pointer-events: none`: announcing
   it would read the same sentence a second time, and catching the pointer would
   let it cover the card it belongs to.

   `hidden` (the attribute) is the off state, so the panel costs no layout and
   no paint until something asks for it.
   ========================================================================== */

.tr-tools-tip {
	position: fixed;
	z-index: 60;
	max-width: 20rem;
	padding: var(--space-3) var(--space-4);
	border: 1px solid var(--tiorai-tools-box-border);
	border-radius: var(--radius-lg);
	background: var(--surface-base);
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
	line-height: 1.5;
	box-shadow: var(--shadow-lg);
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.12s var(--tiorai-tools-ease);

	/* A cap, so the affordance cannot become the page. Eight lines is the whole
	   of every excerpt the 42-word trim can produce; the clamp is a backstop for
	   a term or overview that somehow exceeds it. */
	display: -webkit-box;
	-webkit-line-clamp: 8;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.tr-tools-tip[hidden] {
	display: none;
}

.tr-tools-tip.is-visible {
	opacity: 1;
}

/*
 * The footer no longer stacks on a narrow card.
 *
 * C-02 stacked it below 219px because a category and a pricing pill could not
 * share 167px. The pricing pill has moved onto the screenshot, so the row now
 * holds a category and a 16px glyph — and stacking those would put an arrow on
 * a line of its own, which is not a row, it is a stray icon. One line at every
 * width, with the category ellipsing into whatever is left.
 *
 * The container is kept: the list card's description still keys off it below.
 */

/*
 * ── Narrow cards ───────────────────────────────────────────────────────────
 *
 * All three rules below are keyed to the CARD, not the viewport, because on
 * this archive the two axes are not even ordered the same way: the grid puts
 * four 168px tracks on a 768px tablet and two 192px tracks on a 430px phone, so
 * the *tablet* card is the narrower of the two. Every viewport-keyed type rule
 * here had that backwards — the 480px breakpoint hid the description on the
 * WIDER card and left three ribbons of ~19 characters on the narrower one.
 *
 * The thresholds are measured against the one thing that matters, characters
 * per line at 14px Inter:
 *
 *   card 240px  ->  text column 200px  ->  ~30 characters   (a short sentence)
 *   card 200px  ->  text column 160px  ->  ~24 characters
 *   card 168px  ->  text column 144px  ->  ~21 characters   (a fragment)
 *
 * The list card is not in these rules. It spends ~34% of its width on the
 * thumbnail but is one-up from 900px down, so its text column is the WIDER of
 * the two on a phone: 221px at a 396px card against 144px for the grid tile
 * beside it. It keeps its two lines all the way down.
 */

/*
 * Below ~30 characters a line, three lines read as three fragments rather than
 * a sentence — so the card drops to two, which at 21 characters still lands
 * around seven words ("MobileMonkey is an AI-powered chatbot…"). Seven words of
 * what a tool does beats none.
 *
 * It drops a line; it never hides the description. C-02 and the pass before
 * this one removed it outright below 480px of VIEWPORT, which was wrong twice
 * over: it keyed off the wrong axis, and it answered "this text is cramped" by
 * deleting the only sentence on the card explaining what the tool is.
 */
@container tr-tools-card (max-width: 239px) {
	.tr-tools-cards .card-base--grid.tr-tools-card .card-base__excerpt {
		-webkit-line-clamp: 2;
		min-height: calc(2 * 1.5 * var(--font-size-sm));
	}
}

/*
 * 18px is the right size for a 289px card and the wrong one for a 168px card,
 * where "NinjaOutreach" alone measures 128px and every second name takes two
 * lines. One step down the scale, and the inset comes in with it so the name
 * has the width back. The weight does not change: it is what carries the
 * hierarchy once the size can no longer.
 */
@container tr-tools-card (max-width: 199px) {
	.tr-tools-cards .tr-tools-card .card-base__title {
		font-size: var(--font-size-base);
		letter-spacing: -0.015em;
	}

	.tr-tools-cards .card-base--grid.tr-tools-card .card-base__media {
		margin: var(--space-3) var(--space-3) 0;
	}

	.tr-tools-cards .card-base--grid.tr-tools-card .card-base__body {
		gap: var(--space-2);
		padding: var(--space-3) var(--space-3);
	}

	.tr-tools-cards .card-base--grid.tr-tools-card .card-base__footer {
		margin-inline: var(--space-3);
		padding-bottom: var(--space-3);
	}
}

/* Skeleton. Same geometry as the card it stands in for, so the grid does not
   reflow when the real results replace it — which means it resolves from the
   same inset, the same media ratio and the same media radius, not from copies
   of their values. Every one of those drifted the moment the card changed. */
.tr-tools-card--skeleton {
	border: 1px solid var(--border-default, var(--color-border));
	border-radius: var(--radius-default);
	background: var(--surface-card, var(--color-bg-primary));
	overflow: hidden;
}

.tr-tools-card--skeleton .tr-tools-card__media {
	aspect-ratio: 11 / 5;
	margin: var(--tiorai-tools-card-inset) var(--tiorai-tools-card-inset) 0;
	border-radius: var(--tiorai-tools-shot-radius);
}

.tr-tools-card--skeleton .tr-tools-card__body {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	padding: var(--tiorai-tools-card-inset);
}

.tr-tools-card__skel-line {
	height: 10px;
	border-radius: var(--radius-base);
}

/* Matches the 18px title's box, so the placeholder sits where the name will. */
.tr-tools-card__skel-line--title {
	height: 18px;
	width: 60%;
}

.tr-tools-card__skel-line--short {
	width: 40%;
}

/* ==========================================================================
   Empty state inside the grid
   Spans the grid rather than inheriting one column, which is what put the
   parent's "No tools found" in a 300px strip on an otherwise blank page. A
   descendant combinator, not a child one: the AJAX endpoint returns this block
   as a direct child of the grid, but Top Picks wraps its items, so the card
   system never assumes a grid child is a card.
   ========================================================================== */

.tr-tools-cards :is(.tools-grid, .tr-archive__grid, .tr-tools__grid) .no-tools {
	grid-column: 1 / -1;
	max-width: 60ch;
	padding-block: var(--space-8);
	text-align: start;
}

/* ==========================================================================
   Taxonomy archive — the two differences that are genuinely surface-specific
   ========================================================================== */

/*
 * "Top Picks" marks its entries with an absolutely-positioned badge at the top
 * END corner of the item — which is exactly where the card puts its pricing
 * chip, so the two stacked. The badge moves to the START corner and onto the
 * same coordinates the chip resolves from, so the pair reads as one row across
 * the top of the screenshot instead of two labels fighting for one corner.
 *
 * This is the one place a `.tc-*` selector appears in the card system, and it
 * is here rather than in the card because the badge is not part of the card: it
 * belongs to the section around it and exists on no other surface.
 */
.tr-tools-cards .tc-top-pick-item .tc-top-pick-badge {
	top: calc(var(--tiorai-tools-card-inset) + var(--space-2));
	inset-inline-start: calc(var(--tiorai-tools-card-inset) + var(--space-2));
	inset-inline-end: auto;
	right: auto;
}

/*
 * Top Picks declares four fixed columns and the results grid below it resolves
 * its own count from a 230px track, so at the same viewport the two grids put
 * different-width cards on the same page. The section keeps its own gap; only
 * the track sizing is shared.
 */
.tr-tools-cards .tc-top-picks__grid {
	grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}

/*
 * The gutter — the one composition fix the card adoption needed (§9).
 *
 * The two archives sit in boxes of identical width, and then differ inside it.
 * `/tools/` lays out on `.tr-archive__container`, a 1280px measure with a
 * `clamp(1rem, 4vw, 2rem)` gutter. The taxonomy archive lays out on the parent's
 * shared `.container`, whose desktop padding is `--space-8` — and `--space-8` is
 * the token DESIGN.md flags as redefined from 2rem to 8px by a later `:root`
 * block, so the gutter resolves to 8px rather than 32.
 *
 * 24px per side is not a cosmetic difference here, because the grid resolves its
 * column count from the width it is given. Measured at 1440 before this rule:
 *
 *              content width   tracks   card    description
 *   /tools/         1216px       4       289px   3 lines
 *   taxonomy        1264px       5       237px   2 lines  (under the 239px
 *                                                          container query)
 *
 * So 48px of container produced a card 18% narrower, a screenshot 20% shorter
 * and one less line of description — the exact "different card on the taxonomy
 * page" this adoption exists to remove, arriving through layout rather than
 * through CSS.
 *
 * The correction is one declaration, and it is the gutter rather than the grid
 * because a grid capped on its own would sit inset from the toolbar and the
 * heading above it. All three of the archive's own sections take it together, so
 * the H1, the subcategory grid, Top Picks and the results stay on one left edge.
 * Nothing else about them changes, and the SEO sections below run on
 * `.section-container`, which is a different measure already and is not touched.
 *
 * Fixing `--space-8` at source is the real repair; it lives in the parent's
 * theme-unified.css and is out of scope here.
 */
.tr-tools-cards :is(.tools-archive__header, .tc-top-picks-section, .tools-archive__content) > .container {
	max-width: var(--tiorai-tools-measure);
	padding-inline: var(--tiorai-tools-gutter);
}

/* ==========================================================================
   Responsive — track width only
   Card typography and inset are NOT touched here. Every one of those rules is a
   `@container tr-tools-card` query above, because a viewport width does not tell
   you how wide a card is: a 768px tablet holds four 168px tracks and a 430px
   phone holds two 192px ones.
   ========================================================================== */

@media (max-width: 768px) {
	.tr-tools-cards :is(.tools-grid, .tr-archive__grid, .tr-tools__grid),
	.tr-tools-cards .tc-top-picks__grid {
		grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
		gap: var(--space-4) var(--space-3);
	}
}

@media (max-width: 480px) {
	.tr-tools-cards :is(.tools-grid, .tr-archive__grid, .tr-tools__grid),
	.tr-tools-cards .tc-top-picks__grid {
		grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
	}
}

/* The screenshot's hover scale and the footer arrow's 2px of travel are the
   card's only movement, so they are what this block has to cancel. Zeroing the
   duration alone left both jumping in a single frame, which is the same movement
   without the easing that made it readable. The arrow's colour change is kept:
   it is the state, and it does not move. */
@media (prefers-reduced-motion: reduce) {
	.tr-tools-cards .tr-tools-card,
	.tr-tools-cards .tr-tools-card *,
	.tr-tools-cards .tr-tools-tip {
		transition-duration: 0.01ms !important;
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
	}

	.tr-tools-cards .tr-tools-card:hover {
		transform: none;
	}

	.tr-tools-cards .tr-tools-card:hover .card-base__media-img,
	.tr-tools-cards .tr-tools-card:focus-within .card-base__media-img,
	.tr-tools-cards .tr-tools-card:hover .tr-tools-card__go,
	.tr-tools-cards .tr-tools-card:focus-within .tr-tools-card__go {
		transform: none;
	}
}
