/*
 * Paytrack product site (kirbyservice.net): Campaign manager's component
 * (round 3, plan.md). One floating panel of the app turns its pages as the
 * story moves; Ai, the business's own AI ("Local Ai" on his belly), stands
 * on its right with his line above him. Markup in obj/part_campaigns.php,
 * the typing, the thread and the beats in js/parts/campaigns.js, the runner
 * in js/parts/part.js. Keep this file ASCII.
 *
 * Five steps, one page each: 0 areas, 1 slots, 2 send, 3 chat, 4 book.
 *
 * The whole scene is sized in em: .pt-part-stage takes the unit from the
 * column's width (container units) and the screen's height, so every part
 * keeps its place at every size and no text drops below 10px (the smallest
 * text is .8em of at least 12.5px). Three compositions: wide (desktop and
 * tablet, 35 x 19.4 em: the panel, Ai and his line on its right), short
 * phones (25.6 x 11 em at 12.5px: the panel alone, its pages tightened;
 * with the layer's copy, 360x640 and 375x667 leave about 170 px for the
 * whole component) and tall phones (25.6 x 20.8 em: Ai and his line on
 * top, the panel under them). Motion is transform and opacity only; the
 * steps are the pt-sN classes and data-step (part.js), the beats inside a
 * step are transition delays, keyframes and the cb-* classes
 * (campaigns.js). Elements marked .cm-mv take the quick and snap modes at
 * the end of this file; every one-shot effect also says :not(.cm-quick) or
 * hangs on a beat class.
 *
 * Colours say what a thing is: sand a place (postcodes), dusk the AI (his
 * texts, the times he can offer, the panel's band), sage done (a tick, an
 * update, an approval).
 *
 * Spacing (Dimitar, 02/10/2026, on Get paid: "things overlap"): the objects
 * never touch, with at least 8 px of air on desktop and 6 on phones, the
 * bobs, the hop and Ai's lean towards the pointer included; every text fits
 * its box inside its padding. Checked with tools/browser/paytrack_overlap_check.js.
 */

/* ---------- the stage: the unit, the depth ------------------------------- */
.pt-part-campaigns { container-type: inline-size; }
.pt-part-campaigns .pt-part-stage { position: relative; font-size: 14px;
  font-size: max(12.5px, min(16px, calc(100cqw / 35), 2vh));
  perspective: 1100px; perspective-origin: 42% 42%; }
.pt-part-campaigns .pt-part-tilt { transform-style: preserve-3d; transition: transform .6s cubic-bezier(.2, .8, .3, 1); }
.pt-part-campaigns .cm-set { position: relative; width: 35em; height: 19.4em; margin: 0 auto; transform-style: preserve-3d; line-height: 1.3; }
.pt-part-campaigns .cm-set i { display: block; font-style: normal; }

/* The light in the room: a dusk pool under the panel, a cool one under Ai.
   No surface edges anywhere (the layers are invisible). Pseudo-elements, so
   no check counts them as objects of the scene. The panel's own shadow on
   the floor shrinks as it rises (its bob's clock). */
.pt-part-campaigns .cm-set::before { content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(38% 36% at 35% 84%, rgba(180, 167, 207, .10), rgba(180, 167, 207, 0) 72%),
    radial-gradient(17% 20% at 85% 92%, rgba(147, 183, 198, .10), rgba(147, 183, 198, 0) 72%);
  transform: translateZ(-3em); }
.pt-part-campaigns .cm-set::after { content: ""; position: absolute; left: 1.6em; width: 21.2em; top: 17.9em; height: 1.7em; pointer-events: none;
  background: radial-gradient(closest-side, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0));
  transform: translateZ(-.6em); animation: cm-shadow 6s ease-in-out infinite; }
@keyframes cm-shadow { 0%, 100% { opacity: 1; transform: translateZ(-.6em) scale(1); } 50% { opacity: .72; transform: translateZ(-.6em) scale(.9); } }

/* ---------- Ai, the business's own AI, and his line ------------------------ */
/* He stands right of the panel, a little in front of it. His own bob is 6 px
   here (not site.css's 12), so his line above keeps its air. */
.pt-part-campaigns .cm-ai { position: absolute; right: 1.6em; bottom: .45em; width: 6.8em; transform: translateZ(1.4em); pointer-events: none; }
.pt-part-campaigns .cm-ai .pt-char { width: 100%; }
.pt-part-campaigns .cm-ai .pt-char-ai { animation-name: cm-hover; }
@keyframes cm-hover { 0%, 100% { translate: 0 0; } 50% { translate: 0 -6px; } }
.pt-part-campaigns .cm-ai-in { transform-origin: 50% 100%; }
/* A small hop when the booking is approved (campaigns.js; site.css also swells him with .is-hop). */
.pt-part-campaigns.cb-hop:not(.cm-quick) .cm-ai-in { animation: cm-hop .62s cubic-bezier(.3, .7, .4, 1); }
@keyframes cm-hop { 0%, 62%, 100% { transform: translateY(0); } 35% { transform: translateY(-.75em); } 78% { transform: translateY(-.18em); } }
/* While he thinks and writes (is-talking, campaigns.js) his antennae glow
   fast (site.css) and he points at the panel: it is on his left here, so the
   left hand, and the right one rests instead of waving. */
.pt-part-campaigns .cm-ai .pt-arm-left { transition: rotate .4s var(--pt-spring); }
.pt-part-campaigns .cm-ai .pt-char-ai.is-talking .pt-arm-left { rotate: 64deg; }
.pt-part-campaigns .cm-ai .pt-char-ai.is-talking .pt-arm-right { animation: none; rotate: 0deg; }

/* His line, above him, its tail down to his head (centred over him: his
   middle is 5 em in from the set's right edge, the bubble's right edge .4 em). */
.pt-part-campaigns .cm-say { position: absolute; right: .4em; top: 2.2em; width: 9em; transform: translateZ(1em); }
.pt-part-campaigns .cm-say .pt-bubble { max-width: none; margin: 0; padding: .72em .95em; font-size: .86em; line-height: 1.4;
  border-radius: 1.3em; text-wrap: balance; transform-origin: 50% 100%; }
.pt-part-campaigns .cm-say .pt-bubble::before { left: auto; right: calc(5.35em - 6px); bottom: -6px; clip-path: polygon(100% 0, 100% 100%, 0 100%); }
html:not(.js) .pt-part-campaigns .pt-bubble { opacity: 1; transform: none; }

/* ---------- the panel: floats, turned towards Ai ----------------------------- */
.pt-part-campaigns .cm-float { position: absolute; left: .25em; top: .5em; width: 24.4em; height: 18.2em; transform-style: preserve-3d;
  animation: cm-bob 6s ease-in-out infinite; }
@keyframes cm-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-.35em); } }
/* The card: raised, a thin dusk band along the top (the layer's tone; a
   background, so it follows the rounded corners), a soft static shadow.
   Flat inside: its pages turn in their own perspective, drawn on the card. */
