/* ==========================================================================
   AllPDF.tech - Clean, High-Precision Styling for Default UI
   Matches the Original Layout with High-Fidelity Responsive Design
   ========================================================================== */

:root {
  --primary-color: #4f46e5;
  --primary-hover: #4338ca;
  --primary-light: #eef2ff;
  --bg-color: #f8fafc;
  --surface-color: #ffffff;
  --border-color: #e2e8f0;
  --text-main: #0f172a;
  --text-muted: #64748b;
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
  --shadow-md: 0 4px 12px rgba(0,0,0,0.07);
  --shadow-lg: 0 10px 25px -5px rgba(0,0,0,0.1);
}

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  background-color: var(--bg-color);
  color: var(--text-main);
  line-height: 1.5;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a {
  color: inherit;
  text-decoration: none;
}

/* ==========================================================================
   Header & Top Navigation
   ========================================================================== */
header {
  background: var(--surface-color);
  border-bottom: 1px solid var(--border-color);
  position: sticky;
  top: 0;
  z-index: 100;
  box-shadow: var(--shadow-sm);
}

/* Reserve space equal to the sticky header so anchor jumps and hero
   blocks never land underneath the navbar */
html {
  scroll-padding-top: 8.5rem;
  scroll-behavior: smooth;
}

.header-top {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0.85rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.logo-container {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.brand-logo {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--text-main);
}

.brand-logo i {
  color: #ef4444;
  font-size: 1.7rem;
}

.brand-logo small {
  color: var(--primary-color);
  font-weight: 700;
}

.badge-privacy {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: #ecfdf5;
  color: #059669;
  border: 1px solid #a7f3d0;
  padding: 0.25rem 0.65rem;
  border-radius: 9999px;
  font-size: 0.78rem;
  font-weight: 600;
}

/* Search Bar stays pinned in the header while the page scrolls */
.search-bar-container {
  position: sticky;
  top: 0.85rem;
  flex: 1;
  max-width: 520px;
  z-index: 2;
}

.search-icon {
  position: absolute;
  left: 1rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: 0.9rem;
}

#global-tool-search {
  width: 100%;
  padding: 0.55rem 1rem 0.55rem 2.5rem;
  border: 1px solid var(--border-color);
  border-radius: 9999px;
  font-size: 0.9rem;
  background: #f1f5f9;
  outline: none;
  transition: all 0.2s ease;
}

#global-tool-search:focus {
  border-color: var(--primary-color);
  background: #ffffff;
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
}

/* A category whose entry opens a list of its tools.
   position: relative so the panel can hang off this item rather than off the
   scroll container - the bar scrolls horizontally, and a panel positioned
   against it would slide away with it. */
.nav-item {
  position: relative;
}

.nav-item.has-menu .nav-link {
  padding-right: 1.65rem;
}

.nav-caret {
  position: absolute;
  right: 0.25rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: 0;
  padding: 0.3rem 0.35rem;
  font-size: 0.62rem;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 6px;
  line-height: 1;
}

.nav-caret:hover,
.nav-item.is-open .nav-caret {
  color: var(--primary-color);
  background: var(--primary-light);
}

.nav-item.is-open .nav-caret i {
  transform: rotate(180deg);
}

/* The panel is a child of the item, not of the scrolling bar, so the pointer
   leaving the link and crossing to the panel never passes over nothing. */
/*
 * The panel's position is set per open, in script.js, against the button that
 * opened it - because it has to be fixed rather than absolute. See the note on
 * the block below for why. What is here is only how it looks.
 */
.nav-menu {
  min-width: 17rem;
  background: #fff;
  border: 1px solid var(--border-color);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.14);
  padding: 0.5rem;
  z-index: 40;
}

/*
 * The panel is position: fixed, not absolute.
 *
 * .main-navbar has overflow-x: auto, which the browser resolves to
 * overflow-y: auto as well - so the navbar is a scroll container forty pixels
 * tall, and anything hanging below it is clipped out of existence. An absolute
 * panel was in the DOM, had the right box and the right opacity, and rendered
 * as nothing at all: the scroll container simply did not paint it.
 *
 * Fixed takes the panel out of that container and positions it against the
 * viewport, which is what a dropdown hanging below a scrolling bar needs. The
 * top and left are set where the button is, per open, because a fixed element
 * ignores its parent for positioning.
 *
 * It stays a CHILD of the item that opened it rather than being moved to the
 * body. That is what keeps the pointer handover working: the panel is never
 * outside the element whose mouseenter re-arms the close timer.
 */
.nav-menu {
  position: fixed;
}

.nav-menu-all {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.6rem 0.55rem;
  margin-bottom: 0.3rem;
  border-bottom: 1px solid var(--border-color);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--primary-color);
  text-decoration: none;
  border-radius: 6px;
}

.nav-menu-all:hover {
  background: var(--primary-light);
}

.nav-menu-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-menu-list a {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.42rem 0.6rem;
  border-radius: 6px;
  font-size: 0.83rem;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}

.nav-menu-list a:hover,
.nav-menu-list a:focus-visible {
  background: var(--primary-light);
  color: var(--primary-color);
}

.nav-menu-list i {
  width: 1rem;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.8rem;
}

.nav-menu-list a:hover i,
.nav-menu-list a:focus-visible i {
  color: var(--primary-color);
}

.nav-menu-more {
  margin: 0.35rem 0 0.1rem;
  padding: 0.4rem 0.6rem 0.1rem;
  border-top: 1px solid var(--border-color);
  font-size: 0.72rem;
  color: var(--text-muted);
}

/* A phone has no hover, so the caret beside each category is the whole
   interaction, and the rows are made tall enough to hit without aiming. The
   panel's position comes from script.js at every width - it has to be fixed to
   escape the navbar's scroll container - so nothing here repositions it. */
@media (max-width: 860px) {
  .nav-menu-list a {
    /* Comfortable on a fingertip, and long enough to hit without aiming. */
    padding: 0.6rem 0.65rem;
    white-space: normal;
  }

  .nav-menu-more {
    padding-bottom: 0.35rem;
  }
}

/* Category Navbar Wrapper */
.main-navbar-wrapper {
  position: relative;
  /* The category menus hang below this bar and must paint over the page that
     follows it. Without a z-index the bar is positioned but has no stacking
     order of its own, so the hero below - which is not positioned - paints
     over the open panel and the menu appears to do nothing. */
  z-index: 50;
  background: #ffffff;
  border-top: 1px solid #f1f5f9;
  display: flex;
  align-items: center;
  max-width: 1600px;
  margin: 0 auto;
  padding: 0.35rem 0.75rem;
}

.main-navbar {
  flex: 1;
  overflow-x: auto;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  /* Eliminate browser scrollbar completely */
  scrollbar-width: none !important;
  -ms-overflow-style: none !important;
}

.main-navbar::-webkit-scrollbar {
  display: none !important;
  width: 0 !important;
  height: 0 !important;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.25rem;
  list-style: none;
  white-space: nowrap;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.42rem 0.9rem;
  font-size: 0.84rem;
  font-weight: 500;
  color: var(--text-muted);
  border-radius: 9999px;
  background: transparent;
  border: 1px solid transparent;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  user-select: none;
}

.nav-link i {
  font-size: 0.82rem;
  transition: transform 0.2s ease;
}

.nav-link:hover {
  color: var(--primary-color);
  background: var(--primary-light);
  border-color: rgba(99, 102, 241, 0.15);
}

.nav-link:hover i {
  transform: scale(1.1);
}

.nav-link.active {
  color: #ffffff;
  background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%);
  border-color: transparent;
  box-shadow: 0 2px 8px rgba(79, 70, 229, 0.35);
  font-weight: 600;
}

.nav-link.active i {
  color: #ffffff;
}

/* Nav Left/Right Scroll Arrows */
.nav-scroll-btn {
  background: #ffffff;
  border: 1px solid var(--border-color);
  color: var(--text-muted);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  transition: all 0.15s ease;
  flex-shrink: 0;
  z-index: 2;
  margin: 0 0.2rem;
}

.nav-scroll-btn:hover {
  background: var(--primary-light);
  color: var(--primary-color);
  border-color: var(--primary-color);
  transform: scale(1.06);
}

.nav-scroll-btn:active {
  transform: scale(0.94);
}

/* ==========================================================================
   Google AdSense Banners & Placeholders - REMOVED
   --------------------------------------------------------------------------
   There are no ad styles here any more. The site is new and is deliberately
   running with no ad code at all until it has the traffic to qualify for an ad
   account, so there is nothing to lay out and nothing to hide.

   The rules that used to live here did two unrelated jobs at once: they
   suppressed the slots while the publisher id was a placeholder, and they kept
   suppressing them afterwards, so filling in a real id would have changed
   nothing on the page. That is the failure mode to avoid when the ads do come
   back - the slots must be visible by default and controlled by a real id, not
   hidden by a stylesheet that nobody remembers deleting.

   When ads are turned back on, rebuild this section from the git history
   (commit efa2178 kept the last working version) and read the note in
   deploy/README.md, "Turning AdSense back on".
   ========================================================================== */

/* ==========================================================================
   Main Grid Layout (Left Sidebar | Ultra-Wide Center Main)
   ========================================================================== */
.layout-wrapper {
  max-width: 1600px;
  margin: 0 auto;
  /* Breathing room so the hero never touches the sticky navbar */
  padding: 2rem 1.5rem 0;
  display: flex;
  gap: 1.75rem;
  flex: 1;
  width: 100%;
}

.layout-wrapper.wide-layout {
  max-width: 1600px;
}

/* Sidebars scroll naturally with the page - no sticky pinning */
.sidebar {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  flex-shrink: 0;
}

.right-sidebar {
  width: 265px;
  align-self: flex-start;
}

/* Tool pages use a single right-hand column (related, then popular) */
.left-sidebar {
  display: none !important;
}

.sidebar-block {
  background: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.25rem;
  box-shadow: var(--shadow-sm);
}

.sidebar-block h3 {
  font-size: 0.95rem;
  font-weight: 700;
  margin-bottom: 0.85rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--text-main);
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 0.5rem;
}

.sidebar-tool-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.sidebar-tool-list a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.5rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  color: var(--text-muted);
  transition: all 0.15s ease;
}

.sidebar-tool-list a:hover {
  background: var(--primary-light);
  color: var(--primary-color);
  transform: translateX(3px);
}

.sidebar-tool-list a i {
  color: var(--primary-color);
  width: 18px;
  text-align: center;
}

/* "See all X tools" link that closes a sidebar list */
.sidebar-more-link {
  margin: 0.85rem 0 0;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border-color);
  font-size: 0.8rem;
  line-height: 1.4;
}

.sidebar-more-link a {
  color: var(--primary-color);
  font-weight: 600;
}

