/*
 * Paytrack product site (kirbyservice.net): Fit's component (round 3,
 * plan.md). One window of the app, a job screen, made over step by step as
 * if it were being fitted to a business; a brand kit card, Ai and Db beside
 * it. Markup in obj/part_fit.php, the typing and the beats in js/parts/fit.js,
 * the runner in js/parts/part.js. Keep this file ASCII.
 *
 * Five steps: 0 trade (the appliance job's card slides out, a vehicle job's
 * slides in), 1 steps (the status steps re-order, Waiting for
 * parts slides in), 2 words (Job becomes Work order, the parts take the
 * business's own names, typed), 3 brand (the logo flies in from the kit, a
 * swatch is picked, the accent sweeps across the screen), 4 grow (Branch 2
 * and Team 2 click into place).
 *
 * 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 cuts: wide (desktop and tablet,
 * 35 x 19.4 em: the window, and on its right the kit, Ai's line, Ai and
 * Db), short phones (25.6 x 19.7 em at 12.5px: the kit, Ai and Db on a row
 * over the window, no line; with the layer's copy above, 360x640 and
 * 375x667 leave about 281 px for the whole component, measured 02/10/2026)
 * and tall phones (25.6 x 26.1 em: Ai, his line and Db, then the kit, then
 * the window). Motion is transform and opacity only (a colour changes by a
 * layer of the new colour fading in); the steps are the pt-sN classes and
 * data-step (part.js), the beats inside a step are transition delays,
 * keyframes and the fb-* classes (fit.js). Elements marked .ft-mv take the
 * quick and snap modes at the end of this file; every one-shot effect also
 * says :not(.ft-quick) or hangs on a beat class.
 *
 * Colours: sage is the app's own accent (the layer's tone), clay the
 * business's (picked from the kit in step 3); dusk and mist only in the kit's
 * swatches. Text stays bone and lichen.
 *
 * 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 and the hops 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-fit { container-type: inline-size; }
.pt-part-fit .pt-part-stage { position: relative; font-size: 14px;
  font-size: max(12.5px, min(16px, calc(100cqw / 35), 2vh));
  perspective: 1100px; perspective-origin: 46% 42%; }
.pt-part-fit .pt-part-tilt { transform-style: preserve-3d; transition: transform .6s cubic-bezier(.2, .8, .3, 1); }
.pt-part-fit .ft-set { position: relative; width: 35em; height: 19.4em; margin: 0 auto; transform-style: preserve-3d; line-height: 1.3; }
.pt-part-fit .ft-set i { display: block; font-style: normal; }

/* The light in the room: a sage pool under the window, a warm one under the
   kit and the characters. A pseudo-element, so no check counts it. */
.pt-part-fit .ft-set::before { content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(36% 34% at 33% 80%, rgba(157, 184, 159, .10), rgba(157, 184, 159, 0) 72%),
    radial-gradient(18% 30% at 84% 70%, rgba(217, 197, 154, .08), rgba(217, 197, 154, 0) 72%);
  transform: translateZ(-3em); }

/* ---------- the parts every piece uses ---------------------------------------- */
/* The accent: a layer of the business's colour (clay) over the app's own
   (sage), shown in step 3 (the sweep). */
.pt-part-fit .ft-acc { position: absolute; inset: 0; border-radius: inherit; background: var(--pt-clay); opacity: 0; pointer-events: none; }
@keyframes ft-ring { 0% { opacity: .85; transform: scale(.94); } 100% { opacity: 0; transform: scale(1.18, 1.6); } }
@keyframes ft-ring-round { 0% { opacity: .9; transform: scale(.9); } 100% { opacity: 0; transform: scale(1.6); } }
@keyframes ft-press { 0%, 100% { transform: none; } 35% { transform: scale(.88); } }
@keyframes ft-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-.3em); } }
@keyframes ft-shadow { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .72; transform: scale(.9); } }

/* ---------- the window: a job screen of the app -------------------------------- */
.pt-part-fit .ft-win { position: absolute; left: 0; top: .5em; width: 23.4em; height: 18.6em; transform-style: preserve-3d; }
.pt-part-fit .ft-win::after { content: ""; position: absolute; left: 7%; right: 7%; top: calc(100% - .9em); height: 1.4em; pointer-events: none;
  background: radial-gradient(closest-side, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)); animation: ft-shadow 6s ease-in-out infinite; }
.pt-part-fit .ft-wfloat { position: absolute; inset: 0; transform-style: preserve-3d; animation: ft-bob 6s ease-in-out infinite; }
.pt-part-fit .ft-wcard { position: absolute; inset: 0; display: flex; flex-direction: column; padding: .85em .9em .8em; border-radius: .95em; color: var(--pt-bone);
  background: 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(7deg) rotateX(3deg); }
/* Its accent band along the top; in step 3 the business's colour sweeps
   along it from the side of the kit. */
.pt-part-fit .ft-band { position: absolute; left: 0; right: 0; top: 0; height: .3em; border-radius: .95em .95em 0 0; overflow: hidden; background: var(--pt-sage); }
.pt-part-fit .ft-acc-band { opacity: 1; transform: scaleX(0); transform-origin: 100% 50%; border-radius: 0; }

/* The title bar: the logo, the title, the job's status. */
.pt-part-fit .ft-bar { flex: 0 0 auto; display: flex; align-items: center; gap: .5em; height: 2.55em; padding-bottom: .5em; margin-bottom: .55em;
  box-shadow: 0 1px 0 rgba(236, 230, 218, .08); }