.pt-part-campaigns .cm-panel { position: absolute; inset: 0; border-radius: .95em; color: var(--pt-bone);
  background:
    linear-gradient(var(--pt-dusk), var(--pt-dusk)) 0 0 / 100% .3em no-repeat,
    linear-gradient(180deg, rgba(236, 230, 218, .05), rgba(236, 230, 218, 0) 45%),
    var(--pt-raised);
  box-shadow: 0 1.8em 2.6em -1.2em rgba(0, 0, 0, .75), 0 .2em .5em rgba(0, 0, 0, .25), inset 0 0 0 1px rgba(236, 230, 218, .07);
  transform: rotateY(8deg) rotateX(3deg); }
/* At every page turn it swings a little (two names in turn, so the swing
   starts again each time; from and to are its own pose). */
.pt-part-campaigns[data-step="0"]:not(.cm-quick) .cm-panel,
.pt-part-campaigns[data-step="2"]:not(.cm-quick) .cm-panel,
.pt-part-campaigns[data-step="4"]:not(.cm-quick) .cm-panel { animation: cm-swing-a 1s ease-in-out; }
.pt-part-campaigns[data-step="1"]:not(.cm-quick) .cm-panel,
.pt-part-campaigns[data-step="3"]:not(.cm-quick) .cm-panel { animation: cm-swing-b 1s ease-in-out; }
@keyframes cm-swing-a { 40% { transform: rotateY(13deg) rotateX(4.5deg) translateZ(-.3em); } }
@keyframes cm-swing-b { 40% { transform: rotateY(13deg) rotateX(4.5deg) translateZ(-.3em); } }

/* The pages, stacked in one cell: the one in play is up, the ones before it
   turned away above, the ones after it waiting below. */
.pt-part-campaigns .cm-pages { position: absolute; inset: 1.05em 1em .9em; display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  perspective: 40em; transition: opacity .35s ease; }
.pt-part-campaigns .cm-page { grid-area: 1 / 1; display: flex; flex-direction: column; min-width: 0; min-height: 0;
  opacity: 0; transform: translate3d(0, .9em, 0) rotateX(-20deg); transition: opacity .3s ease, transform .6s var(--pt-spring); }
.pt-part-campaigns[data-step="-1"] .cm-pg-areas,
.pt-part-campaigns[data-step="0"] .cm-pg-areas,
.pt-part-campaigns[data-step="1"] .cm-pg-slots,
.pt-part-campaigns[data-step="2"] .cm-pg-send,
.pt-part-campaigns[data-step="3"] .cm-pg-chat,
.pt-part-campaigns[data-step="4"] .cm-pg-book { opacity: 1; transform: none; }
.pt-part-campaigns.pt-s1 .cm-pg-areas,
.pt-part-campaigns.pt-s2 .cm-pg-slots,
.pt-part-campaigns.pt-s3 .cm-pg-send,
.pt-part-campaigns.pt-s4 .cm-pg-chat { transform: translate3d(0, -.7em, 0) rotateX(20deg); }
/* The new page comes up just after the old one has gone. */
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-pg-slots,
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-pg-send,
.pt-part-campaigns:not(.cm-quick)[data-step="3"] .cm-pg-chat,
.pt-part-campaigns:not(.cm-quick)[data-step="4"] .cm-pg-book { transition-delay: .2s; }

/* Every page: a header line (its title, a note or a chip on the right), then its body. */
.pt-part-campaigns .cm-hd { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: .6em;
  height: 2.1em; padding-bottom: .5em; margin-bottom: .3em; box-shadow: 0 1px 0 rgba(236, 230, 218, .08); }
.pt-part-campaigns .cm-hd-t { display: flex; min-width: 0; }
.pt-part-campaigns .cm-hd-t b { font: 500 .95em/1.2 var(--pt-head); letter-spacing: -.01em; color: var(--pt-bone); white-space: nowrap; }
.pt-part-campaigns .cm-hd-note { font: 500 .8em/1.2 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; }
.pt-part-campaigns .cm-bd { position: relative; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; justify-content: center; }
.pt-part-campaigns .cm-lbl { display: block; margin: 0 0 .35em .1em; font: 600 .8em/1.2 var(--pt-text); letter-spacing: .02em; color: var(--pt-lichen); }

/* ---------- the parts every page uses ---------------------------------------- */
/* Chips: a pill, its words in their own span (above the shine). */
.pt-part-campaigns .cm-chip { position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: .42em; padding: .34em .78em; border-radius: 999px;
  font: 600 .8em/1.2 var(--pt-text); white-space: nowrap; }
.pt-part-campaigns .cm-chip > span { position: relative; }
.pt-part-campaigns .cm-chip b { font-weight: 700; }
.pt-part-campaigns .cm-sand { background: rgba(217, 197, 154, .16); color: var(--pt-sand); }
.pt-part-campaigns .cm-sage { background: rgba(157, 184, 159, .17); color: var(--pt-sage); }
.pt-part-campaigns .cm-dusk { background: rgba(180, 167, 207, .17); color: var(--pt-dusk); }
.pt-part-campaigns .cm-mute { background: rgba(167, 175, 164, .13); color: var(--pt-lichen); }
/* Pops: a ring that spreads and fades, a shine clipped to the chip (the
   chips of llm.php). */
.pt-part-campaigns .cm-pop::before { content: ""; position: absolute; inset: -.24em; border-radius: inherit; border: .12em solid currentColor;
  opacity: 0; pointer-events: none; }
.pt-part-campaigns .cm-shine { position: absolute; inset: 0; border-radius: inherit; overflow: hidden; pointer-events: none; }
.pt-part-campaigns .cm-shine::after { content: ""; position: absolute; top: -20%; bottom: -20%; left: 0; width: 45%;
  background: linear-gradient(105deg, rgba(236, 230, 218, 0), rgba(236, 230, 218, .42) 50%, rgba(236, 230, 218, 0));
  transform: translateX(-120%); }
@keyframes cm-ring { 0% { opacity: .85; transform: scale(.94); } 100% { opacity: 0; transform: scale(1.18, 1.6); } }
@keyframes cm-shine { 0% { transform: translateX(-120%); } 100% { transform: translateX(300%); } }
@keyframes cm-press { 0%, 100% { transform: none; } 35% { transform: scale(.92); } }
@keyframes cm-tap { 0% { opacity: .9; transform: scale(.3); } 100% { opacity: 0; transform: scale(1.6); } }
/* The sage tick, a led and a clock, drawn in CSS (sized in their chip's em). */
.pt-part-campaigns .cm-tick { position: relative; flex: 0 0 auto; width: 1.1em; height: 1.1em; border-radius: 50%; background: var(--pt-sage); }
.pt-part-campaigns .cm-tick::after { content: ""; position: absolute; left: .39em; top: .2em; width: .28em; height: .52em;
  border: solid var(--pt-night); border-width: 0 .13em .13em 0; transform: rotate(45deg); }