.sidebar-more-link a:hover {
  text-decoration: underline;
}

.privacy-notice p {
  font-size: 0.82rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.category-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.category-pill {
  background: #f1f5f9;
  border: 1px solid var(--border-color);
  border-radius: 9999px;
  padding: 0.25rem 0.65rem;
  font-size: 0.76rem;
  color: var(--text-muted);
  transition: all 0.15s ease;
}

.category-pill:hover {
  background: var(--primary-light);
  color: var(--primary-color);
  border-color: var(--primary-color);
}

/* ==========================================================================
   Center Main Content Workspace
   ========================================================================== */
.main-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

/* Holds whatever the router rendered on the landing page. It sits inside
   <main>, so it has to reproduce <main>'s own column layout and gap or the
   category sections would stack flush against each other. */
#home-content {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

/* Empty favorites must not add an extra flex gap between sections. */
#personal-tools-mount:empty {
  display: none;
}

/* The home layout already provides spacing below Quick edits. */
#home-content > .quick-edit {
  margin-bottom: 0;
}

/* Category landing page hero. Same solid treatment as the landing hero so the
   two read as one family, but with more top padding because it sits directly
   under the sticky header with no page-level chrome above it. */
.cat-hero {
  background: var(--primary-color);
  border: 1px solid var(--primary-hover);
  border-radius: var(--radius-lg);
  padding: 2.5rem 2rem;
  box-shadow: var(--shadow-md);
  color: #ffffff;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
}

.cat-hero h1 {
  font-size: 2.05rem;
  font-weight: 800;
  color: #ffffff;
  letter-spacing: -0.02em;
}

.cat-hero > p {
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.98rem;
  max-width: 68ch;
}

.cat-hero .hero-points {
  justify-content: flex-start;
}

/* Category title on the homepage doubles as the link to that category's page. */
.category-title a {
  color: inherit;
}

.category-title a:hover {
  color: var(--primary-color);
}

/* Hero Header */
.hero-header {
  background: var(--primary-color);
  border: 1px solid var(--primary-hover);
  border-radius: var(--radius-lg);
  padding: 2.75rem 2rem;
  box-shadow: var(--shadow-md);
  color: #ffffff;
}

.hero-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.85rem;
}

.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.3);
  color: #ffffff;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.35rem 0.85rem;
  border-radius: 9999px;
}

.hero-header h1 {
  font-size: 2.3rem;
  font-weight: 800;
  color: #ffffff;
  margin-bottom: 0;
  letter-spacing: -0.02em;
}

.hero-header p {
  color: rgba(255, 255, 255, 0.9);
  font-size: 1rem;
  max-width: 640px;
}

/* Search sits inside the hero rather than the sticky header, so it needs its
   own styling: the shared .search-bar-container rule assumes a light page. */
/* relative, NOT static: the shared rule makes this sticky, which the hero must
   not be, but .search-icon is position:absolute and needs a positioned
   ancestor. static left the icon anchored to the viewport. */
.hero-search {
  position: relative;
  /* cancel the sticky offset the shared rule sets: static used to ignore it,
     relative does not, and it would shove the hero down by 0.85rem */
  top: auto;
  max-width: 620px;
  width: 100%;
  margin-top: 0.5rem;
}

/* muted, not white: the input it sits on has a white background, so a white
   glyph would be invisible once the icon is correctly positioned. */
.hero-search .search-icon {
  color: var(--text-muted);
  font-size: 1rem;
}

.hero-search #global-tool-search {
  padding: 0.9rem 1.1rem 0.9rem 3rem;
  font-size: 1rem;
  border: 2px solid transparent;
  background: #ffffff;
  color: var(--text-main);
  box-shadow: var(--shadow-md);
}

.hero-search #global-tool-search::placeholder {
  color: #94a3b8;
}

.hero-search #global-tool-search:focus {
  border-color: #ffffff;
  background: #ffffff;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.28);
}

/* ==========================================================================
   Search results controls
   The hero's search field is hidden the moment results render, so the results
   view carries its own field plus an explicit way back. Without them the first
   search is also the last one.
   ========================================================================== */

.results-search {
  position: relative;
  top: auto; /* same sticky-offset reset as .hero-search */
  max-width: 560px;
  width: 100%;
  margin: 1.25rem auto 0;
}

/* see .hero-search .search-icon - white on a white input is invisible */
.results-search .search-icon {
  color: var(--text-muted);
}

.results-search #results-tool-search {
  padding: 0.85rem 3rem 0.85rem 3rem;
  font-size: 1rem;
  border: 2px solid transparent;
  border-radius: 12px;
  background: #ffffff;
  color: var(--text-main);
  box-shadow: var(--shadow-md);
  width: 100%;
}

.results-search #results-tool-search:focus {
  outline: none;
  border-color: #ffffff;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.28);
}

/* Clear button sits inside the field on the right, so it needs the same
   absolute centring as the magnifier icon on the left. */
.search-clear {
  position: absolute;
  right: 0.85rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.75rem;
  height: 1.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: #e2e8f0;
  color: #475569;
  font-size: 0.8rem;
  cursor: pointer;
}

.search-clear:hover {
  background: #cbd5e1;
  color: #0f172a;
}

.search-clear:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.results-actions {
  display: flex;
  justify-content: center;
  margin-top: 1rem;
}

.results-actions .btn-secondary {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 1.15rem;
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  color: #ffffff;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}

.results-actions .btn-secondary:hover {
  background: rgba(255, 255, 255, 0.3);
  transform: translateY(-1px);
}

.results-actions .btn-secondary:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.results-empty {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  padding: 3rem 1rem;
  color: var(--text-muted);
  text-align: center;
}

.results-empty i {
  font-size: 2.25rem;
  opacity: 0.4;
}

.hero-points {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.5rem;
  list-style: none;
  margin: 0.35rem 0 0;
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.85rem;
  font-weight: 500;
}

.hero-points i {
  color: #a7f3d0;
  margin-right: 0.35rem;
}

/* Category Sections & Tools Grid */
.category-section {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.category-title {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text-main);
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--border-color);
}

.category-title i {
  color: var(--primary-color);
}

.tools-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1rem;
}

/* Tool Card */
.tool-card {
  position: relative;
  background: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  transition: all 0.2s ease;
  box-shadow: var(--shadow-sm);
}

.tool-card-favorite {
  position: absolute;
  top: 0.65rem;
  right: 0.65rem;
  border: 0;
  background: transparent;
  color: #94a3b8;
  cursor: pointer;
  padding: 0.35rem;
  font-size: 1rem;
  z-index: 1;
}
.tool-card-favorite:hover, .tool-card-favorite.is-favorite { color: #f59e0b; }
.tool-card-link {
  display: block;
  height: 100%;
  color: inherit;
  text-decoration: none;
}
.personal-tools {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 1rem;
  margin-bottom: 1.5rem;
}
.tool-presets {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 0.85rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-color);
  font-size: 0.8rem;
}
.tool-presets label { font-weight: 700; color: var(--text-muted); }
.tool-presets select, .tool-presets input { min-height: 2rem; padding: 0.25rem 0.45rem; border: 1px solid var(--border-color); border-radius: var(--radius-sm); }
.tool-presets input { flex: 1 1 9rem; min-width: 8rem; }
.tool-presets small { flex-basis: 100%; color: var(--text-muted); }

.tool-card:hover {
  transform: translateY(-3px);
  border-color: #a5b4fc;
  box-shadow: var(--shadow-md);
}

.tool-icon {
  width: 44px;
  height: 44px;
  background: var(--primary-light);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  color: var(--primary-color);
  margin-bottom: 0.85rem;
  transition: transform 0.2s ease;
}

.tool-card:hover .tool-icon {
  background: var(--primary-color);
  color: #ffffff;
  transform: scale(1.05);
}

/* --------------------------------------------------------------------------
   UNIFORM TOOL CARDS
   --------------------------------------------------------------------------
   A grid row is as tall as its tallest card, so one two-line name ("Office
   Rubber Status Stamp") made its whole row taller than the row above it. The
   homepage measured five different card heights across its 134 cards: 164,
   182, 187, 205 and 223 pixels.

   `grid-auto-rows: 1fr` does NOT fix this and was tried first: in a grid whose
   height is indefinite, an fr track is sized against that row's own content, so
   every row comes back to the height it started with. The only thing that makes
   cards equal is giving every card the same CONTENT HEIGHT, which means
   reserving two lines for the name and four for the description.

   The two are both needed. The clamp alone caps the long tail but leaves a
   three-line description shorter than a four-line one; the min-height alone
   leaves a five-line description growing its card. Together: every card is the
   same size.

   What it costs, measured rather than guessed: across the 134 homepage cards
   the names are 1 line 115 times and 2 lines 19 times, and the descriptions
   run 2, 3, 4 and 5 lines. So the name is never cut, and exactly 2 of 134
   descriptions lose their last line. Clamping the description to 3 lines
   instead would shrink the card by ~18px but would cut a line from 53 of them.
   ------------------------------------------------------------------------ */