.pt-part-fit .ft-logo { position: relative; flex: 0 0 auto; display: grid; width: 6.1em; height: 1.95em; }
.pt-part-fit .ft-logo > * { grid-area: 1 / 1; }
.pt-part-fit .ft-logo-ph { display: flex; align-items: center; justify-content: center; border-radius: .5em; border: 1px dashed rgba(236, 230, 218, .2);
  font: 600 .8em/1 var(--pt-text); color: var(--pt-lichen); transition: opacity .3s ease; }
.pt-part-fit .ft-logo-on,
.pt-part-fit .ft-klogo { position: relative; display: flex; align-items: center; gap: .38em; }
.pt-part-fit .ft-logo-on span,
.pt-part-fit .ft-klogo span { font: 700 .84em/1.1 var(--pt-text); letter-spacing: -.005em; color: var(--pt-bone); white-space: nowrap; }
.pt-part-fit .ft-glyph { position: relative; flex: 0 0 auto; width: 1.75em; height: 1.75em; border-radius: .5em; background: var(--pt-clay);
  box-shadow: inset 0 0 0 1px rgba(14, 19, 17, .12); }
.pt-part-fit .ft-glyph i { position: absolute; left: 50%; top: 50%; width: .86em; height: .86em; margin: -.43em 0 0 -.43em; border-radius: 50%;
  border: .19em solid var(--pt-night); }
.pt-part-fit .ft-logo-on { opacity: 0; transform: scale(.6); transition: opacity .25s ease, transform .5s var(--pt-spring); }
.pt-part-fit .ft-logo-on::before { content: ""; position: absolute; inset: -.3em -.4em; border-radius: .7em; border: .12em solid var(--pt-clay); opacity: 0; pointer-events: none; }
.pt-part-fit .ft-title { flex: 1 1 auto; min-width: 0; font: 500 .88em/1.2 var(--pt-head); letter-spacing: -.01em; color: var(--pt-bone); white-space: nowrap; }
.pt-part-fit .ft-tw.is-typing::after { content: ""; display: inline-block; width: 2px; height: 1.05em; margin-left: 1px; vertical-align: -.17em;
  background: var(--pt-sage); animation: ft-caret .9s linear infinite; }
@keyframes ft-caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
.pt-part-fit .ft-pill { position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: .42em; padding: .32em .72em; border-radius: 999px;
  background: rgba(157, 184, 159, .2); color: var(--pt-bone); font: 600 .8em/1.2 var(--pt-text); white-space: nowrap; }
.pt-part-fit .ft-acc-pill { background: rgba(208, 154, 124, .26); }
.pt-part-fit .ft-pill > span { position: relative; }
.pt-part-fit .ft-pdot { position: relative; flex: 0 0 auto; width: .55em; height: .55em; border-radius: 50%; background: var(--pt-sage); }
.pt-part-fit .ft-pdot::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: var(--pt-clay); opacity: 0; transition: opacity .4s ease; }

/* The body: the item and its parts on the left, the status steps on the right. */
.pt-part-fit .ft-body { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 9.3em; column-gap: .8em; }
.pt-part-fit .ft-left,
.pt-part-fit .ft-right { display: flex; flex-direction: column; min-width: 0; }
.pt-part-fit .ft-lbl { display: block; margin: 0 0 .3em .1em; font: 600 .8em/1.2 var(--pt-text); letter-spacing: .02em; color: var(--pt-lichen); }

/* The job's card: which trade, its icon, its model and its id. Two cards
   in one place (Dimitar, 02/10/2026: "replace the card by sliding it out
   and the new in"): the appliance job's card swings out to the left and
   away, the vehicle job's swings in from the right and settles (0), and
   stays for the rest of the story. Solid, and over the steps beside it, so
   nothing shows through while it passes. */
.pt-part-fit .ft-jobs { position: relative; z-index: 2; flex: 0 0 auto; display: grid; margin: 0 0 .65em; perspective: 26em; }
.pt-part-fit .ft-jobs > * { grid-area: 1 / 1; }
.pt-part-fit .ft-job { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: .42em; min-width: 0;
  padding: .5em .65em .55em .95em; border-radius: .75em;
  background: linear-gradient(rgba(236, 230, 218, .06), rgba(236, 230, 218, .06)), var(--pt-raised);
  box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .1), 0 .6em 1.1em -.5em rgba(0, 0, 0, .55); }
.pt-part-fit .ft-jedge { position: absolute; left: .32em; top: .6em; bottom: .6em; width: .2em; border-radius: .1em; overflow: hidden; background: var(--pt-sage); }
.pt-part-fit .ft-acc-edge { border-radius: 0; }
.pt-part-fit .ft-trade { position: relative; display: inline-flex; align-items: center; gap: .4em; padding: .2em .58em; border-radius: 999px;
  background: rgba(236, 230, 218, .09); font: 600 .8em/1.2 var(--pt-text); color: var(--pt-bone); white-space: nowrap; }
.pt-part-fit .ft-trade > span { position: relative; }
.pt-part-fit .ft-tdot { flex: 0 0 auto; width: .5em; height: .5em; border-radius: 50%; background: var(--pt-lichen); }
.pt-part-fit .ft-trade::before { content: ""; position: absolute; inset: -.24em; border-radius: inherit; border: .12em solid var(--pt-bone); opacity: 0; pointer-events: none; }
.pt-part-fit .ft-item { display: flex; align-items: center; gap: .65em; min-width: 0; }
.pt-part-fit .ft-icon { flex: 0 0 auto; display: grid; place-items: center; width: 2.5em; height: 2.5em; border-radius: .65em;
  background: rgba(236, 230, 218, .07); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .1); }
