/**
 * PPC Sherpa - Ad Copy Analysis
 * (/app/google-ads/{id}/ad-copy/)
 *
 * Page-specific styles only. Everything this page could reuse, it reuses:
 * .section, .container, .btn, .alert, .text-muted, .app-breadcrumb and
 * .sherpa-loader are all defined elsewhere and are NOT redefined here.
 *
 * ***** THE TABLES ARE THE SHARED DATA TABLE. ***** The controls bar, Advanced
 * filters, headings and sorting, the pager, selection and its notice, the
 * empty row, the stacked cards, the detail rows, the status pills and the
 * summary chips are all assets/css/data-table.css (.dt-*), loaded ahead of
 * this file. This page's own copies of those rules were deleted when it moved
 * onto the component, not overridden. What stays here is this page's cell
 * content, its column budget, its row actions and everything that is not a
 * table.
 *
 * .app-loading / .app-error-alert / .app-retry-btn are the exception: despite
 * the app- prefix they are not global, they are duplicated verbatim into each
 * /app/ page's own stylesheet. The copies below are byte-identical to
 * page-gads-broad-match.css's.
 *
 * Every color, size, space and radius below is a design token from style.css.
 * No literal hex value is introduced anywhere in this file except inside the
 * four state tints, which pair with the existing .alert/.badge tint families
 * (#DCFCE7/#166534 success, #FEE2E2/#991B1B danger) so a state block and an
 * alert of the same severity read as the same product.
 *
 * ================================================================
 * ***** THE FOUR STATES MUST LOOK CLEARLY DIFFERENT FROM EACH OTHER. *****
 * ================================================================
 *
 * This is a functional requirement, not decoration. Three of the four states
 * arrive with an empty findings array, so the ONLY thing separating "your copy
 * is fine" from "the check crashed" on screen is this styling plus the wording.
 * They are given different fills, different border colors and different icon
 * colors, and only --clear uses the green success family. A failed check
 * sharing the reassuring treatment would tell a user their copy was reviewed
 * and cleared when it was not.
 *
 *   --found      neutral surface, primary accent   the count
 *   --clear      green                             the ONLY reassuring one
 *   --failed     red                               did not complete
 *   --never-run  amber                             never analyzed
 *   --unknown    amber                             cannot tell (unrecognized)
 *
 * --never-run and --unknown deliberately share the amber family: both mean "no
 * answer yet", neither is reassuring and neither is a failure. They are still
 * separated by their wording and their icons.
 *
 * ***** NO CLIPPING overflow ANYWHERE IN THIS FILE, DELIBERATELY. ***** This
 * theme has hit the same bug three times: a non-visible `overflow` on an
 * ancestor clips absolutely positioned descendants, including anything using
 * the shared .has-popover / .popover-content component. This page renders no
 * popover today, but the blocks are a stack of rows -- exactly the shape that
 * invites an `overflow` guard -- and adding one would silently break the first
 * popover anybody adds. See CLAUDE.md.
 *
 * ***** THERE IS NO DOLLAR FIGURE ON THIS PAGE, SO THERE IS NO RULE FOR ONE.
 * ***** The API returns no money-shaped field on either finding block. See the
 * JS docblock.
 */


/* ============================================
   SHARED /app/ STATES (duplicated per page -- see the docblock above)
   ============================================ */

.app-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-12);
  text-align: center;
}

.app-loading[hidden] {
  display: none;
}

.app-error-alert {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
}

.app-retry-btn {
  background: transparent;
  color: var(--color-danger);
  border: 1.5px solid var(--color-danger);
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  border-radius: var(--radius-md);
  white-space: nowrap;
  transition: background-color var(--transition-fast);
}

.app-retry-btn:hover {
  background: #FEE2E2;
}


/* ============================================
   PAGE HEADER

   NO LEDE. The page carried one under the title; it was removed because the two
   section headings below already name what is being checked. .adcopy-header__lede
   went with it rather than being left as a rule nothing emits.
   ============================================ */

/* Title left, settings gear right. Values are .mon-header's, not new ones, so
   every feature page carrying a gear aligns it identically. align-items is
   flex-start so the gear sits with the title rather than centring against the
   --space-5 the title carries beneath it (see .adcopy-header__title below --
   that margin is load-bearing for the chip row's gap and is untouched here). */
.adcopy-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.adcopy-header__text {
  min-width: 0;
}

/* The settings gear is the shared .app-settings-gear (style.css), rendered
   by template-parts/settings-gear.php. No page keeps its own copy. */

/* The title is the only thing in the header now: the subtitle beneath it was
   removed and .adcopy-header__subtitle went with it.

   ***** THE TITLE TAKES BACK THE GAP THE SUBTITLE WAS CARRYING. ***** That line
   held the --space-5 down to the chip row, so dropping it without this would
   have left only the header's own --space-4, pulling the chips up tight under
   30px type. --space-5 is what the negative keywords title carried for exactly
   this reason before its own subtitle was added (see .negkw-header__title). */
/* Size comes from .app-page-title (style.css), shared by all six feature
   page headings. Only the margin is page-local. */
.adcopy-header__title {
  margin-bottom: var(--space-5);
}


/* ============================================
   THE TWO BLOCKS

   Blocked-by-Google sits above under-served in the markup and nothing here
   reorders them. Blocked copy is not serving at all; under-served copy is
   serving less than it could. The broken thing stays above the improvable one.
   ============================================ */

.adcopy-results[hidden] {
  display: none;
}

.adcopy-block + .adcopy-block {
  margin-top: var(--space-10);
}