.tool-card h4 {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.35rem;
  line-height: 1.35;
  min-height: 2.7em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tool-card p {
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.4;
  flex: 1;
  min-height: 5.6em;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The line budget above is written for a card about 250px wide. Below this the
   grid goes two-up and each card is roughly 185px, which is narrow enough that
   four of the descriptions need a fifth line and two of the names a third -
   measured, not assumed. So both budgets grow here, and every card on the page
   grows with them, which is the deal: uniform size, or the whole text. */
@media (max-width: 560px) {
  .tool-card h4 {
    min-height: 4.05em;
    -webkit-line-clamp: 3;
  }

  .tool-card p {
    min-height: 8.4em;
    -webkit-line-clamp: 6;
  }
}

/* ==========================================================================
   Homepage: the "see all" link in a category heading
   --------------------------------------------------------------------------
   The heading already links to the category page, so this is the second,
   wordier route to the same place. It is there because the heading link says
   only "(14)" while the grid below shows eight cards - the pill is what tells
   a reader that the other six are one click away instead of leaving them to
   work out that the number is bigger than the grid.
   ========================================================================== */
.category-seeall {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--primary-color);
  white-space: nowrap;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--border-color);
  border-radius: 9999px;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.category-seeall:hover {
  background: var(--primary-light);
  border-color: var(--primary-color);
}

.category-seeall i {
  font-size: 0.7rem;
}

@media (max-width: 560px) {
  /* Two rows on a phone: the category name on one, the pill under it. The
     heading is a flex row, so the pill takes the full width and wraps. */
  .category-title {
    flex-wrap: wrap;
  }

  .category-seeall {
    margin-left: 1.8rem;
  }
}

/* ==========================================================================
   Tool Studio Workspace
   ========================================================================== */
.tool-studio {
  background: var(--surface-color);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: 2rem;
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.tool-studio-header {
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 1rem;
}

.tool-studio-header h2 {
  font-size: 1.6rem;
  font-weight: 800;
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.tool-studio-header h2 i {
  color: var(--primary-color);
}

.tool-studio-header p {
  color: var(--text-muted);
  font-size: 0.88rem;
  margin-top: 0.25rem;
}

/* Dropzone */
.dropzone {
  border: 2px dashed #a5b4fc;
  border-radius: var(--radius-md);
  background: #f8faff;
  padding: 2.5rem 1.5rem;
  text-align: center;
  cursor: pointer;
  transition: all 0.2s ease;
}

.dropzone:hover {
  background: #eef2ff;
  border-color: var(--primary-color);
}

.dropzone i {
  font-size: 2.8rem;
  color: var(--primary-color);
  margin-bottom: 0.75rem;
}

.dropzone h3 {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.35rem;
}

.dropzone p {
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* ==========================================================================
   The studio, as one vertical flow
   --------------------------------------------------------------------------
   ORDER IS THE WHOLE POINT
   ----------------------
   upload -> the files you chose -> the settings -> the live preview ->
   Process.

   Two things pushed this away from a side-by-side grid. First, a two-column
   layout puts the preview and the settings at the same height, so the settings
   you have not reached yet sit beside a preview that does not reflect them.
   Second, on any panel taller than the viewport - and most of them are, now
   that a page range, a colour and a size are on every tool - one column is
   scrolled out of view no matter which way round they go.

   So it is a single column in a fixed order, and each step is numbered. The
   numbering is not decoration: it is what makes it obvious that the preview is
   the RESULT of the settings above it rather than a separate step, and it is
   what tells someone landing on the page mid-way that they have not chosen a
   file yet.
   ========================================================================== */
.tool-studio-grid {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  margin-top: 1.25rem;
}

.tool-studio-step {
  position: relative;
}

/* The step marker sits in the left gutter on wide screens and inline above the
   content on narrow ones. Either way it is the same element, so the numbering
   can never drift out of step with the markup order. */
.tool-studio-step-head {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.6rem;
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.tool-studio-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  flex: 0 0 1.5rem;
  border-radius: 999px;
  background: #e2e8f0;
  color: #475569;
  font-size: 0.78rem;
  font-weight: 800;
}

/* A step with nothing to do yet is dimmed rather than hidden, so the shape of
   the whole flow is visible before a file is chosen. */
.tool-studio-step[data-idle="true"] .tool-studio-step-num {
  background: #f1f5f9;
  color: #94a3b8;
}

.tool-studio-step[data-idle="true"] .tool-studio-step-head {
  opacity: 0.55;
}

.tool-studio-col-preview,
.tool-studio-col-options {
  min-width: 0;
}

/* In the vertical flow the preview is the widest thing on the page, so it gets
   a cap rather than the page cap: a full-height A4 page at 100% zoom pushes
   everything below it off the screen. */
.tool-studio-col-preview .preview-stage-wrapper {
  max-height: 62vh;
}

@media (max-width: 640px) {
  .tool-studio-col-preview .preview-stage-wrapper {
    max-height: 52vh;
  }
}

/* Tool Config Form */
.tool-options {
  background: #f8fafc;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 1.25rem;
}

/* Signatures, colour pickers and canvas tools are authored at full width. In a
   narrower column they must still fit rather than force the whole grid wide. */
.tool-options canvas,
.tool-options img,
.tool-options table {
  max-width: 100%;
  height: auto;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-bottom: 0.85rem;
}

.form-group:last-child {
  margin-bottom: 0;
}

.form-group label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-main);
}

.form-group input,
.form-group select,
.form-group textarea {
  background: #ffffff;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.85rem;
  font-size: 0.88rem;
  font-family: inherit;
  color: var(--text-main);
  outline: none;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.15);
}

/* Action Button */
.btn-primary,
.tool-presets .btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  background: var(--primary-color);
  color: #ffffff;
  font-weight: 600;
  font-size: 0.95rem;
  border-radius: var(--radius-sm);
  border: none;
  cursor: pointer;
  transition: all 0.2s ease;
}

.btn-primary:hover,
.tool-presets .btn-secondary:hover {
  background: var(--primary-hover);
}

/* Optional site watermark checkbox - sits just above the Process button so
   it reads as a download option, not a tool setting. */
.site-watermark-opt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  font-size: 0.88rem;
  color: var(--text-muted, #666);
  cursor: pointer;
  user-select: none;
}

.site-watermark-opt input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--primary-color);
  cursor: pointer;
  flex-shrink: 0;
}

.site-watermark-opt .fa-stamp {
  color: var(--primary-color);
  opacity: 0.75;
}

.site-watermark-opt:hover {
  color: var(--text, #333);
}

/* ==========================================================================
   Batch Processing Panel
   ========================================================================== */
.batch-panel {
  background: linear-gradient(135deg, #f5f3ff 0%, #ffffff 100%);
  border: 1px solid #ddd6fe;
  border-radius: var(--radius-md, 10px);
  padding: 1rem 1.25rem;
}

.batch-toggle {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  cursor: pointer;
  user-select: none;
}

.batch-toggle input[type="checkbox"] {
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.15rem;
  accent-color: #7c3aed;
  cursor: pointer;
  flex-shrink: 0;
}

.batch-toggle-text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.batch-toggle-text i {
  color: #7c3aed;
  margin-right: 0.3rem;
}

.batch-hint {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-weight: 400;
}

.batch-limits {
  margin-top: 0.85rem;
  padding-top: 0.75rem;
  border-top: 1px dashed #ddd6fe;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}

.batch-limits i {
  color: #7c3aed;
  margin-right: 0.25rem;
}

.batch-eligible-note {
  flex-basis: 100%;
  line-height: 1.4;
}

/* Dropzone highlighted while batch mode is armed */
.dropzone.batch-active {
  border-color: #7c3aed;
  background: #f5f3ff;
}

/* Progress */
.batch-progress-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}

#batch-progress-count {
  color: var(--text-muted);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.btn-batch-cancel {
  padding: 0.15rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: #dc2626;
  background: #fee2e2;
  border: 1px solid #fca5a5;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.btn-batch-cancel:hover {
  background: #fecaca;
  color: #b91c1c;
}

.btn-batch-cancel:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.batch-progress-track {
  height: 8px;
  background: #e2e8f0;
  border-radius: 9999px;
  overflow: hidden;
}

.batch-progress-bar {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #7c3aed, #a78bfa);
  border-radius: 9999px;
  transition: width 0.25s ease;
}

/* Present to a screen reader, absent to the eye. The progress announcement uses
   it so the spoken copy can exist without a second visible label. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A phase nobody can put a number on - pdf-lib parses or saves a document in a
   single call, so there is no fraction to show. The bar slides instead of
   filling, which reads as "working" rather than "3% done". A stuck slide still
   says the tab is alive; a percentage that jumps to 80% on a timer and waits
   there does not, and that is the version this deliberately is not. */
.batch-progress-track.is-indeterminate .batch-progress-bar {
  width: 40%;
  transition: none;
  animation: progress-slide 1.15s ease-in-out infinite;
}

@keyframes progress-slide {
  0% { transform: translateX(-110%); }
  100% { transform: translateX(260%); }
}

@media (prefers-reduced-motion: reduce) {
  .batch-progress-track.is-indeterminate .batch-progress-bar {
    animation-duration: 3.5s;
  }
}

.batch-file-list {
  list-style: none;
  margin-top: 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-height: 220px;
  overflow-y: auto;
}

.batch-file-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.8rem;
  padding: 0.4rem 0.6rem;
  border-radius: 6px;
  background: #f8fafc;
}

.batch-file-row i {
  flex-shrink: 0;
}

.batch-file-row.pending i { color: #94a3b8; }
.batch-file-row.done i { color: #16a34a; }
.batch-file-row.done { background: #f0fdf4; }
.batch-file-row.failed i { color: #dc2626; }
.batch-file-row.failed { background: #fef2f2; }

.batch-file-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.batch-file-out,
.batch-file-err {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--text-muted);
  white-space: nowrap;
}

.batch-file-err { color: #b91c1c; }

/* Failure summary */
.batch-failure-summary {
  margin-top: 1rem;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: var(--radius-md, 10px);
  padding: 1rem 1.15rem;
}

.batch-failure-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.55rem;
  font-size: 0.85rem;
  color: #991b1b;
  margin-bottom: 0.6rem;
}

.batch-failure-head i {
  color: #dc2626;
}

.batch-failure-head span {
  flex-basis: 100%;
  font-size: 0.78rem;
  color: #b91c1c;
  font-weight: 400;
}

.batch-failure-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.batch-failure-list li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  font-size: 0.78rem;
  padding: 0.35rem 0.5rem;
  background: #ffffff;
  border-radius: 5px;
}

.batch-failure-list li span {
  color: #b91c1c;
}

/* Clear / remove selected files button inside the file info card */
.btn-clear-files {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.78rem;
  font-weight: 600;
  font-family: inherit;
  color: #b91c1c;
  background: #fee2e2;
  border: 1px solid #fecaca;
  border-radius: 9999px;
  cursor: pointer;
  transition: all 0.15s ease;
  white-space: nowrap;
}

.btn-clear-files:hover {
  background: #ef4444;
  border-color: #ef4444;
  color: #ffffff;
}

.btn-clear-files:active {
  transform: scale(0.96);
}

.button {
  margin: 0.3rem 0;
  padding: 0.5rem 1rem;
  border: 1px solid #cbd5e1;
  background-color: #f1f5f9;
  cursor: pointer;
  font-size: 0.85rem;
  border-radius: 0.4rem;
}

.button:hover {
  background-color: #e2e8f0;
}

/* ==========================================================================
   Interactive Live Preview System
   ========================================================================== */
.live-preview-container {
  background: #0f172a;
  border: 1px solid #1e293b;
  border-radius: var(--radius-md);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.25);
  margin-top: 0.5rem;
}

.live-preview-toolbar {
  background: #1e293b;
  padding: 0.65rem 1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  border-bottom: 1px solid #334155;
  color: #f1f5f9;
}

.preview-controls-left,
.preview-controls-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.preview-badge {
  font-size: 0.8rem;
  font-weight: 700;
  color: #818cf8;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: rgba(99, 102, 241, 0.15);
  padding: 0.2rem 0.55rem;
  border-radius: 9999px;
  border: 1px solid rgba(99, 102, 241, 0.3);
}

.preview-page-indicator {
  font-size: 0.82rem;
  color: #94a3b8;
  font-weight: 600;
  min-width: 90px;
}

/* Input / Output switch ---------------------------------------------------
   The point of this control is that "Output" means the tool's real result, so
   it is styled to read as a state change rather than another toolbar button.
   Green for output because that is the answer; indigo is what the page already
   uses for the input it is leaving behind. */
.preview-mode-switch {
  display: inline-flex;
  background: #0f172a;
  border: 1px solid #334155;
  border-radius: 9999px;
  padding: 2px;
  gap: 2px;
}

.preview-mode-btn {
  background: transparent;
  border: 0;
  color: #94a3b8;
  font-size: 0.74rem;
  font-weight: 700;
  padding: 0.22rem 0.7rem;
  border-radius: 9999px;
  cursor: pointer;
  transition: all 0.15s ease;
  white-space: nowrap;
}

.preview-mode-btn:hover {
  color: #e2e8f0;
}

.preview-mode-btn.is-active {
  background: #475569;
  color: #f8fafc;
}

.preview-mode-switch.is-output .preview-mode-btn.is-active {
  background: #15803d;
  color: #ffffff;
}

/* Status line under the toolbar. Carries the size/page/timing of the live
   result, and - more importantly - carries the reasons the preview cannot be
   shown, so a tool that is too slow or produced an unviewable format says so
   instead of quietly leaving the previous page on screen. */
.preview-live-status-bar {
  background: #0f172a;
  border-bottom: 1px solid #334155;
  padding: 0.4rem 1rem;
}

.preview-live-status {
  display: none;
  font-size: 0.76rem;
  color: #94a3b8;
}

.preview-live-status.is-ok {
  color: #4ade80;
}

.preview-live-status.is-busy {
  color: #fbbf24;
}

.preview-live-status.is-warn {
  color: #fbbf24;
}

.preview-live-text {
  margin: 0;
  padding: 1rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #f8fafc;
  background: #1e293b;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 100%;
  overflow: auto;
}

.preview-btn {
  background: #334155;
  border: 1px solid #475569;
  color: #f8fafc;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  cursor: pointer;
  transition: all 0.15s ease;
}

.preview-btn:hover {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: #ffffff;
}

.preview-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  background: #1e293b;
}

.preview-separator {
  color: #475569;
  font-size: 0.8rem;
}

.preview-zoom-text {
  font-size: 0.8rem;
  font-weight: 600;
  color: #cbd5e1;
  min-width: 45px;
  text-align: center;
}

.preview-stage-wrapper {
  padding: 1.5rem 1rem;
  overflow: auto;
  max-height: 540px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: radial-gradient(circle at center, #1e293b 0%, #0f172a 100%);
  min-height: 280px;
}

.preview-stage {
  position: relative;
  display: inline-block;
  box-shadow: 0 12px 35px rgba(0, 0, 0, 0.5);
  border-radius: 4px;
  background: #ffffff;
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  max-width: 100%;
}

#preview-main-canvas {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 4px;
}

.preview-overlay-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: hidden;
  border-radius: 4px;
}

/* Image Thumbnails Strip for Multiple Images */
.image-preview-gallery {
  display: flex;
  gap: 0.65rem;
  padding: 0.75rem 1rem;
  background: #1e293b;
  border-bottom: 1px solid #334155;
  overflow-x: auto;
}

.image-thumb-card {
  position: relative;
  flex-shrink: 0;
  width: 72px;
  height: 72px;
  border-radius: 6px;
  overflow: hidden;
  border: 2px solid #334155;
  cursor: pointer;
  background: #0f172a;
  transition: all 0.2s ease;
}

.image-thumb-card:hover,
.image-thumb-card.active {
  border-color: var(--primary-color);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
}

.image-thumb-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.image-thumb-badge {
  position: absolute;
  bottom: 2px;
  right: 2px;
  background: rgba(0, 0, 0, 0.75);
  color: #ffffff;
  font-size: 0.62rem;
  padding: 1px 4px;
  border-radius: 3px;
}

/* Output Preview Styles */
.result-success-banner {
  padding: 1rem 1.25rem;
  background: #dcfce7;
  color: #166534;
  border: 1px solid #bbf7d0;
  border-radius: var(--radius-md);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.output-preview-container {
  background: #ffffff;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

/* On-page statistics a tool returns beside its download (see
   renderResultSummary in script.js). Hidden unless a tool sends a summary. */
.result-summary-box {
  margin-top: 1rem;
  padding: 1rem 1.15rem;
  background: #f8fafc;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
}

.summary-file {
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--text-main);
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 0.6rem;
}

.summary-file i {
  color: var(--primary-color);
}

.summary-file + .summary-grid + .summary-file {
  margin-top: 1rem;
  padding-top: 0.85rem;
  border-top: 1px dashed var(--border-color);
}

.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.6rem;
}

.summary-cell {
  background: #ffffff;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 0.55rem 0.75rem;
  min-width: 0;
}

.summary-label {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin-bottom: 0.2rem;
}

.summary-value {
  display: block;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-main);
  word-break: break-word;
}