.pt-part-fit .ft-icon svg { width: 1.5em; height: 1.5em; fill: none; stroke: var(--pt-bone); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; overflow: visible; }
.pt-part-fit .ft-itx { display: flex; flex-direction: column; gap: .16em; min-width: 0; }
.pt-part-fit .ft-model { font: 600 .88em/1.25 var(--pt-text); color: var(--pt-bone); white-space: nowrap; }
.pt-part-fit .ft-id { font: 500 .8em/1.25 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The appliance card is tinted in the mist blue (Dimitar, 02/10/2026:
   "avalir card in fit - tint in blue"): a low mist wash over the raised
   colour, a mist band along its top (in place of the side edge), its chip,
   dot and icon tile in mist. The vehicle card keeps the plain wash and the
   sage edge, so the swap reads as another card. Contrast on the wash, worked
   from these colours: bone 9.8:1, lichen (the serial) 5.4:1, bone on the
   chip 6.1:1. */
.pt-part-fit .ft-job-was { overflow: hidden;
  background: linear-gradient(rgba(147, 183, 198, .16), rgba(147, 183, 198, .16)), var(--pt-raised);
  box-shadow: inset 0 0 0 1px rgba(147, 183, 198, .3), 0 .6em 1.1em -.5em rgba(0, 0, 0, .55); }
.pt-part-fit .ft-job-was .ft-jedge { left: 0; right: 0; top: 0; bottom: auto; width: auto; height: .22em; border-radius: 0; background: var(--pt-mist); }
.pt-part-fit .ft-job-was .ft-trade { background: rgba(147, 183, 198, .24); }
.pt-part-fit .ft-job-was .ft-tdot { background: var(--pt-mist); }
.pt-part-fit .ft-job-was .ft-icon { background: rgba(147, 183, 198, .14); box-shadow: inset 0 0 0 1px rgba(147, 183, 198, .28); }
/* The slide, like a carousel, clipped to the job card's own column: the old
   card slides out to the left its whole width, fully visible all the way,
   and the new one slides in from the right (Dimitar, 02/10/2026: "i dont see
   the card slide in and out animation in fit when switching the design"; it
   was a short swing that mostly faded). The clip is sideways only, so the
   cards' shadows still show (the delays are in step 0 below). */
.pt-part-fit .ft-jobs { clip-path: inset(-1.5em 0 -1.5em 0); }
.pt-part-fit .ft-job-was { transition: transform .75s cubic-bezier(.55, 0, .45, 1), opacity 0s linear .75s; }
.pt-part-fit .ft-job-now { transform: translate3d(108%, 0, 0); transition: transform .8s cubic-bezier(.25, .8, .3, 1); }
/* Once out of sight it is also transparent, so nothing counts it as on the
   page beyond the screen's edge (a 360 phone's checks did). */
.pt-part-fit.pt-s0 .ft-job-was { transform: translate3d(-108%, 0, 0); opacity: 0; }
.pt-part-fit.pt-s0 .ft-job-now { transform: none; }

/* The parts: their names typed in the business's own words (2). */
.pt-part-fit .ft-parts { display: flex; flex-direction: column; gap: .3em; }
.pt-part-fit .ft-part { display: flex; align-items: baseline; justify-content: space-between; gap: .6em; padding: .34em .6em .38em; border-radius: .55em;
  background: rgba(236, 230, 218, .045); box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .06); }
.pt-part-fit .ft-pn { min-width: 0; font: 600 .84em/1.25 var(--pt-text); color: var(--pt-bone); white-space: nowrap; }
.pt-part-fit .ft-pn.is-typing::after { content: ""; display: inline-block; width: 2px; height: 1.05em; margin-left: 1px; vertical-align: -.17em;
  background: var(--pt-sage); animation: ft-caret .9s linear infinite; }
.pt-part-fit .ft-q { flex: 0 0 auto; font: 600 .8em/1.25 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; font-variant-numeric: tabular-nums; }
.pt-part-fit .ft-q small { margin-right: .1em; font-size: 1em; }

/* The status steps: a rail, a dot each, the job's own (In service) lit.
   Each holds its final place; before step 1, In service and Quote sit in
   each other's, Ready one higher and Waiting for parts is not there yet. */
.pt-part-fit .ft-flow { position: relative; flex: 0 0 auto; height: calc(5 * 1.55em); }
.pt-part-fit .ft-rail { position: absolute; left: .55em; top: .7em; bottom: .7em; width: .1em; margin-left: -.05em; border-radius: .05em; background: rgba(236, 230, 218, .12); }
.pt-part-fit .ft-st { position: absolute; left: 0; right: 0; height: 1.55em; display: flex; align-items: center; gap: .5em; padding-left: .25em;
  transition: transform .6s var(--pt-spring), opacity .3s ease; }
.pt-part-fit .ft-st-booked { top: 0; }
.pt-part-fit .ft-st-service { top: 1.55em; transform: translateY(1.55em); }
.pt-part-fit .ft-st-quote { top: 3.1em; transform: translateY(-1.55em); }
.pt-part-fit .ft-st-wait { top: 4.65em; opacity: 0; transform: translateX(1.2em) scale(.9); transform-origin: 0 50%; }
.pt-part-fit .ft-st-ready { top: 6.2em; transform: translateY(-1.55em); }
.pt-part-fit.pt-s1 .ft-st { opacity: 1; transform: none; }
.pt-part-fit .ft-sdot { position: relative; flex: 0 0 auto; width: .62em; height: .62em; border-radius: 50%; background: var(--pt-raised);
  box-shadow: inset 0 0 0 .12em rgba(236, 230, 218, .35); }
