/* One file per family covering every weight — 69 KB for both, against 195 KB
   for the three static DM Sans TTFs this replaces. font-display:swap so text
   paints immediately in the fallback and reflows once, rather than sitting
   invisible while the font loads. */
@font-face{
  font-family:'Inter';
  src:url('https://thedesigntools.site/wp-content/uploads/2026/08/inter-var-latin.woff2') format('woff2');
  font-weight:100 900; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Space Grotesk';
  src:url('https://thedesigntools.site/wp-content/uploads/2026/08/space-grotesk-var-latin.woff2') format('woff2');
  font-weight:300 700; font-style:normal; font-display:swap;
}
/* Light theme is the bare :root. Three values were changed on
   13 Aug 2026 because they failed WCAG AA contrast, measured against
   the surface each is actually painted on:
     --faint  #A1A1AA on #FFFFFF  2.56:1  -> #6B7280  4.83:1
     --deal   #B4530A on #FDF0E3  4.48:1  -> #9A4708  5.72:1
     --free   #0B8A57 on #E7F6EF  3.93:1  -> #076B43  5.90:1
   --faint was the worst: it sets dates, captions and card meta text
   on every page of the site. */
:root{
  --ink:#171717;
  --ink-2:#3F3F42;
  /* Was #71717A, which measured 4.83:1 on white — exactly the same step as
     --faint (#6B7280, also 4.83:1). Two tokens, two different palette
     families (zinc and gray), one visible weight: the three-tier grey
     hierarchy existed in the token names but not on screen. Darkened so the
     tiers are actually distinguishable, and all three still clear AA:
     --ink-2 10.49 -> --sub 6.82 -> --faint 4.83. Dark mode already had a
     real step (6.81 -> 5.09) and is unchanged. */
  --sub:#5A5A63;
  --faint:#71717A;   /* zinc, matching --sub's family */
  --paper:#FFFFFF;
  --canvas:#F7F7F7;
  --line:#E6E6E6;
  --line-2:#EFEFEF;
  /* Subtle inset tint for placeholder surfaces: the "coming soon" deal cards,
     their banner gradient and the soon chip. These were hard-coded to
     #F1F1F3 / #f0f0f2 / rgba(247,247,247,.5), which stayed near-white in dark
     mode while the text on them switched to the dark palette — the partner
     "coming soon" cards were the worst of it, effectively white-on-white. */
  --wash:#FAFAFB;
  /* --line is a hairline for dividing content, and at 1.25:1 it is far below
     the 3:1 WCAG 1.4.11 requires for the BOUNDARY of an interactive control.
     Filter chips used it and were barely distinguishable from the page.
     --control-line is the same idea at a contrast that actually qualifies. */
  --control-line:#8A8A94;   /* 3.42:1 on --paper */
  /* --accent flips to a LIGHT blue in dark mode, so white-on-accent silently
     drops from 5.55:1 to 2.62:1 there. Any button painted with --accent must
     take its text colour from this, never a literal #fff. */
  --accent-ink:#FFFFFF;
  --line-hover:#D4D4D8;
  --accent:#2454FF;
  --accent-ink:#FFFFFF;
  --free:#076B43;
  --free-bg:#E7F6EF;
  --deal:#9A4708;
  --deal-bg:#FDF0E3;
  --sponsored:#7C3AED;
  --sponsored-bg:#F3E8FF;
  --oss:#0F766E;
  --oss-bg:#E6F4F1;
  /* In-house reuses --accent as its foreground rather than a new colour —
     it marks our own product, so our own brand blue is the correct colour
     for it, not a fourth arbitrary hue alongside free/deal/sponsored/oss.
     Only the background tint is new, sized the same pale way as those. */
  --inhouse-bg:#EAF0FF;
  --glass:rgba(255,255,255,.72);
  --glass-line:rgba(23,23,23,.08);
  /* --ink was doing two jobs: text colour AND the dark surface behind solid
     pills and the footer. Those coincide in light mode and must diverge in
     dark, so the surface roles get their own tokens. Without this the footer
     background flipped to near-white while its text stayed white. */
  --solid:#171717;          /* high-contrast pill: Submit a Tool, active chips */
  --solid-ink:#FFFFFF;
  --cta:#1E40AF;            /* brand-blue primary pills (Newsletter, hero CTAs, search Go) */
  --cta-ink:#FFFFFF;
  --header-bg:rgba(255,255,255,.78);
  --header-line:rgba(255,255,255,.9);
  --header-hover:rgba(23,23,23,.05);
  --footer-bg:#171717;
  --footer-ink:#FFFFFF;
  --footer-muted:#8A8A93;
  --footer-head:#8A8A93;    /* was #71717A — 3.71:1, fails AA at 10px */
  --footer-link:#D4D4D8;
  --footer-line:#27272A;
  --scrim:rgba(255,255,255,.92);
  --hero-wash:1;
  --hero-grid:rgba(23,23,23,.055);
  /* Glass system. The spec's rgba(255,255,255,.12) fill assumes a dark or
     vivid backdrop; over a near-white page it is invisible and text loses
     its ground. Each theme therefore gets a fill tuned to its own surface
     while keeping the same edge-light construction. */
  --glass-fill:rgba(255,255,255,.62);
  --glass-border:rgba(255,255,255,.75);
  --glass-hi:rgba(255,255,255,.85);      /* inset top edge — specular */
  --glass-shadow:0 20px 40px rgba(23,23,23,.10);
  --glass-blur:blur(20px) saturate(180%);
  --ease-spring:cubic-bezier(.16,1,.3,1);
  --r-glass:22px;
  --noise-opacity:.035;
  --noise-blend:multiply;
  --r-sm:8px; --r:12px; --r-lg:20px; --r-xl:24px;

  /* ---------------------------------------------------- typography system
     Two families, deliberately. Space Grotesk has real character in its
     numerals and wide apertures, which reads well large — and gets fussy
     small. Inter is the opposite: engineered for UI at small sizes. So the
     split is by SIZE, not by semantics: display type is Grotesk, everything
     a person actually reads is Inter. Card titles stay Inter for that reason,
     even though they are headings.

     Fallbacks are named explicitly. Four rules previously said
     font-family:"DMSans" with no stack at all, which drops to Times if the
     face fails to load. */
  --font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --font-display:'Space Grotesk','Inter',-apple-system,BlinkMacSystemFont,sans-serif;
  --font-mono:ui-monospace,'SF Mono',Menlo,Consolas,monospace;

  /* Type scale. The site had 16 distinct hardcoded sizes between 9.5px and
     58px, several a half-pixel apart and visually indistinguishable. This is
     a ~1.25 scale, rounded to whole pixels. */
  --fs-2xs:11px;   /* tags, chips, uppercase micro-labels */
  --fs-xs:12px;    /* dense meta */
  --fs-sm:13px;
  /* 14px is used in ~14 places but had no token, which is how the scale
     drifted in the first place — an unnamed size gets copied as a literal. */
  --fs-sm-plus:14px;    /* secondary text, form labels */
  --fs-base:15px;  /* card body, default UI text */
  --fs-md:16px;    /* prose, lede */
  --fs-lg:19px;    /* card and block headings */
  --fs-xl:22px;    /* section headings */
  --fs-2xl:28px;   /* statistics, major headings */
  --fs-3xl:32px;   /* page titles */
  --fs-display:clamp(36px,6vw,58px); /* hero only */

  /* Weights. Space Grotesk tops out at 700, so the 750 and 650 values used
     before could not render in the display face and silently snapped. */
  --fw-normal:400; --fw-medium:500; --fw-semi:600; --fw-bold:700;

  /* Optical letter-spacing: large type needs negative tracking to avoid
     looking loose, uppercase micro-labels need positive to stay legible. */
  --ls-display:-.035em; --ls-heading:-.022em; --ls-body:-.006em; --ls-caps:.06em;

  --lh-display:1.08; --lh-heading:1.25; --lh-body:1.55; --lh-prose:1.65;

  /* Spacing scale on a 4px grid. --gutter and --rhythm remain the responsive
     page-level values; these are for components. */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px;
  --s-5:24px; --s-6:32px; --s-7:48px; --s-8:64px;
  --shadow:0 1px 2px rgba(23,23,23,.04), 0 8px 24px rgba(23,23,23,.06);
  --shadow-lg:0 2px 4px rgba(23,23,23,.04), 0 16px 40px rgba(23,23,23,.10);
  /* Widened from 1280 on 19 Aug 2026 — at 1920px the site content column
     was leaving ~37% of the screen as idle side margins. 1440 is used by
     both the plain WP pages (line ~973) and, via the .e-con override
     below, every Elementor-built page — one number governs both template
     types, so they stay in step with each other. */
  --max:1440px;
}

/* ---------------------------------------------------------- dark theme
   The whole site already styles through these variables, so redefining
   the token values here gives every page dark mode without touching a
   single component rule. Every foreground below was measured against
   --paper #151A24 and clears 4.5:1:
     ink 14.74  ink-2 10.51  sub 6.81  faint 5.09  accent 6.64
     free 10.00  deal 8.72  sponsored 8.50  oss 11.78
   --accent shifts from #2454FF (only 3.14:1 on the dark surface) to a
   lighter blue; the brand blue stays exactly as-is in light mode. */
:root[data-theme="dark"]{
  --ink:#E9ECF4;
  --ink-2:#C3C9D8;
  --sub:#9AA2B5;
  --faint:#828B9E;
  --paper:#151A24;
  --canvas:#0B0D12;
  --line:rgba(255,255,255,.10);
  --line-2:rgba(255,255,255,.06);
  --wash:rgba(255,255,255,.07);
  --control-line:#6B7488;   /* 3.72:1 on --paper in dark */
  --accent-ink:#0B0D12;
  --line-hover:rgba(255,255,255,.28);
  --accent:#7D9BFF;
  --accent-ink:#0B0D12;
  --free:#4ADE80;
  --free-bg:rgba(74,222,128,.13);
  --deal:#F0A868;
  --deal-bg:rgba(240,168,104,.13);
  --sponsored:#C4A6FF;
  --sponsored-bg:rgba(196,166,255,.14);
  --oss:#5EEAD4;
  --oss-bg:rgba(94,234,212,.13);
  --inhouse-bg:rgba(125,155,255,.16);
  --glass:rgba(28,34,48,.62);
  --glass-line:rgba(255,255,255,.09);
  --solid:#E9ECF4;
  --solid-ink:#0B0D12;
  --cta:#1E40AF;
  --cta-ink:#FFFFFF;
  --header-bg:rgba(26,33,48,.72);
  --header-line:rgba(255,255,255,.10);
  --header-hover:rgba(255,255,255,.07);
  --footer-bg:#0F131B;
  --footer-ink:#F4F6FA;
  --footer-muted:#9AA2B5;
  --footer-head:#8891A3;
  --footer-link:#C3C9D8;
  --footer-line:rgba(255,255,255,.08);
  --scrim:rgba(28,34,48,.92);
  --hero-wash:.42;
  --hero-grid:rgba(255,255,255,.05);
  --glass-fill:rgba(18,22,32,.55);
  --glass-border:rgba(255,255,255,.14);
  --glass-hi:rgba(255,255,255,.10);
  --glass-shadow:0 20px 44px rgba(0,0,0,.55);
  --noise-opacity:.05;
  --noise-blend:overlay;
  --shadow:0 1px 2px rgba(0,0,0,.5), 0 8px 24px rgba(0,0,0,.4);
  --shadow-lg:0 2px 4px rgba(0,0,0,.5), 0 18px 44px rgba(0,0,0,.55);
  color-scheme:dark;
}

/* Light is the default theme, deliberately.

   This block used to re-declare the dark palette under
   @media (prefers-color-scheme: dark) for visitors who had made no explicit
   choice, so a dark-OS visitor landed on a dark site. The owner asked for
   light by default instead: the directory's cards, screenshots and tool logos
   were designed against a white ground and read better there, and a first-time
   visitor now always sees the same site the screenshots show.

   Dark mode is still fully supported — it is now opt-in via the toggle, which
   stamps data-theme="dark" and persists it in localStorage. The boot script in
   wp_head re-stamps it before first paint, so anyone who chooses dark keeps it
   with no white flash. Only the automatic OS-following behaviour is gone. */

/* ------------------------------------------------- base heading hierarchy

   The theme's global-styles-inline-css sets `h1,h2,h3,h4,h5,h6` to ProstoOne,
   a face nobody picked. Component rules below name a class and so out-rank it,
   but every *unclassed* heading lost — which meant article h2 and h3 rendered
   in ProstoOne while the body around them was Inter and the page title was
   Space Grotesk. Three faces in one article is precisely the incoherence this
   system exists to remove.

   `body h1` is specificity 0,0,2 against the theme's 0,0,1, so this wins
   regardless of source order, while the classed component rules further down
   still beat this. Weights are normalised too: the theme asked for 750 and 650,
   which Space Grotesk cannot render (it stops at 700) and so snapped
   unpredictably. */
body h1,body h2,body h3,body h4,body h5,body h6{
  font-family:var(--font-display);
  font-weight:var(--fw-bold);
  letter-spacing:var(--ls-heading);
  line-height:var(--lh-heading);
  text-wrap:balance;           /* stops one-word orphans on headings */
}
body h1{ font-size:var(--fs-3xl); letter-spacing:var(--ls-display); }
body h2{ font-size:var(--fs-xl); }
body h3{ font-size:var(--fs-lg); }
body h4{ font-size:var(--fs-md); }
body h5,body h6{ font-size:var(--fs-base); letter-spacing:var(--ls-body); }

/* Long-form prose: measure and rhythm. Article text was running the full
   container width on desktop, which is far past the 60-75 character line
   comfortable reading needs. */
.entry-content p,.entry-content li{
  font-size:var(--fs-md); line-height:var(--lh-prose); letter-spacing:var(--ls-body);
}
/* The theme sets 750 and 650 on article headings with enough specificity to
   beat the base rule above. Neither weight exists in Space Grotesk, which tops
   out at 700 — the browser clamps silently, so h2 and h3 were asking for
   weights they could never render. Normalised to the two that do exist, which
   also gives h2 and h3 a real weight difference instead of an imaginary one. */
.entry-content h2{ font-weight:var(--fw-bold); margin-top:var(--s-7); margin-bottom:var(--s-3); }
.entry-content h3{ font-weight:var(--fw-semi); margin-top:var(--s-6); margin-bottom:var(--s-2); }
.entry-content h4{ font-weight:var(--fw-semi); margin-top:var(--s-5); margin-bottom:var(--s-2); }

/* Measure. Long-form articles ran the full container width on desktop, well
   past the 60-75 characters that stays comfortable to read. */