/* The heading is a row now: text, then the total, then the help marker. Flex
   with a baseline alignment so the lighter count sits on the same baseline as
   the heading rather than centred against its larger line box.

   No clipping overflow here: the help marker inside carries a .popover-content
   that would be clipped by it. See the file docblock. */
/* ***** THE HEADING AND THE PILLS SHARE ONE ROW. ***** The pills used to sit
   in their own bar below the <h2>, which left a band of empty space between the
   heading and the results. They are now the heading row's last child.

   align-items: center, not baseline: the pills are 30px-tall controls and the
   heading text is 20px type, so baseline alignment would hang the pills low
   against it. The help marker and the heading text still align to each other
   because they are the same type -- centering a row whose other two children
   are the same height changes nothing between them.

   ***** flex-wrap IS WHAT HANDLES THE NARROW CASE. ***** With no room for both,
   the pill group drops to its own line beneath the heading and stays right
   aligned (margin-left: auto still applies to a wrapped flex item). Nothing
   overlaps and nothing forces a horizontal scroll. row-gap keeps that wrapped
   line off the heading text.

   No clipping overflow here: the help marker inside carries a .popover-content
   that would be clipped by it. See the file docblock. */
.adcopy-block__heading {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: var(--text-xl);
  /* Unchanged at --space-1: the DISAPPROVED heading also uses this class and is
     followed immediately by its own lede ("Blocked from serving."), which must
     stay tight to it. The under-served heading carries the pills and needs a
     larger gap down to the results, so it takes that on a modifier below rather
     than here, where it would push the other section's lede away. */
  margin-bottom: var(--space-1);
}

/* The heading that hosts the pill row. It is followed directly by the results
   (no lede), so it owns the full gap down to them -- the gap the pills' old
   wrapper used to carry. */
.adcopy-block__heading--with-filters {
  margin-bottom: var(--space-4);
}

/* The count that used to sit here is gone: it leads the summary chip row above
   now (.dt-stat-pill), exactly as the recommendation count does on the
   negative keywords page, so keeping it here too would state one number twice
   on one screen. .adcopy-block__count and its separator went with it. */

.adcopy-block__heading-text {
  font-weight: var(--weight-bold);
}

.adcopy-block__lede {
  font-size: var(--text-sm);
  line-height: 1.5;
  margin-bottom: var(--space-4);
  max-width: 68ch;
}


/* ============================================
   ***** THE FOUR STATES *****

   Base layout is shared; every modifier below changes fill, border and icon
   color so the states cannot be mistaken for one another at a glance. See the
   docblock.
   ============================================ */

.adcopy-state {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
}

.adcopy-state__icon {
  display: flex;
  flex-shrink: 0;
  /* Optical alignment with the first line of text beside it, which sits on a
     1.4 line-height at --text-base. */
  margin-top: 1px;
}

.adcopy-state__text {
  min-width: 0;
}

.adcopy-state__title {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: 1.4;
}

.adcopy-state__body,
.adcopy-state__reason,
.adcopy-state__retry {
  font-size: var(--text-sm);
  line-height: 1.55;
  margin-top: var(--space-2);
}

/* The server's own error_message. Monospaced and set apart because it is
   operator text quoted verbatim, not a sentence this product wrote -- run
   together with the prose it would read as our own wording. */
.adcopy-state__reason {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  /* No clipping overflow: long text wraps, it does not scroll. See docblock. */
  overflow-wrap: break-word;
  word-break: break-word;
}


/* ---- RAN, FOUND THINGS ----------------------------------------
   No rules. The found state used to open with a large count card
   (.adcopy-state--found / __count / __count-value / __count-noun); the total
   moved into the summary chip row (.dt-stat-pill) and the card was
   removed, so its rules went with it rather than being left as CSS nothing
   emits. The findings list now starts immediately. */

/* No rules. .adcopy-truncation-note ("Showing N of M. The rest are not on
   this page yet.") was removed from the page along with the
   select-every-match link it shared its arithmetic with, so its amber rule
   went with it rather than being left as CSS nothing emits -- the same
   reasoning as the count card above. */


/* ---- RAN, FOUND NOTHING ---------------------------------------
   ***** THE ONLY STATE ALLOWED TO READ AS REASSURING. ***** Green, matching the
   .alert--success / .badge--success family. Nothing else on this page may use
   this treatment. */

.adcopy-state--clear {
  background: #DCFCE7;
  border-color: var(--color-success);
  color: #166534;
}

.adcopy-state--clear .adcopy-state__icon {
  color: var(--color-success);
}


/* ---- DID NOT COMPLETE -----------------------------------------
   Red, matching the .alert--danger family. This must never be mistakable for
   the green state above: a user who reads a crashed check as a clean bill of
   health will act on that belief. */

.adcopy-state--failed {
  background: #FEE2E2;
  border-color: var(--color-danger);
  color: #991B1B;
}

.adcopy-state--failed .adcopy-state__icon {
  color: var(--color-danger);
}


/* ---- NEVER ANALYSED, and CANNOT TELL --------------------------
   Amber, matching the .alert--warning family. Both mean "no answer yet":
   neither is reassuring and neither is a failure. Their wording and icons keep
   them apart. */

.adcopy-state--never-run,
.adcopy-state--unknown {
  background: #FEF3C7;
  border-color: var(--color-warning);
  color: #92400E;
}

.adcopy-state--never-run .adcopy-state__icon,
.adcopy-state--unknown .adcopy-state__icon {
  color: var(--color-warning-dark);
}