.pt-part-fit .ft-st > span { position: relative; font: 500 .8em/1.2 var(--pt-text); color: var(--pt-lichen); white-space: nowrap; }
.pt-part-fit .ft-st.is-now .ft-sdot { background: var(--pt-sage); box-shadow: 0 0 0 .2em rgba(157, 184, 159, .2); }
.pt-part-fit .ft-acc-dot { border-radius: 50%; }
.pt-part-fit .ft-st.is-now > span { font-weight: 700; color: var(--pt-bone); }
/* The new step: a sage outline that rings as it lands (on the label, round
   its words, so the words' own box holds them). */
.pt-part-fit .ft-sl { display: block; }
.pt-part-fit .ft-sl > span { display: block; }
.pt-part-fit .ft-st-wait .ft-sl::after { content: ""; position: absolute; inset: -.25em -.45em; border-radius: .5em; border: .12em solid var(--pt-sage); opacity: 0; pointer-events: none; }

/* The foot: the app's modules; two more click into place as you grow (4). */
.pt-part-fit .ft-foot { flex: 0 0 auto; display: flex; align-items: center; gap: .4em; padding-top: .55em; margin-top: .55em; box-shadow: 0 -1px 0 rgba(236, 230, 218, .08); }
.pt-part-fit .ft-mod { position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: .38em; padding: .34em .72em; border-radius: .6em;
  background: rgba(236, 230, 218, .06); color: var(--pt-lichen); font: 600 .8em/1.2 var(--pt-text); white-space: nowrap; }
.pt-part-fit .ft-mod > span { position: relative; }
.pt-part-fit .ft-mod.is-on { background: rgba(157, 184, 159, .2); color: var(--pt-bone); }
.pt-part-fit .ft-acc-mod { background: rgba(208, 154, 124, .26); }
.pt-part-fit .ft-mod [data-ft].is-typing::after { content: ""; display: inline-block; width: 2px; height: 1.05em; margin-left: 1px; vertical-align: -.17em;
  background: var(--pt-sage); animation: ft-caret .9s linear infinite; }
.pt-part-fit .ft-add { background: rgba(157, 184, 159, .12); color: var(--pt-bone); box-shadow: inset 0 0 0 1px rgba(157, 184, 159, .4);
  opacity: 0; transform: translateY(-.9em) scale(.6); transition: opacity .2s ease, transform .5s cubic-bezier(.3, 1.6, .5, 1); }
.pt-part-fit.pt-s4 .ft-add { opacity: 1; transform: none; }
.pt-part-fit .ft-add::before { content: ""; position: absolute; inset: -.24em; border-radius: .75em; border: .12em solid var(--pt-sage); opacity: 0; pointer-events: none; }
.pt-part-fit .ft-plus { position: relative; flex: 0 0 auto; width: .9em; height: .9em; border-radius: 50%; background: var(--pt-sage); }
.pt-part-fit .ft-plus::before,
.pt-part-fit .ft-plus::after { content: ""; position: absolute; left: 50%; top: 50%; width: .5em; height: .12em; margin: -.06em 0 0 -.25em; border-radius: .06em; background: var(--pt-night); }
.pt-part-fit .ft-plus::after { transform: rotate(90deg); }

/* ---------- the brand kit: the business's logo and colours ------------------- */
.pt-part-fit .ft-kit { position: absolute; left: 24.3em; top: .4em; width: 10.6em; height: 6.15em; transform-style: preserve-3d; transform: translateZ(.8em); }
.pt-part-fit .ft-kfloat { position: absolute; inset: 0; transform-style: preserve-3d; animation: ft-bob 5.2s ease-in-out -1.6s infinite; }
.pt-part-fit .ft-kcard { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; gap: .5em; padding: .55em .75em .65em;
  border-radius: .85em; color: var(--pt-bone);
  background: linear-gradient(180deg, rgba(236, 230, 218, .07), rgba(236, 230, 218, .02) 60%), var(--pt-raised);
  box-shadow: 0 1.2em 2em -1em rgba(0, 0, 0, .7), inset 0 0 0 1px rgba(236, 230, 218, .08);
  transform: rotateY(-10deg) rotateX(3deg); }