.single .entry-content > p,
.single .entry-content > ul,
.single .entry-content > ol{ max-width:68ch; }

body{ color:var(--ink);
  font-family:var(--font-sans);
  letter-spacing:var(--ls-body);
  /* Inter ships its optical niceties behind feature flags: cv11 gives the
     single-storey a nicer disambiguated l/I, ss01 the alternate digits. */
  font-feature-settings:"cv11","ss01";
  font-optical-sizing:auto;
  -webkit-font-smoothing:antialiased;
  position:relative;
  /* Soft tinted wash from the top gives the flat canvas some depth without
     introducing a coloured band. Fixed attachment so it stays put on scroll. */
  background-color:var(--canvas);
  background-image:
    radial-gradient(ellipse 120% 72% at 50% -8%, rgba(36,84,255,.055), transparent 60%),
    radial-gradient(ellipse 80% 46% at 88% 4%, rgba(124,58,237,.05), transparent 62%),
    radial-gradient(ellipse 70% 40% at 8% 2%, rgba(14,165,233,.035), transparent 60%);
  background-repeat:no-repeat;
  background-attachment:fixed;
}
/* Film-grain overlay. feTurbulence rather than a tiled PNG so it stays sharp
   at any density and costs no extra request. pointer-events:none keeps it
   from swallowing clicks. */
body::before{
  content:""; position:fixed; inset:0; pointer-events:none; z-index:9998;
  /* Driven by tokens so the theme toggle changes it. The previous version
     hard-coded opacity here and adjusted it in a prefers-color-scheme
     block, which meant it never responded to an explicit theme choice —
     dark grain settings simply never applied to a toggled-dark page. */
  opacity:var(--noise-opacity);
  mix-blend-mode:var(--noise-blend);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce){ body::before{ opacity:calc(var(--noise-opacity) * .6); } }
/* The tinted wash above was tuned for a near-white canvas; at dark-theme
   luminance the same blue/purple radials read as muddy patches, so they are
   strengthened and re-centred rather than left to wash out. */
:root[data-theme="dark"] body{
  background-image:
    radial-gradient(ellipse 120% 70% at 50% -10%, rgba(125,155,255,.10), transparent 62%),
    radial-gradient(ellipse 90% 50% at 85% 5%, rgba(196,166,255,.07), transparent 60%);
}

body a{ text-decoration:none; }
.wp-site-blocks > *{ margin-block-start:0; }

/* ---------- layout shell ----------
   The gutter is a variable so the header, hero, sections, archives and
   Elementor containers all line up on the same vertical edges. Change it
   here and everything moves together. */
:root{ --gutter:32px; --rhythm:72px; }

/* Every one of our components sizes with border-box. Without this the header
   and footer used content-box, so their max-width:1280 + 2x32 gutter came to
   1344px total and their content landed 32px left of every Elementor section —
   the misaligned vertical edge that read as "crooked". */
[class*="tdt-"]{ box-sizing:border-box; }
[class*="tdt-"]::before,
[class*="tdt-"]::after{ box-sizing:border-box; }

.tdt-section{
  max-width:var(--max); margin:0 auto;
  padding:var(--rhythm) var(--gutter) 0;
}
.tdt-section:last-child{ padding-bottom:var(--rhythm); }

/* Elementor ships its own kit (Roboto + a blue palette) which would repaint
   any native Heading/Text widget you drag in. Remapping Elementor's own
   global custom properties to our tokens means anything added in the editor
   inherits the design system automatically — no per-widget styling needed.
   body[class] (0,1,1) outbids Elementor's .elementor-kit-N (0,1,0). */
body[class]{
  --e-global-color-primary:var(--ink);
  --e-global-color-secondary:var(--ink-2);
  --e-global-color-text:var(--ink-2);
  --e-global-color-accent:var(--accent);
  --e-global-typography-primary-font-family:"Space Grotesk";
  --e-global-typography-secondary-font-family:"Space Grotesk";
  --e-global-typography-text-font-family:"Inter";
  --e-global-typography-accent-font-family:"Inter";
}
.elementor-widget-heading .elementor-heading-title,
.elementor-widget-text-editor,
.elementor-widget-button .elementor-button{ font-family:inherit; }
.elementor-widget-heading .elementor-heading-title{
  color:var(--ink); letter-spacing:-.025em; }

/* Elementor pages: match the same gutter and max width so an Elementor-built
   page sits on the identical grid as the archive templates. */
.elementor-section.elementor-section-boxed > .elementor-container,
.e-con.e-parent > .e-con-inner,
.e-con{ max-width:var(--max)!important; }
.e-con.e-parent{ padding-left:var(--gutter); padding-right:var(--gutter); }
.elementor-widget{ width:100%; }
.e-con .tdt-browse,
.e-con .tdt-tool,
.e-con .tdt-archive-head,
.e-con .tdt-section{ padding-left:0; padding-right:0; }
/* Our widgets are full-bleed blocks; kill Elementor's default bottom margin
   so section rhythm is controlled in one place. */
.elementor-widget-tdt_tool_grid,
.elementor-widget-tdt_curated,
.elementor-widget-tdt_deals,
.elementor-widget-tdt_discipline_picker,
.elementor-widget-tdt_section_heading,
.elementor-widget-tdt_stats,
.elementor-widget-tdt_browse,
.elementor-widget-tdt_hero,
.elementor-widget-tdt_persona_grid{ margin-bottom:0; }

/* Vertical rhythm on Elementor pages. A section heading always opens a new
   block, so it carries the gap — keeping spacing in one place rather than
   spread across each container's padding settings. */
.elementor-widget-tdt_section_heading{ margin-top:var(--rhythm); }
.elementor-widget-tdt_newsletter{ margin-top:var(--rhythm); margin-bottom:var(--rhythm); }
.elementor-widget-tdt_stats{ margin-top:4px; }
/* The gutter already comes from the container; don't double it. */
.e-con .tdt-hero,

/* ---------- sticky nav with refined spacing ---------- */
/* backdrop-filter deliberately lives on .tdt-header-inner, not .site-header:
   filter/backdrop-filter on an ancestor creates a new containing block for
   position:fixed descendants, which would break the fixed-position mobile
   nav panel (it would anchor to the header's own box instead of the
   viewport). Keeping it one level down avoids that entirely. */
/* Floating pill header: detached from the viewport edges and rounded, so the
   tinted page wash reads continuously behind and above it. The bar itself is
   transparent — the pill is .tdt-header-inner — which is why there is no
   border-bottom here any more. */
/* z-index 10000 puts the header — and the mobile menu it contains — above the
   film-grain overlay at 9998. Only the consent dialog (99999) sits higher,
   which it must. */
.site-header{ background:transparent; border-bottom:0;
  position:sticky; top:0; z-index:10000; padding:12px var(--gutter) 0; }
/* position+z-index are load-bearing, not cosmetic: .tdt-mobile-nav is a
   sibling with a positive z-index, so a static pill paints underneath it and
   the close X disappears behind the open menu. The hamburger's own z-index
   cannot fix this — backdrop-filter makes this pill a stacking context, so
   any z-index on its children is confined inside it. */
.tdt-header-inner{ position:relative; z-index:2;
  max-width:var(--max); margin:0 auto; padding:11px 12px 11px 18px;
  display:flex; align-items:center; justify-content:space-between; gap:20px;
  background:var(--header-bg); border:1px solid var(--header-line);
  border-radius:999px;
  box-shadow:0 1px 2px rgba(23,23,23,.04), 0 10px 30px rgba(23,23,23,.08);
  backdrop-filter:saturate(180%) blur(14px); -webkit-backdrop-filter:saturate(180%) blur(14px); }
@media(max-width:640px){
  .site-header{ padding:8px var(--gutter) 0; }
  .tdt-header-inner{ border-radius:var(--r-xl); padding:10px 10px 10px 14px; }
}
.tdt-logo{ display:flex; align-items:center; gap:10px; white-space:nowrap; }
.tdt-logo__mark{ flex:0 0 auto; width:34px; height:34px; border-radius:9px;
  box-shadow:0 2px 6px rgba(36,84,255,.22); transition:transform .2s cubic-bezier(.4,0,.2,1); }
.tdt-logo:hover .tdt-logo__mark{ transform:rotate(-6deg) scale(1.04); }
.tdt-logo__word{ font-weight:750; font-size:19px; letter-spacing:-.03em; color:var(--ink)!important; }
.tdt-logo b{ color:var(--accent); font-weight:750; }
@media(max-width:640px){
  .tdt-logo__mark{ width:30px; height:30px; border-radius:8px; }
  .tdt-logo__word{ font-size:17px; }
}
.tdt-nav{ display:flex; gap:2px; align-items:center; flex-wrap:wrap; }
.tdt-nav a{ font-size:13px; font-weight:550; color:var(--ink-2)!important;
  padding:8px 13px; border-radius:var(--r-sm); transition:all .15s; }
.tdt-nav a:hover{ background:var(--header-hover); color:var(--ink)!important; }
.tdt-nav a.tdt-cta{ background:var(--accent); color:var(--accent-ink)!important; padding:9px 18px; border-radius:999px; font-weight:600; box-shadow:0 1px 2px rgba(36,84,255,.18); }
.tdt-cta:hover{ background:var(--accent); filter:brightness(1.08); }
.tdt-nav a.tdt-cta:hover{ background:var(--accent); }

/* ---------- Disciplines dropdown ----------
   All 9 disciplines live here so every one is one click from anywhere on
   the site — previously only 5 had top-level links and the other 4
   (Physical Product Design, Architecture, Design Systems & UI Kits,
   Cross-Discipline) were reachable only from the mobile menu. */
.tdt-nav-dropdown{ position:relative; }
.tdt-nav-dropdown__trigger{
  display:inline-flex; align-items:center; gap:6px;
  font:inherit; font-size:13px; font-weight:550; color:var(--ink-2);
  background:none; border:none; cursor:pointer;
  padding:8px 13px; border-radius:var(--r-sm); transition:all .15s; }
.tdt-nav-dropdown__trigger svg{ transition:transform .18s ease; flex:0 0 auto; }
.tdt-nav-dropdown__trigger:hover,
.tdt-nav-dropdown.is-open .tdt-nav-dropdown__trigger{ background:rgba(23,23,23,.05); color:var(--ink); }
.tdt-nav-dropdown.is-open .tdt-nav-dropdown__trigger svg{ transform:rotate(180deg); }
.tdt-nav-dropdown__menu{
  position:absolute; top:calc(100% + 8px); left:0; z-index:60;
  display:none; flex-direction:column; min-width:240px; padding:8px;
  background:var(--paper); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--shadow-lg); }
.tdt-nav-dropdown.is-open .tdt-nav-dropdown__menu{ display:flex; }

/* The utilities menu lists the five apps we host. Without a label it reads as
   "the free tools in the directory", which is the opposite of what it is —
   the directory holds 473 mostly-external tools. The label states ownership
   and the footnote gives the directory its own route out, so neither is
   mistaken for the other. */
.tdt-nav-dropdown__menu--utilities{ min-width:268px; }
.tdt-nav-dropdown__label{
  margin:2px 9px 6px; font-size:var(--fs-2xs); font-weight:var(--fw-semi);
  letter-spacing:var(--ls-caps); text-transform:uppercase; color:var(--faint);
}
.tdt-nav-dropdown__note{
  margin:8px 9px 2px; padding-top:9px; border-top:1px solid var(--line);
  font-size:var(--fs-2xs); color:var(--sub); line-height:1.5;
}
.tdt-nav-dropdown__note a{
  color:var(--accent)!important; text-decoration:underline;
  text-underline-offset:2px; padding:0!important; display:inline!important;
}
.tdt-nav-dropdown__menu a{
  font-size:13px!important; font-weight:550!important; padding:9px 12px!important;
  border-radius:var(--r-sm)!important; color:var(--ink-2)!important; }
.tdt-nav-dropdown__menu a:hover{ background:var(--oss-bg)!important; color:var(--ink)!important; }

/* ---------- iframe-embedded tools: escape the 700px content cap ----------
   Same bug as the CMF/invoice tool shells: any direct child of the theme's
   .is-layout-constrained that isn't .alignwide/.alignfull gets capped at
   --wp--style--global--content-size (700px) via a :where() rule, which has
   zero specificity — so an ordinary class here overrides it outright. Used
   by the /screenshot-beautifier/ page so its iframe gets real desktop width
   instead of being squeezed into the article column. */
.tdt-wide-embed{ max-width:1376px!important; width:100%; margin-inline:auto; }

/* ---------- hero: clear value prop ---------- */
.tdt-hero-wrap{ position:relative; overflow:hidden; }
/* Hero colour wash. Both layers were literal light-mode colours, which is
   why the hero was the last thing still wrong in dark mode: the wash kept
   its full-strength saturation against a near-black ground, and the grid
   drew dark lines on a dark surface so it vanished entirely. Alpha values
   and the grid colour are tokenised so each theme gets its own weight. */