/* ============================================
   FINDINGS -- shared bits

   ***** THE TWO BLOCKS SHARE ONE ROW SHAPE. ***** Disapproved copy used to be
   a .sherpa-table with its own columns while under-served copy was a stack of
   cards, and the two halves of one page read as two different products. Both
   now render .adcopy-asset rows from the same function, so the kind label, the
   wording and the spacing are the same code rather than two rule sets that
   happen to agree.

   A disapproved row is the SAME row with less on it: no comparison sentence and
   no suggestion block, because a policy_blocked finding carries neither. The
   difference the user sees is what each finding actually has, not a second
   design.
   ============================================ */

/* Asset wording, wherever it appears. Set apart from the surrounding UI text
   so it reads as the advertiser's own copy being quoted back at them rather
   than as our prose. */
.adcopy-asset__text {
  color: var(--color-text);
  font-weight: var(--weight-medium);
  /* Long copy wraps; it never scrolls and is never truncated. A headline the
     user cannot read in full is a row they cannot act on. */
  overflow-wrap: break-word;
  word-break: break-word;
}


/* ---- the full asset text and its figures, in the detail row ---- */

.adcopy-asset__text {
  font-size: var(--text-base);
  line-height: 1.5;
}

/* ***** TWO LABELLED FIGURES, ON ONE LINE. ***** This replaced a paragraph
   that spent three clauses saying what two numbers say.

   flex-wrap so the pair drops to two lines on a narrow viewport rather than
   overflowing. The row gap is the smaller of the two so a wrapped second figure
   sits closer to the first than the block does to the copy above it. */
.adcopy-asset__figures {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-5);
  margin-top: var(--space-2);
}

/* Label above value, so the two figures scan as a pair of columns rather than
   as a run-on phrase. */
.adcopy-figure {
  display: flex;
  flex-direction: column;
}

/* Muted and small: the label names the number, it is not the number. Sentence
   case comes from the strings themselves -- no text-transform here, which would
   fight the "Long headline" casing. */
.adcopy-figure__label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* The number itself carries the weight. Tabular figures so the digits line up
   between the two columns and down a long list, which is what makes them
   scannable rather than just present. */
.adcopy-figure__value {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}


/* ---- the suggested replacement ----

   ***** SITS IN THE SAME ROW AS THE ASSET IT REPLACES. ***** Never a separate
   section and never a strip at the bottom of the page: a replacement is only
   meaningful beside the wording it replaces.

   Tinted and left-marked so it reads as a distinct object the user can act on,
   and selectable so it can be copied by hand.

   NOTHING HERE RENDERS FOR A BLOCKED ASSET. `suggestion` is always null on a
   policy_blocked finding, and the JS omits the whole element rather than
   leaving an empty slot that would imply one is coming. */

/* ***** NO FILL AND NO COLORED EDGE. ***** This was a filled --color-primary-
   light block with a 3px --color-primary left border, and it shouted louder
   than the ad copy it was suggesting -- the thing the user is actually reading.
   Both are gone. It sits on the same white surface as everything else and is
   separated by spacing alone.

   The padding went with the fill: without a background there is no box to inset
   content from, and keeping it would have left the suggestion floating oddly
   right of the asset text above it. */
.adcopy-suggestion {
  margin-top: var(--space-3);
}

/* The small uppercase label stays. Muted rather than brand blue: on the old
   tinted block the blue read as part of that object, but on the white surface a
   colored label would be the loudest thing in the row again. It now matches
   .adcopy-asset__label, the other small uppercase label in the same panel. */
.adcopy-suggestion__label {
  display: block;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  margin-bottom: var(--space-2);
}

/* .adcopy-suggestion__line is gone. The field and the buttons no longer share a
   row: the field stands on its own line, and the buttons sit below the attempts
   count. Nothing needs a flex container to hold that together. */


/* ============================================
   THE SUGGESTION ACTIONS

   Only under-served rows carry these. The blocked block has no suggestions, so
   it has no actions, and there is no rule here that would give it any.

   ***** "NOT THIS SUGGESTION" IS GONE. ***** It threw away one suggestion and
   then waited for the next scheduled run to write another, which on a monthly
   account is a month of nothing. Editing the wording and asking for another one
   are both instant and both still here, so it added nothing. Its rules were
   removed with it rather than left behind. "Stop suggesting" (ignore) went the
   same way; Dismiss is the only way to set a row aside.

   The click-to-edit field and the pending-button spinner are the SHARED
   .editable-field and .btn-spinner / .btn--pending components from style.css
   and are NOT redeclared here. Only the placement specific to this page is
   below.
   ============================================ */

/* .adcopy-actions is gone: the controls and the attempts line used to be nested
   inside it, and it carried the margin that stacked them below the field. They
   are siblings now -- the controls share the field's line, the attempts line
   sits beneath it -- so there is nothing left for a wrapper to do.

   The row itself keeps its own flex-wrap: at the narrowest widths the two
   buttons wrap against each other after the group has already wrapped beneath
   the field. */
/* Base for the three text-ish controls. Deliberately small: these sit under
   every asset in a long list, and full-size buttons on a hundred rows would
   drown the copy they are meant to serve. */
.adcopy-action {
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

/* ***** THE HELP MARKER ON THE BLOCK HEADINGS. ***** Uses the shared
   .has-popover / .popover-content component from style.css for the panel
   itself; only the circled question mark is styled here. */
/* ***** NO CURSOR OVERRIDE. THE SHARED COMPONENT'S `cursor: help` STANDS.
   ***** This rule used to set `cursor: pointer`, justified in a comment as
   "this one is focusable, so it behaves like a control". That was this page
   drifting from the pattern: the style guide defines .has-popover as an
   explainer that reveals text on hover, `cursor: help` is what style.css sets,
   and pointer promises a click that does nothing here. The marker is also no
   longer focusable (see the template), so the justification is gone too.

   Only the layout bits this page genuinely needs are left. */
.adcopy-help {
  display: inline-flex;
  border-radius: var(--radius-full);
}

.adcopy-help__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  font-size: 11px;
  font-weight: var(--weight-bold);
  line-height: 1;
}