.pt-part-fit .ft-klbl { font: 600 .8em/1.2 var(--pt-text); letter-spacing: .02em; color: var(--pt-lichen); white-space: nowrap; }
/* The logo in its slot: it leaves for the window in step 3, the slot stays. */
.pt-part-fit .ft-kslot { position: relative; display: block; width: 6.1em; height: 1.95em; border-radius: .5em; box-shadow: inset 0 0 0 1px rgba(236, 230, 218, .12); }
.pt-part-fit .ft-klogo { height: 100%; }
.pt-part-fit.pt-s3 .ft-klogo { opacity: 0; }
.pt-part-fit .ft-sw { display: flex; gap: .45em; }
.pt-part-fit .ft-dot { position: relative; flex: 0 0 auto; width: 1.2em; height: 1.2em; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(14, 19, 17, .2); }
.pt-part-fit .ft-d1 { background: var(--pt-sage); }
.pt-part-fit .ft-d2 { background: var(--pt-mist); }
.pt-part-fit .ft-d3 { background: var(--pt-clay); }
.pt-part-fit .ft-d4 { background: var(--pt-dusk); }
/* The picked colour wears the ring: sage, the app's own, then clay (3). */
.pt-part-fit .ft-ring { position: absolute; inset: -.24em; border-radius: 50%; border: .13em solid var(--pt-bone); transition: opacity .3s ease, transform .45s var(--pt-spring); }
.pt-part-fit .ft-ring3 { opacity: 0; transform: scale(.6); }
.pt-part-fit.pt-s3 .ft-ring1 { opacity: 0; transform: scale(.6); }
.pt-part-fit.pt-s3 .ft-ring3 { opacity: 1; transform: none; }
.pt-part-fit .ft-tap { position: absolute; left: 50%; top: 50%; width: 2.4em; height: 2.4em; margin: -1.2em 0 0 -1.2em; border-radius: 50%;
  border: .13em solid var(--pt-bone); background: rgba(236, 230, 218, .2); opacity: 0; pointer-events: none; }
@keyframes ft-tap { 0% { opacity: .9; transform: scale(.3); } 100% { opacity: 0; transform: scale(1.4); } }

/* ---------- Ai, Db and Ai's line ------------------------------------------------ */
/* They stand under the kit, Ai with his line above him. Their own bobs are
   small here (5 px), and the room between them is measured for a hop at its
   top (fit.js: the lift below and site.css's swell), so the line and the two
   of them keep their air even then. */
.pt-part-fit .ft-ai { position: absolute; left: 24.45em; top: 13em; width: 4.6em; transform: translateZ(1.1em); transform-style: preserve-3d; pointer-events: none; }
.pt-part-fit .ft-db { position: absolute; left: 30.45em; top: 13.8em; width: 4.4em; transform: translateZ(.9em); pointer-events: none; }
.pt-part-fit .ft-ai .pt-char,
.pt-part-fit .ft-db .pt-char { width: 100%; }
.pt-part-fit .ft-ai .pt-char-ai,
.pt-part-fit .ft-db .pt-char-db { animation-name: ft-hover; }
@keyframes ft-hover { 0%, 100% { translate: 0 0; } 50% { translate: 0 -5px; } }
.pt-part-fit .ft-ai-in,
.pt-part-fit .ft-db-in { transform-origin: 50% 100%; }
@keyframes ft-hop { 0%, 62%, 100% { transform: translateY(0); } 35% { transform: translateY(-.5em); } 78% { transform: translateY(-.12em); } }
.pt-part-fit.fb-hop:not(.ft-quick) .ft-ai-in { animation: ft-hop .62s cubic-bezier(.3, .7, .4, 1); }
.pt-part-fit.fb-dbhop:not(.ft-quick) .ft-db-in { animation: ft-hop .62s cubic-bezier(.3, .7, .4, 1); }
/* While the words are typed (2) he points at the window: it is on his left,
   so the left hand. */
.pt-part-fit .ft-ai .pt-arm-left { transition: rotate .4s var(--pt-spring); }
.pt-part-fit .ft-ai .pt-char-ai.is-talking .pt-arm-left { rotate: 64deg; }
.pt-part-fit .ft-ai .pt-char-ai.is-talking .pt-arm-right { animation: none; rotate: 0deg; }
/* His line, over him, its tail down to his head. */
.pt-part-fit .ft-say { position: absolute; left: 24.3em; top: 7.25em; width: 10.6em; transform: translateZ(.9em); }
.pt-part-fit .ft-say .pt-bubble { max-width: none; margin: 0; padding: .72em .95em; font-size: .86em; line-height: 1.4;
  border-radius: 1.3em 1.3em 1.3em .45em; transform-origin: 20% 100%; }
.pt-part-fit .ft-say .pt-bubble::before { left: calc(2.9em - 6px); bottom: -6px; clip-path: polygon(0 0, 100% 100%, 0 100%); }
html:not(.js) .pt-part-fit .pt-bubble { opacity: 1; transform: none; }

/* ---------- step 0: your trade ------------------------------------------------- */
/* The appliance card slides out from .45 s to 1.2 s; the vehicle card slides
   in from .95 s and settles at 1.75 s, so for a moment both are seen, the
   blue one leaving on the left, the new one coming in on the right; its chip
   rings as it settles. */
.pt-part-fit:not(.ft-quick)[data-step="0"] .ft-job-was { transition-delay: .45s, 1.2s; }
.pt-part-fit:not(.ft-quick)[data-step="0"] .ft-job-now { transition-delay: .95s; }
.pt-part-fit:not(.ft-quick)[data-step="0"] .ft-job-now .ft-trade::before { animation: ft-ring .7s ease-out 1.8s forwards; }
@keyframes ft-pop { 0%, 100% { transform: none; } 40% { transform: scale(1.08); } }

/* ---------- step 1: your statuses and steps -------------------------------------- */
/* In service and Quote change places, Ready makes room, Waiting for parts
   slides in and rings. */
.pt-part-fit:not(.ft-quick)[data-step="1"] .ft-st-service,
.pt-part-fit:not(.ft-quick)[data-step="1"] .ft-st-quote { transition-delay: .5s; }
.pt-part-fit:not(.ft-quick)[data-step="1"] .ft-st-ready { transition-delay: 1.25s; }
.pt-part-fit:not(.ft-quick)[data-step="1"] .ft-st-wait { transition-delay: 1.5s; }
.pt-part-fit:not(.ft-quick)[data-step="1"] .ft-st-wait .ft-sl::after { animation: ft-ring .7s ease-out 1.9s forwards; }
.pt-part-fit:not(.ft-quick)[data-step="1"] .ft-st-service .ft-sdot { animation: ft-pop .5s ease-out 1s; }