.tdt-hero-wrap::before{
  content:none; /* colour wash removed — the hero reads clean on the page background */
}
.tdt-hero-wrap::after{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background-image:
    linear-gradient(var(--hero-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--hero-grid) 1px, transparent 1px);
  background-size:36px 36px;
  mask-image:radial-gradient(ellipse 85% 75% at 50% 15%, #000 45%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 85% 75% at 50% 15%, #000 45%, transparent 100%);
}
.tdt-hero{ position:relative; z-index:1; max-width:var(--max); margin:0 auto;
  padding:72px var(--gutter) 4px; text-align:center; }
.tdt-pill{ display:inline-flex; align-items:center; gap:7px; padding:7px 16px 7px 12px;
  border-radius:999px; background:var(--paper); border:1px solid var(--line);
  font-size:12px; font-weight:650; color:var(--ink-2); margin-bottom:22px;
  box-shadow:0 1px 2px rgba(0,0,0,.03); }
.tdt-pill--live .tdt-pill__dot{ width:7px; height:7px; border-radius:50%;
  background:var(--free); box-shadow:0 0 0 3px color-mix(in srgb, var(--free) 18%, transparent);
  animation:tdt-pulse 2s ease-in-out infinite; }
@keyframes tdt-pulse{ 0%,100%{ opacity:1; } 50%{ opacity:.4; } }
/* .tdt-hero__title carries the hero's visual weight without being an <h1>.
   Two pages (/no-signup/, /screenshot-beautifier/) put an <h1> in the hero
   while the theme was already rendering the page title as an <h1>, giving
   both pages two competing top-level headings. The hero keeps the look; the
   page keeps a single H1. */
.tdt-hero h1, .tdt-hero .tdt-hero__title{ font-family:var(--font-display); font-size:var(--fs-display);
  font-weight:var(--fw-bold); letter-spacing:var(--ls-display); line-height:var(--lh-display);
  line-height:1.05; margin:0 0 16px; color:var(--accent); }
/* The title uses the same brand blue as the logo (--accent adapts to the
   lighter #7D9BFF on the dark canvas), so it reads as one identity. */
.tdt-hero__lede{ font-size:var(--fs-md); line-height:var(--lh-prose); color:var(--sub); max-width:580px; margin:0 auto; }

/* ---------- stats: proof of scale ---------- */
.tdt-stats{ display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:40px;
  justify-content:center; margin:40px auto 0; max-width:660px;
  padding:0 var(--gutter); }
.tdt-stat{ text-align:center; }
.tdt-stat b{ display:block; font-family:var(--font-display); font-size:var(--fs-2xl);
  font-weight:var(--fw-bold); letter-spacing:var(--ls-heading); color:var(--accent);
  font-variant-numeric:tabular-nums; }
.tdt-stat span{ font-size:11px; color:var(--sub); text-transform:uppercase; letter-spacing:.08em; font-weight:600; margin-top:4px; }

/* ---------- editorial trust statement ---------- */
.tdt-quality-note{ max-width:640px; margin:20px auto 0; text-align:center;
  font-size:13px; line-height:1.65; color:var(--sub); }
.tdt-quality-note strong{ color:var(--ink-2); font-weight:600; }
.tdt-quality-note em{ font-style:normal; color:var(--sponsored); font-weight:600; }

/* ---------- section head: hierarchy + CTA ---------- */
.tdt-section-head{ display:flex; justify-content:space-between; align-items:flex-start;
  gap:16px; flex-wrap:wrap; margin-bottom:24px; }
.tdt-section-head h2{ font-family:var(--font-display); font-size:var(--fs-xl);
  font-weight:var(--fw-bold); letter-spacing:var(--ls-heading); line-height:var(--lh-heading); margin:0 0 6px; }
.tdt-section-head p{ color:var(--sub); font-size:13px; margin:0; max-width:60ch; line-height:1.5; }
.tdt-section-note{ color:var(--sub); font-size:13px; margin:0 0 12px; font-style:italic; }
.tdt-see-all{ font-size:12px; font-weight:650; color:var(--accent)!important; white-space:nowrap;
  padding:8px 14px; border:1px solid var(--line); border-radius:999px; background:var(--paper);
  transition:all .15s; }
.tdt-see-all:hover{ border-color:var(--accent); background:rgba(36,84,255,.02); }

/* ---------- grid: responsive, breathing ---------- */
.tdt-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(248px,1fr)); gap:16px; }
.tdt-grid.tdt-deal-grid{ grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:16px; }

/* ---------- card: cover-led, sponsored state ----------
   The card leads with a tinted cover rather than a small corner favicon.
   Rationale: at 4-across, 400+ near-identical white rectangles are very hard
   to scan — the eye has no anchor. A large coloured cover per tool gives each
   card a distinct silhouette, which is what makes directory grids browsable.
   The tint hue comes from the tool slug (see tdt_tool_hue) so it is stable
   per tool; only lightness/saturation are fixed here, keeping every cover in
   the same pastel register no matter the hue. */
.tdt-card{ background:var(--paper); border:1px solid var(--line); border-radius:var(--r-lg);
  transition:border-color .18s, box-shadow .18s, transform .18s; overflow:hidden; }