.adcopy-help:hover .adcopy-help__mark {
  border-color: var(--color-text-muted);
  color: var(--color-text);
}

/* The remaining-attempts line. ***** SHOWN BEFORE THE CLICK, NOT AFTER. *****
   The response carries regenerations_remaining, so a user can see they have one
   left rather than discovering it by spending it. */
.adcopy-actions__allowance {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}


/* ---- per-row status, and the block-level notice ----

   Two kinds, and the difference is load-bearing:

   --note   something expected happened, such as a used-up regeneration
            allowance. ***** RUNNING OUT OF ATTEMPTS IS THE NORMAL END OF A
            BOUNDED ALLOWANCE AND MUST NOT LOOK LIKE A FAULT. *****
   --error  something actually went wrong and the user may want to retry. */

.adcopy-asset__status {
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  line-height: 1.5;
}

.adcopy-asset__status[hidden] {
  display: none;
}

.adcopy-asset__status--note {
  background: var(--color-navy-light);
  color: var(--color-text);
}

.adcopy-asset__status--error {
  background: #FEE2E2;
  color: #991B1B;
}

/* The edit refusal. ***** THE SERVER'S OWN SENTENCE GOES HERE, VERBATIM. *****
   over_limit and duplicate are the same two rules Google enforces at
   submission, where the rejection is undiagnosable; turning that into a plain
   sentence is the whole point, so it is never replaced with a generic message. */
.adcopy-edit-error {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-danger);
}

.adcopy-edit-error[hidden] {
  display: none;
}

/* ***** THE SUGGESTION FIELD IS ONE LINE AND ONLY AS WIDE AS IT NEEDS. *****
   It was a full-width, two-row textarea, which dominated the row it belongs to
   and offered room for something the field cannot hold: every asset here is one
   line of ad copy with a hard character limit.

   The shared .editable-field component is unchanged and still does the work --
   these rules only stop its read state filling the container, and size the
   single-line input inside it. Nothing page-local replaces the component. */

/* .editable-field__read is width: 100% in style.css, which is right for the
   settings page's full-width purpose box and wrong here. Shrink to content,
   with a floor so a short suggestion is still a comfortable click target and a
   ceiling so a long one cannot push the row wider. */
.adcopy-suggestion .editable-field__read {
  width: auto;
  min-width: 16rem;
  max-width: 100%;
}

.adcopy-edit-input {
  font-size: var(--text-sm);
  /* The `size` attribute sets the natural width; this only stops a long value
     overflowing the row on a narrow screen. */
  max-width: 100%;
}

/* The counter sits under its own field, left-aligned with it rather than pushed
   to the far edge of the card -- the field no longer spans that width, so a
   right-aligned counter would float away from the thing it counts. */
.adcopy-edit-hint {
  text-align: left;
  transition: color var(--transition-fast);
}

/* The editing controls line up under the field, not against the card's right
   edge, for the same reason. */
.adcopy-suggestion .editable-field__actions {
  justify-content: flex-start;
}

/* Save is disabled while the field is empty or only spaces. Same treatment as
   .adcopy-btn-apply[disabled]. */
.adcopy-suggestion .editable-field__actions .btn[disabled] {
  opacity: 0.6;
  cursor: not-allowed;
}


/* ============================================
   THE STATUS PILLS
   ============================================ */

/* ***** THE PILLS SIT RIGHT ON THE HEADING'S OWN ROW. ***** Their look is the
   shared .dt-status-filter (data-table.css); this only places the group.

   margin-left: auto is what pushes them right, and it is also what keeps them
   right aligned once they WRAP: an auto inline margin resolves against the
   wrapped line's own free space, so the group stays at the right edge of its
   new line rather than jumping left.

   A <span> in the markup rather than a <div>, because this lives inside an
   <h2>, where flow content is not valid. */
.adcopy-status-filters {
  margin-left: auto;
}


/* ============================================
   NARROW SCREENS
   ============================================ */

@media (max-width: 640px) {
  .adcopy-state {
    padding: var(--space-4);
  }
}

/* ================================================================
   APPLY MODAL -- CONTENT ONLY
   ================================================================

   The window itself is the shared apply modal (.apply-modal in style.css),
   outcome message tones included: success for a clean job, warning for a
   quota stop or a completion with refusals (neither may render green),
   danger for a failed job. What stays here is this page's own list inside
   it: the changes about to be sent (confirm) and every asset the job
   refused or Google rejected, each with the server's own explanation
   (result). */

.adcopy-apply-detail__lead {
  margin: 0 0 12px;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--color-text-muted);
}

.adcopy-apply-detail__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.adcopy-apply-detail__item {
  padding: 10px 12px;
  background: var(--color-bg);
  border-left: 3px solid var(--color-warning);
  border-radius: 0 6px 6px 0;
}

/* A change about to be sent, listed in the confirm state. Nothing has gone
   wrong, so the marker is the primary token rather than warning. */
.adcopy-apply-detail__item--change {
  border-left-color: var(--color-primary);
}

.adcopy-apply-detail__kind {
  display: block;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  margin-bottom: 3px;
}

.adcopy-apply-detail__text {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-dark);
  margin-bottom: 4px;
}

