/* ==========================================================================
   Hacking Demand Tools — Design Tokens
   Source of truth: uplead-page-v3-dark-mockup.html (Hacking Demand project folder)
   Do not substitute #ff7a59 here. That is HubSpot's brand orange, not ours.
   ========================================================================== */

:root {
  /* Surfaces */
  --bg:      #0d1722;
  --card:    #172636;
  --chip:    #1e2f42;
  --line:    #26394e;

  /* Type */
  --ink:     #f2f6fb;
  --body:    #c4d2e0;
  --mut:     #8ea3b8;

  /* Accents */
  --brand:      #ff5a3c;
  --brand-ink:  #1a0f08;
  --blue:       #5aa9ff;
  --good:       #39d98a;

  /* Type stacks */
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Code", monospace;

  /* Rhythm */
  --radius:    14px;
  --radius-sm: 8px;
  --maxw:      1100px;
  --gutter:    20px;

  /* Reserved ad slot heights. These exist to prevent Cumulative Layout Shift.
     Ad scripts inject asynchronously; without a reserved box the page reflows
     when they land, which damages CLS and therefore rankings. Never remove. */
  --ad-leaderboard-h: 280px;
  --ad-leaderboard-h-desktop: 100px;
  --ad-rail-w: 300px;
  --ad-rail-h: 600px;
  --ad-footer-h: 280px;
}

/* --------------------------------------------------------------------------
   Reset
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--body);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { max-width: 100%; height: auto; display: block; }

a {
  color: var(--blue);
  text-decoration: none;
}
a:hover { text-decoration: underline; }

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 4px;
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  align-items: start;
}

@media (min-width: 1024px) {
  .layout {
    grid-template-columns: minmax(0, 1fr) var(--ad-rail-w);
    gap: 40px;
  }
}

/* --------------------------------------------------------------------------
   Typography
   -------------------------------------------------------------------------- */
h1, h2, h3, h4 {
  color: var(--ink);
  line-height: 1.2;
  margin: 0 0 .5em;
  font-weight: 700;
  letter-spacing: -0.02em;
}

h1 { font-size: clamp(1.9rem, 1.3rem + 2.4vw, 2.75rem); }
h2 { font-size: clamp(1.35rem, 1.1rem + 1.1vw, 1.75rem); margin-top: 2em; }
h3 { font-size: 1.1rem; margin-top: 1.6em; }

p { margin: 0 0 1em; }

/* The mono "_" header treatment carried over from the marketplace pages. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--mut);
  margin: 0 0 .75rem;
}
.eyebrow::after { content: "_"; color: var(--brand); }

.lede {
  font-size: 1.1rem;
  color: var(--body);
  max-width: 68ch;
}

.muted { color: var(--mut); font-size: .875rem; }

/* --------------------------------------------------------------------------
   Components
   -------------------------------------------------------------------------- */
.chip {
  display: inline-block;
  background: var(--chip);
  border: 1px solid var(--line);
  color: var(--body);
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: .3rem .6rem;
  border-radius: 999px;
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 24px;
}

/* The TL;DR answer box. Per per-tool-page-design-spec.md this is the single
   most citable block on the page for AI search engines. Keep it first,
   keep it one paragraph, keep it answer-first. */
.tldr {
  background: linear-gradient(180deg, rgba(255,90,60,.08), rgba(255,90,60,.02));
  border: 1px solid rgba(255,90,60,.35);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius);
  padding: 20px 24px;
  margin: 0 0 32px;
}
.tldr h2 {
  margin: 0 0 .4em;
  font-size: .8rem;
  font-family: var(--font-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--brand);
}
.tldr p { margin: 0; color: var(--ink); }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  background: var(--brand);
  color: var(--brand-ink);
  font-weight: 650;
  border: 1px solid var(--brand);
  border-radius: var(--radius-sm);
  padding: .7rem 1.15rem;
  cursor: pointer;
  transition: filter .15s ease, transform .05s ease;
}
.btn:hover { filter: brightness(1.08); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn[disabled] { opacity: .5; cursor: not-allowed; }

.btn-ghost {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--line);
}
.btn-ghost:hover { border-color: var(--brand); filter: none; }

label {
  display: block;
  font-size: .8rem;
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 .35rem;
}

.field { margin-bottom: 18px; }