.tdt-card:hover{ border-color:#D4D4D8; box-shadow:var(--shadow-lg); transform:translateY(-4px); }
.tdt-card.tdt-card--sponsored{ border:1.5px solid var(--sponsored); }
.tdt-card.tdt-card--placeholder{ border:1px dashed var(--line); background:rgba(247,247,247,.5); }
.tdt-card.tdt-card--placeholder:hover{ border-color:var(--line); transform:none; box-shadow:none; }
.tdt-card__link{ display:flex; flex-direction:column; height:100%; color:inherit!important; }

.tdt-card__cover{ position:relative; display:grid; place-items:center;
  height:112px; flex:0 0 auto; overflow:hidden;
  border-bottom:1px solid var(--line-2);
  /* Fallback for browsers without hsl() space-separated syntax support. */
  background:#F4F5F7;
  background-image:
    linear-gradient(135deg, hsl(var(--tint,220) 72% 94%) 0%, hsl(calc(var(--tint,220) + 38) 66% 88%) 100%);
}
/* A soft light bloom keeps the flat gradient from looking like a solid block. */
.tdt-card__cover::after{ content:""; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(ellipse 70% 60% at 50% 12%, rgba(255,255,255,.55), transparent 70%); }
.tdt-card--placeholder .tdt-card__cover{ background:#FAFAFA; background-image:none; }

/* Favicons vary wildly in padding and shape, so the tile does the visual
   normalising: fixed size, consistent inset, subtle inner surface. Raised
   above the cover's ::after bloom so the logo never sits behind the wash. */
.tdt-card__logo{ position:relative; z-index:1; width:56px; height:56px; border-radius:14px;
  background:var(--scrim);
  border:1px solid rgba(255,255,255,.75); display:grid; place-items:center;
  flex:0 0 auto; overflow:hidden;
  box-shadow:0 1px 2px rgba(23,23,23,.06), 0 6px 16px rgba(23,23,23,.10);
  backdrop-filter:saturate(1.4) blur(2px); }
.tdt-card__logo--dash{ font-size:24px; font-weight:300; color:var(--faint); }
/* When a favicon 404s the img removes itself and this monogram takes over,
   so a tile is never left blank. */
/* ---- Search results: what kind of thing each result is ------------------
   Added by tdt-search-result-types-js (snippet 329) on the search page only.
   Three weights, quietest to loudest: an article is the default and takes the
   neutral surface, a prompt takes the green already used for free tools, and a
   tool takes the accent, because a tool listing is what someone searching a
   tool directory is usually after. Every colour comes from a token that has a
   dark-mode value, so the badges follow the theme. */
.tdt-restype{
  display:inline-block; margin:0 0 7px; padding:3px 9px;
  border-radius:5px; font-size:10px; font-weight:700;
  letter-spacing:.07em; text-transform:uppercase; line-height:1.5;
  background:var(--canvas); color:var(--sub);
}
.tdt-restype--tool{ background:color-mix(in srgb, var(--accent) 13%, transparent); color:var(--accent); }
.tdt-restype--prompt{ background:var(--free-bg); color:var(--free); }

/* ---- Browse filters, folded away on phones ------------------------------
   Injected by tdt-browse-filters-mobile (snippet 329). The toggle exists only
   under 768px; above it the rules below never apply and the bar renders as it
   always has. If the script does not run, neither class is ever set and the
   bar stays open — the pre-existing behaviour. */
.tdt-filters-toggle{ display:none; }
@media (max-width: 767px){
  .tdt-filters-toggle{
    display:inline-flex; align-items:center; gap:8px;
    margin:0 0 12px; padding:10px 16px;
    background:var(--paper); color:var(--ink);
    border:1.5px solid color-mix(in srgb, var(--accent) 26%, var(--line));
    border-radius:999px; font:inherit; font-size:14px; font-weight:600;
    cursor:pointer;
  }
  .tdt-filters-toggle__n{
    min-width:20px; padding:1px 7px; border-radius:999px;
    background:var(--accent); color:var(--accent-ink);
    font-size:12px; font-weight:700; text-align:center;
  }
  .tdt-filters-toggle__caret{ transition:transform .18s; color:var(--sub); }
  .is-filters-open .tdt-filters-toggle__caret{ transform:rotate(90deg); }
  .is-filters-collapsed .tdt-browse__bar{ display:none; }
}
@media (prefers-reduced-motion: reduce){
  .tdt-filters-toggle__caret{ transition:none; }
}

.tdt-card__logo.no-logo::after{
  content:attr(data-initial); font-weight:700; font-size:22px; color:var(--ink-2); }
/* Our own tools carry two initials rather than one (see tdt_tool_card, which
   adds this class): step the size down and tighten the tracking so "CM" sits
   in the tile the way a single letter does. */
.tdt-card__logo--duo.no-logo::after{ font-size:17px; letter-spacing:-.02em; }
.tdt-card__logo img{ width:30px; height:30px; object-fit:contain; display:block; }
.tdt-card__initial{ font-weight:700; font-size:22px; color:var(--ink-2); }

/* Tags float over the cover, top-right — this frees the body for title and
   description, which is what actually gets read. */
.tdt-card__tags{ position:absolute; z-index:2; top:10px; right:10px;
  display:flex; gap:5px; flex-wrap:wrap; justify-content:flex-end; max-width:calc(100% - 20px); }

.tdt-card__body{ display:flex; flex-direction:column; flex:1 1 auto; padding:14px 16px 16px; }
.tdt-card__title{ font-size:var(--fs-base)!important; font-weight:var(--fw-semi)!important; letter-spacing:var(--ls-heading);
  margin:0 0 5px!important; color:var(--ink)!important; line-height:1.3; }
.tdt-card__desc{ font-size:13px; line-height:1.5; color:var(--sub); margin:0 0 auto;
  flex:1 1 auto; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
  overflow:hidden; min-height:calc(3 * 1.5em); }
.tdt-card__foot{ display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding-top:10px; margin-top:10px; border-top:1px solid var(--line-2); }
.tdt-card__price{ font-size:11px; font-weight:600; color:var(--faint); }
.tdt-card__arrow{ font-size:13px; color:var(--faint); transition:transform .18s,color .18s; }
.tdt-card:hover .tdt-card__arrow{ transform:translateX(3px); }
/* The cover lifts very slightly on hover — enough to feel responsive without
   the layout shifting. */
.tdt-card__cover{ transition:transform .35s cubic-bezier(.2,.7,.3,1); }
.tdt-card:hover .tdt-card__cover{ transform:scale(1.03); }
@media (prefers-reduced-motion: reduce){ .tdt-card:hover .tdt-card__cover{ transform:none; } }

/* ---------- tags: priced, typed ---------- */
.tdt-tag{ display:inline-block; font-size:10px; font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; padding:4px 9px; border-radius:999px; white-space:nowrap; }
.tdt-tag--free{ background:var(--free-bg); color:var(--free); }
.tdt-tag--freemium{ background:#EFF3FF; color:var(--accent); }
.tdt-tag--paid,.tdt-tag--onetime{ background:#F1F1F3; color:var(--sub); }
.tdt-tag--deal{ background:var(--deal-bg); color:var(--deal); }
.tdt-tag--sponsored{ background:var(--sponsored-bg); color:var(--sponsored); }
.tdt-tag--oss{ background:var(--oss-bg); color:var(--oss); }
.tdt-tag--inhouse{ background:var(--inhouse-bg); color:var(--accent); }
/* Tags sitting on a tinted cover need to hold their own against an arbitrary
   hue behind them, so they get an opaque-ish backing and a hairline ring. */
.tdt-card__tags .tdt-tag{ box-shadow:0 1px 2px rgba(23,23,23,.10), inset 0 0 0 1px rgba(255,255,255,.6); }
/* The price badge in the card footer sits on white and reads as the primary
   at-a-glance fact, so it is allowed slightly more presence than a cover tag. */
.tdt-card__foot .tdt-tag{ font-size:10px; padding:5px 10px; }
.tdt-tag--tax{ background:#F1F1F3; color:var(--ink-2)!important; text-transform:none;
  letter-spacing:0; font-weight:600; font-size:11px; padding:6px 10px; }
.tdt-tag--tax:hover{ background:var(--solid); color:var(--solid-ink)!important; }

/* ---------- deal card: image-forward, trust-building ---------- */
.tdt-deal-card{ display:flex; flex-direction:column; background:var(--paper); border:1px solid var(--line);
  border-radius:var(--r-lg); overflow:hidden; transition:all .18s; text-decoration:none!important; color:inherit!important; }
.tdt-deal-card:hover{ border-color:var(--line-hover); box-shadow:var(--shadow); transform:translateY(-2px); }
.tdt-deal-card.tdt-deal-card--placeholder{ border:1px dashed var(--line); background:var(--wash); }
.tdt-deal-card.tdt-deal-card--placeholder:hover{ transform:none; box-shadow:none; }
/* Honest empty state, shown instead of fake "coming soon" cards when there
   are genuinely no live deals. Reads as a sentence, not a broken grid. */
.tdt-deals-empty{
  margin:0; padding:20px 22px;
  border:1px dashed var(--line); border-radius:var(--r-lg);
  background:var(--wash); color:var(--sub);
  font-size:var(--fs-sm); line-height:var(--lh-prose); max-width:68ch;
}
.tdt-deal-card__banner{ height:120px; background:linear-gradient(135deg, var(--canvas) 0%, var(--wash) 100%);
  display:grid; place-items:center; overflow:hidden; position:relative; }
.tdt-deal-card__banner--dash{ font-size:48px; font-weight:300; color:var(--faint); }
.tdt-deal-card__banner img{ width:100%; height:100%; object-fit:cover; }
.tdt-deal-card__body{ padding:16px; flex:1 1 auto; display:flex; flex-direction:column; }
.tdt-deal-card__body h3{ font-size:var(--fs-sm); font-weight:var(--fw-semi); margin:0 0 6px; line-height:var(--lh-heading); }
.tdt-deal-card__body p{ font-size:12px; color:var(--sub); margin:0 0 auto; line-height:1.4; }
.tdt-code-chip{ display:inline-block; background:var(--deal-bg); color:var(--deal); font-size:11px;
  font-weight:700; padding:6px 10px; border-radius:6px; margin-top:10px; letter-spacing:.04em; }
.tdt-code-chip--link{ background:var(--accent); color:#fff; }
/* Measured, not eyeballed. --sub on --wash is 4.63:1 in light and 5.63:1 in
   dark, both clearing AA for this 11px text. --faint was the original colour
   and lands at 4.21:1 in dark, so it is deliberately not used here. */
.tdt-code-chip--soon{ background:var(--wash); color:var(--sub); }

/* ---------- partner offer cards: deal-card layout + disclosure ---------- */
.tdt-partner-card__badge{ position:absolute; top:10px; right:10px; z-index:2; }
.tdt-partner-disclosure{ grid-column:1/-1; font-size:11px; line-height:1.5;
  color:var(--faint); margin:12px 0 0; }
/* Partner banners usually hold a favicon, not a wide banner image. The generic
   .tdt-deal-card__banner img rule stretches images edge-to-edge with
   object-fit:cover, which turned Fiverr's 32x32 favicon into a badly
   pixelated smear across a 280px-wide banner. When there is no Featured
   Image, the logo instead sits at its natural size inside a white tile on a
   tinted field — the same language as the tool-card covers. A Featured Image,
   when set, is a real banner and still fills the frame via the generic rule. */
.tdt-partner-card__banner{
  background:#F4F5F7;
  background-image:linear-gradient(135deg, hsl(var(--tint,220) 72% 94%) 0%, hsl(calc(var(--tint,220) + 38) 66% 88%) 100%);
}
.tdt-partner-card__logo{ position:relative; z-index:1; width:64px; height:64px; border-radius:16px;
  background:var(--scrim); border:1px solid var(--header-line);
  display:grid; place-items:center; overflow:hidden;
  box-shadow:0 1px 2px rgba(23,23,23,.06), 0 6px 16px rgba(23,23,23,.10); }
/* Extra class on the ancestor keeps this above the generic banner-img rule
   without needing !important. */
.tdt-partner-card .tdt-partner-card__logo img{
  width:34px; height:34px; object-fit:contain; display:block; }
.tdt-partner-card__logo.no-logo::after{
  content:attr(data-initial); font-weight:750; font-size:26px; color:var(--ink-2); }

/* ---------- browse: discoverable filters ---------- */
.tdt-browse{ max-width:var(--max); margin:0 auto; padding:var(--rhythm) var(--gutter); }
/* Horizontal padding is not decoration here: the bar is a bordered, rounded
   glass panel, and its children are fully-rounded pills. With padding:14px 0
   those pills ran into the panel's own curved edge, which is what made the
   module read as a pill jammed inside a square. */
.tdt-browse__bar{ position:sticky; top:68px; z-index:50; background:var(--canvas);
  padding:16px 18px 18px; margin-bottom:14px; }
.tdt-filter-group{ margin-top:14px; }
.tdt-filter-group:first-child{ margin-top:0; }
.tdt-filter-group__label{ display:block; font-size:11px; font-weight:650;
  text-transform:uppercase; letter-spacing:.06em; color:var(--faint); margin-bottom:8px; }
.tdt-filters{ display:flex; gap:7px; flex-wrap:wrap; }
.tdt-chip{ font:inherit; font-size:var(--fs-xs); font-weight:var(--fw-medium);
  padding:7px 13px; border-radius:999px;
  border:1px solid var(--control-line); background:var(--paper); color:var(--ink-2);
  cursor:pointer; transition:all .15s; }
.tdt-chip:hover{ border-color:var(--ink); background:var(--wash); }
.tdt-chip.is-active{ background:var(--solid); border-color:var(--solid); color:var(--solid-ink); font-weight:600; }
.tdt-count{ font-size:12px; color:var(--faint); margin:16px 0 12px; font-weight:550; }
.tdt-load-more{
  display:block; margin:28px auto 0; padding:12px 32px; font:inherit; font-size:13px;
  font-weight:650; color:var(--ink); background:var(--paper); border:1px solid var(--line);
  border-radius:999px; cursor:pointer; transition:all .15s; }
.tdt-load-more:hover{ border-color:var(--ink); }
.tdt-load-more:disabled{ opacity:.55; cursor:wait; }
/* Author-origin display:block above outranks the browser's own [hidden]
   rule regardless of specificity ties, since UA-stylesheet rules always
   lose to author styles. The browse filter JS correctly sets the hidden
   attribute when a filtered query has no further pages, but the button
   stayed visibly display:block anyway — confirmed live filtering to a
   zero-result combination. Same fix as the swatch tray's [hidden] rule
   elsewhere in this file. */
.tdt-load-more[hidden]{ display:none; }

/* Breadcrumb trail — inserted client-side directly before the page's H1
   (see tdt_get_breadcrumbs()/wp_footer in snippet_seo.php). A BreadcrumbList
   schema described this same hierarchy to search results for a while with
   no on-page equivalent for an actual visitor to click through.

   --center is added by the same script when the H1 it sits above is
   centred (most tool titles), so the trail reads as part of the same
   block instead of a stray left-aligned line over a centred headline. */
.tdt-breadcrumb{ margin:0 0 var(--s-4); }
.tdt-breadcrumb ol{
  display:flex; flex-wrap:wrap; align-items:center; gap:7px;
  list-style:none; margin:0; padding:0;
  font-size:13px; font-weight:var(--fw-medium,550);
  letter-spacing:var(--ls-body);
}
.tdt-breadcrumb--center ol{ justify-content:center; }
.tdt-breadcrumb li{ display:flex; align-items:center; gap:7px; }
.tdt-breadcrumb li:not(:last-child)::after{
  content:"/"; color:var(--line-2, var(--line));
  font-size:13px; line-height:1; transform:translateY(-1px);
}
.tdt-breadcrumb a{
  color:var(--faint); text-decoration:none;
  padding:2px 1px; border-radius:2px;
  transition:color .15s var(--ease-spring, ease);
}
.tdt-breadcrumb a:hover{ color:var(--accent); }
.tdt-breadcrumb a:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.tdt-breadcrumb span[aria-current]{ color:var(--sub); }
.tdt-empty{ text-align:center; color:var(--sub); padding:48px 24px; font-size:14px; }

/* ---------- single tool: refined, focused ---------- */
.tdt-tool{ max-width:820px; margin:0 auto; padding:var(--rhythm) var(--gutter); }
.tdt-tool__hero{ display:flex; align-items:flex-start; gap:18px; flex-wrap:wrap;
  padding:36px 0 28px; border-bottom:1px solid var(--line); margin-bottom:28px; }
.tdt-tool__logo{ width:64px; height:64px; border-radius:16px; background:var(--paper);
  border:1px solid var(--line); display:grid; place-items:center; flex:0 0 auto; }
.tdt-tool__logo img{ width:40px; height:40px; object-fit:contain; }
.tdt-tool__heading{ flex:1 1 240px; }
.tdt-tool__title{ font-family:var(--font-display); font-size:var(--fs-3xl)!important;
  font-weight:var(--fw-bold)!important; letter-spacing:var(--ls-display); line-height:var(--lh-heading);
  margin:0 0 8px!important; line-height:1.1; }
.tdt-tool__meta{ display:flex; gap:6px; flex-wrap:wrap; align-items:center; }
.tdt-btn{ display:inline-block; background:var(--accent); color:var(--accent-ink)!important; font-weight:600;
  font-size:13px; padding:11px 22px; border-radius:999px; transition:all .15s; cursor:pointer; border:none; box-shadow:0 1px 2px rgba(36,84,255,.18); }
.tdt-btn:hover{ background:var(--accent); filter:brightness(1.08); box-shadow:0 4px 14px rgba(36,84,255,.28); }
.tdt-btn:hover{ background:var(--accent); transform:translateY(-1px); }
.tdt-btn--lg{ font-size:14px; padding:13px 28px; }
.tdt-deal{ display:flex; align-items:center; gap:12px; background:var(--deal-bg);
  border:1px solid #F2DCC2; border-radius:var(--r); padding:14px 18px; margin-bottom:24px; }
.tdt-deal strong{ font-size:10px; text-transform:uppercase; letter-spacing:.08em; color:var(--deal); }
.tdt-deal span{ font-size:13px; color:var(--ink); font-weight:550; }
.tdt-tool__lede{ font-size:16px; line-height:1.65; color:var(--ink-2); margin:0 0 28px; }
.tdt-tool__facts{ display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:1px;
  background:var(--line); border:1px solid var(--line); border-radius:var(--r);
  overflow:hidden; margin-bottom:36px; }
.tdt-tool__facts > div{ background:var(--paper); padding:14px 16px; }
.tdt-tool__facts span{ display:block; font-size:10px; text-transform:uppercase;
  letter-spacing:.07em; color:var(--faint); font-weight:650; margin-bottom:5px; }
.tdt-tool__facts b{ font-size:13px; font-weight:600; color:var(--ink); }
.tdt-tool h2{ font-family:var(--font-display); font-size:var(--fs-lg)!important;
  font-weight:var(--fw-bold)!important; letter-spacing:var(--ls-heading);
  margin:36px 0 12px!important; }
.tdt-tool p{ font-size:15px; line-height:1.68; color:var(--ink-2); }
.tdt-pros-cons{ display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:14px; margin:28px 0; }
.tdt-pros,.tdt-cons{ background:var(--paper); border:1px solid var(--line);
  border-radius:var(--r); padding:20px; }
.tdt-pros h3,.tdt-cons h3{ font-size:11px!important; text-transform:uppercase; letter-spacing:.07em;
  margin:0 0 12px!important; font-weight:700!important; }
.tdt-pros h3{ color:var(--free); }
.tdt-cons h3{ color:var(--deal); }
.tdt-pros ul,.tdt-cons ul{ margin:0; padding-left:18px; }
.tdt-pros li,.tdt-cons li{ font-size:13px; line-height:1.55; color:var(--ink-2); margin-bottom:7px; }
.tdt-related-title{ margin-top:48px!important; padding-top:28px; border-top:1px solid var(--line); }
.tdt-tool__cta{ text-align:center; margin-top:40px; }

/* ---------- newsletter: trust + simplicity ---------- */
.tdt-newsletter{ background:var(--paper); border:1px solid var(--line); border-radius:var(--r-lg);
  padding:calc(var(--rhythm) * 0.45) var(--gutter); text-align:center; max-width:500px;
  margin:calc(var(--rhythm) * 0.66) auto; }
.tdt-newsletter h2{ font-family:var(--font-display); font-size:var(--fs-xl);
  font-weight:var(--fw-bold); letter-spacing:var(--ls-heading); margin:0 0 12px; }
.tdt-newsletter p{ color:var(--sub); font-size:14px; line-height:1.6; margin:0 0 20px; }
.tdt-newsletter__form{ display:flex; gap:8px; }
/* 44px is the accessibility floor for a tap target. The field was 38 and the
   submit 36, which on a phone is the difference between subscribing and
   mis-tapping — on the primary conversion of the whole site. */
.tdt-newsletter__form input{ flex:1 1 auto; min-height:44px; padding:10px 14px; font-size:13px; border:1px solid var(--line);
  border-radius:var(--r); background:var(--canvas); color:var(--ink); outline:none; transition:border .15s; }
.tdt-newsletter__form input:focus{ border-color:var(--accent); }
.tdt-newsletter__form button{ min-height:44px; padding:10px 20px; background:var(--accent); color:var(--accent-ink); border:none;
  border-radius:var(--r); font-weight:600; font-size:13px; cursor:pointer; transition:background .15s; }
.tdt-newsletter__form button:hover{ background:#1843CC; }
.tdt-hp{ position:absolute; left:-9999px; opacity:0; pointer-events:none; }
.tdt-newsletter__msg{ font-size:12px; color:var(--faint); margin-top:10px; min-height:16px; }
/* Fine print sits under the button, where the hesitation actually happens.
   Naming the exit ("unsubscribe in one click") at the point of commitment
   lowers the perceived cost of typing an address, and the privacy link is
   expected for an EU audience. */
.tdt-newsletter__fine{ font-size:11px!important; color:var(--faint)!important;
  margin:10px 0 0!important; line-height:1.5; }
.tdt-newsletter__fine a{ color:var(--sub); text-decoration:underline; text-underline-offset:2px; }

/* In-article variant: appears after the body of every blog post. Left-aligned
   and full-width rather than the centred 500px card, so it reads as the next
   step in the article instead of a boxed advert the eye skips. The accent top
   border ties it to the brand without shouting. */
.tdt-newsletter--inline{ max-width:none; text-align:left; margin:calc(var(--rhythm) * 0.6) 0 0;
  border-top:3px solid var(--accent); }
.tdt-newsletter--inline h2{ font-size:19px!important; margin-bottom:8px!important; }
.tdt-newsletter--inline p{ margin-bottom:16px; }
.tdt-newsletter--inline .tdt-newsletter__form{ max-width:420px; }

/* ---------- blog archive: wide card grid ----------
   The posts page is WordPress's own Posts page, not an Elementor layout, so
   its markup is core block classes (.wp-block-post-*). Styling those directly
   keeps posts appearing automatically — nothing to rebuild or re-add per post.

   Layout notes, since this went through two wrong versions first:
   - A narrow centred column (760px) was tried and looked thin and unbalanced
     on a 1440px screen with acres of empty space either side.
   - The container is now the site's own --max, so the archive lines up with
     the tool directory and every other page instead of inventing its own width.
   - Posts carry no images, so the cards lean on type and a coloured accent bar
     for rhythm. The first post spans two columns as a lead story, which gives
     the page a focal point without needing a hero image. */
/* The theme already applies its own global padding to .wp-block-query. Adding
   var(--gutter) on top produced a 44px gutter on tablet where the rest of the
   site uses 20px, so the blog sat visibly narrower than /tools/. Padding is
   left to the theme; only the max-width and centring are overridden here. */
.blog .wp-block-query{ max-width:var(--max)!important; margin-left:auto!important;
  margin-right:auto!important;
  /* main already supplies the outer gutter; the theme's has-global-padding
     applies it a second time here, which pushed the blog grid 44px in on
     tablet where the rest of the site sits at ~20px. */
  padding-left:0!important; padding-right:0!important; }
.wp-block-post-template{
  list-style:none!important;
  padding:0!important;
  margin:0!important;
  /* The theme's is-layout-constrained rule caps children of .wp-block-query at
     --wp--style--global--content-size (700px). Without releasing that, the grid
     stays a 700px column no matter how wide its parent is. */
  max-width:none!important;
  width:100%;
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(320px,1fr));
  gap:22px;
  align-items:stretch;
}
.wp-block-post-template > li.wp-block-post{
  position:relative; display:flex; flex-direction:column;
  margin:0; padding:26px 24px 24px;
  background:var(--paper);
  border:1px solid var(--line); border-radius:var(--r-lg);
  overflow:hidden;
  transition:border-color .18s, box-shadow .18s, transform .18s;
}
/* Accent bar stands in for the thumbnail these posts do not have. Rotating by
   position keeps consecutive cards distinct without needing any per-post data. */
.wp-block-post-template > li.wp-block-post::before{
  content:""; position:absolute; top:0; left:0; right:0; height:4px;
  background:linear-gradient(90deg, hsl(212 78% 60%), hsl(258 72% 66%));
}
.wp-block-post-template > li.wp-block-post:nth-child(3n+2)::before{
  background:linear-gradient(90deg, hsl(158 62% 46%), hsl(190 70% 52%)); }
.wp-block-post-template > li.wp-block-post:nth-child(3n+3)::before{
  background:linear-gradient(90deg, hsl(22 84% 60%), hsl(340 72% 62%)); }
.wp-block-post-template > li.wp-block-post:hover{
  border-color:#D4D4D8; box-shadow:var(--shadow-lg); transform:translateY(-3px); }

/* No lead-story span. A full-width first card left most of its own row empty
   on the right, and with a small post count it also stranded an empty column.
   A uniform grid fills exactly at 3 and 6 posts, which is where this blog is.
   Worth revisiting once there are enough posts to carry a featured row the way
   larger design blogs do. */

.wp-block-post-date{ font-size:11px!important; color:var(--faint)!important;
  text-transform:uppercase; letter-spacing:.08em; font-weight:700;
  margin:0 0 9px!important; }
/* .wp-block-post-title is two different things: the small card title on the
   blog archive, and the actual page H1 on a single post. Sizing it once meant
   single-post headlines rendered at 19px — smaller than the h2s beneath them,
   which inverted the hierarchy on every article. Scope the card size to the
   listing contexts and let a single post use the h1 scale. */
.blog .wp-block-post-title,
.archive .wp-block-post-title,
.search .wp-block-post-title{
  font-family:var(--font-display); font-size:var(--fs-lg)!important;
  font-weight:var(--fw-bold)!important; letter-spacing:var(--ls-heading);
  line-height:var(--lh-heading)!important; margin:0 0 9px!important; }

.single .wp-block-post-title{
  font-family:var(--font-display); font-size:var(--fs-3xl)!important;
  font-weight:var(--fw-bold)!important; letter-spacing:var(--ls-display);
  line-height:var(--lh-heading)!important; margin:0 0 var(--s-4)!important; }
.wp-block-post-title a{ color:var(--ink)!important; text-decoration:none!important; }
.wp-block-post-title a:hover{ color:var(--accent)!important; }

.wp-block-post-excerpt{ margin:0 0 auto!important; }
.wp-block-post-excerpt__excerpt{ font-size:14px!important; line-height:1.6!important;
  color:var(--sub)!important; display:-webkit-box; -webkit-line-clamp:3;
  -webkit-box-orient:vertical; overflow:hidden; }
.wp-block-post-excerpt__more-link{ display:inline-block; margin-top:16px; font-size:13px;
  font-weight:650; color:var(--accent)!important; text-decoration:none!important; }
.wp-block-post-excerpt__more-link:hover{ text-decoration:underline!important; }

/* This used to be `.blog .wp-block-post-featured-image{ display:none!important }`.
   The reasoning was sound at the time — only some posts had an image, and a
   grid where one card carries a large image and the rest do not reads as
   broken. That premise is gone: every post now has a purpose-made graphic, so
   the listing shows them. Kept as a note because the rule was deliberate and
   the next person to see a ragged grid should know why it was there. */

/* Archive heading, aligned to the same container as the grid. */
.blog main h1{ max-width:var(--max)!important; margin-left:auto!important;
  margin-right:auto!important; margin-bottom:6px!important;
  font-size:36px!important; font-weight:800!important; letter-spacing:-.035em; }

@media(max-width:640px){
  .wp-block-post-template{ grid-template-columns:1fr; gap:18px; }
  .blog main h1{ font-size:var(--fs-2xl)!important; }
  .blog .wp-block-post-title,
  .archive .wp-block-post-title{ font-size:var(--fs-md)!important; }
  /* A single post's headline still steps down on small screens, but stays the
     largest thing on the page rather than dropping below its own subheadings. */
  .single .wp-block-post-title{ font-size:var(--fs-2xl)!important; }
}

/* Reading measure for the post body. Long lines are the single biggest
   readability problem on a full-width layout — anything past ~75 characters
   costs the eye its place on the return sweep. */
.single-post .wp-block-post-content,
.single-post .entry-content{ max-width:68ch; margin-left:auto; margin-right:auto; }
.single-post .entry-content p{ font-size:16px; line-height:1.75; }
.single-post .entry-content h2{ margin-top:1.8em; letter-spacing:-.02em; }
.single-post .entry-content h3{ margin-top:1.5em; }

/* ---------- footer: scannable, trustful ---------- */
.site-footer{ background:var(--footer-bg)!important; }
.tdt-footer-inner{ max-width:var(--max); margin:0 auto; padding:var(--rhythm) var(--gutter) calc(var(--rhythm) * 0.44);
  display:grid; grid-template-columns:1.2fr 1fr 1fr 1fr 1fr; gap:32px; }
/* A second heading inside one column needs separating from the list above it,
   otherwise "About" reads as another link rather than a new group. */
.tdt-footer-h3--spaced{ margin-top:22px !important; }
.tdt-footer-logo{ font-weight:var(--fw-bold); font-size:15px; color:var(--footer-ink); margin-bottom:10px; letter-spacing:-.02em; }
.tdt-footer-col p{ color:var(--footer-muted); font-size:12px; line-height:1.65; margin:0; }
.tdt-footer-col h3{ font-size:10px; text-transform:uppercase; letter-spacing:.08em;
  color:var(--footer-head); font-weight:700; margin:0 0 13px; }
.tdt-footer-col a{ display:block; color:var(--footer-link)!important; font-size:13px; margin-bottom:9px; transition:color .15s; }
.tdt-footer-col a:hover{ color:var(--footer-ink)!important; }
.tdt-footer-bottom{ border-top:1px solid var(--footer-line); padding:calc(var(--rhythm) * 0.3) var(--gutter); text-align:center;
  color:var(--footer-head); font-size:12px; }

/* ---------- long-form pages (About, Deals) ---------- */
.tdt-prose{ max-width:760px; margin:0 auto; padding-top:8px; }
.tdt-prose h2{ font-size:22px; font-weight:700; letter-spacing:-.025em; margin:38px 0 12px; }
.tdt-prose h3{ font-size:17px; font-weight:650; margin:28px 0 10px; }
.tdt-prose p{ font-size:15px; line-height:1.72; color:var(--ink-2); margin:0 0 16px; }
.tdt-prose ul,.tdt-prose ol{ margin:0 0 20px; padding-left:22px; }
.tdt-prose li{ font-size:15px; line-height:1.7; color:var(--ink-2); margin-bottom:8px; }
.tdt-prose a{ color:var(--accent); text-decoration:underline; text-underline-offset:2px; }
.tdt-prose > *:first-child{ margin-top:0; }
.tdt-prose table{ width:100%; border-collapse:collapse; margin:0 0 20px; font-size:14px; }
.tdt-prose th{ text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.05em;
  color:var(--sub); font-weight:650; padding:8px 12px; border-bottom:2px solid var(--line); }
.tdt-prose td{ padding:10px 12px; border-bottom:1px solid var(--line-2); color:var(--ink-2); vertical-align:top; }
.tdt-prose td code, .tdt-prose p code{ background:var(--line-2); padding:2px 6px; border-radius:4px; font-size:.9em; }

/* ==================== BLOG (native WP posts) ====================
   The theme's own single/index block templates already do the job
   structurally (title, image, content, date, excerpt, pagination) — this
   just reskins the generic WP block classes so blog posts don't look like
   a different site glued onto this one. */
.single-post main, .page-template-default.page-blog main{ max-width:var(--max); margin:0 auto; }
.hostinger-ai-page-title{ font-weight:750!important; letter-spacing:-.03em!important; }
.single-post .wp-block-post-content{ max-width:720px; margin:0 auto; }
.single-post .wp-block-post-content p{ font-size:16px; line-height:1.75; color:var(--ink-2); margin:0 0 20px; }
.single-post .wp-block-post-content h2{ font-size:24px; font-weight:750; letter-spacing:-.025em;
  color:var(--ink); margin:42px 0 14px; }
.single-post .wp-block-post-content h3{ font-size:19px; font-weight:650; letter-spacing:-.015em;
  color:var(--ink); margin:30px 0 12px; }
.single-post .wp-block-post-content ul, .single-post .wp-block-post-content ol{ margin:0 0 20px; padding-left:22px; }
.single-post .wp-block-post-content li{ font-size:16px; line-height:1.7; color:var(--ink-2); margin-bottom:8px; }
.single-post .wp-block-post-content a{ color:var(--accent); text-decoration:underline; text-underline-offset:2px; }
.single-post .wp-block-post-content table{ width:100%; border-collapse:collapse; margin:8px 0 24px; font-size:14px; }
.single-post .wp-block-post-content th{ text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.05em;
  color:var(--sub); font-weight:650; padding:9px 12px; border-bottom:2px solid var(--line); }
.single-post .wp-block-post-content td{ padding:11px 12px; border-bottom:1px solid var(--line-2); color:var(--ink-2); }
.single-post .wp-block-post-content strong{ color:var(--ink); font-weight:650; }
.single-post .wp-block-post-content blockquote{ border-left:3px solid var(--accent); padding:4px 0 4px 18px;
  margin:0 0 20px; color:var(--sub); font-style:italic; }
.wp-block-post-date time{ font-size:12px; color:var(--faint); font-weight:550; }

/* Archive/index listing (also used for /blog/) */
.blog main .wp-block-query, .archive main .wp-block-query{ max-width:var(--max); margin:0 auto; padding:0 var(--gutter); }
/* NOTE: the archive listing rules that used to live here duplicated the
   "blog archive: centred editorial list" block above and, being later in
   the file with equal specificity, silently overrode it — post titles
   rendered at 22px instead of 27px. Removed; the block above is the single
   source of truth for the archive. Pagination rules below are kept because
   that block does not cover them. */
.wp-block-query-pagination{ margin-top:16px; }
.wp-block-query-pagination a{ color:var(--accent)!important; font-weight:600; font-size:14px; }

/* ---------- archive: clear hierarchy ---------- */
.tdt-archive-head{ max-width:var(--max); margin:0 auto; padding:var(--rhythm) var(--gutter) calc(var(--rhythm) * 0.3); }
.tdt-archive-head h1{ font-size:36px!important; font-weight:750!important; letter-spacing:-.03em; margin:0 0 10px!important; }
.tdt-archive-head p{ color:var(--sub); font-size:15px; max-width:60ch; margin:0 0 20px; }

/* ---------- responsive: shrink the shared gutter, not each rule ---------- */
@media(max-width:1100px){
  :root{ --gutter:28px; --rhythm:64px; }
  .tdt-footer-inner{ grid-template-columns:1fr 1fr; }
}
@media(max-width:768px){
  :root{ --gutter:20px; --rhythm:52px; }
  .tdt-hero{ padding:56px var(--gutter) 4px; }
  .tdt-hero h1, .tdt-hero .tdt-hero__title{ font-size:clamp(28px,5vw,42px); }
  .tdt-stats{ grid-template-columns:1fr 1fr; gap:28px; }
  .tdt-grid{ grid-template-columns:repeat(auto-fill,minmax(158px,1fr)); gap:12px; }
  .tdt-grid.tdt-deal-grid{ grid-template-columns:1fr; }
}
@media(max-width:640px){
  :root{ --gutter:16px; --rhythm:44px; }
  .tdt-header-inner{ gap:10px; }
  .tdt-section-head{ flex-direction:column; }
  .tdt-section-head h2{ font-size:19px; }
  .tdt-tool__title{ font-size:26px!important; }
  .tdt-footer-inner{ grid-template-columns:1fr; gap:24px; }
  .tdt-newsletter__form{ flex-direction:column; }
  .tdt-newsletter__form input,.tdt-newsletter__form button{ width:100%; }
}

/* ---------- discipline picker: get to what matters fast ---------- */
.tdt-disc-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); gap:12px; margin:0; }
.tdt-disc-card{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px;
  padding:24px 12px; background:var(--paper); border:1px solid var(--line); border-radius:var(--r-lg);
  text-decoration:none!important; color:inherit!important; transition:all .18s; text-align:center; }
.tdt-disc-card:hover{ border-color:hsl(var(--tint,220) 60% 72%); box-shadow:var(--shadow-lg); transform:translateY(-3px); }
/* Per-discipline tint instead of one uniform blue block: nine identical
   gradients made the grid read as a wall, and colour is what lets someone
   re-find "their" discipline on a return visit. Deep ink text on a pastel
   tile keeps contrast well above 4.5:1 at every hue, which white-on-gradient
   did not for the lighter ones. */
.tdt-disc-card__mono{ display:flex; align-items:center; justify-content:center; width:56px; height:56px;
  border-radius:14px;
  background:hsl(var(--tint,220) 68% 93%);
  background-image:linear-gradient(135deg, hsl(var(--tint,220) 72% 94%) 0%, hsl(calc(var(--tint,220) + 38) 66% 87%) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.7);
  color:hsl(var(--tint,220) 55% 27%); font-weight:750; font-size:19px; letter-spacing:-.02em; }
.tdt-disc-card__name{ font-weight:600; font-size:13px; line-height:1.3; }
.tdt-disc-card__count{ font-size:11px; color:var(--faint); }

/* ---------- blog / archive / search post cards ----------
   The blog is a core Query Loop, not Elementor. Elementor's free tier has no
   Loop Grid widget (that is Pro), so a dynamic post grid cannot be built from
   native Elementor widgets here — and paid plugins are off the table. Styling
   the core blocks keeps the listing fully editable in the Site Editor with
   drag-and-drop blocks, and costs nothing.

   Scoped to the listing contexts only, so a Query Loop dropped anywhere else
   keeps its own look. */
body.blog .wp-block-post-template,
body.archive .wp-block-post-template,
body.search .wp-block-post-template{
  display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr));
  gap:22px; list-style:none; padding:0; margin:0;
}
body.blog .wp-block-post,
body.archive .wp-block-post,
body.search .wp-block-post{
  display:flex; flex-direction:column;
  background:var(--paper); border:1px solid var(--line);
  border-radius:var(--r-lg); overflow:hidden;
  transition:border-color .18s, box-shadow .18s, transform .18s;
}
body.blog .wp-block-post:hover,
body.archive .wp-block-post:hover,
body.search .wp-block-post:hover{
  border-color:var(--line-hover); transform:translateY(-2px);
  box-shadow:0 10px 30px rgba(23,23,23,.08);
}

/* The template puts the title before the image. Reordering visually rather
   than editing the template keeps the heading first in the DOM, which is the
   right reading order for a screen reader, while the eye still gets the image
   first — the usual card convention. */
body.blog .wp-block-post-featured-image,
body.archive .wp-block-post-featured-image,
body.search .wp-block-post-featured-image{
  order:-1; margin:0; width:100%; aspect-ratio:1200/630; overflow:hidden;
  background:var(--wash); border-bottom:1px solid var(--line);
}
/* The core block wraps the img in an anchor, which is display:inline and so
   has no height for the img's height:100% to resolve against — the whole
   figure collapsed to 0x0 and no post image rendered at all. */
body.blog .wp-block-post-featured-image a,
body.archive .wp-block-post-featured-image a,
body.search .wp-block-post-featured-image a{
  display:block; width:100%; height:100%;
}
body.blog .wp-block-post-featured-image img,
body.archive .wp-block-post-featured-image img,
body.search .wp-block-post-featured-image img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .4s var(--ease-spring);
}
body.blog .wp-block-post:hover .wp-block-post-featured-image img,
body.archive .wp-block-post:hover .wp-block-post-featured-image img{ transform:scale(1.03); }

body.blog .wp-block-post-title,
body.archive .wp-block-post-title,
body.search .wp-block-post-title{
  font-family:var(--font-display); font-size:var(--fs-md); font-weight:var(--fw-semi);
  letter-spacing:var(--ls-heading); line-height:var(--lh-heading);
  margin:18px 20px 0; order:1;
}
body.blog .wp-block-post-title a,
body.archive .wp-block-post-title a,
body.search .wp-block-post-title a{ color:var(--ink)!important; text-decoration:none!important; }

/* The date sits in two nested core groups; target the date itself so the
   wrappers cannot reintroduce their own spacing. */
body.blog .wp-block-post-date,
body.archive .wp-block-post-date,
body.search .wp-block-post-date{
  font-size:var(--fs-2xs); font-weight:var(--fw-medium); letter-spacing:var(--ls-caps);
  text-transform:uppercase; color:var(--faint); margin:10px 20px 0; order:2;
}
body.blog .wp-block-post-date time{ margin:0; }

body.blog .wp-block-post-excerpt,
body.archive .wp-block-post-excerpt,
body.search .wp-block-post-excerpt{ margin:10px 20px 20px; order:3; flex:1 1 auto;
  display:flex; flex-direction:column; }
body.blog .wp-block-post-excerpt__excerpt,
body.archive .wp-block-post-excerpt__excerpt,
body.search .wp-block-post-excerpt__excerpt{
  font-size:var(--fs-sm); line-height:var(--lh-body); color:var(--sub); margin:0;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
  overflow:hidden; flex:1 1 auto;
}
body.blog .wp-block-post-excerpt__more-text,
body.archive .wp-block-post-excerpt__more-text,
body.search .wp-block-post-excerpt__more-text{ margin:14px 0 0; }
body.blog .wp-block-post-excerpt__more-link,
body.archive .wp-block-post-excerpt__more-link,
body.search .wp-block-post-excerpt__more-link{
  font-size:var(--fs-sm); font-weight:var(--fw-medium);
  color:var(--accent)!important; text-decoration:none!important;
}
/* Was 20px high. Padding rather than a height keeps it reading as a text
   link while giving the finger something to land on. */
body.blog .wp-block-post-excerpt__more-link,
body.archive .wp-block-post-excerpt__more-link,
body.search .wp-block-post-excerpt__more-link{
  display:inline-flex; align-items:center; min-height:44px; }
body.blog .wp-block-post-excerpt__more-link:hover{ text-decoration:underline!important; }
body.blog .wp-block-post-excerpt__more-link::after{ content:" \2192"; }

/* The template's trailing spacer was doing the job padding now does. */
body.blog .wp-block-post .wp-block-spacer,
body.archive .wp-block-post .wp-block-spacer,
body.search .wp-block-post .wp-block-spacer{ display:none; }

/* A card with no featured image should not collapse to a title on bare page
   background — it still needs the top edge the image was providing. */
body.blog .wp-block-post:not(.has-post-thumbnail) .wp-block-post-title,
body.archive .wp-block-post:not(.has-post-thumbnail) .wp-block-post-title,
body.search .wp-block-post:not(.has-post-thumbnail) .wp-block-post-title{ margin-top:22px; }

/* The header is a floating pill that overlaps the top of the content, so a
   title with no top margin sits almost against it. Centred, because these
   pages are a single tool rather than an article — there is no column of
   body text for a left-aligned title to align to. */
.tdt-tool-h1{ font-family:var(--font-display); font-size:var(--fs-2xl);
  font-weight:var(--fw-bold); letter-spacing:var(--ls-display);
  line-height:var(--lh-heading); color:var(--ink); text-align:center;
  max-width:var(--max); margin:0 auto var(--s-5);
  /* padding, not margin: this h1 is the first child of its container, so a
     top margin collapses out through the parent and the measured gap below
     the floating header stayed at 0. */
  padding:var(--s-7) var(--gutter) 0; }
@media(max-width:640px){
  .tdt-tool-h1{ padding-top:var(--s-5); font-size:var(--fs-xl); }
}

/* The CMF and invoice pages are ordinary WP pages, so their title comes from
   the theme's post-title block rather than the injected .tdt-tool-h1 the
   Elementor tool pages use. It was rendering flush against the floating
   header — a measured 0px gap. :has() identifies a tool page by the presence
   of the app wrapper; where it is unsupported the page simply keeps today's
   spacing, so there is nothing to break. */
/* Same idea, one property further: the theme's title block also renders at
   its own x-large preset (38px/750), a full size step above .tdt-tool-h1
   (--fs-2xl, ~28px/700) on the other three tools — reported directly: the
   invoice generator's title read visibly bigger than its siblings'. Pinned
   to the same values here rather than changed at the source, since that
   source is the theme's global title styling, shared with every ordinary
   page on the site that isn't a tool.

   !important required: .has-x-large-font-size (the theme's own utility
   class, present alongside wp-block-post-title on these titles) sets its
   own font-size with !important. Without matching it, this rule loses
   despite the higher specificity from :has() — confirmed live after the
   first deploy shipped with no !important and the title measured 38px,
   unchanged.

   margin-bottom: .tdt-tool-h1 (the other three tools) carries 0 top /
   var(--s-5) bottom. This title needs the opposite top value — 48px, not
   0, because it clears the floating header from a different ancestor
   chain than Elementor's own section padding provides — but had no
   bottom margin of its own, so it sat flush against the tool switcher
   below it while the other three tools kept a 24px gap there. Matched to
   the same var(--s-5) now so the rhythm below the title reads the same
   everywhere even though the rhythm above it, for template reasons,
   still doesn't. */
body:has(.tdt-app) .wp-block-post-title{
  margin-top:var(--s-7); margin-bottom:var(--s-5) !important;
  font-size:var(--fs-2xl) !important; font-weight:var(--fw-bold) !important;
}
@media(max-width:640px){
  body:has(.tdt-app) .wp-block-post-title{ margin-top:var(--s-5); font-size:var(--fs-xl) !important; }
}

/* ---------- tool page copy ----------
   The explanatory text under each tool was a wall of paragraphs. It has to
   stay — it is what these pages rank on, and it is where the honest caveats
   live — but a spec sheet of prose is not why anyone opened a colour picker.
   Cut shorter and laid out as cards, so it scans in a glance and still reads
   as content to a crawler. */
.tdt-toolcopy{ max-width:var(--max); margin:var(--s-6) auto 0; padding:0 var(--gutter); }
.tdt-toolcopy__lede{ font-size:var(--fs-md); line-height:var(--lh-body);
  color:var(--ink-2); max-width:68ch; margin:0 0 var(--s-5); }
.tdt-toolcopy__lede strong{ color:var(--ink); font-weight:var(--fw-semi); }

.tdt-toolgrid{ display:grid; gap:16px; margin:0 0 var(--s-6);
  grid-template-columns:repeat(auto-fit,minmax(232px,1fr)); }
.tdt-toolcard{ background:var(--paper); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:18px 20px; }
.tdt-toolcard h3{ font-family:var(--font-display); font-size:var(--fs-sm);
  font-weight:var(--fw-semi); letter-spacing:var(--ls-heading);
  color:var(--ink); margin:0 0 6px; }
.tdt-toolcard p{ font-size:var(--fs-xs); line-height:var(--lh-body);
  color:var(--sub); margin:0; }
.tdt-toolcard__k{ display:block; font-size:var(--fs-2xs); font-weight:var(--fw-semi);
  letter-spacing:var(--ls-caps); text-transform:uppercase;
  color:var(--accent); margin:0 0 8px; }

/* The honest-limits block. Deliberately not styled as a warning — these are
   trade-offs, not errors, and dressing them in red would overstate them. */
.tdt-toolnote{ background:var(--wash); border:1px solid var(--line);
  border-left:3px solid var(--control-line);
  border-radius:var(--r-sm); padding:14px 18px; margin:0 0 var(--s-6); }
.tdt-toolnote p{ font-size:var(--fs-xs); line-height:var(--lh-body);
  color:var(--ink-2); margin:0 0 6px; }
.tdt-toolnote p:last-child{ margin-bottom:0; }
.tdt-toolnote strong{ color:var(--ink); }

.tdt-toolcopy h2{ font-family:var(--font-display); font-size:var(--fs-lg);
  font-weight:var(--fw-semi); letter-spacing:var(--ls-heading);
  color:var(--ink); margin:0 0 var(--s-4); }
.tdt-toolcopy h3{ font-family:var(--font-display); }
/* FAQ answers stay plain prose — they are the schema source. */
.tdt-toolfaq h3{ font-size:var(--fs-sm); font-weight:var(--fw-semi);
  color:var(--ink); margin:var(--s-4) 0 4px; }
.tdt-toolfaq p{ font-size:var(--fs-sm); line-height:var(--lh-body);
  color:var(--ink-2); margin:0; max-width:72ch; }

/* ---------- focus system ----------
   Audited 2026-08-18: of 72 focusable elements on the home page, 34 had a
   designed focus ring and 38 had nothing, falling through to the browser's
   default — a 1px auto outline in orange (rgb(229,151,0) in Chrome). So
   keyboard users were not locked out, but tabbing through the site alternated
   between a deliberate 2px blue ring and an off-brand browser hairline. That
   reads as unfinished, and on the dark theme the browser default sits at low
   contrast against the page.

   :where() gives this zero specificity, so it is a floor rather than an
   override: every component that already defines its own ring keeps it, and
   everything else stops falling back to the UA default. */
:where(a[href], button, input, select, textarea, summary, [tabindex]):focus-visible{
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:3px;
}
/* Focus must stay visible over a dark card or a colour swatch, where a blue
   ring alone can disappear. The second ring is drawn in the page background
   colour so there is always an edge against whatever sits underneath. */
:where(a[href], button, input, select, textarea, summary, [tabindex]):focus-visible{
  box-shadow:0 0 0 4px var(--canvas);
}
/* The skip link is the first thing a keyboard user meets. It must be
   unmissable when focused, not a 1px hairline on a 1px target. */
.skip-link:focus,
.skip-link:focus-visible{
  outline:2px solid var(--accent); outline-offset:2px;
  background:var(--paper); color:var(--ink)!important;
  padding:12px 20px; border-radius:var(--r-sm);
  box-shadow:0 10px 30px rgba(23,23,23,.18);
}

/* ---------- link affordance ----------
   Body links were accent-coloured but the underline was inconsistent, so on
   a page of dark text a link read as emphasis rather than as something you
   could click. Colour alone is also not a sufficient signal — it fails for
   anyone who cannot distinguish it, which is why WCAG 1.4.1 asks for a
   second cue. These rules give every in-content link a persistent underline
   and a hover state that changes, rather than one that only appears.

   Deliberately scoped to prose: navigation, cards, footers and buttons are
   already unambiguous from their position and shape, and underlining those
   would be noise. */
.tdt-prose a,
.tdt-page-intro a,
.single-post .wp-block-post-content p a,
.single-post .wp-block-post-content li a,
.page .wp-block-post-content p a,
.page .wp-block-post-content li a{
  color:var(--accent);
  text-decoration:underline;
  text-decoration-thickness:1.5px;
  text-underline-offset:2px;
  text-decoration-color:rgba(36,84,255,.42);
  border-radius:2px;
  transition:text-decoration-color .15s, color .15s, background .15s;
}
.tdt-prose a:hover,
.tdt-page-intro a:hover,
.single-post .wp-block-post-content p a:hover,
.single-post .wp-block-post-content li a:hover,
.page .wp-block-post-content p a:hover,
.page .wp-block-post-content li a:hover{
  text-decoration-color:currentColor;
  background:rgba(36,84,255,.07);
}
:root[data-theme="dark"] .tdt-prose a,
:root[data-theme="dark"] .tdt-page-intro a,
:root[data-theme="dark"] .single-post .wp-block-post-content p a,
:root[data-theme="dark"] .single-post .wp-block-post-content li a,
:root[data-theme="dark"] .page .wp-block-post-content p a,
:root[data-theme="dark"] .page .wp-block-post-content li a{
  text-decoration-color:rgba(125,155,255,.50);
}
:root[data-theme="dark"] .tdt-prose a:hover,
:root[data-theme="dark"] .tdt-page-intro a:hover,
:root[data-theme="dark"] .single-post .wp-block-post-content p a:hover,
:root[data-theme="dark"] .page .wp-block-post-content p a:hover{
  background:rgba(125,155,255,.12);
}
/* Keyboard users get the same treatment everywhere, not just in prose. */
.tdt-prose a:focus-visible,
.tdt-page-intro a:focus-visible,
.wp-block-post-content a:focus-visible{
  outline:2px solid var(--accent); outline-offset:2px;
}

/* ---------- global search ---------- */
.tdt-vh{ position:absolute!important; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }

.tdt-search-toggle{ display:inline-flex; align-items:center; justify-content:center;
  width:36px; height:36px; padding:0; border-radius:999px; cursor:pointer;
  border:1px solid transparent; background:none; color:var(--ink-2);
  transition:background .15s, color .15s, border-color .15s; }
.tdt-search-toggle svg{ width:18px; height:18px; }
.tdt-search-toggle:hover{ background:var(--wash); color:var(--ink); border-color:var(--control-line); }
.tdt-search-toggle[aria-expanded="true"]{ background:var(--solid); border-color:var(--solid); color:var(--solid-ink); }
.tdt-search-toggle:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

/* Sits under the floating pill rather than inside it: the pill is a fixed
   height and a full-width field cannot live there without squeezing the nav. */
.tdt-search-panel{ max-width:var(--max); margin:10px auto 0; padding:0 var(--gutter); }
.tdt-search-panel[hidden]{ display:none; }

.tdt-search-form{ display:flex; align-items:center; gap:10px;
  background:var(--paper); border:1px solid var(--control-line);
  border-radius:999px; padding:6px 6px 6px 16px;
  box-shadow:0 10px 30px rgba(23,23,23,.08); }
.tdt-search-form__icon{ display:inline-flex; color:var(--sub); flex:0 0 auto; }
.tdt-search-form__icon svg{ width:18px; height:18px; }
.tdt-search-form__input{ flex:1 1 auto; min-width:0; font:inherit;
  font-size:var(--fs-base); color:var(--ink); background:none; border:0;
  outline:none; padding:10px 0; }
.tdt-search-form__input::placeholder{ color:var(--faint); }
.tdt-search-form__input::-webkit-search-cancel-button{ -webkit-appearance:none; }
.tdt-search-form__go{ flex:0 0 auto; font:inherit; font-size:var(--fs-sm);
  font-weight:var(--fw-medium); min-height:40px; padding:0 20px; border-radius:999px;
  border:1px solid var(--solid); background:var(--solid); color:var(--solid-ink);
  cursor:pointer; transition:opacity .15s; }
.tdt-search-form__go:hover{ opacity:.88; }
.tdt-search-form__go:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.tdt-search-form:focus-within{ border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(36,84,255,.10), 0 10px 30px rgba(23,23,23,.08); }

/* In the mobile menu the field is always visible — an icon that reveals a
   field is a desktop affordance; on a phone the menu is already the overlay. */
.tdt-mobile-nav__search{ margin-bottom:14px; }
.tdt-mobile-nav__search .tdt-search-form{ box-shadow:none; }
.tdt-mobile-nav__search .tdt-search-form__go{ padding:0 14px; }

/* ---------- in-body tool links + related reading ---------- */
/* Distinguishable from ordinary prose links without shouting: a reader should
   be able to tell that this one goes to a directory listing. */
.tdt-autolink{ color:var(--accent)!important; text-decoration:none!important;
  border-bottom:1px solid rgba(36,84,255,.28); transition:border-color .15s; }
.tdt-autolink:hover{ border-bottom-color:var(--accent); }

.tdt-related-posts{ margin:var(--s-7) 0 var(--s-5); padding-top:var(--s-5);
  border-top:1px solid var(--line); }
.tdt-related-posts__h{ font-family:var(--font-display); font-size:var(--fs-lg);
  font-weight:var(--fw-semi); letter-spacing:var(--ls-heading);
  line-height:var(--lh-heading); color:var(--ink); margin:0 0 var(--s-4); }
.tdt-related-posts__grid{ display:grid; gap:16px;
  grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); }
.tdt-related-post{ display:flex; flex-direction:column; text-decoration:none!important;
  background:var(--paper); border:1px solid var(--line); border-radius:var(--r-lg);
  overflow:hidden; transition:border-color .18s, transform .18s, box-shadow .18s; }
.tdt-related-post:hover{ border-color:var(--line-hover); transform:translateY(-2px);
  box-shadow:0 10px 30px rgba(23,23,23,.08); }
.tdt-related-post__img{ display:block; aspect-ratio:1200/630; background:var(--wash);
  border-bottom:1px solid var(--line); overflow:hidden; }
.tdt-related-post__img img{ width:100%; height:100%; object-fit:cover; display:block; }
.tdt-related-post__body{ display:block; padding:14px 16px 16px; }
.tdt-related-post__title{ display:block; font-family:var(--font-display);
  font-size:var(--fs-sm); font-weight:var(--fw-semi); line-height:var(--lh-heading);
  letter-spacing:var(--ls-heading); color:var(--ink); }
.tdt-related-post__date{ display:block; margin-top:8px; font-size:var(--fs-2xs);
  font-weight:var(--fw-medium); letter-spacing:var(--ls-caps); text-transform:uppercase;
  color:var(--faint); }

/* ---------- share strip (foot of every post) ---------- */
.tdt-share{ margin:var(--s-7) 0 var(--s-5); padding-top:var(--s-5);
  border-top:1px solid var(--line); }
.tdt-share__h{ font-family:var(--font-display); font-size:var(--fs-lg);
  font-weight:var(--fw-semi); letter-spacing:var(--ls-heading);
  line-height:var(--lh-heading); color:var(--ink); margin:0 0 var(--s-4); }
.tdt-share__row{ display:flex; flex-wrap:wrap; gap:var(--s-2); }
/* min-height 44px is the accessibility floor for a tap target, and the row
   wraps rather than scrolls so no option is ever hidden off-screen. */
.tdt-share__btn{ font:inherit; font-size:var(--fs-sm); font-weight:var(--fw-medium);
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px; padding:0 18px; border-radius:999px;
  border:1px solid var(--control-line); background:var(--paper);
  color:var(--ink)!important; text-decoration:none!important; cursor:pointer;
  transition:background .15s, border-color .15s; }
.tdt-share__btn:hover{ background:var(--wash); border-color:var(--ink); }
.tdt-share__btn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.tdt-share__btn--copy, .tdt-share__btn--native{ background:var(--solid);
  border-color:var(--solid); color:var(--solid-ink)!important; }
.tdt-share__btn--copy:hover, .tdt-share__btn--native:hover{ opacity:.88;
  background:var(--solid); border-color:var(--solid); }
.tdt-share__status{ font-size:var(--fs-xs); color:var(--sub);
  margin:var(--s-3) 0 0; min-height:1.2em; }

/* ---------- mobile hamburger nav ---------- */
/* The bars are --ink, which is correct in both themes, but the button sat on
   no background of its own: once the menu opened, the X floated over whatever
   the panel behind it happened to be and could vanish entirely. Giving it its
   own surface makes it legible against anything, and takes the tap target from
   28px to the 44px accessibility floor. */
.tdt-hamburger{ display:none; flex-direction:column; align-items:center; justify-content:center;
  gap:5px; border:1px solid var(--control-line); border-radius:var(--r-sm);
  background:var(--paper); cursor:pointer; width:44px; height:44px;
  z-index:201; position:relative; transition:background .15s, border-color .15s; }
.tdt-hamburger:hover{ background:var(--wash); }
.tdt-hamburger span{ width:20px; height:2px; background:var(--ink); border-radius:1px;
  transition:all .3s; display:block; }
/* Open state: solid, so the close X is unmistakable in either theme. */
.tdt-hamburger.is-open{ background:var(--solid); border-color:var(--solid); }
.tdt-hamburger.is-open span{ background:var(--solid-ink); }
.tdt-hamburger.is-open span:nth-child(1){ transform:rotate(45deg) translate(5px,5px); }
.tdt-hamburger.is-open span:nth-child(2){ opacity:0; }
.tdt-hamburger.is-open span:nth-child(3){ transform:rotate(-45deg) translate(5px,-5px); }
/* top was hard-coded to 60px, which never matched the floating pill: the
   header's real bottom edge is ~149px at rest and ~74px once it pins on
   scroll. The panel therefore sliced across the header, and the amount it
   covered changed with scroll position. It now starts at 0 and pads its
   content down to whatever --header-h currently measures (set in JS), so the
   pill floats cleanly above it at any scroll offset. */
.tdt-mobile-nav{ position:fixed; inset:0; background:var(--canvas);
  transform:translateX(-100%); transition:transform .3s; z-index:1;
  padding:calc(var(--header-h, 74px) + 12px) 20px 20px; overflow-y:auto;
  overscroll-behavior:contain; display:none; }
.tdt-mobile-nav.is-open{ transform:translateX(0); display:block; }
/* The drawer scrolls beneath the header: .tdt-header-inner is a glass pill
   floating ~8px down from the top (not flush), and .site-header itself has
   no background — the header only visually reads as opaque because the
   PILL is. .tdt-mobile-nav is also a descendant of .site-header, so a
   backing layer added on the drawer sits in the same stacking context as
   the pill and loses: it painted BEHIND the pill instead of hiding the
   links scrolling past above and beside it (confirmed live — three
   discipline links sat visible in the gap around the pill while the
   drawer was open).
   Fixing it at the source instead: while the drawer is open, pin the
   header flush to the viewport top and give it its own opaque backing.
   :has() is used elsewhere in this stylesheet already, no polyfill risk
   for this codebase. */
body:has(.tdt-mobile-nav.is-open) .site-header{
  position:fixed; top:0; left:0; right:0; z-index:10000;
}
body:has(.tdt-mobile-nav.is-open) .site-header::after{
  content:""; position:absolute; inset:0; z-index:-1; background:var(--canvas);
}
/* The JS has added .nav-open to <body> all along, but no rule ever matched it,
   so the page scrolled freely behind the open menu. */
body.nav-open{ overflow:hidden; }
.tdt-mobile-nav a{ display:block; padding:12px 16px; color:var(--ink)!important;
  border-bottom:1px solid var(--line); font-weight:500; }
.tdt-mobile-nav a:hover{ background:rgba(23,23,23,.05); }
/* Section labels in the mobile drawer. The list was 22 flat links with no
   grouping — everything looked equally important, which is the same as
   nothing being important. Labels give it scannable structure. */
.tdt-mobile-nav__label{
  margin:16px 16px 2px; font-size:11px; font-weight:600;
  text-transform:uppercase; letter-spacing:.5px; color:var(--faint);
}
.tdt-mobile-nav__label:first-of-type{ margin-top:10px; }
/* The one highlighted action in the drawer, matching the desktop CTA. */
.tdt-mobile-nav a.tdt-mobile-nav__cta{
  margin:10px 16px; padding:12px 16px; border-radius:999px;
  background:var(--solid); color:var(--solid-ink)!important;
  text-align:center; font-weight:600;
}
.tdt-mobile-nav a.tdt-mobile-nav__cta:hover{ background:var(--accent); }
/* Collapsible groups in the drawer. Before this, "Browse by discipline"
   (9 links) and "Built by us" (6 links) were dumped flat — 15 of the
   drawer's 26 links, over half of it, needing 2.2 screens of scroll to
   reach the bottom. Desktop already collapses both behind Browse ▾ and
   Built by Us ▾; this is the same disclosure on mobile via accordion
   instead of a hover dropdown, so the two finally match instead of one
   being progressive and the other dumping everything at once. */
.tdt-mobile-nav__group-trigger{
  width:100%; display:flex; align-items:center; justify-content:space-between;
  padding:12px 16px; background:none; border:none; border-bottom:1px solid var(--line);
  font:inherit; font-weight:500; font-size:15px; color:var(--ink); cursor:pointer;
  text-align:left;
}
.tdt-mobile-nav__group-trigger:hover{ background:rgba(23,23,23,.05); }
.tdt-mobile-nav__chevron{ flex:0 0 auto; transition:transform .2s ease; color:var(--faint); }
.tdt-mobile-nav__group-trigger[aria-expanded="true"] .tdt-mobile-nav__chevron{ transform:rotate(180deg); }
.tdt-mobile-nav__group-panel{ display:none; }
.tdt-mobile-nav__group-panel.is-open{ display:block; }
/* Nested links keep their own border-bottom rule from .tdt-mobile-nav a;
   only the indent is added here, so an expanded item still reads as
   "inside Browse" rather than back at the drawer's top level. */
.tdt-mobile-nav__group-panel a{ padding-left:28px; }
.tdt-mobile-nav__group-panel .tdt-mobile-nav__label:first-child{ margin-top:6px; }

@media(max-width:768px){
  .tdt-hamburger{ display:flex; }
  .tdt-nav{ display:none; }
  .tdt-disc-grid{ grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:10px; }
}

/* ---------- override Elementor spacing ---------- */
.elementor-widget-spacer{ margin-bottom:0!important; }
.elementor-widget-text-editor h1,.elementor-widget-text-editor h2,.elementor-widget-text-editor h3{
  margin-block-start:0!important; }

/* ==================== LLM RANKING (AI discipline page) ==================== */
.tdt-llm-ranking{ max-width:var(--max); margin:0 auto var(--rhythm); padding:0 var(--gutter); }
.tdt-llm-ranking__head{ margin-bottom:20px; }
.tdt-llm-ranking__head h2{ font-size:20px; font-weight:750; letter-spacing:-.02em; margin:0 0 6px; }
.tdt-llm-ranking__head p{ color:var(--sub); font-size:13px; line-height:1.6; max-width:64ch; margin:0 0 14px; }
.tdt-llm-sort{ display:flex; gap:6px; }
.tdt-llm-sort-btn{ font:inherit; font-size:12px; font-weight:600; padding:6px 12px;
  border-radius:999px; border:1px solid var(--line); background:var(--paper);
  color:var(--ink-2); cursor:pointer; transition:all .15s; }
.tdt-llm-sort-btn:hover{ border-color:var(--ink); }
.tdt-llm-sort-btn.is-active{ background:var(--solid); border-color:var(--solid); color:var(--solid-ink); }
.tdt-llm-table{ border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden; background:var(--paper); }
.tdt-llm-table p{ margin:0; }
.tdt-llm-row{
  display:grid; grid-template-columns:44px 1fr 96px 90px; align-items:center;
  gap:12px; padding:12px 18px; text-decoration:none!important; color:var(--ink)!important;
  border-bottom:1px solid var(--line-2); transition:background .15s; }
.tdt-llm-row:last-child{ border-bottom:none; }
a.tdt-llm-row:hover{ background:var(--line-2); }
.tdt-llm-row--head{ font-size:11px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--sub); font-weight:650; padding-top:10px; padding-bottom:10px; }
.tdt-llm-rank{ font-weight:700; color:var(--accent); font-size:13px; }
.tdt-llm-name{ font-family:ui-monospace,"SF Mono",Menlo,monospace; font-size:13px;
  font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tdt-llm-stat{ font-size:13px; color:var(--ink-2); font-weight:600; }
.tdt-llm-source{ font-size:12px; color:var(--faint); margin:12px 2px 0; }
.tdt-llm-source a{ color:var(--sub); text-decoration:underline; }
@media(max-width:640px){
  .tdt-llm-row{ grid-template-columns:32px 1fr 70px; }
  .tdt-llm-row > *:nth-child(4){ display:none; }
}

/* ---------- prominent legal callout (GDPR Art. 21(4)) ---------- */
.tdt-legal-callout{ border:1px solid var(--line); border-left:3px solid var(--accent);
  border-radius:var(--r); background:var(--paper); padding:22px 24px; margin:32px 0; }
.tdt-legal-callout h2{ margin-top:0!important; font-size:19px; }
.tdt-legal-callout p:last-child{ margin-bottom:0; }

/* ---------- trademark attribution ---------- */
.tdt-tm-notice{ font-size:12px; line-height:1.6; color:var(--sub);
  max-width:70ch; margin:0; }
.tdt-footer-bottom p{ margin:0; }
.tdt-footer-tm{ margin-top:8px!important; font-size:11px; line-height:1.55;
  opacity:.75; max-width:80ch; margin-inline:auto!important; }
.tdt-tool .tdt-tm-notice{ margin:28px auto 0; padding-top:18px;
  border-top:1px solid var(--line-2); text-align:center; }

/* ---------- cookie consent banner ---------- */
.tdt-consent{
  position:fixed; left:0; right:0; bottom:0; z-index:99999;
  max-width:640px; margin:0 auto 20px; margin-inline:20px;
  background:var(--footer-bg); color:var(--footer-ink); border-radius:var(--r-lg);
  padding:20px 22px; box-shadow:0 12px 40px rgba(0,0,0,.25);
  animation:tdt-consent-in .3s cubic-bezier(.4,0,.2,1);
}
@media(min-width:700px){ .tdt-consent{ margin-inline:auto; left:20px; right:auto; } }
@keyframes tdt-consent-in{ from{ transform:translateY(16px); opacity:0; } to{ transform:translateY(0); opacity:1; } }
/* Text was hardcoded rgba(255,255,255,...) on the assumption the banner
   background was always var(--footer-bg) (dark in both themes). A later
   pass added .tdt-consent to the shared glass-surface treatment further
   down this file, which overrides the background to var(--glass-fill) —
   near-white in light mode — with !important, leaving white text on a
   near-white panel. Switched to the same theme-aware ink tokens the glass
   newsletter box already uses correctly, so this adapts with it instead
   of assuming a background that glass no longer guarantees. */
.tdt-consent p{ margin:0 0 14px; font-size:13px; line-height:1.55; color:var(--ink); }
.tdt-consent__actions{ display:flex; gap:10px; }
.tdt-consent__accept, .tdt-consent__decline{
  font:inherit; font-size:13px; font-weight:650; padding:9px 18px;
  border-radius:999px; cursor:pointer; border:1px solid transparent; transition:all .15s;
}
.tdt-consent__accept{ background:var(--solid); color:var(--solid-ink); }
.tdt-consent__accept:hover{ background:var(--accent); color:#fff; }
.tdt-consent__decline{ background:transparent; color:var(--sub); border-color:var(--line); }
.tdt-consent__decline:hover{ border-color:var(--ink); color:var(--ink); }
.tdt-consent__link{ margin:12px 0 0!important; }
.tdt-consent__link a{ font-size:11px; color:var(--faint); text-decoration:underline; }

/* ---------- open-source toggle & clear filters ----------
   A binary filter reads better as one switch than as an All/Open chip pair,
   and it keeps the filter bar to two chip rows instead of three. */
.tdt-filter-row{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:16px; }
.tdt-toggle{
  display:inline-flex; align-items:center; gap:8px;
  font:inherit; font-size:12px; font-weight:600; padding:7px 14px 7px 11px;
  border-radius:999px; border:1px solid var(--line); background:var(--paper);
  color:var(--ink-2); cursor:pointer; transition:all .15s; }
.tdt-toggle__dot{
  width:9px; height:9px; border-radius:3px; flex:0 0 auto;
  border:1.5px solid var(--faint); transition:all .15s; }
.tdt-toggle:hover{ border-color:var(--oss); color:var(--oss); }
.tdt-toggle:hover .tdt-toggle__dot{ border-color:var(--oss); }
.tdt-toggle.is-active{ background:var(--oss-bg); border-color:var(--oss); color:var(--oss); }
.tdt-toggle.is-active .tdt-toggle__dot{ background:var(--oss); border-color:var(--oss); }
.tdt-toggle:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
.tdt-clear{
  font:inherit; font-size:12px; font-weight:550; padding:7px 12px;
  border:none; background:none; color:var(--sub); cursor:pointer;
  text-decoration:underline; text-underline-offset:2px; border-radius:6px; }
.tdt-clear:hover{ color:var(--ink); }
.tdt-clear:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

/* ---------- open-source panel on a tool page ---------- */
.tdt-oss-panel{
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  margin:22px 0 0; padding:13px 16px;
  background:var(--oss-bg); border:1px solid rgba(15,118,110,.18);
  border-radius:var(--r); }
.tdt-oss-panel__label{ font-size:12px; font-weight:700; letter-spacing:.02em;
  text-transform:uppercase; color:var(--oss); }
.tdt-oss-panel__body{ display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  font-size:13px; color:var(--ink-2); }
.tdt-oss-panel__lic{ font-family:ui-monospace,"SF Mono",Menlo,monospace;
  font-size:12px; background:rgba(255,255,255,.7); padding:3px 8px;
  border-radius:5px; color:var(--oss); font-weight:600; }
.tdt-oss-panel__body a{ color:var(--oss); font-weight:650; text-decoration:underline;
  text-underline-offset:2px; }

/* ---------- UI polish pass ---------- */

/* Loading: dim and lock the grid while a filter request is in flight, so the
   swap reads as intentional rather than as a flicker. */
.tdt-grid.is-loading{ opacity:.4; pointer-events:none; transition:opacity .12s ease; }

/* Empty state — a dead end with no way out is a usability failure, so this
   gets a shape and an escape hatch instead of one line of grey text. */
.tdt-empty{ text-align:center; padding:56px 20px; border:1px dashed var(--line);
  border-radius:var(--r-lg); background:rgba(247,247,247,.4); }
.tdt-empty__title{ font-size:16px; font-weight:650; color:var(--ink); margin:0 0 6px; }
.tdt-empty__hint{ font-size:14px; color:var(--sub); margin:0 0 18px; }
.tdt-empty__reset{
  font:inherit; font-size:13px; font-weight:650; padding:9px 20px; border-radius:999px;
  border:1px solid var(--line); background:var(--paper); color:var(--ink); cursor:pointer;
  transition:all .15s; }
.tdt-empty__reset:hover{ border-color:var(--ink); }

/* Cards: a slightly stronger hover makes the whole card read as the click
   target, rather than only the title looking interactive. */
/* card transition + hover consolidated into one rule further down —
   three separate declarations were overriding each other. */
.tdt-card:focus-within{ outline:2px solid var(--accent); outline-offset:2px; }

/* Chips on mobile: three wrapped rows pushed the grid far below the fold.
   Scrolling them horizontally keeps the filter bar to a predictable height. */
@media(max-width:640px){
  .tdt-filters{ flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
    -webkit-overflow-scrolling:touch; padding-bottom:2px;
    /* fade the right edge so it's visibly scrollable rather than cut off */
    mask-image:linear-gradient(90deg,#000 88%,transparent);
    -webkit-mask-image:linear-gradient(90deg,#000 88%,transparent); }
  .tdt-filters::-webkit-scrollbar{ display:none; }
  .tdt-chip{ flex:0 0 auto; }
  .tdt-browse__bar{ padding-bottom:8px; }
}

/* Honour the OS-level reduced-motion preference across everything we animate. */
@media (prefers-reduced-motion: reduce){
  .tdt-card:hover{ transform:none; }
  .tdt-consent{ animation:none; }
  .tdt-logo:hover .tdt-logo__mark{ transform:none; }
  .tdt-pill--live .tdt-pill__dot{ animation:none; }
}

/* ---------- open-source landing page ---------- */
.tdt-oss-stats{ display:grid; grid-template-columns:repeat(3,1fr); gap:20px;
  margin:0 0 8px; padding:24px; background:var(--oss-bg);
  border:1px solid rgba(15,118,110,.16); border-radius:var(--r-lg); }
.tdt-oss-stat{ text-align:center; }
.tdt-oss-stat__n{ display:block; font-size:30px; font-weight:750; letter-spacing:-.03em; color:var(--oss); }
.tdt-oss-stat__l{ display:block; font-size:12px; color:var(--ink-2); margin-top:2px; }

/* Swap rows: a three-column "instead of X → try Y" reads faster than a table
   and degrades to stacked blocks on mobile without a horizontal scroll. */
.tdt-swap{ display:flex; flex-direction:column; gap:2px; }
.tdt-swap__row{
  display:grid; grid-template-columns:minmax(150px,1fr) 32px 2fr;
  align-items:start; gap:14px; padding:18px 20px;
  border:1px solid var(--line); border-radius:var(--r);
  background:var(--paper); transition:border-color .15s; }
.tdt-swap__row:hover{ border-color:#D4D4D8; }
.tdt-swap__label{ display:block; font-size:11px; font-weight:650; letter-spacing:.06em;
  text-transform:uppercase; color:var(--faint); margin-bottom:4px; }
.tdt-swap__from a, .tdt-swap__plain{ font-size:15px; font-weight:650; color:var(--ink); }
.tdt-swap__from a{ text-decoration:none; }
.tdt-swap__from a:hover{ color:var(--accent); text-decoration:underline; }
.tdt-swap__arrow{ color:var(--faint); font-size:17px; padding-top:18px; text-align:center; }
.tdt-swap__alts a{ font-size:15px; font-weight:650; color:var(--oss); text-decoration:none; }
.tdt-swap__alts a:hover{ text-decoration:underline; }
.tdt-swap__sep{ color:var(--faint); font-size:13px; font-weight:400; margin:0 7px; }
.tdt-swap__note{ font-size:13px; line-height:1.6; color:var(--sub); margin:6px 0 0; max-width:62ch; }

@media(max-width:700px){
  .tdt-oss-stats{ grid-template-columns:1fr; gap:16px; padding:20px; }
  .tdt-oss-stat{ display:flex; align-items:baseline; justify-content:center; gap:8px; }
  .tdt-oss-stat__n{ font-size:24px; }
  .tdt-swap__row{ grid-template-columns:1fr; gap:10px; padding:16px; }
  /* Rotate the connector so the stacked layout still reads top-to-bottom. */
  .tdt-swap__arrow{ padding-top:0; text-align:left; transform:rotate(90deg);
    transform-origin:left center; height:14px; }
}

/* ---------- FTC affiliate disclosure ----------
   16 CFR 255 requires this to be as legible as the surrounding copy and
   adjacent to the link — so deliberately NOT small grey fine print. */
.tdt-aff-notice{
  margin:10px 0 0; font-size:13px; line-height:1.5; color:var(--ink-2);
  max-width:52ch; }
.tdt-tool__cta .tdt-aff-notice{ margin-inline:auto; text-align:center; }

/* ==================== FAQ ACCORDION ==================== */
.tdt-faq{ max-width:760px; margin:0 auto; }
.tdt-faq-item{ border-bottom:1px solid var(--line); }
.tdt-faq-item:first-child{ border-top:1px solid var(--line); }
.tdt-faq-trigger{
  width:100%; padding:20px 0; background:none; border:none; cursor:pointer;
  display:flex; justify-content:space-between; align-items:center; gap:16px;
  font-family:inherit; font-size:15px; font-weight:650; color:var(--ink);
  text-align:left; letter-spacing:-.01em; transition:color .18s; }
.tdt-faq-trigger:hover{ color:var(--accent); }
.tdt-faq-trigger:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; border-radius:4px; }
.tdt-faq-trigger::after{
  content:""; flex:0 0 auto; width:10px; height:10px; margin-right:4px;
  border-right:2px solid var(--sub); border-bottom:2px solid var(--sub);
  transform:rotate(45deg) translate(-2px,-2px); transition:transform .22s ease, border-color .18s; }
.tdt-faq-trigger:hover::after{ border-color:var(--accent); }
.tdt-faq-item.is-open .tdt-faq-trigger::after{ transform:rotate(-135deg) translate(-3px,-3px); }
.tdt-faq-answer{ overflow:hidden; max-height:0; transition:max-height .26s ease; }
.tdt-faq-item.is-open .tdt-faq-answer{ max-height:420px; }
.tdt-faq-answer p{ margin:0; padding:0 0 20px; color:var(--sub);
  font-size:14px; line-height:1.7; max-width:65ch; }

/* ==================== SUBMIT-A-TOOL FORM ==================== */
.tdt-form-section{ max-width:620px; margin:0 auto; }
.tdt-form-intro{ color:var(--sub); font-size:14px; line-height:1.7; margin:0 0 28px; }
.tdt-form-group{ margin-bottom:20px; }
.tdt-form-group label{ display:block; font-size:13px; font-weight:650;
  color:var(--ink-2); margin-bottom:7px; letter-spacing:-.005em; }
.tdt-form-group .req{ color:var(--deal); }
.tdt-form-group input,
.tdt-form-group select,
.tdt-form-group textarea{
  width:100%; padding:11px 13px; font-family:inherit; font-size:14px;
  color:var(--ink); background:var(--paper);
  border:1px solid var(--line); border-radius:var(--r);
  transition:border-color .16s, box-shadow .16s; }
.tdt-form-group textarea{ resize:vertical; min-height:96px; line-height:1.6; }
.tdt-form-group input:focus,
.tdt-form-group select:focus,
.tdt-form-group textarea:focus{
  outline:none; border-color:var(--accent); box-shadow:0 0 0 3px rgba(36,84,255,.12); }
.tdt-form-hint{ font-size:12px; color:var(--sub); margin:6px 0 0; }
.tdt-form-submit{
  background:var(--solid); color:var(--solid-ink); border:none; border-radius:999px;
  padding:13px 28px; font-family:inherit; font-size:14px; font-weight:650;
  cursor:pointer; transition:background .16s, transform .16s; }
.tdt-form-submit:hover{ background:var(--accent); transform:translateY(-1px); }
.tdt-form-submit:disabled{ opacity:.55; cursor:not-allowed; transform:none; }
.tdt-form-note{ font-size:12px; color:var(--sub); margin:14px 0 0; line-height:1.6; }
.tdt-form-msg{ margin-top:18px; padding:14px 16px; border-radius:var(--r);
  font-size:14px; line-height:1.6; }
.tdt-form-msg--ok{ background:#ECFDF5; border:1px solid #A7F3D0; color:#065F46; }
.tdt-form-msg--err{ background:#FEF2F2; border:1px solid #FECACA; color:#991B1B; }

/* ==================== ADVERTISE INQUIRY FORM ====================
   Reuses .tdt-form-group (inputs/labels, already styled above) and
   .tdt-form-submit (the Submit-a-Tool button) directly — only the parts
   unique to this form's markup (container, 2-col row, status message)
   needed new rules. */
.tdt-adv-contact{ background:var(--paper); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:28px; margin-top:8px; max-width:620px; }
.tdt-adv-form__row--2col{ display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.tdt-adv-form__submit{ width:100%; background:var(--accent,#2454FF); color:var(--accent-ink,#fff); }
/* Base .tdt-form-submit is solid-black with an accent hover; the advertise
   form's main action uses the corp blue instead, like the Claim-a-slot CTA
   right above it. Filter (not a second background) gives the hover feedback
   since base and hover share the same background. */
.tdt-adv-form__submit:hover{ background:var(--accent,#2454FF); filter:brightness(.92); }
.tdt-adv-form__msg{ margin-top:14px; font-size:13px; line-height:1.6; }
.tdt-adv-form__msg--ok{ color:var(--free); }
.tdt-adv-form__msg--err{ color:var(--deal); }
@media(max-width:600px){ .tdt-adv-form__row--2col{ grid-template-columns:1fr; } }