.output-preview-header {
  padding: 0.75rem 1rem;
  background: #f8fafc;
  border-bottom: 1px solid var(--border-color);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.output-preview-header h4 {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--text-main);
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0;
}

.output-preview-header h4 i {
  color: var(--primary-color);
}

.output-preview-viewport {
  padding: 1.5rem;
  background: #334155;
  display: flex;
  justify-content: center;
  /* flex-start, not center: the pane scrolls a whole stack of pages, and
     centred tall content overflows equally at both ends - the top of the
     document ends up above the scroll origin and cannot be scrolled to. */
  align-items: flex-start;
  overflow: auto;
  max-height: 70vh;
}

/* The document preview: every page of the output, stacked in one scroll so
   the reader moves through the file with the wheel rather than with a button
   per page turn. */
.result-preview-pages {
  width: 100%;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.result-preview-page {
  width: 100%;
  display: flex;
  justify-content: center;
}

.result-preview-page canvas {
  max-width: 100%;
  height: auto;
  border-radius: 4px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  background: #ffffff;
}

#result-preview-canvas {
  max-width: 100%;
  height: auto;
  border-radius: 4px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  background: #ffffff;
}

.result-images-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
  width: 100%;
  padding: 0.5rem;
}

.result-image-item {
  background: #ffffff;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid #cbd5e1;
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
}

.result-image-item img {
  width: 100%;
  height: 140px;
  object-fit: contain;
  background: #f1f5f9;
}

.result-image-footer {
  padding: 0.45rem 0.65rem;
  font-size: 0.78rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #ffffff;
}

/* ==========================================================================
   Footer
   ========================================================================== */
footer {
  background: var(--surface-color);
  border-top: 1px solid var(--border-color);
  margin-top: 3.5rem;
  padding: 3rem 0 2rem;
}

/* ==========================================================================
   Site Footer
   --------------------------------------------------------------------------
   The column template is supplied by the page as --footer-cols, from the same
   list that generates the sections. It used to be fixed at "2fr 1fr 1fr" while
   the tool page emitted four sections, so the fourth wrapped onto a row of its
   own and the middle of the footer sat empty; the category page emitted two,
   which left a column empty. Hard-coding a count next to markup that can change
   is how that happened.

   The three link columns are pushed apart deliberately. They are separate
   lists, and at a 1600px measure a small gap leaves them reading as one
   continuous run of links rather than three groups.
   ========================================================================== */
.footer-container {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 1.5rem;
  display: grid;
  grid-template-columns: var(--footer-cols, 1fr 1fr);
  column-gap: 3.5rem;
  row-gap: 2.25rem;
  align-items: start;
}

.brand-info h3 {
  font-size: 1.35rem;
  font-weight: 800;
  margin-bottom: 0.65rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.brand-info h3 i {
  color: #ef4444;
}

.brand-info p {
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.5;
  margin-bottom: 1rem;
}

.copyright {
  font-size: 0.8rem;
  color: #94a3b8;
}

.footer-links h4 {
  font-size: 0.95rem;
  font-weight: 700;
  margin-bottom: 0.85rem;
}

.footer-links ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.footer-links a {
  font-size: 0.85rem;
  color: var(--text-muted);
  transition: color 0.15s ease;
}

.footer-links a:hover {
  color: var(--primary-color);
}

/* ==========================================================================
   Responsive Breakpoints & Mobile Optimization
   ========================================================================== */
@media (max-width: 992px) {
  .layout-wrapper {
    /* Keep the top gap so the hero stays clear of the navbar on tablet too */
    padding: 1.5rem 1rem 0;
    gap: 1rem;
  }
  .right-sidebar,
  .left-sidebar {
    display: none !important;
  }
  .footer-container {
    grid-template-columns: 1fr;
    column-gap: 1.75rem;
    row-gap: 1.75rem;
  }
}

@media (max-width: 768px) {
  .header-top {
    flex-direction: column;
    align-items: stretch;
    gap: 0.65rem;
    padding: 0.75rem 1rem;
  }
  .layout-wrapper {
    padding: 1.25rem 1rem 0;
  }
  html {
    scroll-padding-top: 11rem;
  }
  .logo-container {
    justify-content: space-between;
    width: 100%;
  }
  .search-bar-container {
    max-width: 100%;
    width: 100%;
  }
  .main-navbar-wrapper {
    padding: 0.25rem 0.5rem;
  }
  .nav-scroll-btn {
    display: none; /* Mobile touch swipe is faster and native */
  }
  .nav-link {
    padding: 0.35rem 0.75rem;
    font-size: 0.8rem;
  }
  .hero-header {
    padding: 1.75rem 1.1rem;
  }
  .hero-header h1 {
    font-size: 1.5rem;
  }
  .hero-header p {
    font-size: 0.88rem;
  }
  .hero-search #global-tool-search {
    padding: 0.75rem 0.9rem 0.75rem 2.6rem;
    font-size: 0.9rem;
  }
  .hero-points {
    font-size: 0.78rem;
    gap: 0.4rem 1rem;
  }
  .tools-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
  }
  .tool-card {
    padding: 1rem 0.75rem;
  }
  .tool-card i {
    font-size: 1.8rem;
    margin-bottom: 0.6rem;
  }
  .tool-card h4 {
    font-size: 0.92rem;
  }
  .tool-card p {
    font-size: 0.78rem;
    -webkit-line-clamp: 2;
  }
  .tool-studio {
    padding: 1.25rem 0.85rem;
  }
  .dropzone {
    padding: 1.75rem 1rem;
  }
  .btn-primary {
    width: 100%;
  }
}

@media (max-width: 420px) {
  .tools-grid {
    grid-template-columns: 1fr;
  }
  .badge-privacy {
    font-size: 0.72rem;
    padding: 0.2rem 0.5rem;
  }
}

/* ==========================================================================
   SEO Tool Pages (/<category-slug>/<tool-id>.html)
   Crawlable editorial content rendered beneath the live tool studio.
   ========================================================================== */