input[type="text"], input[type="email"], input[type="url"],
input[type="number"], select, textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: .65rem .8rem;
  color: var(--ink);
}
input::placeholder, textarea::placeholder { color: var(--mut); }
textarea { min-height: 120px; resize: vertical; }

.hint { font-size: .78rem; color: var(--mut); margin-top: .35rem; }

/* Output block */
.output {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--font-mono);
  font-size: .875rem;
  line-height: 1.7;
  color: var(--ink);
  min-height: 120px;
}
.output:empty::before {
  content: "Your result appears here.";
  color: var(--mut);
}

/* FAQ */
.faq details {
  border-bottom: 1px solid var(--line);
  padding: 14px 0;
}
.faq summary {
  cursor: pointer;
  font-weight: 650;
  color: var(--ink);
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::before { content: "+ "; color: var(--brand); font-family: var(--font-mono); }
.faq details[open] summary::before { content: "− "; }
.faq details > p { margin: .75rem 0 0; }

/* Affiliate disclosure. FTC requires this to be clear and conspicuous, and
   placing it adjacent to the link (not buried in a footer) is what satisfies
   "clear and conspicuous". Ad networks also check for it. */
.disclosure {
  font-size: .75rem;
  color: var(--mut);
  border-left: 2px solid var(--line);
  padding-left: 10px;
  margin: 12px 0;
}

/* --------------------------------------------------------------------------
   Ad slots — 3-unit layout, network agnostic.
   Heights are reserved so nothing shifts when the network script loads.
   -------------------------------------------------------------------------- */
.ad {
  display: block;
  background: var(--card);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  position: relative;
  /* Reserve a strip for the "Advertisement" label. Previously the label was
     absolutely positioned over the slot, which was fine while the slots were
     empty boxes but would have sat ON TOP of a real ad once AdSense filled
     them. Obscuring an ad is an AdSense policy violation, so the label now
     gets its own space rather than overlapping the creative. */
  padding-top: 18px;
}
.ad::after {
  content: "Advertisement";
  position: absolute;
  top: 4px; left: 10px;
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--mut);
  pointer-events: none;
}

/* Ad slot visibility. READ THIS BEFORE CHANGING IT AGAIN.

   THE BUG THIS FIXES (found 2026-08-16, live, by Charles noticing the AdSense
   ad preview showed nothing):

     .ad { display: none; }
     .ad:has(> ins.adsbygoogle[data-ad-status="filled"]) { display: block; }

   That rule was a DEADLOCK and it broke ad serving completely.

   AdSense sizes a responsive unit by measuring the width of the element at the
   moment `adsbygoogle.push()` runs. A `display: none` container measures 0, so
   every push threw, on every page, three times per page:

     TagError: adsbygoogle.push() error: No slot size for availableWidth=0

   A slot that throws never requests an ad, so it never fills, so it is never
   stamped data-ad-status="filled", so the rule never reveals it. The condition
   for becoming visible could not occur, because being invisible is what
   prevented it. Approval would have changed nothing; the site would have sat
   at zero revenue with three broken slots per page and no obvious symptom.

   THE RULE THAT ACTUALLY WORKS

   The container must always have real WIDTH so AdSense can measure it. What we
   suppress is its HEIGHT and its decoration, which is what makes an empty slot
   visible to a human. So:

     - default: full width, zero height, no border, no background, no label.
       Invisible to a visitor, fully measurable to AdSense.
     - unfilled: removed from layout entirely once Google confirms no ad.
     - filled:   label strip and reserved height, the only state that shows.

   Do NOT put `display: none` on `.ad` itself. If a slot must be hidden before
   the ad loads (the mobile rail below), hide it with a media query on the
   CONTAINER so the push never runs there at all, rather than hiding a slot
   that is still going to request an ad. */

/* Base: measurable, but invisible. */
.ad {
  display: block;
  width: 100%;
  min-height: 0;
  padding-top: 0;
  border: none;
  background: none;
  border-radius: 0;
}
.ad::after { content: none; }

/* Google answered and had nothing to show: take it out of the layout. */
.ad:has(> ins.adsbygoogle[data-ad-status="unfilled"]) { display: none; }

/* Google filled it: this is the only state that is visible to a visitor.
   The label gets its own 18px strip rather than sitting on top of the
   creative, because obscuring an ad violates AdSense policy. */