/* The server's explanation, verbatim. Never reworded here and never replaced
   with a message keyed off the outcome value. */
.adcopy-apply-detail__why {
  display: block;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--color-text);
}

/* ================================================================
   FOOTER BULK ACTION BAR
   ================================================================

   The shared bar (.bulk-action-bar* in style.css, built by
   bulk-action-bar.js), contents included. Its button, Clear Selection and
   breakpoint rules used to be copied here, with a 640px breakpoint and its
   own hover colors; they are deleted. Do not add a rule for the bar here. */

/* ================================================================
   THE ROW CONTROL LINE

   ***** ONE LINE, MATCHING THE NEGATIVE KEYWORD PAGE'S ACTIONS CELL. *****
   Every value below is copied from .negkw-btn-approve / .negkw-action-link /
   .negkw-btn-apply / .negkw-applied-label so the two pages read identically.
   Per-page duplication is this theme's convention (CLAUDE.md); the VALUES are
   deliberately the same, not merely similar.

   This replaced two competing clusters: a decision pair and a separate
   suggestion pair, four buttons on one pending row.
   ================================================================ */

.adcopy-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 12px;
}

/* The primary decision. Identical to .negkw-btn-approve. */
.adcopy-btn-approve {
  background: transparent;
  border: 1.5px solid var(--color-primary);
  color: var(--color-primary);
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 600;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--transition-fast);
}

.adcopy-btn-approve:hover {
  background: var(--color-primary-light);
}

/* Writes to the live account. Identical to .negkw-btn-apply, including the
   success ground that distinguishes it from the outline Approve. */
.adcopy-btn-apply {
  background: var(--color-success);
  border: none;
  color: var(--color-surface);
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 600;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--transition-fast);
}

.adcopy-btn-apply:hover {
  background: #128a3e;
}

/* Disabled while a job is in flight account-wide, since the contract allows
   one job per customer. Same treatment as .negkw-btn-apply[disabled]. */
.adcopy-btn-apply[disabled] {
  opacity: 0.6;
  cursor: not-allowed;
}

/* .adcopy-action-link, the secondary action beside the primary (Dismiss,
   Undo, Restore), moved to style.css so the alert bell's "Dismiss all" can
   reuse it on every /app/ page. Its values are unchanged. */