/* ---------- step 2: your parts and your words ------------------------------------ */
/* All typed (fit.js), in the page's order: the title's word, the two parts,
   the module's name. */

/* ---------- step 3: your logo and colours ---------------------------------------- */
/* The logo lifts out of the kit and flies onto the window's title bar (the
   way to it in --fx and --fy, per cut), it lands there with a ring, Ai
   hops; then the clay swatch is tapped, its ring moves over, and the colour
   sweeps across the screen from the kit's side: the band, the status, the
   step, the job card's edge, the module. */
.pt-part-fit .ft-klogo { --fx: -25.6em; --fy: -1.2em; }   /* measured: the kit and the window lean apart, so the way is longer than the plan's */
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-klogo { animation: ft-fly 1.2s cubic-bezier(.45, .05, .4, 1) .35s both; }
@keyframes ft-fly {
  0% { opacity: 1; transform: none; }
  18% { opacity: 1; transform: translate3d(0, -.5em, 1.2em) scale(1.08); }
  88% { opacity: 1; transform: translate3d(var(--fx), var(--fy), .6em) scale(1); }
  100% { opacity: 0; transform: translate3d(var(--fx), var(--fy), 0) scale(1); }
}
.pt-part-fit.pt-s3 .ft-logo-ph { opacity: 0; }
.pt-part-fit.pt-s3 .ft-logo-on { opacity: 1; transform: none; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-logo-ph { transition-delay: 1.3s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-logo-on { transition-delay: 1.38s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-logo-on::before { animation: ft-ring .7s ease-out 1.5s forwards; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-tap { animation: ft-tap .6s ease-out 1.85s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-ring1 { transition-delay: 1.9s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-ring3 { transition-delay: 1.95s; }
.pt-part-fit.pt-s3 .ft-acc { opacity: 1; }
.pt-part-fit.pt-s3 .ft-acc-band { transform: none; }
.pt-part-fit.pt-s3 .ft-pdot::after { opacity: 1; }
.pt-part-fit .ft-acc { transition: opacity .45s ease, transform .8s cubic-bezier(.5, 0, .3, 1); }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-acc-band { transition-delay: 2.1s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-acc-pill,
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-pdot::after { transition-delay: 2.15s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-acc-dot { transition-delay: 2.35s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-acc-edge { transition-delay: 2.55s; }
.pt-part-fit:not(.ft-quick)[data-step="3"] .ft-acc-mod { transition-delay: 2.75s; }

/* ---------- step 4: changes as you grow ------------------------------------------ */
/* Branch 2 and Team 2 click into place, each with a ring; the window gives a
   small jolt as each lands, Db and Ai hop (fit.js). */
.pt-part-fit:not(.ft-quick)[data-step="4"] .ft-add1 { transition-delay: .5s; }
.pt-part-fit:not(.ft-quick)[data-step="4"] .ft-add2 { transition-delay: .95s; }
.pt-part-fit:not(.ft-quick)[data-step="4"] .ft-add1::before { animation: ft-ring .7s ease-out .75s forwards; }
.pt-part-fit:not(.ft-quick)[data-step="4"] .ft-add2::before { animation: ft-ring .7s ease-out 1.2s forwards; }
.pt-part-fit:not(.ft-quick)[data-step="4"] .ft-wcard { animation: ft-jolt 1s linear .5s; }
@keyframes ft-jolt {
  0%, 100% { transform: rotateY(7deg) rotateX(3deg); }
  20% { transform: rotateY(7deg) rotateX(3deg) translateY(.12em); }
  40% { transform: rotateY(7deg) rotateX(3deg); }
  65% { transform: rotateY(7deg) rotateX(3deg) translateY(.12em); }
  85% { transform: rotateY(7deg) rotateX(3deg); }
}

/* ---------- the rewind: the window's contents and the kit fade, the set stays */
.pt-part-fit .ft-body,
.pt-part-fit .ft-bar,
.pt-part-fit .ft-foot,
.pt-part-fit .ft-kcard > * { transition: opacity .35s ease; }
.pt-part-fit.is-rewind .ft-body,
.pt-part-fit.is-rewind .ft-bar,
.pt-part-fit.is-rewind .ft-foot,
.pt-part-fit.is-rewind .ft-kcard > * { opacity: 0; }

/* ---------- the steps under the scene ------------------------------------------ */
/* Desktop: small floating pills in a compact wrap; the one in play is lit in
   sage, the layer's tone. */
.pt-part-fit .pt-part-steps { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 12px 0 0; padding: 0; list-style: none; }
.pt-part-fit .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: ft-pill 6s ease-in-out infinite; animation-delay: calc(var(--i, 0) * -1.2s); }
.pt-part-fit .pt-part-steps li::before { content: ""; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--tone, var(--pt-sage)); opacity: .45; transform: scale(.85); transition: opacity .3s ease, transform .4s var(--pt-spring); }
.pt-part-fit .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(157, 184, 159, .55), rgba(157, 184, 159, 0) 15px), rgba(236, 230, 218, .07);
  opacity: 0; transition: opacity .35s ease; }
.pt-part-fit .pt-part-steps li > span { opacity: .7; transition: opacity .3s ease; }
.pt-part-fit .pt-part-steps li.is-on::before { opacity: 1; transform: scale(1.2); }
.pt-part-fit .pt-part-steps li.is-on::after,
.pt-part-fit .pt-part-steps li.is-on > span { opacity: 1; }
.pt-part-fit .pt-part-steps li:focus-visible { outline: 2px solid var(--pt-sand); outline-offset: 3px; }
@keyframes ft-pill { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }

/* Nothing moves in a layer that is not the one in front (the characters'
   own loops are paused by site.css). */
.pt-layer:not(.is-current) .pt-part-fit .ft-wfloat,
.pt-layer:not(.is-current) .pt-part-fit .ft-kfloat,
.pt-layer:not(.is-current) .pt-part-fit .ft-win::after,
.pt-layer:not(.is-current) .pt-part-fit .ft-tw::after,
.pt-layer:not(.is-current) .pt-part-fit .ft-pn::after,
.pt-layer:not(.is-current) .pt-part-fit .ft-mod [data-ft]::after,
.pt-layer:not(.is-current) .pt-part-fit .pt-part-steps li { animation-play-state: paused; }

/* ---------- the lg column (992 to 1199, 444 wide): the steps as dots ---------- */
@media (min-width: 992px) and (max-width: 1199.98px) {
  html.js .pt-part-fit .pt-part-steps { position: relative; flex-wrap: nowrap; justify-content: center; gap: 0; height: 50px; margin-top: 12px; }
  html.js .pt-part-fit .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-fit .pt-part-steps li::after { display: none; }
  html.js .pt-part-fit .pt-part-steps li.is-on::before { transform: scale(1.45); }
  html.js .pt-part-fit .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-fit .pt-part-steps li.is-on > span { opacity: 1; transform: none; pointer-events: auto; }
  html.js .pt-part-fit[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 (206 px), about 281 px are left for the whole
   component, clear of the arrow. A fixed 12.5px unit: the kit, Ai and Db on
   a row, the window under them the whole width, 246 px, 271 with the
   caption. No line (no room: the kit and the window carry the story), the
   window's two small labels gone, the cards turned less. */
@media (max-width: 575.98px) {
  .pt-part-fit .pt-part-stage { font-size: 12.5px; perspective: 900px; perspective-origin: 50% 45%; }
  .pt-part-fit .ft-set { width: 25.6em; height: 19.7em; }
  .pt-part-fit .ft-set::before { background:
      radial-gradient(46% 36% at 50% 74%, rgba(157, 184, 159, .10), rgba(157, 184, 159, 0) 72%),
      radial-gradient(30% 18% at 76% 12%, rgba(217, 197, 154, .08), rgba(217, 197, 154, 0) 72%); }
  .pt-part-fit .ft-say { display: none; }
  .pt-part-fit .ft-kit { left: 0; top: .1em; width: 15.4em; height: 4.4em; transform: translateZ(.5em); }
  .pt-part-fit .ft-kcard { display: grid; grid-template-columns: auto 1fr; grid-template-areas: "lbl lbl" "slot sw"; align-content: center;
    column-gap: .8em; row-gap: .35em; padding: .45em .6em .5em; transform: rotateY(-5deg) rotateX(3deg); }
  .pt-part-fit .ft-klbl { grid-area: lbl; }
  .pt-part-fit .ft-kslot { grid-area: slot; }
  .pt-part-fit .ft-sw { grid-area: sw; align-self: center; gap: .35em; }
  .pt-part-fit .ft-dot { width: 1.05em; height: 1.05em; }
  .pt-part-fit .ft-ai { left: 16.2em; top: .05em; width: 3.55em; transform: translateZ(.6em); }
  .pt-part-fit .ft-db { left: 21em; top: .45em; width: 3.6em; transform: translateZ(.5em); }
  .pt-part-fit .ft-ai .pt-char-ai,
  .pt-part-fit .ft-db .pt-char-db { animation-name: ft-hover-sm; }
  .pt-part-fit .ft-win { top: 5.15em; width: 25.6em; height: 14.45em; }
  .pt-part-fit .ft-win::after { top: calc(100% - .7em); height: 1em; }
  .pt-part-fit .ft-wfloat,
  .pt-part-fit .ft-kfloat { animation-name: ft-bob-sm; }
  .pt-part-fit .ft-wcard { padding: .62em .7em .55em; border-radius: .85em; transform: rotateY(3deg) rotateX(2deg); }
  .pt-part-fit:not(.ft-quick)[data-step="4"] .ft-wcard { animation-name: ft-jolt-sm; }
  .pt-part-fit .ft-bar { height: 2.25em; padding-bottom: .4em; margin-bottom: .4em; }
  .pt-part-fit .ft-body { grid-template-columns: minmax(0, 1fr) 9.6em; column-gap: .6em; }
  .pt-part-fit .ft-lbl { display: none; }
  .pt-part-fit .ft-jobs { margin-bottom: .45em; }
  .pt-part-fit .ft-job { gap: .3em; padding: .38em .55em .42em .85em; }
  .pt-part-fit .ft-job-was .ft-jedge { height: .18em; }
  .pt-part-fit .ft-trade { padding: .16em .5em; }
  .pt-part-fit .ft-item { gap: .55em; }
  .pt-part-fit .ft-icon { width: 2.2em; height: 2.2em; }
  .pt-part-fit .ft-icon svg { width: 1.35em; height: 1.35em; }
  .pt-part-fit .ft-part { padding: .26em .55em .3em; }
  .pt-part-fit .ft-parts { gap: .25em; }
  .pt-part-fit .ft-flow { height: calc(5 * 1.4em); }
  .pt-part-fit .ft-st { height: 1.4em; }
  .pt-part-fit .ft-st-service { top: 1.4em; transform: translateY(1.4em); }
  .pt-part-fit .ft-st-quote { top: 2.8em; transform: translateY(-1.4em); }
  .pt-part-fit .ft-st-wait { top: 4.2em; }
  .pt-part-fit .ft-st-ready { top: 5.6em; transform: translateY(-1.4em); }
  .pt-part-fit .ft-foot { padding-top: .42em; margin-top: .42em; }
  .pt-part-fit .ft-mod { padding: .28em .62em; }
  /* the logo drops from the kit straight down onto the bar */
  .pt-part-fit .ft-klogo { --fx: 0em; --fy: 3.9em; }
}
@keyframes ft-hover-sm { 0%, 100% { translate: 0 0; } 50% { translate: 0 -4px; } }
@keyframes ft-bob-sm { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-.2em); } }
@keyframes ft-jolt-sm {
  0%, 100% { transform: rotateY(3deg) rotateX(2deg); }
  20% { transform: rotateY(3deg) rotateX(2deg) translateY(.1em); }
  40% { transform: rotateY(3deg) rotateX(2deg); }
  65% { transform: rotateY(3deg) rotateX(2deg) translateY(.1em); }
  85% { transform: rotateY(3deg) rotateX(2deg); }
}
/* 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-fit .pt-part-steps { position: relative; flex-wrap: nowrap; justify-content: flex-start; gap: 0; height: 22px; margin-top: 3px; }
  html.js .pt-part-fit .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-fit .pt-part-steps li::after { display: none; }
  html.js .pt-part-fit .pt-part-steps li::before { width: 6px; height: 6px; }
  html.js .pt-part-fit .pt-part-steps li.is-on::before { transform: scale(1.5); }
  html.js .pt-part-fit .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-fit .pt-part-steps li.is-on > span { opacity: 1; transform: none; pointer-events: auto; }
  html.js .pt-part-fit[data-step="-1"] .pt-part-steps li:first-child > span { opacity: .5; transform: none; }
}
/* Tall phones (390x844 leaves about 440 px below the copy): room for Ai's
   line. Ai, his line and Db on the first row, the kit the whole width under
   them (its label, logo and swatches in one line), then the window. After
   the phone rules, which it overrides. */
@media (max-width: 575.98px) and (min-height: 760px) {
  .pt-part-fit .pt-part-stage { font-size: max(12.5px, min(13.6px, calc(100cqw / 25.6))); }
  .pt-part-fit .ft-set { height: 26.1em; }
  .pt-part-fit .ft-set::before { background:
      radial-gradient(46% 30% at 50% 78%, rgba(157, 184, 159, .10), rgba(157, 184, 159, 0) 72%),
      radial-gradient(16% 14% at 10% 12%, rgba(147, 183, 198, .10), rgba(147, 183, 198, 0) 72%); }
  .pt-part-fit .ft-ai { left: .2em; top: .1em; width: 4.2em; }
  .pt-part-fit .ft-say { display: block; left: 5.6em; top: .55em; width: 14.75em; transform: translateZ(.6em); }
  .pt-part-fit .ft-say .pt-bubble { border-radius: .45em 1.3em 1.3em 1.3em; transform-origin: 0 30%; }
  .pt-part-fit .ft-say .pt-bubble::before { left: -6px; bottom: auto; top: calc(1.35em - 6px); clip-path: polygon(0 0, 0 100%, 100% 100%); }
  .pt-part-fit .ft-db { left: 21.4em; top: .6em; width: 4em; }
  .pt-part-fit .ft-kit { left: 0; top: 6.1em; width: 25.6em; height: 3.5em; }
  .pt-part-fit .ft-kcard { grid-template-columns: auto auto 1fr; grid-template-areas: "lbl slot sw"; justify-items: start; padding: .4em .7em; }
  .pt-part-fit .ft-sw { justify-self: end; gap: .45em; }
  .pt-part-fit .ft-dot { width: 1.15em; height: 1.15em; }
  .pt-part-fit .ft-win { top: 10.3em; height: 15.6em; }
  .pt-part-fit .ft-lbl { display: block; }
  .pt-part-fit .ft-klbl { display: block; }
  .pt-part-fit .ft-klogo { --fx: -5.1em; --fy: 4.15em; }
}

/* ---------- instant draws (fit.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. The characters'
   wrappers are not .ft-mv (the snap would stop their own bob inside). */
.pt-part-fit.ft-quick .ft-mv { transition-duration: .3s; transition-delay: 0s; animation: none; }
.pt-part-fit.ft-quick .ft-mv > *,
.pt-part-fit.ft-quick .ft-mv::before,
.pt-part-fit.ft-quick .ft-mv::after { transition-delay: 0s; animation: none; }
.pt-part-fit.ft-quick .ft-pdot::after,
.pt-part-fit.ft-quick .ft-wcard { transition-duration: .3s; transition-delay: 0s; animation: none; }
.pt-part-fit.ft-snap .ft-mv,
.pt-part-fit.ft-snap .ft-mv *,
.pt-part-fit.ft-snap .ft-mv::before,
.pt-part-fit.ft-snap .ft-mv::after,
.pt-part-fit.ft-snap .ft-pdot::after { transition: none; animation: none; }