.ad:has(> ins.adsbygoogle[data-ad-status="filled"]) {
  background: var(--card);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  position: relative;
  padding-top: 18px;
}
.ad:has(> ins.adsbygoogle[data-ad-status="filled"])::after {
  content: "Advertisement";
  position: absolute;
  top: 4px; left: 10px;
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--mut);
  pointer-events: none;
}

/* The ad itself should fill the slot below the label. */
.ad > ins.adsbygoogle { display: block; width: 100%; }

.ad-empty { display: none; }

/* Slot 1: below H1 and intro, above the tool. Deliberately NOT above the
   content. Above-the-fold ad density is what Google's page layout algorithm
   targets. See BUILD-SPEC-REVISIONS C6. */
/* Reserved heights apply ONLY once a slot is filled. Applying them
   unconditionally would give every empty slot a tall blank gap on every page,
   which is the thing we are trying to avoid. Reserving on fill still prevents
   the layout shift that matters, because that is the state that has content. */
.ad-leaderboard { margin: 28px 0; }
.ad-leaderboard:has(> ins.adsbygoogle[data-ad-status="filled"]) {
  min-height: var(--ad-leaderboard-h);
}
@media (min-width: 768px) {
  .ad-leaderboard:has(> ins.adsbygoogle[data-ad-status="filled"]) {
    min-height: var(--ad-leaderboard-h-desktop);
  }
}

/* Slot 2: sticky rail alongside the feed. */
.ad-rail {
  width: var(--ad-rail-w);
  position: sticky;
  top: 24px;
}
.ad-rail:has(> ins.adsbygoogle[data-ad-status="filled"]) {
  min-height: var(--ad-rail-h);
}
@media (max-width: 1023px) {
  .ad-rail { display: none; }
}

/* Slot 3: below the generated output. */
.ad-footer { margin: 32px 0; }
.ad-footer:has(> ins.adsbygoogle[data-ad-status="filled"]) {
  min-height: var(--ad-footer-h);
}

/* --------------------------------------------------------------------------
   Modal (rate limit / key entry)
   -------------------------------------------------------------------------- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(5, 10, 16, .72);
  backdrop-filter: blur(3px);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 100;
}
.modal-backdrop[data-open="true"] { display: flex; }

.modal {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  max-width: 480px;
  width: 100%;
  padding: 28px;
  transform: translateY(12px);
  opacity: 0;
  animation: modal-in .22s ease forwards;
}
@keyframes modal-in {
  to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .modal { animation: none; opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   Utility
   -------------------------------------------------------------------------- */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.stack > * + * { margin-top: var(--s, 16px); }
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }

/* The [hidden] attribute is only honoured by the user agent stylesheet, which
   any class carrying an explicit `display` will beat on specificity. Without
   this rule, `.row[hidden]` still renders, and the Copy and Download buttons
   appear on page load with nothing to copy. Caught on the live site, not in
   the headless test, because the test only asserted they appeared AFTER a
   generation and never that they were hidden before one. */
[hidden] { display: none !important; }
.grow { flex: 1 1 auto; }

/* --------------------------------------------------------------------------
   Deep content sections (added 2026-08-12)
   Styling for the mistakes list and benchmarks table introduced when the
   pages were rewritten for depth rather than mere distinctness.
   -------------------------------------------------------------------------- */
.mistakes { margin: 0 0 28px; }
.mistakes dt {
  font-weight: 650;
  color: var(--ink);
  margin-top: 18px;
}
.mistakes dt::before {
  content: "✕";
  color: var(--brand);
  font-weight: 700;
  margin-right: 8px;
}
.mistakes dd {
  margin: 6px 0 0 22px;
  color: var(--body);
}
.bench {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 28px;
  font-size: .93rem;
}
.bench th, .bench td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.bench th {
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--mut);
}
.bench td:nth-child(2) {
  font-family: var(--font-mono);
  color: var(--ink);
  white-space: nowrap;
}

/* Grid heading. Added 2026-08-12: flattening the per-category sections had
   removed every h2 from the homepage, leaving h1 followed directly by the h3
   card titles. A skipped heading level is both an accessibility defect and a
   structure signal both Google and Bing ask for. */
.grid-heading {
  margin: 0 0 6px;
  font-size: 1.15rem;
}