/* Terminal state label. Identical to .negkw-applied-label. */
.adcopy-applied-label {
  color: var(--color-success);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

/* ***** THE REFRESH ICON. ***** A small circular icon button at the end of
   the control row, replacing a full "Suggest another" button that competed
   with the decision for attention. */
.adcopy-btn-refresh {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-text-muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.adcopy-btn-refresh:hover {
  color: var(--color-primary);
  border-color: var(--color-primary);
}

.adcopy-btn-refresh[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

.adcopy-btn-refresh[aria-disabled="true"]:hover {
  color: var(--color-text-muted);
  border-color: var(--color-border);
}

/* ***** IT SPINS WHILE A NEW SUGGESTION LOADS. ***** setPending() adds
   .btn--pending; the icon itself is the spinner, so there is no second
   element to keep in step with the request. */
.adcopy-btn-refresh.btn--pending {
  cursor: progress;
  color: var(--color-primary);
  border-color: var(--color-primary);
}

.adcopy-btn-refresh.btn--pending .adcopy-btn-refresh__icon {
  animation: adcopy-refresh-spin 0.7s linear infinite;
}

@keyframes adcopy-refresh-spin {
  to { transform: rotate(360deg); }
}

/* ================================================================
   THE SUGGESTION FIELD AND ITS REFRESH CONTROL

   ***** THE REFRESH SITS WITH THE FIELD IT ACTS ON. ***** It was in the
   control row beside Approve and Dismiss, which put "give me a different
   suggestion" among the decisions ABOUT a suggestion.
   ================================================================ */

.adcopy-suggestion__field {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* ***** THE FIELD SHRINK-WRAPS ITS CONTENT. ***** It carried
   `flex: 1 1 auto`, which stretched it across the whole column and pushed the
   refresh icon to the far right edge of the row, metres from the text it acts
   on. `flex: 0 1 auto` lets it take only the width it needs (and still shrink
   when the column is narrow), so the icon sits immediately beside it with
   just the 8px gap. */
.adcopy-suggestion__field .editable-field {
  flex: 0 1 auto;
  min-width: 0;
}

/* The row does not stretch its children across the free space either;
   anything left over sits to the right of the icon. */
.adcopy-suggestion__field {
  justify-content: flex-start;
}

.adcopy-suggestion__field .adcopy-btn-refresh {
  flex: 0 0 auto;
}

/* ================================================================
   THE DETAIL ROW'S TWO COLUMNS

     [ replacement              |  original              ]
       SUGGESTED REPLACEMENT        CURRENT COPY
       editable text (refresh)      the copy
       N attempts left              ad / headline impr.

   ***** THE REPLACEMENT IS THE LEFT COLUMN. ***** Swapped in source order in
   assetRowHTML(), not with CSS `order`, so the DOM, reading and tab order all
   match what is on screen.

   ***** THE DECISION CONTROLS SIT BELOW THE GRID, ON ITS LEFT EDGE. ***** All
   three act on the suggested replacement, which is now the left column, so
   they read as belonging to it while still giving the row one left edge.

   ***** THE GRID FOLLOWS CONTENT, NOT BLOCK. ***** It used to be omitted for
   every policy_blocked finding, on the assumption that a disapproved row never
   has a replacement. That assumption no longer holds: a disapproved finding
   CAN carry replacement text, and when it does it gets the two-column layout
   like any other row. Every row that reaches this grid has a replacement,
   because a finding without one is not rendered at all -- see
   hasReplacement() in the page JS, which each table takes as its `include`.
   ================================================================ */

.adcopy-asset__cols {
  display: grid;
  /* Roughly equal, and minmax(0,1fr) rather than 1fr so long unbroken copy
     cannot force a column wider than its share and push the row sideways. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-6);
  /* ***** BOTH COLUMNS START AT THE SAME VERTICAL POSITION. ***** So the two
     labels line up across the row. */
  align-items: start;
}

.adcopy-asset__col {
  min-width: 0;
}

/* The first thing in each column carries no top margin, so the two labels
   share a baseline. .adcopy-suggestion sets margin-top for its old
   single-column position; inside the grid it starts flush with the kind
   label opposite it. */
.adcopy-asset__col > :first-child {
  margin-top: 0;
}

/* The original column's own spacing, unchanged in kind -- only its container
   is new. */
.adcopy-asset__col--original .adcopy-asset__text {
  margin-top: var(--space-2);
}

/* ***** THE CONTROLS SIT BELOW THE GRID, NOT INSIDE A COLUMN. ***** They
   align to the row body's left edge, which is the same edge the original copy
   starts on, giving the row a single left edge. This rule replaced a
   `.adcopy-asset__col--replacement .adcopy-actions` selector from when they
   lived in the right column. */
.adcopy-asset__cols + .adcopy-actions {
  margin-top: var(--space-4);
}

/* ***** BELOW THE BREAKPOINT THE COLUMNS STACK, REPLACEMENT FIRST. ***** That
   matches the side-by-side order above: the replacement leads, the copy it
   replaces follows. assetRowHTML() emits the replacement first, so a
   single-column grid needs no `order` rule -- the stack follows source order,
   which is also the reading and tab order.

   768px matches the breakpoint the rest of this page and the style guide's
   own two-column helper use. */
@media (max-width: 768px) {
  .adcopy-asset__cols {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }

  /* ***** THE RULE FOLLOWS THE SECOND COLUMN, WHICH IS NOW THE ORIGINAL.
     ***** Stacked, whichever group sits underneath needs the separation the
     column gap gave it side by side. This was on --replacement when the
     original led; leaving it there would have drawn the divider above the
     first group instead of between the two. */
  .adcopy-asset__col--original {
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border);
  }
}


/* ================================================================
   WHY GOOGLE REJECTED IT

   Sits in the ORIGINAL column, under the copy it is about, because it is a
   statement about what is live now rather than about the fix. Shown on every
   disapproved row, including one carrying a rewrite.
   ================================================================ */

.adcopy-policy-reason {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* Same small uppercase treatment as .adcopy-asset__label and
   .adcopy-suggestion__label, so this reads as another field label on the row
   rather than a new kind of object. */
.adcopy-policy-reason__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
}

/* ***** THE REASON ITSELF IS THE LOUDER HALF. ***** It is the one fact on a
   disapproved row the user cannot get anywhere else on this page. Full text
   color against the muted label, and never wrapped in a badge: the policy
   topics are phrases ("third-party consumer technical support"), not the
   one-word statuses .badge is shaped for. */
.adcopy-policy-reason__value {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}


/* ============================================
   THE TABLE'S CELLS AND CARD CONTENT
   ============================================
   The table itself is the shared data table (data-table.css): the results
   card, the table-or-cards breakpoint, the headings, sorting, checkboxes,
   numeric and unknown cells, the actions cell, the detail row and toggle, the
   card chrome and the pager. What is left here is this page's own content
   inside them. */

/* The gap before the next block. The shared card carries no margin of its
   own, because a page decides what follows it. */
.adcopy-block .dt-results-card {
  margin-bottom: var(--space-6);
}

.adcopy-td-kind {
  white-space: nowrap;
  font-weight: var(--weight-semibold);
  color: var(--color-dark);
}

/* ***** THE COPY CELL IS ONE LINE, TRUNCATED WITH AN ELLIPSIS. ***** The full
   wording lives in the detail row. Truncation is CSS rather than a substring,
   so the whole string stays in the DOM and find-in-page still matches it. The
   column's width comes from the budget at the end of this file. */
.adcopy-copy-text {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: var(--weight-semibold);
  color: var(--color-dark);
}

.adcopy-td-campaign,
.adcopy-td-group {
  color: var(--color-text-muted);
}

/* ***** THE AD GROUP COLUMN. ***** A real name wraps to two lines rather than
   being clipped: this column is the point of the table, so its value is never
   the thing that gets sacrificed for width. */
.adcopy-group-name {
  display: block;
  overflow-wrap: break-word;
  word-break: break-word;
  color: var(--color-text);
  font-weight: var(--weight-medium);
}

/* ***** A BARE ID IS MARKED AS ONE. ***** Rendered only where no name exists
   at all. Monospaced and muted so it cannot be mistaken for a name the
   advertiser chose. */
.adcopy-group-id {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.adcopy-td-state {
  white-space: nowrap;
}

/* The "Current copy" label above the full asset text in the detail row. */
.adcopy-asset__label {
  display: block;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}

/* ***** THE WINDOW THE FIGURES COVER. ***** Small italic text under the
   numbers. In the table this renders once per open detail row; in the card
   layout it renders under every card's numbers, since there is no column
   heading there to carry it. */
.adcopy-window-note {
  margin: var(--space-1) 0 0;
  font-size: var(--text-xs);
  font-style: italic;
  color: var(--color-text-muted);
}

/* ---- The stacked card's own content (below 768px) ----
   The card itself -- surface, border, radius, head row, stacked actions and
   the detail panel -- is .dt-card. */

.adcopy-card__kind {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
}

.adcopy-card__text {
  margin: 0 0 var(--space-2);
  font-weight: var(--weight-semibold);
  color: var(--color-dark);
  overflow-wrap: break-word;
}

.adcopy-card__where {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.adcopy-card__campaign {
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

.adcopy-card__sep {
  color: var(--color-border);
}

.adcopy-card__metrics {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

.adcopy-card__metric {
  display: flex;
  flex-direction: column;
}

.adcopy-card__metric-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.adcopy-card__metric-value {
  font-size: 14px;
  font-weight: var(--weight-bold);
  color: var(--color-dark);
  font-variant-numeric: tabular-nums;
}


/* ============================================
   COLUMN WIDTH REBALANCE
   ============================================
   Three corrections made after seeing the table at real widths with real
   names. All three are about the same thing: the columns were sharing width
   badly, so two of them wrapped while a third had room to spare.

   ***** THE ACTIONS COLUMN WAS TOO NARROW. ***** Approve and Dismiss wrapped
   onto two lines, which made EVERY row in the table taller than it needed to
   be -- a table's row height is set by its tallest cell, so one wrapping cell
   costs height on every row beside it. `white-space: nowrap` was already on
   the cell, but with no width reserved the column still collapsed and the
   flex row inside it wrapped instead. The min-width is what actually reserves
   the space; nowrap alone could not.

   ***** THE CAMPAIGN COLUMN WRAPPED WHILE AD GROUP HAD ROOM TO SPARE. *****
   Both were capped at 200px, but campaign names in a real account
   ("Brand - Search - Exact") run longer than ad group names, so campaign wrapped
   to two lines while ad group sat half empty. Campaign is widened and allowed
   to ellipsis on one line; ad group keeps enough width to stay plainly
   readable, because it is the reason this page became a table at all and must
   never be the column that gets sacrificed. */

/* ***** WRAPS, LIKE THE NEGATIVE KEYWORDS PAGE. ***** No ellipsis and no
   `direction: rtl`. The front-truncation this briefly carried put a leading
   ellipsis on almost every row and made the column hard to scan; the reference
   page simply lets a long campaign name take a second line inside the cell. */
.adcopy-campaign-text {
  display: block;
  overflow-wrap: break-word;
}




/* ============================================
   THE COLUMN WIDTH BUDGET -- THE ONE PLACE COLUMN WIDTHS LIVE
   ============================================
   Replaces two competing sets of width rules, one left by the table conversion
   and one by the filter pass, which disagreed with each other and produced
   three visible defects at 1440px on real data:

     1. a thin vertical line running down through every row, a little in from
        the right edge, lining up with no column;
     2. the filter bar visibly narrower than the table below it, their right
        edges not matching;
     3. campaign names truncated ("Houston Spanish" showing as "Houston S...").

   ***** ONE CAUSE BEHIND THE FIRST TWO. ***** The table's own intrinsic
   minimum was 1228px inside a 1150px wrapper, so it OVERFLOWED its card by
   78px. The "vertical line" was the results card's own 1px right border, drawn
   at the card's edge while the table ran on past it -- so it cut through the
   Actions cell mid-column and matched no column edge, exactly as reported. The
   "narrower filter bar" was the same 78px: the bar correctly spans the card,
   and it was the TABLE that was too wide. Fixing the overflow fixes both; they
   were never two problems.

   ***** WHY `table-layout: fixed`, AND WHY NOTHING ELSE WORKED. ***** Measured
   in the browser: with EVERY author width declaration removed, the columns
   still demanded 1504px against a 1150px wrapper -- Copy alone wanted 485px and
   Campaign 314px, because `.sherpa-table th` sets `white-space: nowrap` and
   `table-layout: auto` sizes a column to its widest unbreakable content. No
   combination of max-width could bring that under the wrapper, because
   max-width does not shrink a column below its content's intrinsic minimum. It
   is `table-layout: fixed` that ends the negotiation: column widths come from
   the first row's declarations and the table honors `width: 100%`.

   /app/ pages set `.sherpa-table-wrap { overflow-x: visible }` (style.css, so
   the sticky header has the viewport to pin to), so there is no scrollbar to
   absorb an overflow. A table that does not fit spills over the page instead.

   ***** THE BUDGET, MEASURED AGAINST REAL ACCOUNT DATA. ***** The percentages
   below were derived from the actual naming pattern on this account -- several
   hyphen-joined segments for campaigns ("Houston - Performance Max - Retail -
   All Products", 287px of text) and short ad group names ("Store", "Near Me",
   "Hours", widest 122px including a long outlier). The previous pass sized this
   column against sample data that had it backwards: a 14-character campaign and
   LONGER ad groups. That is why it measured clean and then truncated on the
   live page.

   Widths are percentages, not pixels, so the same budget holds at every width
   down to the 768px card breakout instead of needing a second set of rules. */

.adcopy-table {
  table-layout: fixed;
  width: 100%;
}

/* The six columns whose width is set by fixed content -- a checkbox, a type
   word, two numbers, a badge and two buttons. None of these grows with the
   data, so each takes only what it needs and the rest goes to the three text
   columns below. */
/* ***** THE SHARES ARE SET BY PERCENTAGE ALONE, AND min-width DOES NOT WORK
   HERE. ***** Under `table-layout: fixed` a column takes its declared `width`
   and `min-width` on the cell is ignored outright -- measured: an 8% column with
   `min-width: 96px` still rendered 78px at 1024 and clipped "Description" to
   "Descriptior". Raising the percentage is the only lever, so every column below
   is tuned as a share.

   Type and State are sized so their longest fixed string still fits at 1024,
   which is the narrowest width this table is used at (below 768px the card
   layout replaces it): "Description" measures 95px and the "Pending" badge 97px,
   so both need ~10%. Caught by LOOKING at a 1024px render, not by reading. */
/* ***** KEYED ON EACH COLUMN'S CLASS, NOT ITS POSITION. ***** These were
   :nth-child() selectors, which silently re-point at a different column the
   moment one is added or reordered. The classes are the thClass each column
   declares in tableColumns() in the page JS. */
.adcopy-table .dt-th-check          { width: 3.5%; }
.adcopy-table .adcopy-th-type       { width: 10%; }
.adcopy-table .adcopy-th-ad-impr    { width: 8.5%; }
.adcopy-table .adcopy-th-asset-impr { width: 10%; }
.adcopy-table .adcopy-th-state      { width: 8.5%; }

/* ***** ACTIONS: NARROWER NOW THAT THE CONTROLS STACK. ***** It held 14.5%
   (~167px) to keep Approve and Dismiss side by side on one line. They are
   stacked now, so the column only has to fit the WIDER of the two -- Approve at
   81px -- rather than both plus a gap. 12% is ~138px at 1150, and the 56px that
   frees went to Campaign, which was wrapping to two lines and now does so at
   every width rather than reaching three at 1024. Measured, not guessed. */
.adcopy-table .adcopy-th-actions   { width: 12%; }

/* ***** CAMPAIGN GETS THE LARGEST SHARE, WHICH IS THE POINT OF THIS FIX. *****
   ***** IT WRAPS TO TWO LINES, WHICH IS WHAT THE REFERENCE PAGE DOES. *****
   20% is ~230px at 1150, after taking the width that stacking the actions
   controls freed. Campaign names here are the long ones, so this column is sized
   to take a SECOND line rather than to fit on one: every name on this account
   now wraps to exactly two lines at 1440, 1280 AND 1024, where it previously
   reached three. Ad group beside it stays on one line, which is the balance the
   conversion had backwards.

   One line is NOT the target and is not affordable: the longest name here
   measures 315px including padding, which is 27.4% of the table -- more than
   Copy and Ad group hold together. Two lines is the reference page's own
   behavior. */
.adcopy-table .adcopy-th-campaign  { width: 20%; }

/* ***** AD GROUP FITS ON ONE LINE, WHICH IS THE POINT OF THE REBALANCE. *****
   12% is ~138px at 1150. Its real names here are "Store", "Near Me",
   "Locations", "Directions" -- the widest measures 60px -- so one line holds
   them comfortably at every tested width. It was at 10.5% and wrapping to three
   lines on many rows while campaign, the genuinely long column, did not wrap at
   all. It is the reason this page became a table, so it is never the column
   that gets squeezed. Verified on one line at 1440/1280/1024. */
.adcopy-table .adcopy-th-group     { width: 12%; }

/* ***** COPY IS DECLARED, NOT LEFTOVER. ***** It is deliberately last in
   priority -- its text is truncated to one line by design at any width and the
   full wording is one click away in the detail row, so it is the only column
   whose value does not depend on being fully visible. Declaring it explicitly
   keeps the whole budget visible in one place rather than leaving it to absorb
   whatever the others happen not to claim, which is how the two earlier width
   systems drifted apart.

   ***** 18%, AFTER THE TRADE AGAINST CAMPAIGN (2026-09-23). ***** Copy was
   6%, which rendered about 69px and showed SEVEN characters -- "Fallbac" -- a
   word fragment nobody can recognize a headline from. The copy is the thing the
   user is deciding on, so it now gets the width and Campaign truncates instead.

   Measured at 1440 against this account's real data: 18% shows ~207px, and
   EIGHT of the nine distinct headlines fit in full ("Same Day Delivery
   Available", "Certified Technicians Near You"). At 16% only one did; at 20%
   and above Campaign starts losing whole segments off its tail. Swept
   16/18/20/22% across 1440/1280/1024 with Ad group, Type and State checked for
   clipping at every step.

   The nine shares still total less than 100% on purpose: under
   `table-layout: fixed` the browser distributes the remainder proportionally,
   and that distribution is what keeps Type and State above their content width
   at 1024. Re-run the sweep before changing either share. */
.adcopy-table .adcopy-th-copy      { width: 16.5%; }

/* ***** EVERY CELL MUST BE ABLE TO SHRINK. ***** Under `table-layout: fixed` a
   column takes its declared width, but content that cannot break still paints
   outside the cell. These three keep the text inside its box: the two named
   spans ellipsis on one line, and the ad group name wraps rather than being
   clipped, because a truncated ad group would defeat the column's purpose. */
.adcopy-td-copy,
.adcopy-td-campaign,
.adcopy-td-group,
.adcopy-td-kind,
.adcopy-td-state {
  overflow: hidden;
}

/* The headings wrap, with their values aligned beneath them: that is the
   shared table's rule (.dt-table th), not this page's. */

/* ***** THE CARD MUST NOT CLIP, AND MUST NOT SCROLL. ***** Its 1px right border
   is what drew the stray vertical line while the table overflowed. With the
   table now fitting, the border sits where it should -- at the edge of both the
   filter bar and the table, which is what makes their edges line up. No
   overflow rule is added here on purpose: `overflow: hidden` would make this
   element the sticky positioning container and silently break the pinned header
   (see the note on .dt-results-card in data-table.css). */