.pt-part-campaigns .cm-led { position: relative; flex: 0 0 auto; width: .55em; height: .55em; border-radius: 50%; background: currentColor; }
.pt-part-campaigns .cm-clock { position: relative; flex: 0 0 auto; width: 1.05em; height: 1.05em; border-radius: 50%; box-shadow: inset 0 0 0 .12em currentColor; }
.pt-part-campaigns .cm-clock::before,
.pt-part-campaigns .cm-clock::after { content: ""; position: absolute; left: .465em; border-radius: .06em; background: currentColor; }
.pt-part-campaigns .cm-clock::before { top: .2em; width: .12em; height: .35em; }
.pt-part-campaigns .cm-clock::after { top: .465em; width: .3em; height: .12em; }
/* Buttons: dusk for the action, a quiet outline for the others; a finger's
   tap is a ring that spreads from the middle and fades. */
.pt-part-campaigns .cm-btn { position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; padding: .5em 1.05em; border-radius: .7em;
  font: 600 .84em/1.2 var(--pt-text); white-space: nowrap; }
.pt-part-campaigns .cm-btn > span { position: relative; }
.pt-part-campaigns .cm-btn-ok { background: var(--pt-dusk); color: var(--pt-night); }
.pt-part-campaigns .cm-btn-ghost { background: rgba(236, 230, 218, .07); color: var(--pt-bone); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .1); }
.pt-part-campaigns .cm-tap { position: absolute; left: 50%; top: 50%; width: 2.6em; height: 2.6em; margin: -1.3em 0 0 -1.3em; border-radius: 50%;
  border: .14em solid var(--pt-bone); background: rgba(236, 230, 218, .18); opacity: 0; pointer-events: none; }
/* A typing caret (the text being typed only). */
.pt-part-campaigns .cm-typed.is-typing::after,
.pt-part-campaigns .cm-ty.is-typing::after { content: ""; display: inline-block; width: 2px; height: 1.05em; margin-left: 1px; vertical-align: -.17em;
  background: var(--pt-dusk); animation: cm-caret .9s linear infinite; }
@keyframes cm-caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* ---------- step 0, page areas: the message, then the areas ----------------- */
/* The message is typed (campaigns.js) over the finished one, which keeps the
   field's size and shows once the typing is done (cb-draft hides it); then
   %name% lights up, and the two areas pop in one by one with their counts. */
.pt-part-campaigns .cm-field { position: relative; border-radius: .7em; background: rgba(236, 230, 218, .05); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .07); }
.pt-part-campaigns .cm-text,
.pt-part-campaigns .cm-typed { display: block; padding: .62em .9em .68em; font: 500 .84em/1.38 var(--pt-text); color: var(--pt-bone); }
.pt-part-campaigns .cm-typed { position: absolute; inset: 0; }
.pt-part-campaigns.cb-draft .cm-text { visibility: hidden; }
/* %name%: a dusk token, its pill drawn behind the word without widening it,
   so the typed copy and the finished one break their lines alike. */
.pt-part-campaigns .cm-tok { position: relative; font-weight: inherit; color: var(--pt-dusk); }
.pt-part-campaigns .cm-tok::before { content: ""; position: absolute; left: -.2em; right: -.2em; top: -.06em; bottom: -.06em; border-radius: .4em;
  background: rgba(180, 167, 207, .2); box-shadow: inset 0 0 0 1px rgba(180, 167, 207, .38); }
.pt-part-campaigns .cm-tok .cm-shine { left: -.2em; right: -.2em; top: -.06em; bottom: -.06em; border-radius: .4em; }
.pt-part-campaigns .cm-tok > span { position: relative; }
.pt-part-campaigns .cm-tok::after { content: ""; position: absolute; left: -.38em; right: -.38em; top: -.26em; bottom: -.26em; border-radius: .6em;
  border: .12em solid var(--pt-dusk); opacity: 0; pointer-events: none; }
.pt-part-campaigns.cb-tok:not(.cm-quick) .cm-tok::after { animation: cm-ring .7s ease-out .05s forwards; }
.pt-part-campaigns.cb-tok:not(.cm-quick) .cm-tok .cm-shine::after { animation: cm-shine .8s ease-in-out .15s; }
.pt-part-campaigns .cm-lbl-areas { margin-top: .85em; }
/* An area: its postcode chip, and what is there: people, and how many can
   be texted now (the rest were texted in the last 60 days, opted out, or
   are booked already). */
/* A grid, so the two chips are as wide as the wider one and the counts line up. */
.pt-part-campaigns .cm-alist { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; column-gap: .85em; row-gap: .55em; }
.pt-part-campaigns .cm-area { display: contents; }
.pt-part-campaigns .cm-area .cm-chip { justify-self: stretch; padding-top: .42em; padding-bottom: .42em;
  opacity: 0; transform: scale(.55); transition: opacity .25s ease, transform .55s var(--pt-spring); }
.pt-part-campaigns .cm-cnt { display: flex; flex-direction: column; gap: .3em; min-width: 0;
  opacity: 0; transform: translateX(-.6em); transition: opacity .3s ease .12s, transform .5s var(--pt-spring) .12s; }
.pt-part-campaigns .cm-cnt-a { font: 500 .8em/1.2 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; }
.pt-part-campaigns .cm-cnt-b { font: 600 .84em/1.2 var(--pt-text); color: var(--pt-bone); white-space: nowrap; }
.pt-part-campaigns .cm-cnt b { display: inline-block; min-width: 1.75em; text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.pt-part-campaigns .cm-cnt-b b { color: var(--pt-sage); }
.pt-part-campaigns.cb-a1 .cm-area1 .cm-chip,
.pt-part-campaigns.cb-a1 .cm-area1 .cm-cnt,
.pt-part-campaigns.cb-a2 .cm-area2 .cm-chip,
.pt-part-campaigns.cb-a2 .cm-area2 .cm-cnt,
.pt-part-campaigns.pt-s1 .cm-area .cm-chip,
.pt-part-campaigns.pt-s1 .cm-area .cm-cnt { opacity: 1; transform: none; }
.pt-part-campaigns.cb-a1:not(.cm-quick) .cm-area1 .cm-chip::before,
.pt-part-campaigns.cb-a2:not(.cm-quick) .cm-area2 .cm-chip::before { animation: cm-ring .7s ease-out .12s forwards; }
.pt-part-campaigns.cb-a1:not(.cm-quick) .cm-area1 .cm-shine::after,
.pt-part-campaigns.cb-a2:not(.cm-quick) .cm-area2 .cm-shine::after { animation: cm-shine .9s ease-in-out .3s; }

/* ---------- step 1, page slots: where the team will be ---------------------- */
/* Two days on the team's calendar: the times drop in, then the postcode the
   team will be in snaps onto the day. */
.pt-part-campaigns .cm-days { gap: .7em; }
.pt-part-campaigns .cm-day { padding: .62em .78em .72em; border-radius: .8em; background: rgba(236, 230, 218, .045); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .06); }
.pt-part-campaigns .cm-day-hd { display: flex; align-items: center; justify-content: space-between; gap: .6em; margin-bottom: .55em; }
.pt-part-campaigns .cm-day-hd > b { font: 500 .88em/1.2 var(--pt-head); letter-spacing: -.01em; color: var(--pt-bone); white-space: nowrap; }
.pt-part-campaigns .cm-times { display: flex; gap: .5em; }
.pt-part-campaigns .cm-time { display: inline-flex; align-items: center; padding: .4em .8em; border-radius: .6em;
  background: rgba(180, 167, 207, .2); box-shadow: inset 0 0 0 1px rgba(180, 167, 207, .45);
  font: 600 .84em/1.2 var(--pt-text); color: var(--pt-bone); white-space: nowrap; font-variant-numeric: tabular-nums;
  opacity: 0; transform: translateY(-1.1em) scale(.9); transition: opacity .25s ease, transform .55s var(--pt-spring); }