.seo-breadcrumb {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.seo-breadcrumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
}

.seo-breadcrumb li:not(:last-child)::after {
  content: '/';
  margin-left: 0.4rem;
  color: #cbd5e1;
}

.seo-intro {
  margin-top: 0.5rem;
}

.seo-intro h1 {
  font-size: 1.9rem;
  font-weight: 800;
  color: var(--text-main);
  margin-bottom: 0.5rem;
  letter-spacing: -0.02em;
}

.seo-lede {
  font-size: 1.02rem;
  color: var(--text-main);
  margin-bottom: 0.85rem;
}

.seo-intro p {
  font-size: 0.92rem;
  line-height: 1.65;
  color: var(--text-muted);
  margin-bottom: 0.85rem;
}

.seo-perks {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.6rem;
  margin: 1rem 0 0;
}

.seo-perks li {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-size: 0.83rem;
  color: var(--text-main);
  background: #f8fafc;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 0.6rem 0.75rem;
}

.seo-perks i {
  color: var(--primary-color);
  margin-top: 0.15rem;
  flex-shrink: 0;
}

/* Tool-to-tool workflow trail.
   Deliberately styled apart from .seo-breadcrumb: one says "where am I in the
   site", the other says "what step of a job is this". Collapsing them would
   make the trail read as navigation chrome rather than as editorial context. */
.seo-workflow {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin: 0 0 1.5rem;
}

.seo-workflow-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #6366f1;
  background: #eef2ff;
  border: 1px solid #e0e7ff;
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
  white-space: nowrap;
  flex-shrink: 0;
}