.pt-part-campaigns.pt-s1 .cm-time { opacity: 1; transform: none; }
.pt-part-campaigns .cm-tag { opacity: 0; transform: translateY(-.2em) scale(1.4); transform-origin: 100% 50%;
  transition: opacity .18s ease, transform .42s cubic-bezier(.3, 1.5, .5, 1); }
.pt-part-campaigns.pt-s1 .cm-tag { opacity: 1; transform: none; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-t1 { transition-delay: .75s; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-t2 { transition-delay: 1s; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-day1 .cm-tag { transition-delay: 1.5s; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-day1 .cm-tag::before { animation: cm-ring .7s ease-out 1.6s forwards; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-day1 .cm-shine::after { animation: cm-shine .9s ease-in-out 1.75s; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-t3 { transition-delay: 2.15s; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-day2 .cm-tag { transition-delay: 2.65s; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-day2 .cm-tag::before { animation: cm-ring .7s ease-out 2.75s forwards; }
.pt-part-campaigns:not(.cm-quick)[data-step="1"] .cm-day2 .cm-shine::after { animation: cm-shine .9s ease-in-out 2.9s; }

/* ---------- step 2, page send: tested on the team, then sent ---------------- */
/* The test reaches the team (a sage tick pops by them) and only then does
   Send light up; it is pressed, a confirm rises over the page with the
   count, its Send is pressed, and the texts fly out to the two areas while a
   quiet chip says they only go out from 9:00 to 19:00. The beats are
   keyframes and delays: the end of each one is the step's end state (pt-s2). */
.pt-part-campaigns .cm-send { gap: .95em; }
/* The team (Maria and Ryan, as in Control and Grow) and the tick that the
   test reached them. */
.pt-part-campaigns .cm-test { display: flex; align-items: center; justify-content: space-between; gap: .8em; padding: .5em .6em .5em .55em; border-radius: .8em;
  background: rgba(236, 230, 218, .045); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .06); }
.pt-part-campaigns .cm-team { display: flex; gap: .3em; }
.pt-part-campaigns .cm-team .cm-av { width: 2.3em; height: 2.3em; }
.pt-part-campaigns .cm-team .cm-av-m { background: var(--pt-dusk); }
.pt-part-campaigns .cm-team .cm-av-r { background: var(--pt-sage); }
.pt-part-campaigns .cm-test .cm-chip { opacity: 0; transform: scale(.55); transform-origin: 100% 50%; transition: opacity .25s ease, transform .55s var(--pt-spring); }
.pt-part-campaigns.pt-s2 .cm-test .cm-chip { opacity: 1; transform: none; }
.pt-part-campaigns .cm-lower { position: relative; }
.pt-part-campaigns .cm-under { display: flex; flex-direction: column; align-items: center; gap: .8em; }
/* Who it goes to: the two areas' textable counts together (268 + 187). */
.pt-part-campaigns .cm-reach { display: flex; align-items: baseline; gap: .45em; font: 500 .84em/1.2 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; }
.pt-part-campaigns .cm-reach b { font: 500 1.45em/1 var(--pt-head); letter-spacing: -.02em; color: var(--pt-bone); font-variant-numeric: tabular-nums; }
/* While the texts go out (2) the count says what is left to send and runs
   down to 0 (campaigns.js; Dimitar, 02/10/2026: "count down the messages to
   send from 400'sh to 0"). */
.pt-part-campaigns .cm-reach-b { display: none; }
.pt-part-campaigns.cb-count .cm-reach-a { display: none; }
.pt-part-campaigns.cb-count .cm-reach-b { display: block; }
.pt-part-campaigns .cm-go { display: grid; justify-items: center; align-items: center; }
.pt-part-campaigns .cm-go > * { grid-area: 1 / 1; }
.pt-part-campaigns .cm-btn-send { padding: .62em 1.7em; border-radius: .8em; opacity: .35; }
.pt-part-campaigns.pt-s2 .cm-btn-send { opacity: 0; transform: scale(.85); }
.pt-part-campaigns .cm-sending { padding-top: .45em; padding-bottom: .45em; opacity: 0; transform: scale(.7); transition: opacity .3s ease, transform .5s var(--pt-spring); }
.pt-part-campaigns.pt-s2 .cm-sending { opacity: 1; transform: none; }
.pt-part-campaigns .cm-targets { display: flex; justify-content: center; gap: .9em; }
/* The confirm: a sheet over the lower half of the page (what is under it
   fades out, so no text ever lies on another). */
.pt-part-campaigns .cm-sheet { position: absolute; left: .3em; right: .3em; top: 50%; height: 5.9em; margin-top: -2.95em;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .75em; padding: .7em .9em; border-radius: .9em;
  background: linear-gradient(180deg, rgba(236, 230, 218, .1), rgba(236, 230, 218, .06)), var(--pt-raised);
  box-shadow: 0 1em 2em -.6em rgba(0, 0, 0, .7), inset 0 0 0 1px rgba(236, 230, 218, .1);
  opacity: 0; transform: translateY(1.2em) scale(.96); pointer-events: none; }
.pt-part-campaigns .cm-sheet-t { font: 600 .88em/1.3 var(--pt-text); color: var(--pt-bone); text-align: center; }
.pt-part-campaigns .cm-sheet-b { display: flex; gap: .55em; }
.pt-part-campaigns .cm-sheet .cm-btn { padding: .45em 1.15em; }
/* The texts going out: dots from the chip, in an arc, into each area
   (campaigns.js measures where; --sx, --sy the start, --tx, --ty the way). */
.pt-part-campaigns .cm-fly { position: absolute; left: var(--sx, 50%); top: var(--sy, 1em); width: .55em; height: .55em; margin: -.275em 0 0 -.275em; border-radius: 50%;
  background: var(--pt-dusk); box-shadow: 0 0 .5em rgba(180, 167, 207, .8); opacity: 0; pointer-events: none; }
.pt-part-campaigns .cm-fly2 { --d: .16s; }
.pt-part-campaigns .cm-fly3 { --d: .32s; }
.pt-part-campaigns .cm-fly4 { --d: .08s; }
.pt-part-campaigns .cm-fly5 { --d: .24s; }
.pt-part-campaigns .cm-fly6 { --d: .4s; }
@keyframes cm-fly {
  0% { opacity: 0; transform: translate3d(0, 0, .4em) scale(.6); }
  15% { opacity: 1; }
  50% { transform: translate3d(calc(var(--tx, 0em) * .5), calc(var(--ty, 0em) * .5 - 1.3em), 2.2em) scale(1); }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translate3d(var(--tx, 0em), var(--ty, 0em), .4em) scale(.45); }
}
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-test .cm-chip { transition-delay: .7s; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-test .cm-chip::before { animation: cm-ring .7s ease-out .8s forwards; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-test .cm-shine::after { animation: cm-shine .9s ease-in-out .95s; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-btn-send { animation: cm-send 2.55s linear both; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-btn-send .cm-tap { animation: cm-tap .6s ease-out 2.05s; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-sheet { animation: cm-sheet 1.6s linear 2.4s both; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-btn-yes { animation: cm-press .34s ease-out 3.45s; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-btn-yes .cm-tap { animation: cm-tap .6s ease-out 3.42s; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-under { animation: cm-under 1.85s linear 2.3s both; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-sending { transition-delay: 3.95s; }
/* The texts stream out in four waves, 4.3 to about 8.6 s, and each area rings
   as a wave lands (Dimitar, 02/10/2026: "animate smsing the customers for
   longer"; it was one wave). campaigns.js keeps Ai's antennae glowing as long. */
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-fly1,
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-fly2,
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-fly3 { animation: cm-fly .95s cubic-bezier(.45, .05, .55, .95) calc(4.3s + var(--d, 0s)) 4 both; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-fly4,
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-fly5,
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-fly6 { animation: cm-fly .95s cubic-bezier(.45, .05, .55, .95) calc(4.4s + var(--d, 0s)) 4 both; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-tg1::before { animation: cm-ring .95s ease-out 5.15s 4 forwards; }
.pt-part-campaigns:not(.cm-quick)[data-step="2"] .cm-tg2::before { animation: cm-ring .95s ease-out 5.3s 4 forwards; }
/* Send: dim until the test is through (0 to 1.1 s), lit, pressed at 2.1 s,
   gone under the confirm by 2.55 s. */
@keyframes cm-send {
  0%, 43% { opacity: .35; transform: none; }
  55%, 80% { opacity: 1; transform: none; }
  85% { opacity: 1; transform: scale(.92); }
  90% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: scale(.85); }
}
/* The confirm: up by 2.75 s, its Send pressed at 3.45 s, gone by 4 s. */
@keyframes cm-sheet {
  0% { opacity: 0; transform: translateY(1.2em) scale(.96); }
  22%, 78% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(1em) scale(.96); }
}
/* What lies under the confirm: out while it is up, back as it goes. */
@keyframes cm-under { 0% { opacity: 1; } 12%, 85% { opacity: 0; } 100% { opacity: 1; } }

/* ---------- step 3, page chat: a new address, sorted in the chat ------------ */
/* Tom's thread as the app shows it: his texts on the left, the AI's on the
   right (dusk). Each AI line comes after a short thinking beat (three dots,
   Ai's antennae glowing) and is typed over the finished one (which sizes its
   bubble). The thread climbs inside its window as it grows: campaigns.js
   moves it with a transform and fades out what goes over the top. When Tom
   confirms, his old address is struck through in the header, the new one
   lands, and the app notes it in the thread. */
.pt-part-campaigns .cm-hd-chat { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "av name chip" "av addr addr";
  column-gap: .6em; row-gap: .12em; align-items: center; justify-content: stretch; height: 2.9em; padding-bottom: .45em; }
.pt-part-campaigns .cm-av { grid-area: av; display: flex; align-items: center; justify-content: center; width: 2.6em; height: 2.6em; border-radius: 50%;
  font: 600 .8em/1 var(--pt-head); color: var(--pt-night); background: var(--pt-mist); }
.pt-part-campaigns .cm-name { grid-area: name; font: 500 .95em/1.2 var(--pt-head); letter-spacing: -.01em; color: var(--pt-bone); white-space: nowrap; }
.pt-part-campaigns .cm-lai { grid-area: chip; justify-self: end; padding: .24em .66em; }
.pt-part-campaigns.cb-think .cm-lai .cm-led { animation: cm-led .8s ease-in-out infinite; }
@keyframes cm-led { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.7); } }
.pt-part-campaigns .cm-addr { grid-area: addr; display: grid; justify-items: start; min-width: 0; }
.pt-part-campaigns .cm-addr > span { grid-area: 1 / 1; position: relative; display: flex; align-items: center; gap: .4em;
  font: 500 .8em/1.25 var(--pt-text); white-space: nowrap; transition: opacity .35s ease, transform .5s var(--pt-spring); }
.pt-part-campaigns .cm-addr-old { color: var(--pt-lichen); }
.pt-part-campaigns .cm-addr-new { color: var(--pt-sage); opacity: 0; transform: translateX(.7em); }
.pt-part-campaigns .cm-addr-new .cm-tick { width: 1em; height: 1em; }
.pt-part-campaigns .cm-addr-new .cm-tick::after { left: .35em; top: .18em; width: .25em; height: .48em; border-width: 0 .12em .12em 0; }
/* The strike stays inside the words (a line past them would widen the box). */
.pt-part-campaigns .cm-strike { position: absolute; left: 0; right: 0; top: 52%; height: .13em; margin-top: -.065em; border-radius: .07em;
  background: var(--pt-clay); transform: scaleX(0); transform-origin: 0 50%; transition: transform .4s cubic-bezier(.5, 0, .3, 1); }
.pt-part-campaigns.cb-strike .cm-strike { transform: none; }
/* In turn, never both at once: the old one slides out to the left, then the new one slides in. */
.pt-part-campaigns.cb-moved .cm-addr-old { opacity: 0; transform: translateX(-.7em); transition-duration: .22s, .4s; }
.pt-part-campaigns.cb-moved .cm-addr-new { opacity: 1; transform: none; transition-delay: .24s; }
/* The thread's window: whatever goes over its top has faded already. */
.pt-part-campaigns .cm-win { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; }
.pt-part-campaigns .cm-feed { position: relative; display: flex; flex-direction: column; gap: .5em; padding: .12em .1em 0;
  transition: transform .5s cubic-bezier(.25, .75, .3, 1); }
/* An item: in the thread (is-in) takes its place, arrived (is-on) shows,
   gone over the top (is-gone) fades out. */
.pt-part-campaigns .cm-it { display: none; position: relative; max-width: 84%;
  opacity: 0; transform: translateY(.7em) scale(.94); transition: opacity .3s ease, transform .5s var(--pt-spring); }
.pt-part-campaigns .cm-it.is-in { display: block; }
.pt-part-campaigns .cm-it.is-on { opacity: 1; transform: none; }
.pt-part-campaigns .cm-it.is-gone { opacity: 0; transition-duration: .18s, .5s; }   /* out before the climb takes it under the edge */
.pt-part-campaigns .cm-out,
.pt-part-campaigns .cm-in { padding: .52em .82em .56em; border-radius: 1.1em; font: 500 .84em/1.34 var(--pt-text); color: var(--pt-bone); }
.pt-part-campaigns .cm-out { align-self: flex-end; border-bottom-right-radius: .32em; background: rgba(180, 167, 207, .2); transform-origin: 100% 100%; }
.pt-part-campaigns .cm-in { align-self: flex-start; border-bottom-left-radius: .32em; background: rgba(236, 230, 218, .09); transform-origin: 0 100%; }
.pt-part-campaigns .cm-sys { align-self: center; max-width: none; transform-origin: 50% 50%; }
.pt-part-campaigns .cm-tx { display: block; }
.pt-part-campaigns .cm-ty { position: absolute; inset: .52em .82em .56em; }
.pt-part-campaigns .cm-out.is-ty .cm-tx { visibility: hidden; }
.pt-part-campaigns.cb-upd:not(.cm-quick) .cm-sys .cm-chip::before { animation: cm-ring .7s ease-out .2s forwards; }
.pt-part-campaigns.cb-upd:not(.cm-quick) .cm-sys .cm-shine::after { animation: cm-shine .9s ease-in-out .35s; }
/* The AI thinking: three dusk dots on its side. */
.pt-part-campaigns .cm-think { display: none; align-self: flex-end; align-items: center; gap: .3em; padding: .66em .9em; border-radius: 1.1em; border-bottom-right-radius: .32em;
  background: rgba(180, 167, 207, .14); opacity: 0; transform: translateY(.5em) scale(.9); transform-origin: 100% 100%;
  transition: opacity .25s ease, transform .45s var(--pt-spring); }
.pt-part-campaigns .cm-think.is-in { display: flex; }
.pt-part-campaigns .cm-think.is-on { opacity: 1; transform: none; }
.pt-part-campaigns .cm-think i { width: .45em; height: .45em; border-radius: 50%; background: var(--pt-dusk); opacity: .45; }
.pt-part-campaigns .cm-think.is-on i { animation: cm-dot 1s ease-in-out infinite; }
.pt-part-campaigns .cm-think i:nth-child(2) { animation-delay: .15s; }
.pt-part-campaigns .cm-think i:nth-child(3) { animation-delay: .3s; }
@keyframes cm-dot { 0%, 60%, 100% { opacity: .45; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-.22em); } }

/* ---------- step 4, page book: approve, move or cancel ---------------------- */
/* The booking the AI made, waiting for staff: Approve is pressed, the sage
   Approved lands in place of To approve, Ai hops (campaigns.js). Under it,
   an earlier one, already approved. */
.pt-part-campaigns .cm-book { gap: .8em; }
.pt-part-campaigns .cm-bk { padding: .78em .92em .88em; border-radius: .85em; background: rgba(236, 230, 218, .05); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .08); }
.pt-part-campaigns .cm-bk-top { display: flex; align-items: center; justify-content: space-between; gap: .6em; margin-bottom: .5em; }
.pt-part-campaigns .cm-bk-when { font: 500 .9em/1.2 var(--pt-head); letter-spacing: -.01em; color: var(--pt-bone); white-space: nowrap; }
.pt-part-campaigns .cm-stat { display: grid; justify-items: end; align-items: center; }
.pt-part-campaigns .cm-stat > * { grid-area: 1 / 1; }
.pt-part-campaigns .cm-stat-wait { transition: opacity .25s ease, transform .4s ease; }
.pt-part-campaigns .cm-stat-ok { opacity: 0; transform: scale(.5); transform-origin: 100% 50%; transition: opacity .25s ease, transform .55s var(--pt-spring); }
.pt-part-campaigns.pt-s4 .cm-stat-wait { opacity: 0; transform: scale(.8); }
.pt-part-campaigns.pt-s4 .cm-stat-ok { opacity: 1; transform: none; }
.pt-part-campaigns .cm-bk-who { display: flex; align-items: center; gap: .6em; margin-bottom: .3em; }
.pt-part-campaigns .cm-bk-who > b { font: 600 .9em/1.25 var(--pt-text); color: var(--pt-bone); white-space: nowrap; }
.pt-part-campaigns .cm-by { padding: .2em .62em; }
.pt-part-campaigns .cm-bk-addr { display: block; font: 500 .84em/1.3 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; }
.pt-part-campaigns .cm-acts { display: flex; gap: .5em; margin-top: .75em; }
.pt-part-campaigns .cm-acts .cm-btn { padding: .46em .95em; }
.pt-part-campaigns .cm-approve { transition: opacity .35s ease; }
.pt-part-campaigns.pt-s4 .cm-approve { opacity: .45; }
.pt-part-campaigns .cm-bk2 { display: flex; align-items: center; justify-content: space-between; gap: .6em; padding: .55em .92em .6em; border-radius: .75em;
  background: rgba(236, 230, 218, .03); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .05); }
.pt-part-campaigns .cm-bk2-t { display: flex; flex-direction: column; gap: .12em; min-width: 0; }
.pt-part-campaigns .cm-bk2-t b { font: 600 .84em/1.2 var(--pt-text); color: var(--pt-bone); white-space: nowrap; }
.pt-part-campaigns .cm-bk2-t span { font: 500 .8em/1.2 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; }
.pt-part-campaigns .cm-dim { opacity: .7; }
/* The two ways a booking is made, split clearly (Dimitar, 02/10/2026:
   "campaign manager - split the approval case on cleat auto Ai and manual",
   then "ai books but team also can book"): a label on each card's top edge,
   like a legend, so it costs no height. Booked by AI in dusk, the AI's
   colour (Tom's, from the chat); booked by the team in sand (Ben's). Text at
   least 10 px. */
.pt-part-campaigns .cm-bk,
.pt-part-campaigns .cm-bk2 { position: relative; }
.pt-part-campaigns .cm-bk2 { padding-top: .8em; }
.pt-part-campaigns .cm-mode { position: absolute; left: .75em; top: 0; transform: translateY(-50%); display: inline-flex; align-items: center; gap: .35em;
  padding: .1em .55em; border-radius: 999px; font: 600 .8em/1.2 var(--pt-text); white-space: nowrap; background: var(--pt-raised); }
.pt-part-campaigns .cm-mode-man { color: var(--pt-sand); box-shadow: inset 0 0 0 1px rgba(217, 197, 154, .5); }
.pt-part-campaigns .cm-mode-auto { color: var(--pt-dusk); box-shadow: inset 0 0 0 1px rgba(180, 167, 207, .5); }
.pt-part-campaigns:not(.cm-quick)[data-step="4"] .cm-approve { animation: cm-press .4s ease-out 1.15s; transition-delay: 1.6s; }
.pt-part-campaigns:not(.cm-quick)[data-step="4"] .cm-approve .cm-tap { animation: cm-tap .6s ease-out 1.1s; }
.pt-part-campaigns:not(.cm-quick)[data-step="4"] .cm-stat-wait { transition-delay: 1.4s; }
.pt-part-campaigns:not(.cm-quick)[data-step="4"] .cm-stat-ok { transition-delay: 1.5s; }
.pt-part-campaigns:not(.cm-quick)[data-step="4"] .cm-stat-ok::before { animation: cm-ring .7s ease-out 1.62s forwards; }
.pt-part-campaigns:not(.cm-quick)[data-step="4"] .cm-stat-ok .cm-shine::after { animation: cm-shine .9s ease-in-out 1.78s; }

/* ---------- the rewind: the pages fade, the set stays ----------------------- */
.pt-part-campaigns.is-rewind .cm-pages { opacity: 0; }

/* ---------- the steps under the scene ---------------------------------------- */
/* Desktop: small floating pills in a compact wrap; the one in play is lit in
   dusk, the layer's tone. */
.pt-part-campaigns .pt-part-steps { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 12px 0 0; padding: 0; list-style: none; }
.pt-part-campaigns .pt-part-steps li { position: relative; isolation: isolate; display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px 6px 9px; border-radius: 999px; background: rgba(236, 230, 218, .06); color: var(--pt-bone);
  font: 500 var(--pt-fs-say)/1.3 var(--pt-text); cursor: default;
  animation: cm-pill 6s ease-in-out infinite; animation-delay: calc(var(--i, 0) * -1.2s); }
.pt-part-campaigns .pt-part-steps li::before { content: ""; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--tone, var(--pt-dusk)); opacity: .45; transform: scale(.85); transition: opacity .3s ease, transform .4s var(--pt-spring); }
/* The lit state: a brighter ground and the dot's glow, one layer that fades in. */
.pt-part-campaigns .pt-part-steps li::after { content: ""; position: absolute; z-index: -1; inset: 0; border-radius: inherit;
  background: radial-gradient(circle at 12.5px 50%, rgba(180, 167, 207, .55), rgba(180, 167, 207, 0) 15px), rgba(236, 230, 218, .07);
  opacity: 0; transition: opacity .35s ease; }
.pt-part-campaigns .pt-part-steps li > span { opacity: .7; transition: opacity .3s ease; }
.pt-part-campaigns .pt-part-steps li.is-on::before { opacity: 1; transform: scale(1.2); }
.pt-part-campaigns .pt-part-steps li.is-on::after,
.pt-part-campaigns .pt-part-steps li.is-on > span { opacity: 1; }
.pt-part-campaigns .pt-part-steps li:focus-visible { outline: 2px solid var(--pt-sand); outline-offset: 3px; }
@keyframes cm-pill { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }

/* Nothing moves in a layer that is not the one in front (Ai's own loops are
   paused by site.css). */
.pt-layer:not(.is-current) .pt-part-campaigns .cm-float,
.pt-layer:not(.is-current) .pt-part-campaigns .cm-set::after,
.pt-layer:not(.is-current) .pt-part-campaigns .cm-led,
.pt-layer:not(.is-current) .pt-part-campaigns .cm-think i,
.pt-layer:not(.is-current) .pt-part-campaigns .cm-typed::after,
.pt-layer:not(.is-current) .pt-part-campaigns .cm-ty::after,
.pt-layer:not(.is-current) .pt-part-campaigns .pt-part-steps li { animation-play-state: paused; }

/* ---------- the lg column (992 to 1199, 444 wide): the steps as dots ---------- */
/* Five pills would stack four deep here; as in Control, five dots and the
   lit step's pill centred under them. Only with JS (it lights the step). */
@media (min-width: 992px) and (max-width: 1199.98px) {
  html.js .pt-part-campaigns .pt-part-steps { position: relative; flex-wrap: nowrap; justify-content: center; gap: 0; height: 50px; margin-top: 12px; }
  html.js .pt-part-campaigns .pt-part-steps li { position: static; isolation: auto; justify-content: center; align-items: flex-start;
    width: 22px; height: 14px; padding: 4px 0 0; background: none; animation: none; }
  html.js .pt-part-campaigns .pt-part-steps li::after { display: none; }
  html.js .pt-part-campaigns .pt-part-steps li.is-on::before { transform: scale(1.45); }
  html.js .pt-part-campaigns .pt-part-steps li > span { position: absolute; left: 0; right: 0; top: 21px; width: max-content; margin: 0 auto;
    padding: .4em 1em .45em; border-radius: 999px; background: rgba(236, 230, 218, .08); line-height: 1.3; white-space: nowrap;
    opacity: 0; transform: translateY(5px); pointer-events: none; transition: opacity .3s ease, transform .45s var(--pt-spring); }
  html.js .pt-part-campaigns .pt-part-steps li.is-on > span { opacity: 1; transform: none; pointer-events: auto; }
  html.js .pt-part-campaigns[data-step="-1"] .pt-part-steps li:first-child > span { opacity: .5; transform: none; }
}

/* ---------- phones: short ones (360x640, 375x667) -------------------------- */
/* With the layer's copy above (318 px), about 170 px are left for the whole
   component, clear of the arrow (measured: 174 at 360x640, 169 at 375x667).
   So the panel alone, full width, at a fixed 12.5px unit (a wider phone
   would grow the em, and with it the height): 162 px with the caption, so
   a copy a few px taller on a real phone still clears the arrow. No Ai, no
   line (the thread's dots and the Local Ai chip carry the AI here), labels
   gone, every page tightened. The steps become one line: five dots and the
   caption of the one in play. */
@media (max-width: 575.98px) {
  .pt-part-campaigns .pt-part-stage { font-size: 12.5px; perspective: 820px; perspective-origin: 50% 45%; }
  .pt-part-campaigns .cm-set { width: 25.6em; height: 11em; }
  .pt-part-campaigns .cm-set::before { background:
      radial-gradient(50% 40% at 50% 86%, rgba(180, 167, 207, .10), rgba(180, 167, 207, 0) 72%); }
  .pt-part-campaigns .cm-set::after { left: 1.6em; width: 22.4em; top: 10em; height: 1.1em; }
  .pt-part-campaigns .cm-say,
  .pt-part-campaigns .cm-ai { display: none; }
  .pt-part-campaigns .cm-ai .pt-char-ai { animation-name: cm-hover-sm; }
  .pt-part-campaigns .cm-float { left: 0; top: .2em; width: 25.6em; height: 10.4em; }
  .pt-part-campaigns .cm-panel { border-radius: .85em; transform: rotateY(4deg) rotateX(3deg); }
  .pt-part-campaigns .cm-pages { inset: .62em .72em .45em; }
  .pt-part-campaigns .cm-hd { height: 1.85em; padding-bottom: .4em; margin-bottom: .2em; }
  .pt-part-campaigns .cm-lbl,
  .pt-part-campaigns .cm-cnt-a { display: none; }
  /* areas: the message on two lines, each area on one */
  .pt-part-campaigns .cm-field { margin-bottom: .5em; }
  .pt-part-campaigns .cm-text,
  .pt-part-campaigns .cm-typed { padding: .42em .8em .46em; }
  .pt-part-campaigns .cm-area .cm-chip { padding-top: .3em; padding-bottom: .3em; }
  .pt-part-campaigns .cm-alist { row-gap: .4em; }
  /* slots: the two days without their cards */
  .pt-part-campaigns .cm-days { gap: .55em; }
  .pt-part-campaigns .cm-day { padding: 0; background: none; box-shadow: none; }
  .pt-part-campaigns .cm-day-hd { margin-bottom: .25em; }
  .pt-part-campaigns .cm-day-hd .cm-chip { padding-top: .28em; padding-bottom: .28em; }
  .pt-part-campaigns .cm-time { padding: .28em .7em; }
  /* send: the audience line goes (the confirm says the count) */
  .pt-part-campaigns .cm-send,
  .pt-part-campaigns .cm-under { gap: .42em; }
  .pt-part-campaigns .cm-reach { display: none; }
  .pt-part-campaigns .cm-test { padding: .3em .45em .3em .35em; }
  .pt-part-campaigns .cm-team .cm-av { width: 2em; height: 2em; }
  .pt-part-campaigns .cm-send .cm-chip { padding-top: .28em; padding-bottom: .28em; }
  .pt-part-campaigns .cm-btn-send { padding: .48em 1.5em; }
  .pt-part-campaigns .cm-sheet { left: 0; right: 0; height: 4.9em; margin-top: -2.45em; gap: .5em; padding: .45em .7em; }
  .pt-part-campaigns .cm-sheet .cm-btn { padding: .38em 1.1em; }
  /* chat */
  .pt-part-campaigns .cm-hd-chat { height: 2.55em; padding-bottom: .35em; column-gap: .5em; row-gap: .06em; }
  .pt-part-campaigns .cm-av { width: 2.4em; height: 2.4em; }
  .pt-part-campaigns .cm-feed { gap: .4em; }
  .pt-part-campaigns .cm-it { max-width: 88%; }
  /* book: the one booking */
  .pt-part-campaigns .cm-bk { padding: .45em .75em .5em; }
  .pt-part-campaigns .cm-bk-top { margin-bottom: .25em; }
  .pt-part-campaigns .cm-bk-who { margin-bottom: .12em; }
  .pt-part-campaigns .cm-acts { margin-top: .36em; }
  .pt-part-campaigns .cm-acts .cm-btn { padding: .36em .9em; }
  .pt-part-campaigns .cm-bk2 { display: none; }
}
@keyframes cm-hover-sm { 0%, 100% { translate: 0 0; } 50% { translate: 0 -4px; } }
/* The phone caption: five dots and the lit step's line (with JS; without it
   the pills above stay, all five). */
@media (max-width: 575.98px) {
  html.js .pt-part-campaigns .pt-part-steps { position: relative; flex-wrap: nowrap; justify-content: flex-start; gap: 0; height: 22px; margin-top: 3px; }
  html.js .pt-part-campaigns .pt-part-steps li { position: static; isolation: auto; flex: 0 0 15px; justify-content: center; width: 15px; height: 22px; padding: 0;
    background: none; animation: none; }
  html.js .pt-part-campaigns .pt-part-steps li::after { display: none; }
  html.js .pt-part-campaigns .pt-part-steps li::before { width: 6px; height: 6px; }
  html.js .pt-part-campaigns .pt-part-steps li.is-on::before { transform: scale(1.5); }
  html.js .pt-part-campaigns .pt-part-steps li > span { position: absolute; left: 81px; top: 0; font-size: calc(var(--pt-fs-say) - 1px); line-height: 22px; white-space: nowrap;
    opacity: 0; transform: translateY(6px); pointer-events: none; transition: opacity .3s ease, transform .45s var(--pt-spring); }
  html.js .pt-part-campaigns .pt-part-steps li.is-on > span { opacity: 1; transform: none; pointer-events: auto; }
  html.js .pt-part-campaigns[data-step="-1"] .pt-part-steps li:first-child > span { opacity: .5; transform: none; }
}
/* Tall phones (390x844 leaves about 320 px below the copy): room for Ai and
   his line. Ai at the top left with it beside him, its tail at his head (a
   tail on the right would widen the bubble's own box, as in Grow it points
   left); the panel below, the whole width, every page a little roomier
   than on a short phone. After the phone rules, which it overrides. */
@media (max-width: 575.98px) and (min-height: 760px) {
  .pt-part-campaigns .pt-part-stage { font-size: max(12.5px, min(13.6px, calc(100cqw / 25.6))); }
  .pt-part-campaigns .cm-set { height: 20.8em; }
  .pt-part-campaigns .cm-set::before { background:
      radial-gradient(46% 30% at 50% 82%, rgba(180, 167, 207, .10), rgba(180, 167, 207, 0) 72%),
      radial-gradient(14% 16% at 10% 14%, rgba(147, 183, 198, .10), rgba(147, 183, 198, 0) 72%); }
  .pt-part-campaigns .cm-set::after { left: 1.6em; width: 22.4em; top: 19.7em; height: 1.2em; }
  .pt-part-campaigns .cm-ai { display: block; left: .3em; right: auto; top: 0; bottom: auto; width: 3.9em; transform: translateZ(.8em); }
  .pt-part-campaigns .cm-say { display: block; left: 5.4em; right: auto; top: .35em; width: 15.4em; transform: translateZ(.6em); }
  .pt-part-campaigns .cm-say .pt-bubble { border-radius: .45em 1.3em 1.3em 1.3em; transform-origin: 0 30%; }
  .pt-part-campaigns .cm-say .pt-bubble::before { left: -6px; right: auto; bottom: auto; top: calc(1.4em - 6px); clip-path: polygon(0 0, 0 100%, 100% 100%); }
  .pt-part-campaigns .cm-float { left: .1em; top: 6.7em; width: 25.4em; height: 13.5em; }
  .pt-part-campaigns .cm-panel { transform: rotateY(4deg) rotateX(2deg); }
  .pt-part-campaigns .cm-pages { inset: .75em .75em .55em; }
  .pt-part-campaigns .cm-cnt-a { display: block; }
  .pt-part-campaigns .cm-alist { row-gap: .5em; }
  .pt-part-campaigns .cm-field { margin-bottom: .7em; }
  .pt-part-campaigns .cm-days { gap: .6em; }
  .pt-part-campaigns .cm-day { padding: .45em .7em .52em; border-radius: .75em; background: rgba(236, 230, 218, .045); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .06); }
  .pt-part-campaigns .cm-day-hd { margin-bottom: .35em; }
  .pt-part-campaigns .cm-send,
  .pt-part-campaigns .cm-under { gap: .62em; }
  .pt-part-campaigns .cm-reach { display: flex; }
  .pt-part-campaigns .cm-it { max-width: 86%; }
  .pt-part-campaigns .cm-bk { padding: .62em .8em .7em; }
  .pt-part-campaigns .cm-bk-top { margin-bottom: .4em; }
  .pt-part-campaigns .cm-bk-who { margin-bottom: .2em; }
  .pt-part-campaigns .cm-acts { margin-top: .6em; }
}

/* ---------- instant draws (campaigns.js) --------------------------------------- */
/* A jump to a step (a hover, a tap, the test hook) glides there quickly with
   no delays; the reset snaps. Last, to win the ties. */
.pt-part-campaigns.cm-quick .cm-mv { transition-duration: .3s; transition-delay: 0s; animation: none; }
.pt-part-campaigns.cm-quick .cm-mv > *,
.pt-part-campaigns.cm-quick .cm-mv::before,
.pt-part-campaigns.cm-quick .cm-mv::after { transition-delay: 0s; animation: none; }
.pt-part-campaigns.cm-quick .cm-it,
.pt-part-campaigns.cm-quick .cm-think { transition-duration: .3s; transition-delay: 0s; }
.pt-part-campaigns.cm-snap .cm-mv,
.pt-part-campaigns.cm-snap .cm-mv *,
.pt-part-campaigns.cm-snap .cm-mv::before,
.pt-part-campaigns.cm-snap .cm-mv::after { transition: none; animation: none; }