.seo-workflow ol {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.seo-workflow li {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Chevron between steps, drawn in CSS so it is never read as content.
   Uses ::after rather than "li + li::before": on a narrow screen the trail
   wraps, and a leading separator can end up detached at the end of the
   previous visual line, leaving one item with no separator at all. A trailing
   one always sits with its own item, wrapped or not. */
.seo-workflow li:not(:last-child)::after {
  content: '\203A';
  color: #cbd5e1;
  font-size: 1rem;
  line-height: 1;
  margin-left: 0.35rem;
}

.seo-workflow a {
  color: var(--primary-color);
  font-weight: 600;
  text-decoration: none;
}

.seo-workflow a:hover {
  text-decoration: underline;
}

/* The page you are on: not a link, and visually settled. */
.seo-workflow li[aria-current="step"] {
  color: var(--text-main);
  font-weight: 700;
}

@media (max-width: 640px) {
  .seo-workflow {
    gap: 0.4rem;
  }

  .seo-workflow li {
    font-size: 0.8rem;
  }
}

/* Follow-on tools. Sits inside the intro block so it reads as part of the
   editorial copy rather than as a bolted-on widget, which is also what makes
   it useful contextually rather than just another related-links grid. */
.seo-also {
  margin: 1.25rem 0 0;
  padding: 0.85rem 1rem;
  background: #f8fafc;
  border: 1px solid var(--border-color);
  border-radius: 10px;
  font-size: 0.92rem;
}

.seo-also strong {
  color: var(--text-main);
  margin-right: 0.15rem;
}

/* In-prose cross-links. Underlined so they read as links, not as emphasised
   text, and so keyboard users can spot them. */
.seo-link {
  color: var(--primary-color);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

.seo-link:hover {
  color: #4338ca;
}

.seo-section {
  margin-top: 2.25rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border-color);
}

.seo-section h2 {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.6rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.seo-section h2 i {
  color: var(--primary-color);
}

.seo-section > p {
  font-size: 0.9rem;
  color: var(--text-muted);
  line-height: 1.6;
  margin-bottom: 1rem;
}

.seo-steps {
  list-style: none;
  counter-reset: seo-step;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin: 0;
  padding: 0;
}

.seo-steps li {
  counter-increment: seo-step;
  position: relative;
  padding-left: 2.6rem;
}

.seo-steps li::before {
  content: counter(seo-step);
  position: absolute;
  left: 0;
  top: 0;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--primary-color);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.seo-steps h3 {
  font-size: 0.97rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.2rem;
}

.seo-steps p {
  font-size: 0.87rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.seo-faq {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.seo-faq-item {
  border: 1px solid var(--border-color);
  border-radius: 8px;
  background: #fff;
  overflow: hidden;
}

.seo-faq-item summary {
  cursor: pointer;
  padding: 0.8rem 1rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-main);
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.seo-faq-item summary::-webkit-details-marker {
  display: none;
}

.seo-faq-item summary::after {
  content: '\f078';
  font-family: 'Font Awesome 6 Free';
  font-weight: 900;
  font-size: 0.72rem;
  color: var(--primary-color);
  transition: transform 0.2s ease;
  flex-shrink: 0;
}

.seo-faq-item[open] summary::after {
  transform: rotate(180deg);
}

.seo-faq-item p {
  padding: 0 1rem 0.9rem;
  font-size: 0.86rem;
  line-height: 1.65;
  color: var(--text-muted);
}

/* ==========================================================================
   Category FAQ
   --------------------------------------------------------------------------
   Tool pages have had an accordion for this all along (.seo-faq-item). The
   category page's FAQ shipped with no styles at all: question, answer,
   question, answer ran together as one paragraph with no card, no gap and no
   sign of where one answer stopped. Same card and same rhythm as the tool
   page, but with the answers already open - a category page is a browsing
   page, and an accordion makes someone click to read what they came to read.
   ========================================================================== */
.faq-item {
  background: var(--surface-color);
  border: 1px solid var(--border-color);
  border-left: 3px solid var(--primary-color);
  border-radius: var(--radius-md);
  padding: 1.05rem 1.25rem;
}

/* The items are siblings inside .seo-section with no wrapper of their own, so
   the gap has to come from the sibling selector - adding a wrapper would mean
   regenerating all 17 category pages for something CSS can do. */
.faq-item + .faq-item {
  margin-top: 0.6rem;
}

.faq-item h3 {
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.45;
  color: var(--text-main);
  margin-bottom: 0.5rem;
  display: flex;
  gap: 0.55rem;
}

/* The glyph is a CSS ::before rather than markup in the template: it is
   decoration, and 17 generated pages should not carry an icon per question. */
.faq-item h3::before {
  content: '\f059';
  font-family: 'Font Awesome 6 Free';
  font-weight: 900;
  color: var(--primary-color);
  font-size: 0.8rem;
  flex-shrink: 0;
  margin-top: 0.18rem;
}

.faq-item p {
  font-size: 0.87rem;
  line-height: 1.7;
  color: var(--text-muted);
  margin: 0;
}

/* No rule for links or for a second paragraph in an answer, because neither
   exists: the template renders each answer as one <p> through esc(), not the
   linkified() the tool pages use. Checked across all 17 category pages - 51
   FAQ items, none with a link. Style them the day that stops being true. */

@media (max-width: 560px) {
  .faq-item {
    padding: 0.9rem 1rem;
  }
}

.seo-related-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.seo-related-card {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--border-color);
  border-radius: 10px;
  background: #fff;
  text-decoration: none !important;
  color: inherit;
  transition: all 0.18s ease;
}

.seo-related-card:hover {
  border-color: var(--primary-color);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.seo-related-card > i {
  color: var(--primary-color);
  font-size: 1.05rem;
  margin-top: 0.15rem;
  flex-shrink: 0;
}

.seo-related-card span {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}.seo-related-card strong {
  font-size: 0.87rem;
  color: var(--text-main);
  /* Same uniformity rule as .tool-card, for the same reason: on a category page
     the name is one line 24 times out of 26 and two lines the rest, so the two
     long ones made their row taller. Measured, not assumed. */
  line-height: 1.5;
  min-height: 3em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.seo-related-card em {
  font-size: 0.76rem;
  font-style: normal;
  color: var(--text-muted);
  line-height: 1.4;
  /* Three lines is what the longest description here needs; four lines were
     measured on one card out of 26 and lose their last line. */
  min-height: 4.2em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.seo-more {
  font-size: 0.85rem;
}

/* The studio mounts above the editorial copy on static pages */
#tool-studio-mount:empty {
  display: none;
}

/* ==========================================================================
   Selected File Chips + Batch Auto-Enable Notice
   ========================================================================== */
.file-chips-panel {
  background: #f8fafc;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md, 10px);
  padding: 0.85rem 1rem;
}

.file-chips-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.65rem;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text-main);
}

.btn-add-more {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.76rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--primary-color);
  background: #ffffff;
  border: 1px solid var(--primary-color);
  border-radius: 9999px;
  cursor: pointer;
  transition: all 0.15s ease;
  white-space: nowrap;
}

.btn-add-more:hover {
  background: var(--primary-color);
  color: #fff;
}

.file-chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin: 0;
  padding: 0;
}

.file-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  max-width: 260px;
  padding: 0.35rem 0.4rem 0.35rem 0.65rem;
  background: #ffffff;
  border: 1px solid var(--border-color);
  border-radius: 9999px;
  font-size: 0.78rem;
}

.file-chip > i {
  color: #ef4444;
  font-size: 0.85rem;
  flex-shrink: 0;
}

.file-chip-name {
  font-weight: 600;
  color: var(--text-main);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-chip-size {
  color: var(--text-muted);
  font-size: 0.72rem;
  white-space: nowrap;
}

.file-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  border: none;
  border-radius: 50%;
  background: #e2e8f0;
  color: #64748b;
  font-size: 0.6rem;
  cursor: pointer;
  flex-shrink: 0;
  transition: all 0.15s ease;
}

.file-chip-remove:hover {
  background: #ef4444;
  color: #fff;
}

.file-chip.too-big {
  border-color: #fca5a5;
  background: #fef2f2;
}

/* Auto-enable notice */
/* The batch note sits under the dropzone now that there is no checkbox panel
   below it, so it is an explanation rather than a control. */
.dropzone-batch-hint {
  margin-top: 0.6rem;
  font-size: 0.78rem !important;
  color: var(--text-muted) !important;
}

.dropzone-batch-hint i {
  color: var(--primary-color);
  margin-right: 0.25rem;
}

.batch-autonotice {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  margin-top: 0.75rem;
  background: linear-gradient(135deg, #f5f3ff 0%, #ffffff 100%);
  border: 1px solid #ddd6fe;
  border-left: 4px solid #7c3aed;
  border-radius: var(--radius-md, 10px);
  padding: 0.85rem 1rem;
}

.batch-autonotice > i {
  color: #7c3aed;
  font-size: 1rem;
  margin-top: 0.15rem;
  flex-shrink: 0;
}

.batch-autonotice-text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  flex: 1;
  min-width: 0;
}

.batch-autonotice-text strong {
  font-size: 0.87rem;
  color: #5b21b6;
}

.batch-autonotice-text span {
  font-size: 0.79rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.btn-notice-off {
  padding: 0.35rem 0.75rem;
  font-size: 0.76rem;
  font-weight: 600;
  font-family: inherit;
  color: #5b21b6;
  background: #fff;
  border: 1px solid #c4b5fd;
  border-radius: 9999px;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  transition: all 0.15s ease;
}

.btn-notice-off:hover {
  background: #7c3aed;
  border-color: #7c3aed;
  color: #fff;
}

@media (max-width: 640px) {
  .batch-autonotice {
    flex-wrap: wrap;
  }
  .btn-notice-off {
    margin-left: 1.7rem;
  }
  .file-chip {
    max-width: 100%;
  }
}

/* Transient inline notice (batch mode changes, truncation, etc.) */
.inline-notice {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  border-radius: var(--radius-md, 10px);
  padding: 0.7rem 0.9rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #1e40af;
}

.inline-notice i {
  margin-top: 0.15rem;
  flex-shrink: 0;
}

/* A file was left out of the selection. The user has to act on this, so it
   stays put until dismissed rather than fading out on a timer. */
.inline-notice-sticky {
  background: #fffbeb;
  border-color: #fcd34d;
  color: #92400e;
  align-items: center;
}

.inline-notice .inline-notice-close {
  margin-left: auto;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}

.inline-notice .inline-notice-close:hover {
  background: rgba(146, 64, 14, 0.12);
}

.inline-notice .inline-notice-close:focus-visible {
  outline: 2px solid #92400e;
  outline-offset: 1px;
}

/* ==========================================================================
   Live preview status badge
   --------------------------------------------------------------------------
   Sits in step 4's heading and says what the preview is currently doing, so
   "nothing changed" and "this tool cannot preview itself" are visibly
   different states instead of both looking like a preview that failed.
   ========================================================================== */
.preview-live-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: 0.25rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: #eef2ff;
  color: #4338ca;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}

.preview-live-badge.is-busy {
  background: #fef3c7;
  color: #92400e;
}

.preview-live-badge.is-live {
  background: #dcfce7;
  color: #15803d;
}

.preview-live-badge.is-error {
  background: #fee2e2;
  color: #b91c1c;
}

/* An idle step keeps its heading readable but drops the content, so the flow
   reads as "1 done, 2-5 still to do" without the panel jumping when a file
   arrives. */
.tool-studio-step[data-idle="true"] > *:not(.tool-studio-step-head) {
  display: none;
}

@media (max-width: 560px) {
  .preview-live-badge {
    margin-left: 0;
    margin-top: 0.3rem;
  }
}

/* ============================================================
   Drag to position / free crop on the preview
   The box drawn over the page is not decoration: the tool's
   execute() reads its coordinates back and draws the real
   thing there. See core/drag-position.js.
   ============================================================ */

.drag-position-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 6;
  font-family: inherit;
}

.drag-position-layer .drag-box {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: auto;
  cursor: grab;
  border: 2px dashed var(--accent, #6366f1);
  border-radius: 4px;
  background: rgba(99, 102, 241, 0.06);
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: none;
  min-width: 24px;
  min-height: 16px;
}

.drag-position-layer.is-dragging .drag-box,
.drag-position-layer.is-dragging .drag-crop {
  cursor: grabbing;
}

.drag-position-layer .drag-box-inner {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  pointer-events: none;
}

.drag-position-layer .drag-box-label,
.drag-position-layer .drag-crop-label {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1.4;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.82);
  color: #fff;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.drag-position-layer .drag-box-label { bottom: -30px; }
.drag-position-layer .drag-crop-label { bottom: -30px; opacity: 1; }

.drag-position-layer .drag-box:hover .drag-box-label,
.drag-position-layer.is-dragging .drag-box-label,
.drag-position-layer .drag-crop:hover .drag-crop-label {
  opacity: 1;
}

/* ---- free crop rectangle ---- */

.drag-position-layer .drag-crop {
  position: absolute;
  pointer-events: none;
  border: 2px solid #22c55e;
  border-radius: 2px;
  box-shadow: 0 0 0 9999px rgba(15, 23, 42, 0.42);
  touch-action: none;
}

.drag-position-layer .drag-crop-body {
  position: absolute;
  inset: 0;
  pointer-events: auto;
  cursor: move;
}

.drag-position-layer .drag-crop-edge,
.drag-position-layer .drag-crop-corner {
  position: absolute;
  pointer-events: auto;
  background: #22c55e;
  touch-action: none;
}

.drag-position-layer .drag-crop-edge-n,
.drag-position-layer .drag-crop-edge-s {
  left: 0;
  right: 0;
  height: 12px;
  margin-top: -6px;
}
.drag-position-layer .drag-crop-edge-n { top: 0; cursor: ns-resize; }
.drag-position-layer .drag-crop-edge-s { bottom: 0; cursor: ns-resize; }

.drag-position-layer .drag-crop-edge-e,
.drag-position-layer .drag-crop-edge-w {
  top: 0;
  bottom: 0;
  width: 12px;
  margin-left: -6px;
}
.drag-position-layer .drag-crop-edge-e { right: 0; cursor: ew-resize; }
.drag-position-layer .drag-crop-edge-w { left: 0; cursor: ew-resize; }

.drag-position-layer .drag-crop-corner {
  width: 16px;
  height: 16px;
  border-radius: 3px;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px #16a34a;
}
.drag-position-layer .drag-crop-corner-nw { left: -8px; top: -8px; cursor: nwse-resize; }
.drag-position-layer .drag-crop-corner-ne { right: -8px; top: -8px; cursor: nesw-resize; }
.drag-position-layer .drag-crop-corner-sw { left: -8px; bottom: -8px; cursor: nesw-resize; }
.drag-position-layer .drag-crop-corner-se { right: -8px; bottom: -8px; cursor: nwse-resize; }

/* ---- reset ---- */

.drag-position-layer .drag-reset {
  position: absolute;
  right: 8px;
  bottom: 8px;
  pointer-events: auto;
  cursor: pointer;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.4rem 0.65rem;
  border-radius: 6px;
  border: 1px solid rgba(148, 163, 184, 0.6);
  background: rgba(15, 23, 42, 0.85);
  color: #e2e8f0;
}
.drag-position-layer .drag-reset:hover { background: #0f172a; color: #fff; }

/* The drag box must never swallow a click meant for the preview controls
   underneath it when the tool has nothing to position. */
.tool-studio-step[data-idle="true"] .drag-position-layer { display: none; }

/* ============================================================
   Send to Next Tool + Share
   The pipeline picker, and the share control that sits with it.
   ============================================================ */

/* ---------- the Send to Next Tool button, beside Download ---------- */

.tool-result-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.75rem;
}

.btn-pipeline {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.7rem 1.05rem;
  border-radius: 8px;
  border: 1px solid transparent;
  background: linear-gradient(135deg, #6d28d9, #4f46e5);
  color: #fff;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
  box-shadow: 0 2px 8px rgba(79, 70, 229, 0.28);
}
.btn-pipeline:hover { filter: brightness(1.07); box-shadow: 0 4px 14px rgba(79, 70, 229, 0.36); }
.btn-pipeline:active { transform: translateY(1px); }
.btn-pipeline i { font-size: 0.8rem; }
/* Disabled state for Send to Next Tool when the result cannot be carried on
   (a batch ZIP). Appended after both .btn-pipeline blocks in this file, so
   the disabled state wins every tie against the later hover/active rules
   instead of flickering bright on hover. */
.btn-pipeline:disabled,
.btn-pipeline:disabled:hover,
.btn-pipeline:disabled:active {
  opacity: 0.55;
  cursor: not-allowed;
  filter: none;
  box-shadow: none;
  transform: none;
}

/* ---------- Share ---------- */

.tool-share {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.7rem;
  border-radius: 7px;
  border: 1px solid var(--border, #cbd5e1);
  background: var(--surface, #fff);
  color: var(--text, #0f172a);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.tool-share:hover { background: #f1f5f9; }
.tool-share.is-done { border-color: #16a34a; color: #15803d; }

/* ---------- the modal ---------- */

body.pipeline-open { overflow: hidden; }

.pipeline-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  backdrop-filter: blur(2px);
  z-index: 1200;
  animation: pipeline-fade 0.15s ease;
}

.pipeline-dialog {
  position: fixed;
  z-index: 1201;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(880px, calc(100vw - 2rem));
  max-height: min(82vh, 900px);
  display: flex;
  flex-direction: column;
  background: var(--surface, #fff);
  border-radius: 14px;
  box-shadow: 0 24px 70px rgba(2, 6, 23, 0.42);
  overflow: hidden;
  animation: pipeline-pop 0.18s cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes pipeline-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes pipeline-pop {
  from { opacity: 0; transform: translate(-50%, -46%) scale(0.97); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .pipeline-backdrop, .pipeline-dialog { animation: none; }
}

.pipeline-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.15rem 1.35rem 0.9rem;
  border-bottom: 1px solid var(--border, #e2e8f0);
}

.pipeline-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #7c3aed;
}
.pipeline-eyebrow i { font-size: 0.7rem; }

.pipeline-head h2 {
  margin: 0.15rem 0 0;
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--text, #0f172a);
}

.pipeline-close {
  border: 0;
  background: transparent;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-muted, #64748b);
  cursor: pointer;
  padding: 0.1rem 0.4rem;
  border-radius: 6px;
}
.pipeline-close:hover { background: #f1f5f9; color: #0f172a; }

.pipeline-body {
  padding: 1.05rem 1.35rem 1.35rem;
  overflow-y: auto;
}

/* ---------- the chain ---------- */

.pipeline-chain {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.7rem 0.8rem;
  border: 1px dashed var(--border, #cbd5e1);
  border-radius: 10px;
  background: #f8fafc;
}

.pipeline-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.65rem;
  border-radius: 999px;
  background: #eef2ff;
  border: 1px solid #c7d2fe;
  color: #3730a3;
  font-size: 0.78rem;
  font-weight: 600;
  max-width: 15rem;
}
.pipeline-chip.is-current { background: #4f46e5; border-color: #4f46e5; color: #fff; }
.pipeline-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pipeline-chain-arrow { color: var(--text-muted, #94a3b8); font-size: 0.7rem; }

.pipeline-chain-hint {
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  color: var(--text-muted, #64748b);
}

/* ---------- the file in transit ---------- */

.pipeline-carrying {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.85rem;
  padding: 0.6rem 0.8rem;
  border-radius: 9px;
  background: #ecfdf5;
  border: 1px solid #a7f3d0;
  color: #065f46;
  font-size: 0.82rem;
}
.pipeline-carrying i { color: #059669; }
.pipeline-carrying strong { font-weight: 700; }

.pipeline-empty {
  margin: 1.1rem 0 0;
  padding: 1.1rem;
  border-radius: 10px;
  background: #f8fafc;
  border: 1px solid var(--border, #e2e8f0);
  color: var(--text-muted, #64748b);
  font-size: 0.88rem;
  text-align: center;
}

.pipeline-section {
  margin: 1.15rem 0 0.6rem;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted, #64748b);
}

/*
 * Shown when a file is held that no other tool can open - the .txt, .csv and
 * .json that extract-text and friends produce. Same box as the "nothing held"
 * note, but left-aligned and with a heading above it, because this one is the
 * answer to a question the user asked rather than a prompt for another one.
 */
.pipeline-stranded {
  margin: 0;
  padding: 1.1rem;
  border-radius: 10px;
  background: #f8fafc;
  border: 1px solid var(--border, #e2e8f0);
  color: var(--text-muted, #64748b);
  font-size: 0.88rem;
  line-height: 1.6;
}

.pipeline-stranded strong { color: var(--text, #0f172a); font-weight: 700; }

/* ---------- the picks ---------- */

.pipeline-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  gap: 0.6rem;
}

.pipeline-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.7rem;
  text-align: left;
  padding: 0.75rem 0.8rem;
  border-radius: 11px;
  border: 1px solid var(--border, #e2e8f0);
  background: var(--surface, #fff);
  cursor: pointer;
  font: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.12s ease;
}
.pipeline-card:hover {
  border-color: #a5b4fc;
  box-shadow: 0 4px 14px rgba(79, 70, 229, 0.14);
  transform: translateY(-1px);
}
.pipeline-card:focus-visible { outline: 2px solid #4f46e5; outline-offset: 2px; }

.pipeline-card-icon {
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 9px;
  background: #eef2ff;
  color: #4f46e5;
  font-size: 0.95rem;
  flex-shrink: 0;
}

.pipeline-card-text { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.pipeline-card-name { font-weight: 700; font-size: 0.87rem; color: var(--text, #0f172a); }
.pipeline-card-desc {
  font-size: 0.76rem;
  color: var(--text-muted, #64748b);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.pipeline-card-go {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  font-size: 0.85rem;
  font-weight: 800;
  color: #7c3aed;
  flex-shrink: 0;
}
.pipeline-card:hover .pipeline-card-go { color: #4f46e5; }

.pipeline-foot-note {
  margin: 0.85rem 0 0;
  font-size: 0.76rem;
  color: var(--text-muted, #94a3b8);
}

@media (max-width: 560px) {
  .pipeline-dialog { width: calc(100vw - 1rem); border-radius: 12px; }
  .pipeline-grid { grid-template-columns: 1fr; }
}

/* ============================================================
   Send to Next Tool + Share
   The pipeline picker, and the share control that sits with it.
   ============================================================ */

/* ---------- the Send to Next Tool button, beside Download ---------- */

.tool-result-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.75rem;
}

.btn-pipeline {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.7rem 1.05rem;
  border-radius: 8px;
  border: 1px solid transparent;
  background: linear-gradient(135deg, #6d28d9, #4f46e5);
  color: #fff;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
  box-shadow: 0 2px 8px rgba(79, 70, 229, 0.28);
}
.btn-pipeline:hover { filter: brightness(1.07); box-shadow: 0 4px 14px rgba(79, 70, 229, 0.36); }
.btn-pipeline:active { transform: translateY(1px); }
.btn-pipeline i { font-size: 0.8rem; }
/* Disabled state for Send to Next Tool when the result cannot be carried on
   (a batch ZIP). Appended after both .btn-pipeline blocks in this file, so
   the disabled state wins every tie against the later hover/active rules
   instead of flickering bright on hover. */
.btn-pipeline:disabled,
.btn-pipeline:disabled:hover,
.btn-pipeline:disabled:active {
  opacity: 0.55;
  cursor: not-allowed;
  filter: none;
  box-shadow: none;
  transform: none;
}

/* ---------- Share ---------- */

.tool-share {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.7rem;
  border-radius: 7px;
  border: 1px solid var(--border, #cbd5e1);
  background: var(--surface, #fff);
  color: var(--text, #0f172a);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.tool-share:hover { background: #f1f5f9; }
.tool-share.is-done { border-color: #16a34a; color: #15803d; }

/* ---------- the modal ---------- */

body.pipeline-open { overflow: hidden; }

.pipeline-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  backdrop-filter: blur(2px);
  z-index: 1200;
  animation: pipeline-fade 0.15s ease;
}

.pipeline-dialog {
  position: fixed;
  z-index: 1201;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(880px, calc(100vw - 2rem));
  max-height: min(82vh, 900px);
  display: flex;
  flex-direction: column;
  background: var(--surface, #fff);
  border-radius: 14px;
  box-shadow: 0 24px 70px rgba(2, 6, 23, 0.42);
  overflow: hidden;
  animation: pipeline-pop 0.18s cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes pipeline-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes pipeline-pop {
  from { opacity: 0; transform: translate(-50%, -46%) scale(0.97); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .pipeline-backdrop, .pipeline-dialog { animation: none; }
}

.pipeline-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.15rem 1.35rem 0.9rem;
  border-bottom: 1px solid var(--border, #e2e8f0);
}

.pipeline-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #7c3aed;
}
.pipeline-eyebrow i { font-size: 0.7rem; }

.pipeline-head h2 {
  margin: 0.15rem 0 0;
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--text, #0f172a);
}

.pipeline-close {
  border: 0;
  background: transparent;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-muted, #64748b);
  cursor: pointer;
  padding: 0.1rem 0.4rem;
  border-radius: 6px;
}
.pipeline-close:hover { background: #f1f5f9; color: #0f172a; }

.pipeline-body {
  padding: 1.05rem 1.35rem 1.35rem;
  overflow-y: auto;
}

/* ---------- the chain ---------- */

.pipeline-chain {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.7rem 0.8rem;
  border: 1px dashed var(--border, #cbd5e1);
  border-radius: 10px;
  background: #f8fafc;
}

.pipeline-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.65rem;
  border-radius: 999px;
  background: #eef2ff;
  border: 1px solid #c7d2fe;
  color: #3730a3;
  font-size: 0.78rem;
  font-weight: 600;
  max-width: 15rem;
}
.pipeline-chip.is-current { background: #4f46e5; border-color: #4f46e5; color: #fff; }
.pipeline-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pipeline-chain-arrow { color: var(--text-muted, #94a3b8); font-size: 0.7rem; }

.pipeline-chain-hint {
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  color: var(--text-muted, #64748b);
}

/* ---------- the file in transit ---------- */

.pipeline-carrying {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.85rem;
  padding: 0.6rem 0.8rem;
  border-radius: 9px;
  background: #ecfdf5;
  border: 1px solid #a7f3d0;
  color: #065f46;
  font-size: 0.82rem;
}
.pipeline-carrying i { color: #059669; }
.pipeline-carrying strong { font-weight: 700; }

.pipeline-empty {
  margin: 1.1rem 0 0;
  padding: 1.1rem;
  border-radius: 10px;
  background: #f8fafc;
  border: 1px solid var(--border, #e2e8f0);
  color: var(--text-muted, #64748b);
  font-size: 0.88rem;
  text-align: center;
}

.pipeline-section {
  margin: 1.15rem 0 0.6rem;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted, #64748b);
}

/*
 * Shown when a file is held that no other tool can open - the .txt, .csv and
 * .json that extract-text and friends produce. Same box as the "nothing held"
 * note, but left-aligned and with a heading above it, because this one is the
 * answer to a question the user asked rather than a prompt for another one.
 */
.pipeline-stranded {
  margin: 0;
  padding: 1.1rem;
  border-radius: 10px;
  background: #f8fafc;
  border: 1px solid var(--border, #e2e8f0);
  color: var(--text-muted, #64748b);
  font-size: 0.88rem;
  line-height: 1.6;
}

.pipeline-stranded strong { color: var(--text, #0f172a); font-weight: 700; }

/* ---------- the picks ---------- */

.pipeline-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  gap: 0.6rem;
}

.pipeline-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.7rem;
  text-align: left;
  padding: 0.75rem 0.8rem;
  border-radius: 11px;
  border: 1px solid var(--border, #e2e8f0);
  background: var(--surface, #fff);
  cursor: pointer;
  font: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.12s ease;
}
.pipeline-card:hover {
  border-color: #a5b4fc;
  box-shadow: 0 4px 14px rgba(79, 70, 229, 0.14);
  transform: translateY(-1px);
}
.pipeline-card:focus-visible { outline: 2px solid #4f46e5; outline-offset: 2px; }

.pipeline-card-icon {
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 9px;
  background: #eef2ff;
  color: #4f46e5;
  font-size: 0.95rem;
  flex-shrink: 0;
}

.pipeline-card-text { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.pipeline-card-name { font-weight: 700; font-size: 0.87rem; color: var(--text, #0f172a); }
.pipeline-card-desc {
  font-size: 0.76rem;
  color: var(--text-muted, #64748b);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.pipeline-card-go {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  font-size: 0.85rem;
  font-weight: 800;
  color: #7c3aed;
  flex-shrink: 0;
}
.pipeline-card:hover .pipeline-card-go { color: #4f46e5; }

.pipeline-foot-note {
  margin: 0.85rem 0 0;
  font-size: 0.76rem;
  color: var(--text-muted, #94a3b8);
}

@media (max-width: 560px) {
  .pipeline-dialog { width: calc(100vw - 1rem); border-radius: 12px; }
  .pipeline-grid { grid-template-columns: 1fr; }
}
/* ---------- share row in the studio header ---------- */

.tool-share-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.6rem;
}

.tool-share-hint {
  font-size: 0.76rem;
  color: var(--text-muted, #64748b);
}
/* ==========================================================================
   Run the whole chain.

   The queue sits inside the picker, not beside it: the cards are already the
   list of possible next steps, and the + on each one is the only new target.
   A card that means "take me there" must keep meaning exactly that, so
   "run this later" gets its own small button rather than a second mode the
   whole panel would have to be explained.
   ========================================================================== */

.pipeline-card-wrap {
  position: relative;
}

.pipeline-queue-btn {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 2;
  width: 1.65rem;
  height: 1.65rem;
  display: grid;
  place-items: center;
  border-radius: 6px;
  border: 1px solid var(--border-color, #e2e8f0);
  background: #fff;
  color: var(--text-muted, #64748b);
  font-size: 0.72rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.pipeline-queue-btn:hover {
  border-color: #6d28d9;
  color: #6d28d9;
}

.pipeline-queue-btn.is-on {
  background: linear-gradient(135deg, #6d28d9, #4f46e5);
  border-color: transparent;
  color: #fff;
  font-weight: 700;
}

.pipeline-queue-n {
  line-height: 1;
}

.pipeline-queue-panel {
  margin-top: 1.1rem;
  padding: 0.9rem 1rem 1rem;
  border: 1px solid #ddd6fe;
  border-radius: 10px;
  background: #faf5ff;
}

.pipeline-queue-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}

.pipeline-queue-head .pipeline-section {
  margin: 0;
}

.pipeline-queue-clear {
  border: 0;
  background: none;
  color: var(--text-muted, #64748b);
  font-size: 0.78rem;
  cursor: pointer;
  text-decoration: underline;
}

.pipeline-queue-list {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.pipeline-queue-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.5rem;
  border-radius: 7px;
  background: #fff;
  border: 1px solid #ede9fe;
  font-size: 0.85rem;
}

.pipeline-queue-step {
  flex: 0 0 1.3rem;
  height: 1.3rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: #6d28d9;
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
}

.pipeline-queue-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pipeline-queue-drop {
  border: 0;
  background: none;
  color: var(--text-muted, #64748b);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.2rem;
}

.pipeline-queue-drop:hover {
  color: #dc2626;
}

.pipeline-queue-note {
  margin: 0.7rem 0 0;
  font-size: 0.78rem;
  color: #5b21b6;
  display: flex;
  gap: 0.4rem;
  align-items: flex-start;
}

.pipeline-run-all {
  margin-top: 0.7rem;
  width: 100%;
  justify-content: center;
}

/* --- the progress panel ------------------------------------------------- */

.pipeline-progress-dialog {
  max-width: 520px;
}

.pipeline-prog-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.pipeline-prog-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.55rem 0.6rem;
  border-radius: 8px;
  border: 1px solid var(--border-color, #e2e8f0);
  font-size: 0.86rem;
  color: var(--text-muted, #64748b);
}

.pipeline-prog-step {
  flex: 0 0 1.4rem;
  height: 1.4rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: #f1f5f9;
  color: #475569;
  font-size: 0.74rem;
  font-weight: 700;
}

.pipeline-prog-text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.pipeline-prog-name {
  font-weight: 600;
}

.pipeline-prog-msg {
  font-size: 0.78rem;
  line-height: 1.35;
}

.pipeline-prog-row.is-running {
  border-color: #c7d2fe;
  background: #eef2ff;
  color: #3730a3;
}

.pipeline-prog-row.is-running .pipeline-prog-step {
  background: #4f46e5;
  color: #fff;
}

.pipeline-prog-row.is-done {
  border-color: #bbf7d0;
  background: #f0fdf4;
  color: #166534;
}

.pipeline-prog-row.is-done .pipeline-prog-step {
  background: #16a34a;
  color: #fff;
}

.pipeline-prog-row.is-failed,
.pipeline-prog-row.is-stopped {
  border-color: #fecaca;
  background: #fef2f2;
  color: #991b1b;
}

.pipeline-prog-row.is-failed .pipeline-prog-step,
.pipeline-prog-row.is-stopped .pipeline-prog-step {
  background: #dc2626;
  color: #fff;
}

.pipeline-prog-actions {
  margin-top: 1rem;
  display: flex;
  justify-content: flex-end;
}

.pipeline-stop {
  border: 1px solid var(--border-color, #e2e8f0);
  background: #fff;
  color: #b91c1c;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.5rem 1rem;
  border-radius: 8px;
  cursor: pointer;
}

.pipeline-stop:hover:not(:disabled) {
  border-color: #dc2626;
  background: #fef2f2;
}

.pipeline-stop:disabled {
  opacity: 0.6;
  cursor: default;
}

@media (max-width: 560px) {
  .pipeline-queue-row {
    font-size: 0.8rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pipeline-queue-btn {
    transition: none;
  }
}/* ==========================================================================
   Save & Continue Later, and the banner that offers it back.

   Both are deliberately quiet. Resuming is a thing you ask for, not a thing
   that ambushes you on load, so the banner sits above the studio in one line
   and disappears for good once you have used it or forgotten it.
   ========================================================================== */

.btn-pipeline-save {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.6rem 1rem;
  border-radius: 8px;
  border: 1px solid #c7d2fe;
  background: #eef2ff;
  color: #3730a3;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.btn-pipeline-save:hover:not(:disabled) {
  background: #e0e7ff;
  border-color: #a5b4fc;
}

.btn-pipeline-save:disabled {
  opacity: 0.65;
  cursor: default;
}

/* --- the resume banner ------------------------------------------------- */

.resume-banner {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 0 0 1rem;
  padding: 0.8rem 1rem;
  border: 1px solid #a5b4fc;
  border-left: 4px solid #4f46e5;
  border-radius: 10px;
  background: #eef2ff;
}

.resume-banner-icon {
  font-size: 1.15rem;
  color: #4f46e5;
  flex: 0 0 auto;
}

.resume-banner-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  flex: 1;
  min-width: 0;
}

.resume-banner-text strong {
  font-size: 0.95rem;
  color: #1e1b4b;
}

.resume-banner-text span {
  font-size: 0.8rem;
  color: #4338ca;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.resume-banner-actions {
  display: flex;
  gap: 0.5rem;
  flex: 0 0 auto;
}

.resume-banner-go {
  padding: 0.45rem 0.95rem;
  border-radius: 7px;
  border: 0;
  background: #4f46e5;
  color: #fff;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.resume-banner-go:hover:not(:disabled) {
  background: #4338ca;
}

.resume-banner-go:disabled {
  opacity: 0.7;
  cursor: default;
}

.resume-banner-forget {
  padding: 0.45rem 0.75rem;
  border-radius: 7px;
  border: 1px solid #c7d2fe;
  background: #fff;
  color: #4338ca;
  font-size: 0.85rem;
  cursor: pointer;
}

.resume-banner-forget:hover {
  background: #f5f3ff;
}

@media (max-width: 560px) {
  .resume-banner {
    flex-wrap: wrap;
  }

  .resume-banner-actions {
    width: 100%;
  }

  .resume-banner-go {
    flex: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .btn-pipeline-save {
    transition: none;
  }
}
/* ==========================================================================
   PDF Editor workspace
   Only rendered on /pdf-editor/; every rule is namespaced under .editor-* so
   nothing here can leak into the other 193 tool pages.
   ========================================================================== */

.editor-panel {
  margin-top: 1.25rem;
  border: 1px solid var(--border-color);
  border-radius: 12px;
  background: #fff;
  padding: 1rem;
}

.editor-panel-head {
  font-weight: 700;
  font-size: .95rem;
  margin-bottom: .35rem;
}

.editor-status {
  font-size: .85rem;
  color: var(--text-muted);
  margin-bottom: .75rem;
}

.editor-thumbs {
  display: flex;
  gap: .6rem;
  overflow-x: auto;
  padding: .5rem;
  border: 1px dashed var(--border-color);
  border-radius: 8px;
  min-height: 150px;
  align-items: flex-start;
  background: #fafbfc;
}

.editor-thumb {
  flex: 0 0 auto;
  text-align: center;
  /* Grab rather than text-select, so a drag moves a page instead of highlighting
     every label it passes over. touch-action stops the browser claiming the
     gesture first on a touchscreen and scrolling the page instead. */
  cursor: grab;
  touch-action: none;
}

.editor-thumb:focus-visible {
  outline: 2px solid var(--primary-color, #4f46e5);
  outline-offset: 2px;
}

.editor-thumb.is-dragging {
  cursor: grabbing;
  opacity: .65;
}

.editor-thumb.is-dragging .editor-thumb-canvas {
  box-shadow: 0 6px 16px rgba(0, 0, 0, .28);
  border-color: var(--primary-color, #4f46e5);
}

.editor-thumb-canvas {
  display: block;
  border: 1px solid var(--border-color);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .08);
  background: #fff;
}

.editor-thumb-label {
  display: block;
  font-size: .7rem;
  color: var(--text-muted);
  margin-top: .2rem;
}

.editor-loading {
  font-size: .82rem;
  color: var(--text-muted);
  padding: .5rem;
}

.editor-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .85rem;
}

.editor-log {
  margin: .85rem 0 0;
  padding-left: 1.2rem;
  font-size: .82rem;
  color: var(--text-muted);
}

.editor-footnote {
  margin: .85rem 0 0;
  font-size: .78rem;
  color: var(--text-muted);
  font-style: italic;
}

/* The strip of common edits on the homepage and category pages. Each item
   links into the editor with that operation already chosen, so it is a real
   link rather than a button that would have to find a file it cannot see. */
.quick-edit {
  margin: 1.25rem 0;
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--border-color);
  border-radius: 12px;
  background: var(--bg-secondary, rgba(79, 70, 229, .04));
}

.quick-edit-heading {
  margin: 0 0 .3rem;
  font-size: 1.05rem;
  display: flex;
  align-items: center;
  gap: .45rem;
}

.quick-edit-heading i {
  color: var(--primary-color);
}

.quick-edit-note {
  margin: 0 0 .75rem;
  font-size: .84rem;
  color: var(--text-muted);
}

.quick-edit-list {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.quick-edit-list a {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .5rem .8rem;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  background: var(--bg-primary, #fff);
  color: var(--text);
  font-size: .87rem;
  text-decoration: none;
  transition: border-color .15s, transform .15s;
}

.quick-edit-list a:hover,
.quick-edit-list a:focus-visible {
  border-color: var(--primary-color);
  transform: translateY(-1px);
}

.quick-edit-list i {
  color: var(--primary-color);
}

/* Which operation the page was linked into with. Sits above the picker so the
   arrival is legible before a file is chosen. */
.editor-wanted {
  margin: .5rem 0 .25rem;
  padding: .5rem .7rem;
  border-radius: 6px;
  background: rgba(79, 70, 229, .09);
  color: var(--text);
  font-size: .85rem;
  font-weight: 600;
}

.editor-wanted[hidden] {
  display: none;
}

/* The offer to pick work back up. Full width rather than a toast, because it
   is a decision about somebody's document and a toast that disappears is the
   wrong shape for one. */
.editor-draft {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: .75rem 0;
  padding: .7rem .85rem;
  border: 1px solid var(--primary-color, #4f46e5);
  border-radius: 8px;
  background: rgba(79, 70, 229, .07);
  font-size: .84rem;
}

.editor-draft-text {
  flex: 1 1 14rem;
}

/* A link-sized way out of being remembered. Full stop sizing would compete
   with the sentence it sits in for no benefit. */
.editor-forget {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-style: italic;
  color: var(--text-muted);
  text-decoration: underline;
  cursor: pointer;
}

.editor-forget:hover {
  color: var(--text);
}
