/* Reusable CONTACT-FORM (lead form) module — the .cform + .cff-* widgets for
   the public lead form (bio_sections/contact.html), the Contact Forms lab and
   the dashboard preview.

   ============================================================
   THIS FILE IS NOW HAND-MAINTAINED, AND IT IS THE ONLY PLACE THE
   LEAD FORM'S LOOK IS DEFINED.

   It used to be generated from inquiry_form_v2_concept.css through a 175-entry
   selector map, because the builder drew its own copy of every widget and the
   two had to be kept in step by hand. That is over: the builder now renders
   THIS module (see lead_form_preview_fragment), so the editor and the public
   page are the same markup and the same stylesheet, and there is no second
   copy left to sync.

   The generator is archived at _design_ref/retired/contact_form_layouts.gen.js.
   Do NOT run it: its input no longer holds the widget rules, so it would emit
   empty bodies and strip this file. Edit here instead.

   COLOURS still come from the VENDOR'S THEME, never from a design mock. Every
   rule below resolves through the palette bound in .cform, which reads the
   --cff-* properties Agent.contact_form_style writes inline. No rule below may
   hardcode a colour -- a fixed light-mock palette is what once rendered a
   dark-theme vendor's form black-on-black, every label present and unreadable.
   ============================================================ */

.cform {
  /* THE PALETTE BINDING. Every generated rule below refers to these names, and
     nothing below hardcodes a colour — the generator rewrites the concept's
     literals into them (see COLOR_MAP). This is therefore the only place the
     module's colour is decided, and it resolves to the VENDOR's theme via the
     --cff-* properties Agent.contact_form_style writes inline on this element.

     Why it must be the vendor's and not the concept's: the concept is a fixed
     LIGHT mock. Copying its literals made a dark-themed vendor page render the
     form in near-black on near-black — every label present, visible, and
     unreadable. Deriving the neutrals from --cff-ink instead means one set of
     rules works on any theme, because a translucent wash of the INK colour is
     always a contrasting surface against the page.

     The fallbacks are the concept's own values, for the lab/specimen pages that
     render the module with no vendor theme attached. */
  --ink:   var(--cff-ink, #0F172A);
  --brand: var(--cff-primary, #2D64BC);
  /* Only for a solid fill with --on-brand painted on top — --on-brand is
     proven safe against THIS, not against the raw --brand above it (same
     split as --lk-primary vs --lk-primary-fill in link_layouts.css). */
  --brand-fill: var(--cff-primary-fill, var(--cff-primary, #2D64BC));
  --on-brand: var(--cff-on-primary, #FFFFFF);
  /* No --accent binding. The theme's accent role used to paint the Yes/No
     chip, which made the one answered question on the page the odd one out —
     every other chosen-mark (radio dot, tick, selected package, 1-10 scale) is
     PRIMARY. Bind it from --cff-accent if a rule ever needs it again. */
  --page:  var(--cff-bg, #F5F6F8);
  /* --paper is a widget SURFACE and must stay distinct from --page, or every
     widget that relies on surface-vs-page contrast to show its shape (the 1-10
     scale, package cards, the tag box, the signature pad, popovers) disappears
     into the background. The concept gets this from two fixed literals
     (#FFFFFF on #F5F6F8); binding both to the vendor's one background role
     collapsed them and did exactly that — on the Filled style, where the
     border is transparent, the scale rendered as bare numbers.
     A small opaque wash of INK over the page lifts the surface in whichever
     direction the theme needs: darker on a light page, lighter on a dark one. */
  --paper: color-mix(in srgb, var(--ink) 4%, var(--cff-bg, #FFFFFF));
  --brand-deep: color-mix(in srgb, var(--brand) 82%, #000);
  --tint:      color-mix(in srgb, var(--brand) 12%, transparent);
  --ring:      color-mix(in srgb, var(--brand) 28%, transparent);
  --muted:     color-mix(in srgb, var(--ink) 68%, transparent);
  /* 48% matched the concept's own faint most closely, but left the privacy
     line and placeholders at ~2.8:1 on light themes. 58% keeps them quiet
     while clearing 3:1 on every theme tested. */
  --faint:     color-mix(in srgb, var(--ink) 58%, transparent);
  --hairline:  color-mix(in srgb, var(--ink) 10%, transparent);
  --line:      color-mix(in srgb, var(--ink) 18%, transparent);
  --line-soft: color-mix(in srgb, var(--ink) 11%, transparent);
  --soft:      color-mix(in srgb, var(--ink) 3%, transparent);
  --fill:      color-mix(in srgb, var(--ink) 6%, transparent);
  --fill-2:    color-mix(in srgb, var(--ink) 9%, transparent);
  /* Status hues stay fixed — they mean the same thing on every theme — but
     their SURFACES are mixed so they sit on a dark page as readily as a light
     one. Amber is the star colour and is deliberately not the brand. */
  --danger:#B4232A; --danger-tint: color-mix(in srgb, var(--danger) 12%, transparent);
  --good:#1B7A4B;   --good-tint:   color-mix(in srgb, var(--good) 12%, transparent);
  --amber:#E8A317;  --amber-tint:  color-mix(in srgb, var(--amber) 14%, transparent);
  /* DEVIATION 6, exactly as ui_concept.css states it: the kit gives mono its
     own role (labels, IDs, money, dates) and this codebase RETIRES it, because
     "mono on uppercase words read as clunky". That is precisely what happened
     when this module took the standalone controls library's token block at face
     value: the scale's end labels, the "Suggestions" heading and the weekday
     letters all came out monospaced, which is the look the house rule exists to
     prevent. The library is a standalone spec; ui_concept.css is this codebase's
     kit, and where they disagree the house wins.

     --mono stays as an ALIAS so every var(--mono) call site still resolves and
     nothing has to be rewritten; nothing renders monospace. The tabular-nums
     declarations beside them are deliberately left in place, per the same
     deviation, so this reverses cleanly: point this line back at --cff-mf (which
     Agent.contact_form_style still emits) and the numeric face returns
     everywhere at once. */
  --mono: var(--sans);
  /* the artifact's display face is Fraunces; the public page only loads the
     vendor's own fonts, so this resolves to those and falls back to the same
     serif class rather than silently rendering in a system font */
  --serif: var(--cff-hf, Georgia, serif);
  /* The controls library writes its families through these names rather than as
     literals -- "font: 400 15px/1.5 var(--sans)". Binding --sans to the VENDOR's
     face here is what makes those shorthands resolve to it. Leaving it unbound is
     silent: the shorthand falls back to the initial family and the whole form
     quietly renders in the browser default, not the vendor's.
     (No backticks in this file's CSS comments -- inside the HEADER template
     literal one terminates the string and the generator dies at parse time.) */
  --sans: var(--cff-bf, system-ui, -apple-system, 'Segoe UI', sans-serif);

  /* Field-style tokens. --f-radius-pick / --q-gap arrive as INLINE styles from
     contact.html carrying the vendor's Corners/Spacing picks. Radius is
     indirected through -pick because an inline custom property outranks any
     stylesheet rule: if the pill style wrote --f-radius directly the inline
     value would beat it and pills would render square-ish. */
  --f-radius-pick:10px;
  --f-radius: var(--f-radius-pick);
  /* THREE radii, not one. The controls library separates the corner of a FIELD
     (text input, select trigger, the Yes/No track) from the corner of an OPTION
     (choice row, scale button, tag chip, mark box, list option) and from the
     corner of a CARD or panel (package, dropzone, popover, signature pad, tag
     box, stepper) -- because the capsule that flatters a one-line input bows the
     sides of a tall box, and a card's corner on a 19px mark reads as unrounded.

     All three start from the vendor's Corners pick, which is the whole point: a
     literal radius anywhere in this module is a corner the vendor chose 2px or
     99px for and silently doesn't get. The only literals left below are SHAPES
     rather than corners -- 50% on a circular mark or thumb, 999px on a rail or
     a progress track -- and no Corners pick should be able to square those off. */
  --o-r: var(--f-radius);
  --c-r: var(--f-radius);
  /* Field fill is a translucent SCRIM of --paper, not an opaque block. The
     contact section frequently sits on the vendor's header/background image, and
     an opaque field fill punched a row of solid rectangles through it — the form
     read as a separate widget pasted over the page rather than part of it.
     Fading --paper (rather than mixing a fresh colour) keeps the exact hue the
     field has today and only drops its alpha, so every theme behaves as before
     over a flat background and only gains the show-through where there is
     something behind to show.
     --f-fill-alpha is the one knob: raise it if a vendor's background art is busy
     enough to fight the typed value, lower it for more show-through. It must stay
     high enough that the fill still separates the field from the page, which is
     the whole reason the field has a fill and not just a border. */
  --f-fill-alpha: 60%;
  --f-bg: color-mix(in srgb, var(--paper) var(--f-fill-alpha), transparent);
  /* The same scrim for surfaces that are NOT fields but still need a fill: the
     checkbox/radio mark, the 1-10 scale button, the signature pad. Separate from
     --f-bg because the Underline style zeroes the FIELD fill by design, and
     routing these through --f-bg took the marks and the pad down with it — an
     18px checkbox with no fill at all loses the tick's backing.
     It restates the mix rather than saying var(--f-bg): a style variant declares
     --f-bg on the SAME element as this block, so the alias would resolve to the
     variant's value, not this one, and Underline would zero it after all. */
  --o-bg: color-mix(in srgb, var(--paper) var(--f-fill-alpha), transparent);
  /* Selected/answered version of --o-bg, for the choice row, the multi-select
     tile and the package card. It is the brand tint mixed INTO the scrim, not
     laid beside it: --tint alone is brand at 12% over whatever is behind, which
     on a background image is a 12% wash of brand over a photo — no answered
     state to speak of, and the row lost the fill its unanswered siblings have.
     Mixing keeps the answered row on the same surface as the rest of the form
     and only shifts its hue toward brand. */
  --o-bg-on: color-mix(in srgb, var(--brand) 12%, var(--o-bg));
  --f-border:1px solid var(--line);
  /* Field padding as two axes rather than one shorthand, so a style variant
     flips it in one declaration instead of restating every control's padding. */
  --f-px:13px;
  --f-py:11px;
  --q-gap:18px;
  /* The library's fluid space scale, re-based on cqi: this module is a query
     container, so its rhythm follows the FORM's width, not the viewport's --
     the same reason the type scale above is in cqi. */
  --cff-sp-sm: clamp(12px, 3cqi, 16px);
  --cff-sp-md: clamp(16px, 4cqi, 24px);

  /* Font-size scale. One named step per role, so "the hint under a field" and
     "the hint under the budget slider" can never quietly drift to different
     sizes again. Every other section on the page (hero, about, pricing,
     gallery, ...) sizes its type with a cqi-based clamp() so it shrinks and
     grows with its own container; this scale was flat px, copied straight
     from the design artifact's fixed-size mockup, so the form was the one
     part of the page that stayed the same size no matter how the viewport
     resized. Made fluid here for the same reason — .cform already sets
     container-type:inline-size below, so cqi resolves against the form's own
     width, not the viewport.
     --cff-fs-input's floor is a hard constraint, not a style choice: clamp()
     always returns at least its min, so 16px never erodes even at the
     narrowest phone width — iOS Safari zooms the viewport on focus for any
     real text input under 16px, which on a form this long means every tap
     re-zooms the page.

     EVERY STEP BELOW IS ANCHORED ON A KIT STEP, and where a step ramps it ramps
     between two ADJACENT kit steps -- never to a value that exists in neither.
     That is the rule the rest of the vendor page follows (see the note on
     .faq-q in faq_layouts.css, which ramps --type-h4 16 -> --type-h3 18 for
     exactly this reason). The ladder used to be a bespoke 10/11.5/12.5/13.5/
     14.5 sequence that touched the kit nowhere, so the form was set in a scale
     no other section on the page used.

     Kit steps, for reference: label 11 · body-sm 13 · data 13 · body 15 ·
     h4 16 · body-lg 17 · h3 18 · h2 22 · display-m 26.

     THE FORM READS THE PAGE'S OWN STEPS, it does not run a parallel scale
     beside them. `--fs-body` and `--fs-btn` are the tokens agent_bio.css sets
     for the whole vendor page, so binding the form's ambient text and its
     compact rows straight to them makes the form the SAME SIZE as the page by
     construction rather than by a number that happens to match today.

     That is also what fixes "too big". A bespoke ladder had the form's body
     text at 15.2px on a 390px phone where the page's own body was 14.0px, and
     frozen at 16px on a 1600px screen where the page had grown to 17px — so
     the form read large on a phone, small on a desktop, and never moved with
     the page at either end.

     VW, NOT CQI, for the same reason. The measurement that matters is the one
     the rest of the page uses: agent_bio.css ramps every shared step on vw
     precisely "so the page scales up on big screens instead of staying
     phone-sized". This module used cqi against .cform, whose width CAPS at
     612px — so the form stopped growing at a 1200px viewport while everything
     around it kept going. Container queries still do the LAYOUT here (the
     scale folding to 5-up, the item row reflowing, the range pickers
     stacking); they are the right tool for deciding what fits. They were the
     wrong tool for deciding how big type should be, which on this page is a
     property of the screen, not of the column. */
  --cff-fs-nano:   clamp(10px,    9.40px + 0.16vw, 11.5px);  /* --type-label. Uppercase micro-captions: weekday initials, scale ends, group headings. */
  --cff-fs-micro:  clamp(11px,   10.30px + 0.18vw, 12.5px);  /* --type-label -> --type-data. Small readouts: hex, file size, row index. */
  --cff-fs-2xs:    clamp(12px,   11.10px + 0.24vw, 13.5px);  /* --type-body-sm. Helper text: hints, captions, sub-labels, errors. */
  --cff-fs-xs:     var(--fs-btn,   clamp(13px, 11.80px + 0.31vw, 15px));  /* THE PAGE'S OWN BUTTON STEP. Compact rows: field labels, list options, tags, consent. */
  --cff-fs-base:   var(--fs-body,  clamp(14px, 12.60px + 0.37vw, 17px));  /* THE PAGE'S OWN BODY STEP. Ambient form text: choice rows, package name, select value. */
  --cff-fs-label:  var(--fs-btn,   clamp(13px, 11.80px + 0.31vw, 15px));  /* the page's button step at 600. Segmented-control labels. */
  --cff-fs-input:  max(16px, var(--fs-body, clamp(14px, 12.60px + 0.37vw, 17px)));  /* the page's body step, floored at 16px — see the iOS note above. This is the ONLY step allowed to exceed the page's body size, and only on a phone, where the page's body is 14px and an input may not be. */
  --cff-fs-lg:     clamp(16px,   14.40px + 0.42vw, 19px);    /* one step above body. Emphasis values: package price, stepper glyph. */
  --cff-fs-static: clamp(17px,   15.10px + 0.50vw, 21px);    /* --type-h3 -> --type-h2. Display-only heading block; deliberately well under the page's --fs-h2 (26->32), which belongs to SECTION titles. */
  --cff-fs-jumbo:  clamp(20px,   17.40px + 0.68vw, 26px);    /* the one big standalone number: the budget readout. */

  /* Named type roles, from the controls library's own published tokens. These
     carry the FAMILY as well as the size, which is the part the module was
     missing: the library sets WORDS in sans and VALUES in mono with tabular
     figures, and without that distinction a price, a scale number and a
     sentence were all just "text at some size".
     Set with the `font` shorthand, so mind the order at every call site --
     `font` resets font-variant-numeric, so tabular-nums must come after it. */
  --cff-type-help:  400 var(--cff-fs-2xs)/1.55 var(--sans);
  --cff-type-error: 500 var(--cff-fs-2xs)/1.5  var(--sans);
  --cff-type-label: 500 var(--cff-fs-nano)/1.2 var(--mono);
  --cff-type-data:  400 var(--cff-fs-micro)/1.4 var(--mono);
  /* Tracking + measure, the kit's names and values. */
  --cff-track-label:   .08em;
  --cff-track-eyebrow: .14em;
  --cff-measure-help:  58ch;
  --cff-measure-body:  68ch;

  margin:0; width:100%;
  color: var(--ink); font-family: var(--cff-bf);
  /* The concept's own root type scale (#ifv2). Every copied rule body below was
     authored against these, and anything that doesn't set its own size/leading
     inherits them — without this the form picks up the vendor page's body type
     instead (17px/normal on the bio page), which silently retypes every label,
     choice row and static block. The vendor's FACE still comes through, only
     the scale is the concept's. */
  font-size: var(--cff-fs-base); line-height: 1.5;
  /* the section around the form may be centre-aligned by the vendor's layout
     picks; the form itself is always read left-to-right, as in the concept */
  text-align: start;
  /* query container, so the module reflows to its own width wherever embedded */
  container-type: inline-size;
}
.cform *, .cform *::before, .cform *::after { box-sizing: border-box; }

.cff-form-head { margin-bottom: 20px; }
.cff-eyebrow { display: inline-block; font-size: clamp(11px, 2.9cqi, 12.5px); font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--brand); margin-bottom: 6px; }
.cff-form-title { margin: 0; font-family: var(--cff-hf); font-weight: var(--cff-tw, 700);
  font-size: clamp(20px, 6cqi, 27px); line-height: 1.1; color: var(--ink); }

/* Questions stack and space themselves with their own margin-bottom, exactly as
   the concept's .q does. This was a single-column grid with a gap — visually
   the same spacing, but it made every question a grid ITEM (min-height:auto
   rather than 0), and it meant the concept's own margin rules had to be stripped
   out of every copied body, taking the heading/divider spacing with them. */
.cff-grid { display: block; }
/* Retained because the lab specimens and the complete-site preview still mark
   wide questions with it; the live form is a single column, where it is inert. */
.cff-full { width: 100%; }

/* =================================================================
   GENERATED — bodies copied from _design_ref/inquiry_form_v2.html
   ================================================================= */

/* --- Question wrapper + label --- */
.cff-field { margin-bottom:var(--q-gap,18px); position:relative; min-width:0; }
.cff-field:has(.cff-static-head) { margin-top:calc(var(--q-gap,18px) * .8); }
.cff-field:first-child:has(.cff-static-head) { margin-top:0; }
.cff-field:has(.cff-static-div) { margin-top:calc(var(--q-gap,18px) * .5);margin-bottom:calc(var(--q-gap,18px) * .5); }
.cff-field:last-child { margin-bottom:0; }
.cff-field > label:not(.cff-consent):not(.cff-drop) { display:block;font-size:var(--cff-fs-label);font-weight:600;margin-bottom:6px;letter-spacing:-.005em; }
.cff-hint { font-size:var(--cff-fs-2xs);color:var(--muted);margin:0 0 8px;line-height:1.5; }

/* --- Text inputs / textarea / popover triggers --- */
.cff-control input, .cff-control select, .cff-control textarea { width:100%; font-weight:400; font-size:var(--cff-fs-input); line-height:1.4; background:var(--f-bg); border:var(--f-border); border-radius:var(--f-radius); padding:10px 13px; min-height:44px; display:block;width:100%;color:var(--ink);font-family:inherit;outline:none;-webkit-appearance:none;appearance:none; }
.cff-control textarea { min-height:84px; resize:vertical; }
.cff-frame { display:flex;align-items:center;gap:9px;width:100%;font-size:var(--cff-fs-input);color:var(--faint); background:var(--f-bg);border:var(--f-border);border-radius:var(--f-radius);padding:10px 13px;min-height:44px; cursor:pointer; }
.cff-select-btn { display:flex;align-items:center;gap:9px;width:100%;font-size:var(--cff-fs-input);color:var(--faint); background:var(--f-bg);border:var(--f-border);border-radius:var(--f-radius);padding:10px 13px;min-height:44px; cursor:pointer; cursor:pointer;text-align:start;line-height:inherit; }
.cff-control input:focus, .cff-control select:focus, .cff-control textarea:focus, .cff-frame:focus-within, .cff-select-btn[aria-expanded="true"] { border-color:var(--brand); }

/* --- Radio / checkbox choice rows (multi-select is the SAME row as radio) --- */
.cff-choices { display:flex;flex-direction:column;gap:7px; }
.cff-choice { display:flex;align-items:flex-start;gap:10px;font-size:var(--cff-fs-base);background:var(--o-bg); border:1px solid var(--line-soft);border-left:3px solid var(--line-soft); border-radius:var(--o-r);padding:10px 13px;min-height:44px; cursor:pointer; cursor:pointer;color:var(--ink); }
.cff-choice:has(input:checked), .cff-choice.is-on { border-color:var(--brand);border-left-color:var(--brand);background:var(--o-bg-on); }
/* The option's own sub-paragraph, when the vendor wrote one. min-width:0 lets
   it wrap inside the row instead of pushing the row wider than the form. */
.cff-choice-body { min-width:0; }
.cff-choice-sub { display:block;font-size:var(--cff-fs-2xs);color:var(--muted);line-height:1.45;margin-top:3px;font-weight:400; }
/* The RADIO DOT is gone: a single-choice row is marked by its left accent rail,
   its tint and its weight, not by a dot. That is the controls library's rule and
   it is what keeps the three choice widgets telling different stories -- rail
   for "pick one", corner triangle for "pick several", top rule for a package.
   With a dot on the row as well, "pick one" was saying it twice, and the dot was
   the louder of the two, which made a selected row read as a settings toggle.
   The real <input type=radio> is still there, hidden, so keyboard and AT are
   unchanged; only the drawn duplicate went. */
/* COLOUR ONLY -- never weight. Bumping a chosen row to 500 made it look like it
   had changed FONT, not state: 500 sits between the two weights the module
   already uses, so a chosen row was neither the 400 of its neighbours nor the
   600 of the label above it, and the eye reads an unfamiliar weight of a
   familiar face as a different face. Worse on a vendor whose pairing is loaded
   from Google, where only the weights the pairing actually uses are requested,
   so 500 is SYNTHESISED -- a smeared 400 whose letterforms really are different.
   A form is not the place to discover that. The rail, the border and the tint
   already say "chosen" three times over. */
.cff-choice:has(input:checked), .cff-choice.is-on { color:var(--brand-deep); }
/* .cff-box survives for the multi-select ROWS still drawn by the lab specimen
   and the complete-site gallery. The live form has no square tick box (its
   multi-select is .cff-tiles) and no round one at all. */
.cff-box { position:relative;width:18px;height:18px;border:1.5px solid var(--line);border-radius:min(var(--o-r),6px); flex:none;background:var(--o-bg);display:grid;place-items:center;line-height:0; }
.cff-choice:has(input:checked) .cff-box, .cff-choice.is-on .cff-box { background:var(--brand-fill);border-color:var(--brand-fill); }
.cff-box i { width:9px;height:4.5px;border-left:2.2px solid var(--on-brand);border-bottom:2.2px solid var(--on-brand); transform:rotate(-45deg) translate(1px,-1.5px);opacity:0; }
.cff-choice:has(input:checked) .cff-box i, .cff-choice.is-on .cff-box i { opacity:1; }

/* --- Yes/No segment --- */
/* The groove reads --track-bg rather than restating an ink mix. That token was
   declared for exactly this and then never referenced, so the Yes/No control was
   the one widget whose surface could not be retuned with the rest of them. */
.cff-segment { position:relative;display:flex;gap:2px;padding:3px;background:var(--track-bg);border-radius:var(--f-radius); }
.cff-segment button { flex:1;text-align:center;font-weight:600;font-size:var(--cff-fs-label);line-height:1;color:var(--muted);padding:11px; position:relative;z-index:1; cursor:pointer; border:0;background:none;cursor:pointer;font-family:inherit; }
.cff-segment-bar { position:absolute;top:3px;bottom:3px;left:3px;width:calc(50% - 3px); border-radius:calc(var(--f-radius) - 3px);background:var(--brand-fill); transition:transform .22s cubic-bezier(.4,0,.2,1); }
.cff-segment.is-no .cff-segment-bar { transform:translateX(100%); }
.cff-segment button { flex:1;text-align:center;font-weight:600;font-size:var(--cff-fs-label);line-height:1;color:var(--muted);padding:11px; position:relative;z-index:1; cursor:pointer; position:relative;z-index:1; }
.cff-segment button.is-on { background:var(--brand-fill);color:var(--on-brand); }

/* --- Number stepper --- */
.cff-stepper { display:flex;align-items:center;background:var(--f-bg);border:var(--f-border); border-radius:var(--c-r);overflow:hidden; }
.cff-stepper input { flex:1;text-align:center;font-weight:500;font-size:var(--cff-fs-base);line-height:1;padding:12px 0; min-width:0;border:0;background:none;outline:none;color:var(--ink);font-family:inherit; }
.cff-step { width:40px;text-align:center;font-style:normal;font-size:var(--cff-fs-lg);color:var(--muted);padding:10px 0; cursor:pointer; flex:none;align-self:stretch;border:0;background:none;cursor:pointer;font-family:inherit;line-height:inherit; }

/* --- 1–10 scale --- */
.cff-scale10 { display:grid;grid-template-columns:repeat(10,1fr);gap:4px; }
.cff-scale-btn { aspect-ratio:1;border:var(--f-border);background:var(--o-bg);border-radius:var(--o-r);font-style:normal; font-weight:600;font-size:var(--cff-fs-2xs);line-height:1;color:var(--muted);display:grid;place-items:center; cursor:pointer; padding:0;text-align:inherit;cursor:pointer;font-family:inherit; }
.cff-scale-btn.is-on { background:var(--brand-fill);border-color:var(--brand-fill);color:var(--on-brand); }
.cff-scale-lab { display:flex;justify-content:space-between;font-size:var(--cff-fs-2xs);color:var(--faint);margin-top:7px; }

/* --- Pricing packages --- */
.cff-packages { display:grid;gap:10px; }
.cff-pkg { position:relative;display:flex;flex-direction:column;background:var(--o-bg);border:1px solid var(--line-soft); border-radius:var(--c-r);cursor:pointer; width:100%;text-align:start;font:inherit;font-family:inherit;color:var(--ink);
  /* Padding moved onto .cff-pkg-in when the card went to a stacked layout, so
     nothing here overrides the UA button default (Chrome: ~1px 6px) any more
     -- it was insetting the ::before top rule from both edges instead of
     letting it run flush, the same class of bug -webkit-appearance:none below
     exists to prevent generally. */
  padding:0; -webkit-appearance:none; appearance:none;
  /* The card CLIPS its own top rule -- see the ::before below. */
  overflow:hidden; }
.cff-pkg.is-on { border-color:var(--brand);background:var(--o-bg-on); }
/* The top rule is a normal-flow block -- the first child of this column flex
   box -- rather than an absolutely positioned one, so it needs no radius of
   its own to line up with the card: overflow:hidden trims anything outside
   the card's rounded silhouette either way. But trimming a flat-topped
   rectangle to a curve only hides the pixels outside it -- the rectangle's
   own square corner is still what's INSIDE the curve, so the arc handed off
   to a straight edge partway round the bend, a visible seam right where the
   thin border curve met the thick flat bar. Rounding the bar's own top
   corners to the padding-box curve (--c-r minus the border width) makes it
   describe the same arc as the border above it instead of merely staying
   inside it. */
.cff-pkg::before { content:"";display:block;height:3px;flex:none;
  border-radius: calc(var(--c-r) - 1px) calc(var(--c-r) - 1px) 0 0;
  background:var(--line-soft);transition:background .15s ease; }
.cff-pkg.is-on::before { background:var(--brand); }
.cff-pkg-in { display:flex;flex-direction:column;padding:15px 16px 17px; }
/* Name, tagline, then price stacked one fact per line -- not name-and-price on
   one baseline-aligned row with the price floated right, which read as a
   receipt line item rather than an option to choose. The price sits at the
   bottom via margin-top:auto so cards with different description lengths
   still line their prices up along the same edge. There is no separate
   selected-mark control (no dot, no check): the border colour, tint and top
   rule already say "chosen" three times over, same rule as the choice row
   above.

   Sizing itself lives in the type-ramp pass below (search "package name is a
   card heading" / "Values: mono, tabular figures") — only the box model
   (margins, stacking order) belongs here, or the two passes fight over the
   same properties. */
.cff-pkg-name { margin:0 0 2px; }
.cff-pkg-desc { color:var(--muted);margin:0 0 10px; display:block; }
.cff-pkg-price { margin-top:auto;color:var(--brand); }
.cff-pkg.is-on .cff-pkg-price { color:var(--brand-deep); }

/* --- Tags --- */
.cff-tagbox { display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:var(--f-bg);border:var(--f-border); border-radius:var(--c-r);padding:8px 10px;min-height:44px; cursor:text; }
/* Solid --brand, not --tint: a chosen tag is a committed answer, the same
   weight as a checked choice row (.cff-choice:has(input:checked) .cff-box)
   or a selected day on the date picker, not a passive wash. --on-brand is
   the same paired text token those already use, so it's contrast-safe on
   every vendor theme without a per-selector check of its own. Border colour
   matches the fill (see .cff-scale-btn.is-on) rather than the old neutral
   --line-soft, which read as a mismatched seam once the fill stopped being
   a near-white tint. */
.cff-tag { font-size:var(--cff-fs-xs);font-weight:500;background:var(--brand-fill);color:var(--on-brand);border:1px solid var(--brand-fill);border-radius:var(--o-r);padding:5px 9px; display:inline-flex;align-items:center;gap:6px; }
.cff-tag-input { flex:1;min-width:80px;border:0;outline:none;background:none;font-family:inherit;font-size:var(--cff-fs-base);color:var(--ink); }

/* --- Uploads --- */
.cff-drop { border:1.5px dashed var(--line);border-radius:var(--c-r);padding:16px 14px;text-align:center;background:var(--o-bg); cursor:pointer; display:block;cursor:pointer; }
.cff-drop b { display:block;font-size:var(--cff-fs-xs);font-weight:600;margin-bottom:3px; }
.cff-drop > span:not(.cff-drop-preview) { font-size:var(--cff-fs-2xs);color:var(--muted); }
.cff-drop-grid { display:grid;gap:9px;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));margin-bottom:10px; }
.cff-thumb { position:relative; aspect-ratio:1; border-radius:var(--c-r); display:grid; min-width:0;overflow:hidden; }
.cff-thumb img { width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit; }
.cff-thumb-x { position:absolute;top:4px;right:4px;width:20px;height:20px;border-radius:50%; background:color-mix(in srgb, var(--ink) 72%, transparent);color:var(--paper);display:grid;place-items:center;line-height:0; box-shadow:0 1px 3px color-mix(in srgb, var(--ink) 30%, transparent); cursor:pointer; border:0;padding:0;cursor:pointer; }
.cff-file-chip { display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--hairline); border-radius:var(--c-r);background:var(--o-bg);font-size:var(--cff-fs-xs);margin-bottom:7px; }
.cff-file-name { flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500; }
.cff-file-size { color:var(--faint);font-family:var(--mono);font-size:var(--cff-fs-2xs); }
.cff-file-x { flex:none;width:22px;height:22px;display:grid;place-items:center;border-radius:var(--o-r); color:var(--faint);line-height:0; cursor:pointer; border:0;background:none;padding:0;cursor:pointer;color:inherit; }

/* --- Colour palette builder --- */
.cff-swatch-list { display:grid;gap:8px; }
.cff-swatch-row { position:relative;display:flex;align-items:center;gap:9px; }
.cff-swatch-well { position:relative;width:44px;height:44px;flex:none;border-radius:var(--o-r); border:1px solid color-mix(in srgb, var(--ink) 12%, transparent);box-shadow:inset 0 0 0 3px rgba(255,255,255,.5); padding:0;cursor:pointer;transition:box-shadow .14s,transform .14s; font-family:inherit; }
.cff-swatch-well:hover { box-shadow:inset 0 0 0 3px rgba(255,255,255,.5),0 0 0 3px var(--tint),0 0 0 4px var(--brand); }
.cff-swatch-well:active { transform:scale(.96); }
.cff-swatch-chev { position:absolute;right:-3px;bottom:-3px;width:17px;height:17px;border-radius:50%; background:var(--paper);border:1px solid var(--hairline);color:var(--muted); display:grid;place-items:center;line-height:0;box-shadow:0 1px 3px color-mix(in srgb, var(--ink) 20%, transparent); }
.cff-swatch-well.is-open .cff-swatch-chev { color:var(--brand);border-color:var(--brand); }
.cff-swatch-name { flex:1;min-width:0;font-family:inherit;font-size:var(--cff-fs-base);color:var(--ink);background:var(--f-bg); border:var(--f-border);border-radius:var(--f-radius);padding:10px 12px;min-height:44px;outline:none; font-family:inherit;outline:none; }
.cff-swatch-hex { flex:none;font-family:var(--mono);font-size:var(--cff-fs-micro);color:var(--muted);width:60px;text-align:right; }
.cff-swatch-rm { flex:none;width:30px;height:30px;border:0;background:none;border-radius:var(--o-r); color:var(--faint);display:grid;place-items:center;line-height:0;cursor:pointer; transition:background .12s,color .12s; cursor:pointer; }
.cff-swatch-rm:hover { background:color-mix(in srgb, var(--danger) 9%, transparent);color:var(--danger); }
.cff-swatch-add { display:inline-flex;align-items:center;gap:7px;margin-top:10px;font-weight:600;font-size:var(--cff-fs-2xs);line-height:1; color:var(--brand);background:none;border:1px dashed color-mix(in srgb, var(--brand) 45%, transparent);border-radius:var(--c-r);padding:10px 13px; cursor:pointer;font-family:inherit; }

/* --- Palette strip + suggestions --- */
.cff-palette-strip { display:flex;height:26px;border-radius:var(--c-r);overflow:hidden;margin-top:12px;border:1px solid var(--hairline); cursor:pointer; }
.cff-palette-strip i { flex:1;display:block; }
.cff-palette-cap { font-size:var(--cff-fs-micro);color:var(--faint);margin:6px 0 0; }
.cff-palette-head { display:flex;align-items:center;gap:10px;margin-top:16px; }
.cff-palette-lab { font-weight:600;font-size:var(--cff-fs-micro);line-height:1;letter-spacing:.09em;text-transform:uppercase; color:var(--faint); }
.cff-palette-rule { flex:1;height:1px;background:var(--hairline); }
.cff-palette-shuffle { flex:none;display:inline-flex;align-items:center;gap:6px; font-weight:600;font-size:var(--cff-fs-micro);line-height:1;color:var(--brand);background:none; border:1px solid var(--hairline);border-radius:var(--o-r);padding:6px 11px 6px 9px;cursor:pointer; transition:background .12s,border-color .12s; cursor:pointer;font-family:inherit; }
.cff-palette-shuffle:hover { background:var(--tint);border-color:var(--brand); }
.cff-palette-sets { display:flex;flex-wrap:wrap;gap:6px;margin-top:9px; }
.cff-palette-set { display:flex;align-items:center;gap:2px;padding:6px 10px 6px 7px;border:1px solid var(--hairline); border-radius:var(--o-r);background:var(--paper);cursor:pointer;transition:border-color .12s,background .12s; cursor:pointer;font-family:inherit; }
.cff-palette-set:hover { border-color:var(--brand);background:var(--tint); }
.cff-palette-set i { width:13px;height:13px;border-radius:50%;display:block;border:1px solid color-mix(in srgb, var(--ink) 8%, transparent); }
.cff-palette-set span { font-size:var(--cff-fs-micro);color:var(--muted);margin-left:6px; }

/* --- Colour picker popover (HSV square + hue strip + hex) --- */
.cff-cpop { position:absolute;z-index:30;top:calc(100% + 7px);left:0;width:250px; background:var(--paper);border:1px solid var(--hairline);border-radius:var(--c-r);padding:10px; box-shadow:0 14px 34px color-mix(in srgb, var(--ink) 17%, transparent); }
.cff-cpop-inner { margin-top:0; }
.cff-csv { position:relative;height:104px;border-radius:var(--o-r);cursor:crosshair;touch-action:none; background:linear-gradient(to top,#000,rgba(0,0,0,0)), linear-gradient(to right,var(--paper),hsl(var(--h,210),100%,50%)); }
.cff-cring { position:absolute;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%; border:2px solid var(--paper);pointer-events:none; box-shadow:0 0 0 1px color-mix(in srgb, var(--ink) 45%, transparent),0 1px 4px color-mix(in srgb, var(--ink) 45%, transparent); }
.cff-chue { position:relative;height:12px;margin-top:10px;border-radius:99px;cursor:pointer; touch-action:none; background:linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00); }
.cff-chthumb { position:absolute;top:50%;width:16px;height:16px;margin:-8px 0 0 -8px;border-radius:50%; background:var(--paper);pointer-events:none; box-shadow:0 0 0 1px color-mix(in srgb, var(--ink) 32%, transparent),0 1px 4px color-mix(in srgb, var(--ink) 38%, transparent); }
.cff-csv:focus-visible, .cff-chue:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.cff-cfields { display:flex;align-items:center;gap:7px;margin-top:11px; }
.cff-cprev { flex:none;width:30px;height:30px;border-radius:var(--o-r);border:1px solid color-mix(in srgb, var(--ink) 14%, transparent); }
.cff-chex-input { flex:1;min-width:0;font-family:var(--mono);font-size:var(--cff-fs-2xs);letter-spacing:.03em; text-transform:uppercase;color:var(--ink);background:var(--paper);border:1px solid var(--hairline); border-radius:var(--o-r);padding:8px 9px;outline:none; font-family:var(--mono); }
.cff-chex-input:focus { border-color:var(--brand); }
.cff-cdrop { flex:none;width:30px;height:30px;border-radius:var(--o-r);border:1px solid var(--hairline); background:var(--paper);color:var(--muted);display:grid;place-items:center;line-height:0;cursor:pointer; transition:border-color .12s,color .12s; cursor:pointer;font-family:inherit; }
.cff-cdrop:hover { border-color:var(--brand);color:var(--brand); }

/* --- Item + quantity list --- */
.cff-itemqty-row { display:flex;align-items:center;gap:8px;margin-bottom:8px; }
.cff-itemqty-n { width:16px;flex:none;text-align:center;font-family:var(--mono);font-size:var(--cff-fs-micro);color:var(--faint); }
.cff-itemqty-name { flex:1;min-width:0;font-family:inherit;font-size:var(--cff-fs-base);color:var(--ink);background:var(--f-bg); border:var(--f-border);border-radius:var(--f-radius);padding:10px 12px;min-height:44px;outline:none; font-family:inherit;outline:none; }
.cff-itemqty-step { display:flex;align-items:center;flex:none;background:var(--f-bg);border:var(--f-border); border-radius:var(--c-r);overflow:hidden; }
.cff-itemqty-step .cff-step { width:28px;text-align:center;font-style:normal;font-size:var(--cff-fs-lg);color:var(--muted);padding:10px 0; cursor:pointer; flex:none;align-self:stretch;border:0;background:none;cursor:pointer;font-family:inherit;line-height:inherit; }
.cff-itemqty-step input { width:30px;text-align:center;font-size:var(--cff-fs-base);font-weight:500; flex:none;border:0;background:none;outline:none;color:var(--ink);font-family:inherit;padding:0;line-height:inherit; }
.cff-itemqty-add { display:inline-flex;align-items:center;gap:7px;margin-top:4px;font-weight:600;font-size:var(--cff-fs-2xs);line-height:1; color:var(--brand);background:none;border:1px dashed color-mix(in srgb, var(--brand) 45%, transparent);border-radius:var(--c-r); padding:10px 13px;cursor:pointer; font-family:inherit; }
.cff-itemqty-add:hover { background:var(--tint); }

/* --- Star rating --- */
.cff-star-lab { font-size:var(--cff-fs-2xs);color:var(--muted);margin-top:7px; }
.cff-stars2 { display:flex;gap:5px; }
.cff-star2 { color:var(--line);line-height:0; cursor:pointer; background:none;border:0;padding:0;font-size:inherit;text-align:inherit;cursor:pointer; }
.cff-star2 svg { width:26px;height:26px;fill:currentColor;transition:fill .1s; }
.cff-star2.on { color:var(--amber); }

/* --- Signature --- */
.cff-sig-pad { position:relative;border:var(--f-border);border-radius:var(--c-r);background:var(--o-bg);height:92px; }
.cff-sig-canvas2 { position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:none;cursor:crosshair; }
.cff-sig-line { position:absolute;left:16px;right:16px;bottom:28px;border-bottom:1px solid var(--line); }
.cff-sig-hint { position:absolute;left:16px;bottom:9px;font-size:var(--cff-fs-2xs);color:var(--faint); }

/* --- Consent --- */
.cff-consent { display:flex;gap:11px;align-items:flex-start;font-size:var(--cff-fs-xs); font-weight:400;margin-bottom:0;letter-spacing:0;color:var(--muted);line-height:1.6; cursor:pointer; }
.cff-consent-text { flex:1;min-width:0; }
.cff-consent-box { position:relative;width:18px;height:18px;border:1.5px solid var(--line);border-radius:var(--o-r); flex:none;background:var(--o-bg);display:grid;place-items:center;line-height:0; margin-top:2px; }
.cff-consent-box i { width:9px;height:4.5px;border-left:2.2px solid var(--on-brand);border-bottom:2.2px solid var(--on-brand); transform:rotate(-45deg) translate(1px,-1.5px);opacity:0; }
.cff-consent input:checked ~ .cff-consent-box { background:var(--brand-fill);border-color:var(--brand-fill); }
.cff-consent input:checked ~ .cff-consent-box i { opacity:1; }
/* The vendor's optional privacy link after the consent sentence
   (settings.privacy_url, edited in both builders) — quiet, underlined,
   the consent line's own ink so it reads as part of the sentence. */
.cff-consent-link { color:inherit; text-decoration:underline; text-underline-offset:2px; }
.cff-consent-link:hover { color:var(--ink); }

/* --- Display-only blocks --- */
.cff-static-head { font-family:var(--serif);font-size:var(--cff-fs-static);font-weight:500;letter-spacing:-.01em; color:var(--ink);line-height:1.3; margin:0; }
.cff-static-sub { display:block;font-family:var(--cff-bf, "DM Sans", sans-serif);font-size:var(--cff-fs-2xs);font-weight:400; color:var(--muted);margin-top:4px;line-height:1.5;letter-spacing:0; }
.cff-static-note { margin:0;font-size:var(--cff-fs-xs);color:var(--muted);line-height:1.65;max-width:56ch; }
.cff-static-div { height:1px;background:var(--hairline); }

/* --- Budget range (two thumbs) --- */
.cff-budget-valrow { display:flex;align-items:baseline;gap:8px;margin-bottom:12px; }
.cff-budget-val { color:var(--ink); }
.cff-budget-sub { font-size:var(--cff-fs-2xs);color:var(--muted); }
.cff-budget-track { touch-action:none; position:relative;height:24px;display:flex;align-items:center; }
.cff-budget-fill { position:absolute; height:5px; border-radius:99px; background:var(--brand); }
.cff-budget-ends { display:flex;justify-content:space-between;font-size:var(--cff-fs-2xs);color:var(--faint);font-family:var(--mono);margin-top:9px; }

/* --- Select / time popovers --- */
.cff-select-list, .cff-timepicker-list { position:absolute;z-index:40;top:calc(100% + 6px);left:0;min-width:100%; background:var(--paper);border:1px solid var(--hairline);border-radius:var(--c-r);padding:6px; box-shadow:0 14px 34px -14px color-mix(in srgb, var(--ink) 34%, transparent),0 2px 8px -3px color-mix(in srgb, var(--ink) 12%, transparent); margin:0;list-style:none;right:0;max-height:230px;overflow-y:auto; }
/* Every popover in this module opens downward, unrouted, so a trigger near
   the bottom of a short container (a small preview pane, a short viewport)
   ran the popover straight off the edge instead of anywhere still visible —
   there was nowhere else for it to go. .cff-pop-up is toggled by JS
   (agent-bio-contact.js / contact_forms.js) once it measures that the
   popover doesn't fit below the trigger and there's more room above; the
   same move .bk-select-menu makes for a clipped modal, just a flip instead
   of a fixed-position escape, since these popovers never sit inside an
   overflow:hidden ancestor -- only a short one. */
.cff-select-list.cff-pop-up, .cff-timepicker-list.cff-pop-up,
.cff-et-pop.cff-pop-up, .cff-datepicker-pop.cff-pop-up { top:auto;bottom:calc(100% + 6px); }
.cff-select-list li { display:flex;align-items:center;gap:10px;width:100%;text-align:left;cursor:pointer; font-weight:500;font-size:var(--cff-fs-xs);line-height:1.35;color:var(--ink);background:none;border:0; border-radius:var(--o-r);padding:10px; cursor:pointer; }
.cff-select-list li:hover, .cff-select-list li.is-active { background:color-mix(in srgb, var(--ink) 5.5%, transparent); }
.cff-select-list li.is-sel { color:var(--brand); }
.cff-timepicker-list li { display:block;width:100%;text-align:center;font-weight:500;font-size:var(--cff-fs-xs);line-height:1;color:var(--ink); background:none;border:0;border-radius:var(--o-r);padding:10px 4px;cursor:pointer; cursor:pointer; }
.cff-timepicker-list li:hover { background:color-mix(in srgb, var(--ink) 6%, transparent); }
/* --brand-fill + --on-brand, not --brand + --paper: the palette contract at
   the top of this file only proves the on-brand pairing against the FILL
   token, and --paper on a light vendor theme can sit near-white on a light
   brand. Same pairing every other selected mark already uses. */
.cff-timepicker-list li[aria-selected="true"] { background:var(--brand-fill);color:var(--on-brand); }

/* --- Event type picker (searchable, chunk-rendered) --- */
.cff-et-pop { position:absolute;z-index:40;top:calc(100% + 6px);left:0;min-width:100%; background:var(--paper);border:1px solid var(--hairline);border-radius:var(--c-r);padding:6px; box-shadow:0 14px 34px -14px color-mix(in srgb, var(--ink) 34%, transparent),0 2px 8px -3px color-mix(in srgb, var(--ink) 12%, transparent); }
.cff-et-opt { display:flex;align-items:center;gap:10px;width:100%;text-align:left;cursor:pointer; font-weight:500;font-size:var(--cff-fs-xs);line-height:1.35;color:var(--ink);background:none;border:0; border-radius:var(--o-r);padding:10px; cursor:pointer; }
.cff-et-opt:hover, .cff-et-opt.is-active { background:color-mix(in srgb, var(--ink) 5.5%, transparent); }
.cff-et-opt.is-sel { color:var(--brand); }

/* --- Date picker ---
   Fixed at 282px — enough for seven comfortable circle cells — rather than
   min-width:100% like the other popovers: those are single-column lists that
   are meant to match their trigger's width, but a 7-column grid pinned to a
   wide trigger (a full-width field on a spacious page) stretched its cells
   into oversized circles. width:min(282px,100%) keeps the 282px target while
   still shrinking to fit a trigger narrower than that on a small phone. */
.cff-datepicker-pop { position:absolute;z-index:40;top:calc(100% + 6px);left:0; background:var(--paper);border:1px solid var(--hairline);border-radius:var(--c-r);padding:6px; box-shadow:0 14px 34px -14px color-mix(in srgb, var(--ink) 34%, transparent),0 2px 8px -3px color-mix(in srgb, var(--ink) 12%, transparent); width:min(282px, 100%); }
.cff-dp-days { display:grid;grid-template-columns:repeat(7,1fr);gap:2px; }
.cff-dp-day { aspect-ratio:1;border:0;background:none;border-radius:50%;cursor:pointer; font-weight:500;font-size:var(--cff-fs-xs);line-height:1;color:var(--ink);display:grid;place-items:center; font-family:inherit; }
.cff-dp-day:hover { background:color-mix(in srgb, var(--ink) 6%, transparent); }
.cff-dp-day.is-muted { color:var(--faint); }
/* Fill-token pairing, same reasoning as the time list's selected row above. */
.cff-dp-day.is-selected { background:var(--brand-fill);color:var(--on-brand); }
.cff-dp-nav { width:30px;height:30px;border-radius:50%;border:0;background:none;color:var(--muted); cursor:pointer;display:grid;place-items:center; font-family:inherit; }
.cff-dp-nav:hover { background:color-mix(in srgb, var(--ink) 6%, transparent);color:var(--ink); }
.cff-dp-weekdays span { text-align:center;font-size:var(--cff-fs-micro);font-weight:700;color:var(--faint);padding:3px 0; display:block; }
.cff-dp-head { display:flex;align-items:center;gap:6px;padding:2px 2px 8px; }
.cff-dp-month { flex:1;text-align:center;font-size:var(--cff-fs-xs);font-weight:600; }

/* --- Submit --- */
.cff-submit { width:100%;font-weight:600;font-size:var(--cff-fs-input);line-height:1;color:var(--on-brand);background:var(--brand-fill); border-radius:var(--c-r);padding:15px;text-align:center; display:block;border:0;cursor:pointer;font-family:inherit; }
.cff-submit { margin-top:24px; }
.cff-privacy { margin:10px 0 0;font-size:var(--cff-fs-2xs);color:var(--faint);text-align:center; }

/* =================================================================
   END GENERATED
   Below: parts the artifact's mock-up has no equivalent for — real
   controls (native range inputs, colour inputs, file previews), form
   states (validation, submission, success) and the vendor's own
   field-style variants.
   ================================================================= */

/* Weight lives with the type pass (search ".cff-opt" below) — declaring 500
   here and 400 there left the aside at 400 PLUS a stray 0.7 opacity, dimmer
   than either declaration intended. --muted is the module's one quiet-aside
   ink; no opacity stacking on top of it. */
.cff-opt { color: var(--muted); text-transform: none; letter-spacing: 0; }
.cff-control input::placeholder, .cff-control textarea::placeholder,
.cff-tag-input::placeholder { color: var(--faint); }

/* Event type picker — the search field, group headings and empty state have
   no counterpart in the artifact (its dropdown is a short static list), so they
   are built here from the same tokens the rest of the module uses. */
.cff-eventtype { position: relative; }
.cff-et-pop[hidden] { display: none; }
.cff-et-search { display: flex; align-items: center; gap: 8px; padding: 4px 8px 8px;
  border-bottom: 1px solid var(--hairline); margin-bottom: 5px; }
.cff-et-search-ic { width: 15px; height: 15px; flex: none; color: var(--faint); }
/* font-size must stay >=16px (var(--cff-fs-input)): it's a real text input, and
   anything smaller makes iOS Safari zoom the whole page in on focus. It was
   14px, so tapping the event-type search silently zoomed the viewport. */
.cff-et-input { flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font-family: inherit; font-size: var(--cff-fs-input); color: var(--ink); padding: 4px 0; }
.cff-et-input::placeholder { color: var(--faint); }
.cff-et-list { margin: 0; padding: 0; list-style: none; max-height: 244px; overflow-y: auto;
  overscroll-behavior: contain; }
.cff-et-group { padding: 8px 10px 4px; font-size: var(--cff-fs-micro); font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--faint); }
.cff-et-group:first-child { padding-top: 2px; }
.cff-et-custom { color: var(--brand); font-style: italic; }
/* The sentinel the IntersectionObserver watches: it has to be in the scroll
   container and have real height, or "scrolled into view" never fires. */
.cff-et-more { height: 1px; }
.cff-et-more[hidden] { display: none; }

/* Dropdown / picker trigger internals */
.cff-select { position: relative; }
.cff-select-val { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.cff-select-val.is-placeholder { color: var(--faint); }
.cff-caret { width: 16px; height: 16px; color: var(--muted); flex-shrink: 0; pointer-events: none; }
.cff-select-btn[aria-expanded="true"] .cff-caret { transform: rotate(180deg); }
.cff-tick { width: 16px; height: 16px; margin-left: auto; color: var(--brand); opacity: 0; }
.cff-select-list li.is-sel .cff-tick { opacity: 1; }
.cff-datepicker, .cff-timepicker { position: relative; }
.cff-dp-icon { width: 16px; height: 16px; color: var(--muted); flex-shrink: 0; }
.cff-dp-day.is-today { font-weight: 700; box-shadow: inset 0 0 0 1.5px var(--brand); }
/* Out of bounds — a past day, or one on the wrong side of the range's other
   end. Reads like the leading/trailing greys of the adjacent months (.is-muted)
   so "not offered" looks the same wherever it comes from, and drops the hover
   and the is-today ring so nothing invites the tap. */
.cff-dp-day.is-disabled { color: var(--faint); opacity: .45; cursor: default; box-shadow: none; }
.cff-dp-day.is-disabled:hover { background: none; }
.cff-dp-nav:disabled { opacity: .3; cursor: default; }
.cff-dp-nav:disabled:hover { background: none; }
.cff-dp-weekdays { display: grid; grid-template-columns: repeat(7,1fr); margin-bottom: 4px; }
.cff-range-pickers { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
.cff-range-pickers .cff-datepicker, .cff-range-pickers .cff-timepicker { min-width: 0; }
.cff-range-arrow { color: var(--muted); flex-shrink: 0; }

/* Native date/time range inputs sharing one .cff-frame — stripped bare so the
   frame is the only chrome. */
.cff-daterange { display: flex; align-items: center; gap: 8px; }
.cff-daterange input { flex: 1; min-width: 0; width: auto; font: inherit; font-size: var(--cff-fs-input);
  color: var(--ink); background: transparent; border: 0; border-radius: 0;
  padding: 0; outline: none; -webkit-appearance: none; appearance: none; }
.cff-daterange input::-webkit-calendar-picker-indicator { opacity: 0.55; cursor: pointer; }


/* Budget range: two native sliders stacked on the generated track. Both are
   pointer-transparent EXCEPT their thumbs, so each thumb stays independently
   draggable instead of the upper input swallowing every click on the lower. */
.cff-budgetrange, .cff-slider { display: block; }
.cff-budget-track::before { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 5px;
  transform: translateY(-50%); border-radius: 999px; background: var(--track-bg); }
:is(.cff-budgetrange, .cff-slider) input[type="range"] { position: absolute; left: 0; top: 0; width: 100%;
  height: 24px; margin: 0; background: none; pointer-events: none; -webkit-appearance: none; appearance: none; }
/* This module draws the rail itself (.cff-budget-track::before, in the
   vendor's --fill-2) and stacks the native inputs on top of it purely as drag
   handles. The site-wide control layer in form-controls.css gives every other
   range in the app a track of its own, which here painted a SECOND, brighter
   bar over the designed one. The input's `background: none` above does not
   cover this: a range's rail is a pseudo-element, not the element's own
   background. Blanking both engines' track pseudo keeps the thumbs and loses
   the duplicate. */
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-webkit-slider-runnable-track { background: none; height: auto; }
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-moz-range-track { background: none; height: auto; }
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-webkit-slider-thumb { pointer-events: auto; -webkit-appearance: none;
  width: 22px; height: 22px; border-radius: 50%; cursor: grab;
  background: var(--paper); border: 2px solid var(--brand); box-shadow: 0 1px 4px color-mix(in srgb, var(--ink) 28%, transparent); }
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-moz-range-thumb { pointer-events: auto;
  width: 22px; height: 22px; border-radius: 50%; cursor: grab;
  background: var(--paper); border: 2px solid var(--brand); box-shadow: 0 1px 4px color-mix(in srgb, var(--ink) 28%, transparent); }
:is(.cff-budgetrange, .cff-slider) input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--ring); }
:is(.cff-budgetrange, .cff-slider) input[type="range"]:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px var(--ring); }

/* Hidden native inputs behind the custom marks */
.cff-choice input, .cff-consent input { position: absolute; opacity: 0; width: 0; height: 0; }
/* The dropzone's file input: visually hidden but still FOCUSABLE (1px, not
   display:none — the template dropped the `hidden` attribute so keyboard
   users can reach the uploader at all; Enter/Space on it opens the native
   picker). Focus shows on the zone, same pattern as the choice row below. */
.cff-drop input[type="file"] { position: absolute; opacity: 0; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; }
.cff-drop:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }
.cff-consent input:focus-visible ~ .cff-consent-box { box-shadow: 0 0 0 3px var(--ring); }
/* Focus lands on the ROW now, not on a mark inside it. It used to ring the
   .cff-box, which no longer exists on a single-choice row -- so without this,
   tabbing through a radio group showed nothing at all. */
.cff-choice:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The "MOST POPULAR" flag (.cff-pkg-badge) was here, and is gone. A package's
   badge is MARKETING and belongs on the pricing section, where the vendor is
   selling; inside the lead form the same package list is a QUESTION, and a
   badge on one option is the form answering it. It also had to hang outside the
   card (top:-9px) to read as a flag, which is what stopped the card clipping
   its own top rule -- so removing it is what lets .cff-pkg use overflow:hidden.
   PricingPackage.badge is untouched and still renders on the pricing section. */

/* Real file/image previews — the artifact's are empty placeholders. */
.cff-drop-preview:empty { display: none; }
/* Once the cap is reached the zone shouldn't keep inviting more. */
.cff-dropzone.is-full .cff-drop { opacity: 0.55; pointer-events: none; }
/* Downscaling in progress. The note counts the files off; this stops the zone
   taking a second pick mid-batch, which would arrive while the first batch is
   still holding its slots in reserve. */
.cff-dropzone[data-cff-busy] .cff-drop { opacity: 0.6; pointer-events: none; }
.cff-thumb-x:hover, .cff-file-x:hover { filter: brightness(1.15); }
.cff-drop-preview { margin-top: 10px; width: 100%; }
.cff-drop-files { display: flex; flex-direction: column; }
.cff-drop.is-drag { border-color: var(--brand); }
.cff-drop.has-files { padding: 14px; }

/* The strip and its caption describe the palette, so they say nothing until
   there is one — the concept's mock always has three colours. */
.cff-swatches2:not(.has-colors) :is(.cff-palette-strip, .cff-palette-cap) { display: none; }
/* The picker is positioned against its row. */
.cff-swatch-row { position: relative; }
.cff-sig-clear2 { align-self: flex-start; font: inherit; font-size: var(--cff-fs-2xs); font-weight: 600;
  color: var(--brand); background: none; border: 1px dashed color-mix(in srgb, var(--brand) 45%, transparent);
  border-radius: var(--c-r); padding: 10px 13px; margin-top: 4px; cursor: pointer; }
.cff-sig-clear2:hover { background: var(--tint); }
.cff-signature2 { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.cff-sig-pad { width: 100%; }
/* the rule and prompt are guidance for an empty pad, not decoration on a signed one */
.cff-sig-pad.is-signed :is(.cff-sig-line, .cff-sig-hint) { display: none; }
/* Tag chips: a drawn close that takes the chip's own colour, rather than a
   default button box sitting inside the pill. */
.cff-tag-x { display: grid; place-items: center; width: 14px; height: 14px; flex: none;
  border: 0; padding: 0; background: none; color: inherit; opacity: 0.65;
  border-radius: var(--o-r); cursor: pointer; }
/* A --brand-toned wash reads as invisible sitting ON solid --brand now that
   the chip itself carries the fill — lighten against --on-brand instead,
   same move a close icon on any solid colour badge uses. */
.cff-tag-x:hover { opacity: 1; background: color-mix(in srgb, var(--on-brand) 25%, transparent); }

/* both stay block containers so the inline-flex "+ Add" buttons hug their
   text; the rows space themselves with their own margin-bottom */
.cff-itemqty, .cff-itemqty-rows, .cff-swatches2 { display: block; }
/* Live-only: the concept's mock row has no way to delete a line. A drawn
   glyph (icon-x-mark-sm), not the &times; character this used to be --
   same reasoning as the event-type clear button: a bare × renders however
   small and thin the vendor's own font happens to draw it, easy to miss
   next to a 44px row. --muted rather than --faint by default too, since a
   button a visitor needs to find shouldn't rely on hover to become legible. */
.cff-itemqty-x { flex: none; width: 34px; height: 34px; border: 0; background: none; color: var(--muted);
  line-height: 1; cursor: pointer; border-radius: var(--o-r);
  display: grid; place-items: center; transition: background .12s, color .12s; }
.cff-itemqty-x:hover { background: color-mix(in srgb, var(--danger) 9%, transparent); color: var(--danger); }

/* Validation + submission states.
   The frame list covers every widget that OWNS a border, not just the five
   text-shaped ones it started with: a required tile/package/scale/upload/
   signature/consent question used to show the red message with a completely
   unchanged control. Frameless widgets get their own marks below. */
.cff-field.invalid :is(.cff-control input, .cff-control textarea, .cff-frame, .cff-tagbox, .cff-itemqty-name,
  .cff-choice, .cff-tile, .cff-pkg, .cff-scale-btn, .cff-stepper, .cff-itemqty-step,
  .cff-drop, .cff-sig-pad, .cff-consent-box) {
  border-color: var(--danger); }
/* The segment has no border of its own (it's a groove); a 1px ring stands in. */
.cff-field.invalid .cff-segment { box-shadow: 0 0 0 1px var(--danger); }
/* Unfilled stars pick up a red cast so the rating row itself signals, not
   just the message under it. */
.cff-field.invalid .cff-star2:not(.on) { color: color-mix(in srgb, var(--danger) 45%, var(--line)); }
/* Clicking into the field the validator just flagged must still show focus:
   the border stays red (the field is still invalid) and the brand ring the
   rest of the module uses carries the "you are here" signal. Declared at
   invalid's own specificity + pseudo so neither hover nor the generic focus
   rule can null it. */
.cff-field.invalid :is(.cff-control input, .cff-control textarea, .cff-itemqty-name):focus,
.cff-field.invalid :is(.cff-frame, .cff-tagbox, .cff-stepper, .cff-itemqty-step):focus-within {
  border-color: var(--danger); box-shadow: 0 0 0 3px var(--ring); }
.cff-field-error { margin: 2px 0 0; font-size: var(--cff-fs-2xs); color: var(--danger); }
.cff-error { margin: 14px 0 0; font-size: var(--cff-fs-xs); color: var(--danger); text-align: center; }
.cff-error[hidden] { display: none; }
.cff-honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* Cloudflare Turnstile (core/templatetags/turnstile.py). Collapsed while it
   is only running its silent check, which is every ordinary visitor, so the
   form looks exactly as the vendor built it. .is-interactive arrives from the
   widget's before-interactive callback when Cloudflare wants a click: then it
   gets the form's own spacing and the vendor's card corner (--c-r, which the
   pill style leaves on the Corners pick, so a 65px box never turns into a
   capsule). Its inside is
   Cloudflare's iframe, so light/dark (picked from the vendor's ink) is the
   only colour it can take. */
.cf-turnstile:not(.is-interactive) { height: 0; overflow: hidden; }
/* The iframe sits in Cloudflare's closed shadow root, out of reach of any
   selector, so the corner is clipped on the wrapper instead. */
.cf-turnstile.is-interactive { margin-top: var(--q-gap, 18px); line-height: 0; border-radius: var(--c-r); overflow: hidden; }
.cff-success {
  /* .cform's own translation of the vendor palette (line 43+) doesn't reach
     here — see the style= comment in bio_sections/contact.html — so the
     three roles this panel actually uses (ink/brand/muted) get the same
     resolution repeated locally, off the copy of contact_form_style this
     element now carries inline. */
  --ink: var(--cff-ink, #0F172A);
  --brand: var(--cff-primary, #2D64BC);
  --muted: color-mix(in srgb, var(--ink) 68%, transparent);
  display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center;
  padding: 28px 20px; color: var(--ink); }
.cff-success[hidden] { display: none; }
.cff-success svg { width: 46px; height: 46px; fill: none; stroke: var(--brand); stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round; }
/* Icon draws itself in (ring, then the tick), title and message rise in
   right after. Every animated property's "hidden" value lives ONLY inside
   its @keyframes `from`, never as a plain rule on the class — the
   prefers-reduced-motion block below knocks out `animation` wholesale, and
   if the hidden state were a static property too the checkmark would be
   stuck undrawn / the copy stuck invisible for anyone with that setting on. */
.cff-succ-icon { animation: cff-succ-rise .45s cubic-bezier(.16,1,.3,1) both; }
.cff-succ-ring { stroke-dasharray: 63; animation: cff-succ-draw-ring .55s ease .1s both; }
.cff-succ-tick { stroke-dasharray: 12; animation: cff-succ-draw-tick .3s ease .5s both; }
.cff-success-title { font-family: var(--serif); font-weight: 500; font-size: clamp(16px, 4cqi, 20px);
  animation: cff-succ-rise .45s cubic-bezier(.16,1,.3,1) .15s both; }
.cff-success p { margin: 0; font-size: var(--cff-fs-base); color: var(--muted);
  animation: cff-succ-rise .45s cubic-bezier(.16,1,.3,1) .25s both; }
@keyframes cff-succ-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes cff-succ-draw-ring { from { stroke-dashoffset: 63; } to { stroke-dashoffset: 0; } }
@keyframes cff-succ-draw-tick { from { stroke-dashoffset: 12; } to { stroke-dashoffset: 0; } }
.cff-note { margin: 10px 0 0; text-align: center; font-size: var(--cff-fs-2xs); color: var(--muted); }
.cff-privacy a { text-underline-offset: 2px; text-decoration-color: var(--hairline); transition: opacity .12s; }
.cff-privacy a:hover { opacity: .7; }
.cff-submit:hover { filter: brightness(1.06); }
.cff-submit:disabled { opacity: 0.6; cursor: default; }

/* Disabled + readonly. Outside the calendar internals and the submit button,
   no control in the module had any disabled treatment at all (the dropzone
   improvised opacity + pointer-events for its own two states) — a disabled
   field rendered identical to a live one. One quiet rule for every control
   surface; pointer-events:none also parks the hover styles. */
.cform :is(.cff-control input, .cff-control textarea, .cff-swatch-name, .cff-itemqty-name,
           .cff-tag-input, .cff-stepper input):disabled,
.cform :is(.cff-frame, .cff-select-btn, .cff-choice, .cff-pkg, .cff-scale-btn,
           .cff-step, .cff-star2, .cff-drop):disabled,
.cform .cff-choice:has(> input:disabled) {
  opacity: .55; cursor: not-allowed; pointer-events: none;
}
/* Readonly is fixed-but-real, not inert: the value still reads at full ink,
   only the box stops presenting itself as editable. */
.cform :is(.cff-control input, .cff-control textarea)[readonly]:not(:disabled) {
  border-style: dashed; cursor: default;
}

/* Multi-step (wizard) chrome — lab specimen only */
.cff-steps-bar[hidden], .cff-steps-nav[hidden] { display: none; }
.cff-steps-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
.cff-steps-track { flex: 1; height: 6px; border-radius: 999px; background: var(--fill-2); overflow: hidden; }
.cff-steps-fill { display: block; height: 100%; width: 25%; border-radius: 999px;
  background: var(--brand); transition: width .25s ease; }
.cff-steps-label { flex-shrink: 0; font-size: var(--cff-fs-2xs); font-weight: 600; color: var(--muted); }
.cff-steps-nav { display: flex; gap: 10px; margin-top: 22px; }
.cff-step-back, .cff-step-next { font: inherit; font-size: var(--cff-fs-label); font-weight: 600; padding: 13px 16px;
  border-radius: var(--c-r); cursor: pointer; transition: filter .15s, opacity .15s; }
.cff-step-back { flex: 0 0 auto; min-width: 92px; background: transparent; color: var(--ink);
  border: 1px solid var(--hairline); }
.cff-step-back:disabled { opacity: 0.4; cursor: default; }
.cff-step-next { flex: 1; background: var(--brand-fill); color: var(--on-brand); border: none; }
.cff-step-next:hover { filter: brightness(1.06); }
.cform.is-stepped > .cff-submit { display: none; }

/* ================= field styles =================
   Ported from the controls library's own body[data-v="..."] blocks: each is a
   flip of the --f-* tokens every control above already reads, so a style needs
   no per-widget selector list. Corners/Spacing ride along as independent inline
   axes, so these compose with any combination.

   The library adds a set of tokens the old artifact had no equivalent for, and
   they are bound here rather than inside each variant because every variant
   wants them:

     --fld-bd / --fld-bd-hover   a field border that STEPS UP on hover before it
                                 steps up again on focus. The old module went
                                 straight from resting to brand, so hovering a
                                 field gave no feedback at all.
     --placeholder               placeholder ink, separated from --faint so it
                                 can clear 4.5:1 on the filled variant's tint
                                 without dragging every other quiet label darker.
     --track-bg / --track-ink    the Yes/No groove. It cannot be the page colour:
                                 the selected thumb sits IN it, and on a page-
                                 coloured track the thumb has nothing to sit on.
     --rail                      width of the accent rail that marks a selected
                                 stacked row. This is the library's single
                                 biggest visual change to the module.
     --tag-bg / --tag-bd         tag chips read on any box fill, which a chip
                                 tinted with the page colour does not.

   Neutrals stay derived from --ink (see the palette binding at the top) rather
   than copied from the library's light-mock literals, for the same reason as
   every other colour here. */
.cform {
  --fld-bd:       var(--line);
  --fld-bd-hover: color-mix(in srgb, var(--ink) 35%, transparent);
  --placeholder:  var(--faint);
  /* Same scrim as every other answerable surface. It was --fill (ink 6%), which
     next to a 60%-scrim field read as no groove at all once the page had art
     behind it — and the selected thumb needs something to sit IN. */
  --track-bg:     var(--o-bg);
  --track-ink:    var(--muted);
  --tag-bg:       var(--tint);
  --tag-bd:       color-mix(in srgb, var(--brand) 35%, transparent);
  --rail:         3px;
}
/* One attribute restores a 3:1 border boundary everywhere, for accessibility
   audits. New in the controls library; nothing in the module sets it yet, so it
   is opt-in via a data-contrast="high" attribute on the form. */
.cform[data-contrast="high"] {
  --fld-bd:       color-mix(in srgb, var(--ink) 45%, transparent);
  --fld-bd-hover: color-mix(in srgb, var(--ink) 60%, transparent);
}

/* Hover is a real state now, on every surface that owns a border. Kept off
   :focus so it cannot fight the focus ring, and off touch devices via
   @media(hover:hover) — on a phone :hover sticks after a tap and leaves the
   last-touched field looking permanently active. */
@media (hover: hover) {
  /* :not(.cff-field.invalid *): this rule lands later in the file than the
     invalid border and at the same specificity, so without the guard,
     hovering a flagged field swapped its red border back to the neutral
     hover tint. An invalid field keeps red under the pointer. */
  .cform :is(.cff-control input, .cff-control select, .cff-control textarea,
             .cff-frame, .cff-select-btn, .cff-tagbox, .cff-stepper):hover:not(.cff-field.invalid *) {
    border-color: var(--fld-bd-hover); }
  .cform :is(.cff-choice, .cff-pkg):hover:not(.cff-field.invalid *) { border-color: var(--fld-bd-hover); }
}
.cff-control input::placeholder,
.cff-control textarea::placeholder,
.cff-tag-input::placeholder { color: var(--placeholder); }

/* Each variant is now a flip of the --f-* tokens and nothing else: field fill,
   field border, the three radii, and the two padding axes. The per-widget
   radius lists that used to live here are gone — every widget reads --f-radius,
   --o-r or --c-r, so a style has no widget-shaped exceptions left to state. */

/* Boxed — the default. */
/* Restating --paper here would undo the scrim above for the DEFAULT style — this
   variant re-declares --f-bg only to pair it with --fld-bd, so it has to carry
   the same translucency. */
.cff-boxed { --f-bg: color-mix(in srgb, var(--paper) var(--f-fill-alpha), transparent);
  --f-border:1px solid var(--fld-bd);
  --f-px:13px; --f-py:11px; }

/* Underline — hairline rules, no box. A radius on a bottom rule is meaningless,
   so the Corners axis is ignored FOR FIELDS here rather than producing a curved
   hairline that reads as a rendering bug. Options and cards keep the vendor's
   pick: a package card and a popover are still boxes on this style, and zeroing
   --o-r/--c-r along with --f-radius squared off every one of them. */
.cff-underline { --f-bg:transparent; --f-radius:0;
  --o-r: var(--f-radius-pick); --c-r: var(--f-radius-pick);
  --f-px:2px; --f-py:9px; }
.cff-underline :is(.cff-control input, .cff-control select, .cff-control textarea, .cff-frame,
  .cff-select-btn, .cff-tagbox, .cff-stepper, .cff-itemqty-name, .cff-swatch-name) {
  border: 0; border-bottom: 1.5px solid var(--fld-bd); border-radius: 0;
  padding-left: var(--f-px); padding-right: var(--f-px); background: transparent; }
.cff-underline .cff-choice { border: 0; border-bottom: 1px solid var(--line-soft); border-radius: 0;
  padding-left: 0; padding-right: 0; background: transparent; }
.cff-underline .cff-choice:has(input:checked) { background: transparent; }

/* Filled — soft fill, no border. The card radius runs a step ahead of the field
   radius, as in the library: the same corner that reads as "soft" on a 44px
   input reads as "barely rounded" on a 130px card. That step has to be relative
   to the vendor's Corners pick, not a pair of literal pixel values — pinning
   --f-radius the way this once did silently overrode --f-radius-pick, so the
   Corners control did nothing on this style. That is the exact bug the Pill
   style was retired for (see the note below); Filled had it too. */
.cff-filled { --f-bg: var(--fill); --f-border:1px solid transparent;
  --c-r: calc(var(--f-radius) + 2px); --f-px:14px; --f-py:12px; }
.cff-filled .cff-choice { background: var(--fill); border-color: transparent; }
.cff-filled .cff-choice:has(input:checked) { background: var(--tint); border-color: var(--brand); }
.cff-filled :is(.cff-tagbox, .cff-itemqty-name, .cff-swatch-name) { background: var(--fill); border-color: transparent; }
/* Focus deepens the tint instead of adding a line — on a borderless field a
   border colour change is the one cue that isn't there to change. */
.cff-filled :is(.cff-control input, .cff-control select, .cff-control textarea):focus,
.cff-filled .cff-frame:focus-within,
.cff-filled .cff-select-btn[aria-expanded="true"] { background: var(--tint); }
@media (hover: hover) {
  .cff-filled :is(.cff-choice, .cff-tile):hover { background: var(--fill-2); }
}

/* Pill (.cff-pillfields) was here, and is gone — retired in migration 0179,
   which remapped its vendors to Filled.

   Kept as a note because the reasoning generalises to any style added later. A
   capsule is a shape for ONE line of text, and most of this form is not one
   line: the textarea, dropzone, signature pad, package card, tag box and every
   popover each needed a carve-out to stop 99px bowing them into stadiums, and
   each carve-out was a place the vendor's Corners pick stopped applying. Pinning
   all three radii is also what made the Corners control render its three
   settings identically on that style — a live picker that did nothing.

   So: a field style may set --f-radius and --o-r. If it also wants to pin --c-r,
   that is the signal it has stopped being a style and started being a shape. */

/* ================= controls-library additions =================
   Ported from _design_ref/zulli-fields.css -- the library's ruled-not-filled
   geometry for the choice row, the package card and the scale. This file is the
   only place any of it lives (the builder renders THIS module), so there is no
   second copy to keep in step; the sections below are grouped by widget in the
   order they were ported. */

/* ---- Yes / no ----
   The geometry of the sliding indicator is the library's.
   What stays here is the one thing the mock cannot express: the mock is always
   answered, so it draws the bar at rest. This form starts with NO answer, and a
   bar resting under "Yes" before the visitor has touched it reads as a
   pre-filled answer -- the same objection the design makes to a pre-ticked
   consent box. So it is hidden until the JS sets .has-val on a real choice. */
.cff-segment-bar { opacity: 0; pointer-events: none; transition: transform .22s cubic-bezier(.4,0,.2,1), opacity .15s ease; }
.cff-segment.has-val .cff-segment-bar { opacity: 1; }
.cff-segment button.is-on { background: none; box-shadow: none; }
/* Underline has no track for a thumb to sit in, so there the indicator is a
   rule under the chosen half -- the move the text inputs make on that style. */
.cff-underline .cff-segment { background: transparent; border-radius: 0;
  border-bottom: 1.5px solid var(--line); }
.cff-underline .cff-segment-bar { top: auto; bottom: -1.5px; height: 1.5px;
  border-radius: 0; left: 0; width: 50%; }
.cff-underline .cff-segment button.is-on { color: var(--brand-deep); }

/* ---- Multi-select: corner-mark tiles ----
   From the controls library. Multi-select is the one widget that deliberately
   does NOT share a shape with single-select: a checkbox row and a radio row
   differed only in the shape of their mark, so "pick one" and "pick several"
   read identically until you looked closely. A grid of tiles carries that
   difference structurally.

   Selection is the same brand accent as everywhere else, in the third of the
   library's three marks -- rail for stacked rows, top rule for cards, corner
   for tiles. */
.cff-tiles {
  display: grid; gap: 8px;
  /* auto-fit, not a fixed count: the module is a query container (.cform sets
     container-type), so tiles reflow to the form's own width wherever it is
     embedded rather than to the viewport. */
  grid-template-columns: repeat(auto-fit, minmax(clamp(120px, 32cqi, 164px), 1fr));
}
.cff-tile {
  position: relative; overflow: hidden; cursor: pointer;
  border: 1px solid var(--line-soft); border-radius: var(--c-r);
  /* The corner mark's own outer vertex sits exactly on this corner (see
     .cff-tile-mark below), so a large vendor Corners pick bit a visible
     rounded notch out of its otherwise-sharp triangle -- clean at the
     library's own 4px, ugly at this module's larger defaults. Capping just
     this corner is the same move as .cff-box's min(var(--o-r),6px): the
     vendor still gets their pick everywhere else on the tile. */
  border-top-right-radius: min(var(--c-r), 6px);
  padding: 13px 14px; background: var(--o-bg);
  transition: border-color .15s ease, background .15s ease;
}
.cff-tile input { position: absolute; opacity: 0; width: 0; height: 0; }
.cff-tile-name { display: block; font-size: var(--cff-fs-xs); line-height: 1.35; color: var(--ink); }
/* The tile's own sub-paragraph, same role as .cff-choice-sub on the single-
   select row -- an optional line the vendor writes per option, not per
   question. Right padding clears the corner mark's 26px square so a long
   description's first line doesn't run under it. */
.cff-tile-sub { display: block; font-size: var(--cff-fs-micro); color: var(--muted); line-height: 1.4; margin-top: 4px; padding-right: 22px; }
.cff-tile:has(input:checked) { border-color: var(--brand); background: var(--o-bg-on); }
/* Colour only, never weight -- see the choice row above for why. */
.cff-tile:has(input:checked) .cff-tile-name { color: var(--brand-deep); }
/* The mark is a filled triangle in the top-right corner, revealed on select.
   It animates in from outside its own corner, which is why the tile clips its
   overflow -- unclipped, the hidden state pokes out past the border radius. */
.cff-tile-mark {
  position: absolute; top: 0; right: 0; width: 26px; height: 26px;
  background: var(--brand-fill); color: var(--on-brand);
  clip-path: polygon(100% 0, 0 0, 100% 100%);
  display: grid; place-items: start end; padding: 2px 3px 0 0;
  opacity: 0; transform: translate(6px, -6px); transition: .15s ease;
}
.cff-tile-mark svg { width: 9px; height: 9px; }
.cff-tile:has(input:checked) .cff-tile-mark { opacity: 1; transform: none; }
/* Focus has to be visible on the TILE, since the real checkbox is off-screen. */
.cff-tile:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ================= controls library: the universal widgets =================
   Text input, label, hint, textarea and the popover trigger. These appear on
   EVERY form, which is why they are worth getting exactly right -- and why the
   earlier passes (choice rows, package cards, multi-select) changed so little
   for most vendors: their forms barely contain those widgets.

   Everything below is geometry and type. Colour still resolves through the
   palette bound in .cform, so this works on any vendor theme. */

/* Labels: heavier, tighter, and closer to the control they name. The label and
   its field are one unit; at the old 6px they read as evenly spaced siblings of
   the hint below, so the eye had to work out which label belonged to which box. */
.cff-field > label:not(.cff-consent):not(.cff-drop) {
  font-size: var(--cff-fs-xs); font-weight: 600;
  letter-spacing: -.004em; margin-bottom: 8px; line-height: 1.4;
}
/* Hints get a MEASURE. Helper text runs the full width of the form otherwise,
   and a 90-character line of 12px grey is the least readable thing on the page.
   58ch is the library's cap. */
.cff-hint {
  line-height: 1.55; margin: 0 0 9px;
  max-width: 58ch;
}

/* The textarea grows with the form rather than sitting at a fixed 84px -- it is
   the one control whose height is a hint about how much to write, so on a wide
   form it should look like it wants a paragraph. */
.cff-control textarea { min-height: clamp(96px, 26cqi, 132px); line-height: 1.6; }
/* A MULTI-LINE surface takes the card corner, not the field corner. It is a
   field by every other measure -- same fill, same border, same padding, same
   focus ring -- but a capsule is a shape for one line of text: on the Pill
   style --f-radius bowed a 130px-tall box into a stadium with no straight edge
   left to write against, and the address and message questions (the two tallest
   on most forms) were the worst of it. --c-r follows the vendor's Corners pick
   here as it does on every other tall surface -- the dropzone, the signature
   pad, the package card -- so the textarea rounds with them instead. */
.cff-control textarea { border-radius: var(--c-r); }

/* Focus: the border alone, no ring and no glow — the controls library's own
   rule, and confirmed as the product call 2026-08-11. The border steps to the
   vendor's primary; that is the whole focus treatment. Filled still has a cue
   without a shadow: its resting border is transparent, so the brand border
   appearing (plus its own focus tint-deepen) marks the field. Keyboard
   :focus-visible outlines on bare buttons are separate and stay. */
.cff-control input:focus, .cff-control select:focus, .cff-control textarea:focus,
.cff-frame:focus-within, .cff-select-btn[aria-expanded="true"] {
  border-color: var(--brand); box-shadow: none;
}
/* The same border-to-brand treatment for the typed-into surfaces that own
   their border on a wrapper (tag box, steppers) or sit outside .cff-control
   (swatch name, item name): these were the only text inputs in the module
   with outline:none and NO substitute focus signal at all. A closed-but-
   focused dropdown trigger joins them so tabbing to it reads like tabbing
   into the input beside it, instead of falling back to the generic button
   outline. */
.cff-swatch-name:focus, .cff-itemqty-name:focus,
.cff-tagbox:focus-within, .cff-stepper:focus-within, .cff-itemqty-step:focus-within,
.cff-select-btn:focus-visible {
  border-color: var(--brand); outline: none;
}
/* Hover sits BELOW focus in the ramp so it can never fight the focus ring, and
   is gated on a real pointer -- on touch, :hover sticks after a tap and leaves
   the last-touched field looking permanently active. Same invalid guard as the
   first hover block: without it this later rule swapped a flagged field's red
   border back to neutral under the pointer. */
@media (hover: hover) {
  .cff-control input:hover:not(:focus):not(.cff-field.invalid *),
  .cff-control select:hover:not(:focus):not(.cff-field.invalid *),
  .cff-control textarea:hover:not(:focus):not(.cff-field.invalid *),
  .cff-select-btn:hover:not([aria-expanded="true"]):not(.cff-field.invalid *) { border-color: var(--fld-bd-hover); }
}

/* ---- Stepper: the library's ruled geometry ----
   Layout only; colour and face still come from the vendor's theme.

   Three changes, all structural. It shrink-wraps instead of spanning the form:
   a number input stretched to 600px reads as a text field that happens to have
   arrows, and its value floats alone in the middle of a void -- which is why a
   custom number question with no placeholder looked broken rather than empty.

   The buttons are square 44px targets, and the value sits between two RULES
   rather than inside an unbroken box, so the three parts read as one control.
   min-width on the value stops the whole thing resizing between 9 and 10. */
.cff-stepper {
  display: inline-flex; align-items: stretch; width: auto; overflow: hidden;
}
.cff-step {
  width: 44px; min-height: 44px; flex: none;
  display: grid; place-items: center; padding: 0;
}
.cff-stepper input {
  min-width: 58px; flex: 0 1 auto; padding: 0 6px; text-align: center;
  /* the rules that separate value from buttons; border-color follows the theme */
  border-left: 1px solid var(--line); border-right: 1px solid var(--line);
}
/* The item+qty row's quantity control IS .cff-itemqty-step (it carries
   data-cff-stepper itself); it does not wrap a .cff-stepper. Its grid alignment
   is set with the rest of that row's layout further down. */

/* ---- Consent: the library's geometry ----
   Layout only. The one structural change is a MEASURE: consent copy is the
   longest sentence on most forms and it ran the full width, so on a wide form
   it became a single 100-character line -- the hardest possible reading for the
   one thing a visitor genuinely should read before agreeing to it.

   The box grows 18 -> 19px and squares off 5 -> 4px to match every other mark,
   and the tick is sized with the box rather than left at a fixed 9px. */
/* 68ch literal, not var(--measure-body): that token is the library's and does
   not exist in this module, so the reference read as if it did while only the
   fallback ever applied. */
.cff-consent { max-width: 68ch; }
.cff-consent-box { width: 19px; height: 19px; }
/* A TICK BOX follows the Corners pick with a ceiling. Its shape carries
   meaning -- square-ish says "tick this", round says "one of these" -- so an
   unclamped --o-r turning a 19px box into a circle made the consent box read as
   a radio the visitor could not work out how to unset. min(), not a fixed
   radius: a vendor who picks 2px corners still gets 2px here. Same ceiling is
   applied to .cff-box above, for the same reason. */
.cff-consent-box { border-radius: min(var(--o-r), 6px); }

/* ================= controls library: fields, panels and pickers =================
   The rest of _design_ref/zulli-fields.css, ported the same way as the widgets
   above: LAYOUT AND GEOMETRY ONLY. Every colour still resolves through the
   palette bound in .cform, and every family through the vendor's --sans/--serif,
   so none of this can pull a light mock's ink onto a dark-theme page.

   The library's own field-style tokens (--f-px/--f-py, --o-r, --c-r) do the work
   that per-widget override lists used to: a control states its padding and its
   corner once, in tokens, and the four styles differ only in what those tokens
   resolve to. */

/* ---- Field padding as two axes ----
   Every text-entry surface took a hardcoded 10px 13px, so the four styles were
   identical in the one dimension a visitor actually feels: how much room the
   field gives its own text. The library varies it deliberately -- underline
   pulls in to a 2px optical inset because there is no box to sit inside, pill
   pushes out to 18px because a capsule's curve eats its own corners. */
.cff-control input, .cff-control select, .cff-control textarea,
.cff-frame, .cff-select-btn, .cff-itemqty-name, .cff-swatch-name {
  padding: var(--f-py) var(--f-px);
}

/* ---- Popover trigger ----
   The caret is pushed to the far edge rather than trailing the value by a fixed
   gap: with a short answer ("Yes") the chevron used to sit stranded mid-field,
   which read as part of the value rather than as the control's own affordance. */
.cff-frame, .cff-select-btn { justify-content: space-between; gap: 12px; }
.cff-caret { width: 15px; height: 15px; transition: transform .18s ease; }

/* ---- Dropdown / listbox panels ----
   One panel geometry for every popover in the module: the select list, the
   event-type picker, the date picker and the time picker. They were three
   near-copies that had already drifted a pixel apart from each other. */
.cff-select-list, .cff-et-pop, .cff-datepicker-pop { padding: 5px; }
.cff-select-list { max-height: 250px; overscroll-behavior: contain; }

/* Options carry a left ACCENT RAIL rather than only a fill. It is the same mark
   the chosen stacked row uses, so "this is the one you picked" looks the same
   whether the choice was made in a list or in a row -- and on the Filled style,
   where a hover fill and a selected fill are both washes of the same ink, the
   rail is the only thing that tells them apart at a glance. */
.cff-select-list li, .cff-et-opt {
  border-left: 3px solid transparent; padding: 10px 11px;
  /* Capped at the same 6px ceiling the tick box uses, and for the same kind of
     reason: a border FOLLOWS its corner, so on a 40px row a large pick bends
     the 3px rail into a bracket around the text instead of a straight bar
     beside it -- at 12px it still read as a parenthesis. 6px leaves the rail a
     long straight run while a 2px pick still comes through as 2px. Time slots
     have no rail and are left on --o-r, capsules and all. */
  border-radius: min(var(--o-r), 6px);
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.cff-select-list li:hover, .cff-select-list li.is-active,
.cff-et-opt:hover, .cff-et-opt.is-active { border-left-color: var(--line-soft); }
/* The rail, the brand ink and the tick all mark the chosen option; a fourth
   mark in the form of a weight bump only made it look like a different face. */
.cff-select-list li.is-sel, .cff-et-opt.is-sel { border-left-color: var(--brand); }
.cff-tick { width: 14px; height: 14px; }

/* Group headings STICK to the top of their scroller. The event-type list is 443
   names in 15 groups, so a heading scrolled past leaves the visitor reading a
   column of unlabelled names with no way to tell a religious ceremony from a
   corporate one. -8px, not 0: the panel's own padding sits above the list, and
   at 0 the heading detached and floated a padding's width clear of the edge. */
.cff-et-group { position: sticky; top: -8px; z-index: 1; background: var(--paper); }

/* ---- Time picker: a grid of slots, not a column of rows ----
   48 half-hour slots in a single column is a long scroll to reach an evening
   time. As a grid the same 48 fit a few screens, and reading across a row of
   times is how a clock is scanned anyway. */
.cff-timepicker-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(clamp(64px, 19cqi, 84px), 1fr));
  gap: 5px; padding: 8px; max-height: clamp(220px, 58cqi, 300px);
  overscroll-behavior: contain;
}
.cff-timepicker-list[hidden] { display: none; }
.cff-timepicker-list li {
  min-height: 38px; display: grid; place-items: center;
  border: 1px solid var(--line-soft); padding: 4px;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.cff-timepicker-list li:hover { border-color: var(--brand); }

/* ---- Start/end pairs ----
   Side by side at EVERY width (user call, 2026-08-11). The old sub-380px
   stack put a stray floating arrow between two full-width fields, which read
   as a rendering bug on exactly the screens most visitors use. The pair fits
   on one row all the way down to the module's ~276px floor: the triggers hold
   "Start date" / a chosen "Sep 12, 2026" comfortably to ~300px and truncate
   with their own ellipsis below that, which beats a broken-looking stack.
   The triggers are equal-height single rows, so the arrow centres against
   them with no baseline padding. */
.cff-range-pickers { display: grid; gap: 8px; grid-template-columns: 1fr auto 1fr; align-items: center; }
.cff-range-arrow {
  font-size: var(--cff-fs-nano); letter-spacing: .14em; text-transform: uppercase;
  color: var(--faint); text-align: center; padding: 0;
}

/* ---- Calendar ----
   Days are CIRCLES, not rounded squares: a 7-wide grid of rounded rectangles
   reads as a table of cells, and the eye has to find the number inside each
   box. The nav buttons match -- borderless, same hover-fill as a day -- so the
   whole popover is one family of shapes; neither follows the Corners pick,
   because a squared-off calendar day is a spreadsheet cell rather than a
   date. (Un-bordered per the dashboard's own .bk-cal-nav, 2026-08-13: a
   bordered nav button read as a separate control from the days it sits
   above.) */
.cff-dp-head { justify-content: space-between; gap: 10px; padding: 2px 2px 0;
  margin-bottom: var(--cff-sp-sm); }
.cff-dp-nav { width: 34px; height: 34px; transition: color .12s ease; }
.cff-dp-nav:hover { color: var(--brand); }
.cff-dp-nav-icon { width: 18px; height: 18px; }
.cff-dp-month { font-size: var(--cff-fs-label); letter-spacing: -.01em; }
.cff-dp-weekdays { gap: 2px; }
.cff-dp-weekdays span { font-size: var(--cff-fs-nano); letter-spacing: .08em;
  text-transform: uppercase; padding: 6px 0; }
.cff-dp-day { min-height: 34px; transition: background .12s ease, color .12s ease; }

/* ---- Linear scale ----
   Ten buttons across is unreadable on a phone: each lands near 28px wide, below
   the 44px touch target, and the digits collide with their own borders. Below
   400px it folds to two rows of five -- 1-5 over 6-10, which is how the scale
   is read anyway. min-height is the floor that keeps the fold honest. */
.cff-scale10 { gap: 5px; }
@container (max-width: 400px) {
  .cff-scale10 { grid-template-columns: repeat(5, 1fr); }
}
.cff-scale-btn { min-height: 38px; transition: background .13s ease, border-color .13s ease, color .13s ease; }
/* The end labels are the scale's legend, not body copy: set as micro caps they
   read as the axis they label rather than as an answer option. */
.cff-scale-lab {
  gap: 12px; margin-top: 9px; font-size: var(--cff-fs-nano);
  letter-spacing: .08em; text-transform: uppercase;
}

/* ---- Sliders ----
   The track is 44px tall and the thumb 22px inside it, so the whole strip is a
   touch target rather than a 5px hairline a finger has to find. The rail thins
   to 4px in the process: a heavier rail under a thumb this size reads as a
   progress bar the visitor is watching, not a control they are meant to drag. */
.cff-budget-valrow { gap: 10px; margin-bottom: var(--cff-sp-sm); }
.cff-budget-track { height: 44px; }
.cff-budget-track::before, .cff-budget-fill { height: 4px; }
:is(.cff-budgetrange, .cff-slider) input[type="range"] { height: 44px; }
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-webkit-slider-thumb { border-width: 2.5px; }
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-moz-range-thumb { border-width: 2.5px; }
/* The end labels belong to the track they sit under; at 9px they floated
   between the slider and whatever question came next. */
.cff-budget-ends { gap: 12px; margin-top: 2px; }

/* ---- Item + quantity rows ----
   A four-part row (index, name, quantity, remove) laid out with flex leaves the
   name box a different width on every row, because each row sizes its own
   children. As a grid the columns line up down the whole list, which is the
   only reason a numbered list of items reads as a list. */
.cff-itemqty-rows { display: grid; gap: 9px; }
.cff-itemqty-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 10px; align-items: center; margin-bottom: 0;
}
.cff-itemqty-n { width: 15px; text-align: right; }
/* Under 400px the four columns cannot all hold their content, and the name box
   is the one that suffers -- it collapses to a couple of visible characters.
   The quantity stepper drops to its own line instead, left-aligned under the
   name, which is the part with a floor on how narrow it can usefully get.

   The grid-area names are declared HERE and not on the children generally: a
   grid-area naming an area that no grid-template-areas defines is not inert, it
   resolves as a line name and quietly re-places the item. In the four-column
   layout the children are already in column order, so auto-placement is right. */
@container (max-width: 400px) {
  .cff-itemqty-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "n inp rm" ". qty qty";
    row-gap: 8px;
  }
  .cff-itemqty-n { grid-area: n; }
  .cff-itemqty-name { grid-area: inp; }
  .cff-itemqty-step { grid-area: qty; justify-self: start; }
  .cff-itemqty-x { grid-area: rm; }
}
/* "Add another" is a full-width dashed row: it is the end of the list and it
   should look like the next empty slot in it. Inline, it read as a link that
   happened to have a border. */
.cff-itemqty-add {
  display: flex; width: 100%; min-height: 42px;
  justify-content: center; gap: 8px; margin-top: 11px; padding: 0 13px;
}

/* ---- Uploaders ----
   Thumbs size to the FORM's width rather than to a fixed 72px floor, so a wide
   embed shows fewer, larger previews instead of a strip of stamps. */
.cff-drop-grid {
  gap: 8px; grid-template-columns: repeat(auto-fill, minmax(clamp(72px, 22cqi, 96px), 1fr));
  margin-bottom: 11px;
}
.cff-thumb { border: 1px solid var(--line-soft); place-items: center; }
.cff-thumb-x {
  top: 6px; right: 6px; width: 24px; height: 24px;
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  transition: background .15s ease, transform .12s ease;
}
.cff-thumb-x:hover { transform: scale(1.06); }
/* File rows space themselves from the list, not from their own bottom margin --
   a per-chip margin left a stray gap under the last one, directly above the
   dropzone, which read as the zone being detached from its own attachments. */
.cff-drop-files { display: grid; gap: 8px; margin-bottom: 11px; }
.cff-file-chip { align-items: flex-start; gap: 10px; padding: 11px 11px 11px 12px; margin-bottom: 0; }
/* A filename is not prose: truncating it with an ellipsis hides the extension
   and the end of the name, which is the part that distinguishes contract-v2
   from contract-v3. It wraps at any character instead, because a filename has
   no word boundaries to wrap at. */
.cff-file-name { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
.cff-drop { padding: clamp(16px, 5cqi, 26px) 16px; }
.cff-drop b { margin-bottom: 4px; }
.cff-drop > span:not(.cff-drop-preview) { overflow-wrap: anywhere; }

/* ---- Colour palette rows ----
   Four items in a flex row means the name field takes whatever is left after
   the swatch, the hex and the remove button -- and on a narrow form what is
   left is nothing. The grid pins the swatch and the remove button, gives the
   name the only flexible track, and keeps every row's columns aligned. */
.cff-swatch-row {
  display: grid; gap: 6px 10px; align-items: center;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas: "sw inp hex rm";
}
.cff-swatch-well { grid-area: sw; }
.cff-swatch-name { grid-area: inp; }
.cff-swatch-hex { grid-area: hex; width: auto; white-space: nowrap; }
.cff-swatch-rm { grid-area: rm; }
/* Below 340px the hex drops to its own line rather than the remove button
   wrapping: the remove button has to stay beside the row it removes, or it
   becomes ambiguous which colour it belongs to. */
@container (max-width: 340px) {
  .cff-swatch-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "sw inp rm" ". hex hex";
  }
  .cff-swatch-hex { justify-self: start; }
}
.cff-swatch-chev { right: -4px; bottom: -4px; }
.cff-palette-strip { height: 24px; margin-top: 13px; }
/* "Add a color" stays INLINE -- unlike the item list's add row, this one sits
   under a stack of full-width rows and a second full-width dashed box directly
   below them reads as another empty row rather than as an action. */
.cff-swatch-add {
  min-height: 40px; gap: 8px; margin-top: 11px; padding: 0 15px;
}

/* ---- Colour picker popover ----
   The saturation square scales with the form: at a fixed 104px a wide embed
   gave the one control that needs precision the least room on the page. */
.cff-cpop { top: calc(100% + 8px); width: min(268px, 100%); padding: 11px; }
/* The saturation square is a picking SURFACE, not an option, so it takes the
   card corner: on --o-r the Pill style rounded a 124px canvas into a lens and
   clipped the corner colours -- the exact hues a visitor drags to. */
.cff-csv { height: clamp(96px, 34cqi, 124px); border-radius: var(--c-r); }
.cff-chue { height: 14px; margin-top: 12px; }
.cff-chthumb { width: 14px; height: 14px; margin: -7px 0 0 -7px; }
.cff-cfields { gap: 8px; margin-top: 12px; }
.cff-cprev, .cff-cdrop { width: 34px; height: 34px; }
/* 16px floor, as everywhere else a visitor types: this is a real text input,
   and below 16px iOS Safari zooms the page on focus -- which here means the
   viewport jumps the moment the hex field is tapped, dragging the popover the
   visitor is aiming at out from under their finger. */
.cff-chex-input { font-size: var(--cff-fs-input); padding: 8px 10px; }
.cff-cdrop svg { width: 14px; height: 14px; }

/* ---- Display-only blocks ----
   A heading question is a SECTION BREAK, and a break needs a rule: without one
   it was a slightly larger label floating between two questions, and vendors
   used dividers to fake the effect. The rule is drawn in a strong neutral
   rather than a hairline, because it is dividing the form, not decorating it. */
.cff-static-head { border-top: 1.5px solid var(--muted); padding-top: 13px; }
.cff-static-sub { max-width: 58ch; }
/* 68ch, the library's body measure: a note is the longest running prose on the
   form and it ran the full width of the embed. */
.cff-static-note { max-width: 68ch; }
/* A divider draws its rule with a pseudo-element and keeps its own vertical
   rhythm, so it can carry a centred label later without changing the markup. */
.cff-static-div { display: flex; align-items: center; gap: 13px; height: auto;
  padding: 6px 0; background: none; }
.cff-static-div::before { content: ""; flex: 1; height: 1px; background: var(--hairline); }

/* ---- Star rating ----
   Two changes, both about the mark being a RATING rather than a row of icons.
   Unselected stars are drawn as outlines instead of filled in a paler ink: a
   pale filled star is a star that looks selected in a lighter colour, so at a
   glance a 5-star row read as "all five, faintly". An outline says "empty".

   And each star clears a 44px target. They were 26px glyphs with a 5px gap --
   under the minimum in both dimensions, on the one control that is nothing but
   small tap targets sitting next to each other. */
.cff-stars2 { gap: clamp(2px, 1cqi, 6px); flex-wrap: wrap; align-items: center; }
.cff-star2 {
  min-width: 44px; min-height: 44px; padding: clamp(3px, 1cqi, 6px);
  display: grid; place-items: center; color: var(--fld-bd-hover);
  transition: color .12s ease, transform .12s ease;
}
.cff-star2 svg { width: clamp(28px, 8cqi, 38px); height: clamp(28px, 8cqi, 38px); }
.cff-star2:not(.on) svg path { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; }
.cff-star2.on svg path { fill: currentColor; stroke: none; }
.cff-star2:hover { transform: translateY(-1px); }
.cff-star-lab { margin: 9px 0 0; }

/* ---- Tags ----
   The chip's close button grows to a real 20px target and the box gains the
   height to hold a row of them without the input jumping as the first tag
   lands. The input keeps the 16px floor for the same reason every other text
   entry does. */
.cff-tagbox { gap: 7px; padding: 9px 10px; min-height: 48px; }
.cff-tag { gap: 6px; max-width: 100%; padding: 5px 5px 5px 10px; }
.cff-tag-x { width: 20px; height: 20px; opacity: .6; }
.cff-tag-input {
  flex: 1 1 130px; min-width: 110px; height: 28px; font-size: var(--cff-fs-input);
}

/* ---- Signature ----
   The pad scales with the form instead of sitting at 92px. A signature is drawn
   at the size of the hand drawing it, so on a wide embed a 92px band forced the
   visitor to cramp one; the rule and the prompt move with it. */
.cff-sig-pad { height: clamp(112px, 30cqi, 168px); overflow: hidden; }
.cff-sig-line { left: 14px; right: 14px; bottom: 32px; }
.cff-sig-hint { left: 14px; bottom: 10px; }

/* ---- Focus ----
   Every bare button in the module -- scale numbers, calendar days, time slots,
   list options, stars, steppers -- relied on the browser default ring, which on
   a transparent button over a tinted panel is frequently invisible. */
.cform :is(button, li[role="option"], canvas):focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}

/* Motion in this module is all state feedback (a sliding Yes/No bar, a corner
   mark easing in, the success check drawing itself), so it is safe to drop
   entirely rather than shorten. .cff-success is a SIBLING of .cform, not a
   descendant, so it needs its own branch here. */
@media (prefers-reduced-motion: reduce) {
  .cform *, .cform *::before, .cform *::after,
  .cff-success, .cff-success *, .cff-success *::before, .cff-success *::after {
    animation: none !important; transition: none !important;
  }
}

/* ================= TYPOGRAPHY =================
   One pass over the whole module, in the conventions the rest of the vendor
   page already uses. Two systems meet here and both are honoured:

   THE CONTROLS LIBRARY decides ROLE. Its rule is that WORDS are set in the
   vendor's body face and VALUES are set in mono with tabular figures -- a
   price, a scale number, a calendar date, a time slot, a quantity, a hex code
   and a file size are all data, and data is monospaced so a column of it lines
   up and a changing figure does not reflow the box it sits in. The module had
   none of that: no tabular figures anywhere, mono on five of eleven value
   roles, and the two loudest values on the form (the package price and the
   budget readout) set in the DISPLAY face, which is the one face reserved for
   headings.

   THE PAGE'S TYPE KIT decides SIZE. Every step is a kit step, and anything
   that ramps ramps between two adjacent kit steps -- the ladder at the top of
   this file. Where the library and the page disagree the PAGE wins, because a
   form embedded in a vendor's page should read as part of it: the library sets
   its display heading in sans, but every other heading on this page (section
   titles, FAQ questions) takes the vendor's display face, so the form's
   heading block does too.

   Sizes stay fluid on cqi, against .cform's own container rather than the
   viewport, so the form types itself to the width it is actually given --
   ~350px on a phone, ~550px in the builder pane, wider on a roomy embed. */

/* ---- Words ---- */

/* THE LIBRARY'S LABEL, literal (user call, 2026-08-12, reversing the previous
   day's substitution): .flbl is 600 clamp(13px, 1.4cqi, 14px)/1.4 var(--sans)
   with -.004em, exactly as the spec gives it. Flagged and reconfirmed: 1.4cqi
   never clears 13px within this module's real 276–612px container, so this
   sits pinned at its floor rather than tracking the container the way the
   rest of the ladder above does — a known, chosen tradeoff, not an oversight. */
.cff-field > label:not(.cff-consent):not(.cff-drop) {
  font: 600 clamp(13px, 1.4cqi, 14px)/1.4 var(--sans); letter-spacing: -.004em;
}
/* "optional" rides inside the label, so it must not inherit the label's weight
   or it reads as part of the question rather than as an aside about it. */
.cff-opt { font-weight: 400; }

/* --type-help, with the library's own 58ch measure. Help text is the one thing
   on a form that is always skimmed, so it gets the shorter measure rather than
   the 68ch running-prose one. */
.cff-hint { font: var(--cff-type-help); color: var(--muted); max-width: var(--cff-measure-help); }
.cff-static-sub { font: var(--cff-type-help); color: var(--muted); max-width: var(--cff-measure-help); }
.cff-pkg-desc, .cff-star-lab, .cff-budget-sub, .cff-sig-hint,
.cff-palette-cap, .cff-drop > span:not(.cff-drop-preview) { font: var(--cff-type-help); }
/* --type-error. Weight, not size, marks an error: a message that grows when it
   appears shifts the whole form down by the difference. */
.cff-field-error, .cff-error { font: var(--cff-type-error); }
.cff-privacy, .cff-note { font: var(--cff-type-help); }

/* --type-body -> --type-h4. Ambient form text: the words a visitor chooses
   between, on the library's own hierarchy (its choice rows sit a step below
   its inputs, not level with them). Line-height 1.4 rather than the kit's 1.6
   because these are single lines inside a control, not running prose. */
.cff-choice { font-size: var(--cff-fs-base); line-height: 1.4; }
.cff-tile-name { font-size: var(--cff-fs-xs); line-height: 1.35; }
.cff-select-val, .cff-frame { font-size: var(--cff-fs-input); }
/* --type-h4 at the kit's h4 tracking, dropped one rung to --cff-fs-xs (the
   page's own button/label step): at --cff-fs-base a package name matched the
   page's own BODY text, so a three-line card read as a paragraph with a
   price at the end rather than a compact option to scan. */
.cff-pkg-name { font-size: var(--cff-fs-xs); font-weight: 600;
  line-height: 1.3; letter-spacing: -.008em; }
/* Running prose gets the kit's body measure and body leading. */
.cff-consent { font-size: var(--cff-fs-xs); line-height: 1.6; max-width: var(--cff-measure-body); }
.cff-static-note { font-size: var(--cff-fs-xs); line-height: 1.65; max-width: var(--cff-measure-body); }

/* The display-only heading block keeps the vendor's DISPLAY face. The library
   sets this step in sans, but on this page a heading is the display face --
   section titles and FAQ questions both are — and the form is embedded in that
   page, not in the library. --type-h3 -> --type-h2 with the kit's h2 tracking. */
.cff-static-head { font-family: var(--serif); font-size: var(--cff-fs-static);
  font-weight: 600; line-height: 1.3; letter-spacing: -.013em; }

/* ---- Values: mono, tabular figures ----
   tabular-nums is the whole point of setting these in mono. Every one of them
   CHANGES while the visitor is looking at it — dragging a slider, tapping a
   stepper, moving through a calendar — and proportional digits make the box
   twitch on each change, because a 1 is narrower than a 0. It also lines the
   scale row, the calendar grid and the time grid up into real columns.

   `font` before `font-variant-numeric`, always: the shorthand resets it. */
.cff-pkg-price {
  font: 700 var(--cff-fs-lg)/1 var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
/* The display face, not mono -- unlike the other values in this block, the
   budget readout is the one number on the form meant to read as a headline
   rather than a data point (see .cff-budget-val's own font-family note where
   --serif is bound). Tabular-nums still applies so the digits don't jitter
   the layout while the slider drags. */
.cff-budget-val {
  font: 500 var(--cff-fs-jumbo)/1.1 var(--serif); font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.cff-scale-btn {
  font: 400 var(--cff-fs-2xs)/1 var(--mono); font-variant-numeric: tabular-nums;
}
.cff-dp-day, .cff-timepicker-list li {
  font: 400 var(--cff-fs-xs)/1 var(--mono); font-variant-numeric: tabular-nums;
}
.cff-stepper input, .cff-itemqty-step input {
  font: 500 var(--cff-fs-base)/1 var(--mono); font-variant-numeric: tabular-nums;
}
/* A month heading is a date, so it is data — but it is also the panel's title,
   so it keeps a heading's weight. */
.cff-dp-month {
  font: 500 var(--cff-fs-xs)/1.3 var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
.cff-file-size, .cff-itemqty-n, .cff-budget-ends {
  font: var(--cff-type-data); font-variant-numeric: tabular-nums;
}
.cff-swatch-hex {
  font: var(--cff-type-data); font-variant-numeric: tabular-nums;
  letter-spacing: var(--cff-track-label); text-transform: uppercase;
}
.cff-chex-input {
  font: 400 var(--cff-fs-input)/1.2 var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: .03em; text-transform: uppercase;
}
/* The stepper's own +/- are glyphs, not values: they stay in the body face,
   where a minus sign is drawn to match the surrounding text. */
.cff-step { font: 400 var(--cff-fs-lg)/1 var(--sans); }

/* ---- Micro captions ----
   --type-label: mono, uppercase, tracked. These are labels ON something rather
   than content — the letters above a calendar, the ends of a scale, the heading
   over a group of options. Uppercase mono at 11px is the kit's way of saying
   "this is furniture, read past it", which is exactly right for a caption a
   visitor should never have to stop on. --track-label for a caption sitting on
   its own content, --track-eyebrow for one introducing a block. */
.cff-dp-weekdays span, .cff-scale-lab {
  font: var(--cff-type-label); letter-spacing: var(--cff-track-label);
  text-transform: uppercase;
}
.cff-et-group, .cff-range-arrow, .cff-palette-lab {
  font: var(--cff-type-label); letter-spacing: var(--cff-track-eyebrow);
  text-transform: uppercase;
}
.cff-palette-set span, .cff-palette-shuffle {
  font: var(--cff-type-label); letter-spacing: var(--cff-track-label);
}

/* ---- Controls that are buttons ----
   --type-body -> --type-h4 at 600, the kit's button register. */
.cff-submit, .cff-step-next, .cff-step-back {
  font-size: var(--cff-fs-input); font-weight: 600; line-height: 1;
}
.cff-segment button { font-size: var(--cff-fs-label); font-weight: 600; line-height: 1; }
.cff-select-list li, .cff-et-opt { font-size: var(--cff-fs-xs); line-height: 1.4; }
.cff-tag { font-size: var(--cff-fs-2xs); line-height: 1.3; }
/* A filename is a word, not a value — it is read, not compared — so it stays in
   the body face while its SIZE beside it is data. */
.cff-file-name { font-size: var(--cff-fs-2xs); font-weight: 400; line-height: 1.35; }
.cff-drop b { font-size: var(--cff-fs-xs); font-weight: 600; line-height: 1.4; }
/* The dashed "add" affordances are actions, at the kit's small button step. */
.cff-itemqty-add, .cff-swatch-add, .cff-sig-clear2 {
  font-size: var(--cff-fs-2xs); font-weight: 500; line-height: 1;
}

/* ================= popover fit + a clearable event type ================= */

/* ---- A panel must never leave the form ----
   The calendar needs about 282px to hold seven columns at a usable size, but a
   date RANGE gives each picker half the form. Between roughly 380px (where the
   pair stops stacking) and 600px of form width the panel is therefore wider
   than the trigger it hangs off — and anchored to that trigger's LEFT edge, the
   right-hand one ran straight off the side of the form, taking Friday and
   Saturday with it. Measured at 24-27px of overflow on the public page and
   considerably worse in the builder, whose pane clips it.

   Two rules fix it for good. The right-hand picker of a pair anchors RIGHT, so
   it opens back across the form instead of past it; and no panel may exceed the
   form's own width, expressed in cqi because .cform is the query container --
   100cqi is "the whole form", not "the whole trigger", which is the measurement
   that actually matters here. */
.cff-range-pickers > :last-child .cff-datepicker-pop,
.cff-range-pickers > :last-child .cff-timepicker-list,
.cff-range-pickers > :last-child .cff-select-list { left: auto; right: 0; }
.cff-datepicker-pop, .cff-timepicker-list, .cff-select-list, .cff-et-pop {
  max-width: 100cqi;
}
/* That right-anchoring had a second effect nobody asked for. The base popover
   pins BOTH edges (left:0 + right:0), which sizes a panel to its trigger; the
   rule above releases the left edge, so the END time panel sizes to its own
   content instead -- four columns of slots -- while the START, still pinned,
   got half the field and collapsed to a single column. Same control, two
   widths, and the narrow one is the worse of the pair: 48 half-hour slots in
   one column is the long scroll the grid exists to avoid. So the start
   releases its right edge in turn and sizes to content as well, still opening
   rightward from the form's left edge, and still capped at 100cqi above. */
.cff-range-pickers > :first-child .cff-timepicker-list { right: auto; }

/* ---- Event type: clear ----
   Sits in the caret's slot, so nothing has to be measured against the caret's
   width to place it. The caret gives way while a value is set: choosing a
   different type still works by clicking the field, and with an answer already
   in place "remove this" is the move a visitor is actually looking for. */
.cff-eventtype.has-val .cff-caret { display: none; }
.cff-et-clear {
  position: absolute; top: 0; bottom: 0; right: var(--f-px);
  display: grid; place-items: center;
  width: 22px; padding: 0; border: 0; background: none;
  color: var(--muted); cursor: pointer; line-height: 0;
  border-radius: min(var(--o-r), 6px);
  transition: color .12s, background .12s;
}
.cff-et-clear[hidden] { display: none; }
.cff-et-clear svg { width: 11px; height: 11px; }
.cff-et-clear:hover { color: var(--ink); background: var(--fill); }
.cff-et-clear:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ================= spec-parity polish (zulli-controls-library) =================
   The remaining library details worth having, ported 2026-08-11. Mono stays
   retired throughout -- the body face carries every role the library gave mono,
   with tabular figures kept -- so nothing here touches a font-family. */

/* ---- Time-of-day groups ----
   The library groups the slot grid Morning / Afternoon / Evening with sticky
   headings; a flat 48-slot list made a visitor scroll past the small hours to
   reach 6 PM. "Late night" (midnight-5:30 AM) is this module's addition, kept
   LAST because in event time it follows the evening -- a reception that ends at
   1 AM ends after 11 PM, and dropping those slots (as the library does) would
   lose answers vendors actually receive. Same sticky offset as .cff-et-group:
   the panel's own 8px padding sits above the list.
   The heading is an li inside the listbox grid, so it must undo the slot rules
   it inherits (grid cell, border, hover, mono-alias type) -- and it spans the
   grid's full width, or it becomes one more cell in the flow of times. */
.cff-timepicker-list li.cff-tp-group,
.cff-timepicker-list li.cff-tp-group:hover {
  grid-column: 1 / -1; position: sticky; top: -8px; z-index: 1;
  display: block; min-height: 0; border: 0; cursor: default;
  background: var(--paper);
  font: var(--cff-type-label); letter-spacing: var(--cff-track-eyebrow);
  text-transform: uppercase; color: var(--faint);
  padding: 6px 4px 5px; text-align: start;
}

/* ---- Time-range duration ----
   The library's readout under the pair ("10h 30m"): two chosen times are an
   answer, but the duration is what the vendor actually quotes on. Hidden until
   both ends are set -- see the initialiser in agent-bio-contact.js, which also
   wraps past midnight. */
.cff-time-dur {
  margin: 9px 0 0; font: var(--cff-type-data); font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.cff-time-dur[hidden] { display: none; }

/* ---- Character count on long-text questions ----
   The library's "36 / 400", on the two questions with real ceilings (message,
   custom long text). Right-aligned under the box so it reads as the box's own
   gauge, not as help text -- hints live ABOVE the control in this module. */
.cff-count {
  margin: 7px 0 0; text-align: end;
  font: var(--cff-type-data); font-variant-numeric: tabular-nums;
  letter-spacing: var(--cff-track-label); color: var(--faint);
}

/* ---- 1-10 scale on the Underline style ----
   The one variant treatment the port missed: on Underline the library strips
   the scale's boxes to bottom rules -- resting a hairline, chosen a 2px brand
   rule -- the same move every text field makes on that style. A brand-filled
   square in a form of hairlines was the lone box left on the page.
   Colour marks the chosen number, never weight, as everywhere else. */
.cff-underline .cff-scale-btn {
  background: transparent; border: 0; border-bottom: 1.5px solid var(--fld-bd);
  border-radius: 0;
}
.cff-underline .cff-scale-btn.is-on {
  background: transparent; border-bottom: 2px solid var(--brand);
  color: var(--brand-deep);
}

/* ---- Chosen option in dropdown panels ----
   The library fills the chosen option with the wash as well as railing it, so
   reopening a list shows the answer at a glance instead of asking the eye to
   find the rail. Ink steps to brand-deep for contrast ON the wash -- the same
   pair the chosen choice-row uses. Last in file on purpose: it must outrank
   the hover fill at equal specificity. */
.cff-select-list li.is-sel, .cff-et-opt.is-sel {
  background: var(--tint); color: var(--brand-deep);
}

/* ---- Slider thumbs sit ON the rail ----
   The thumb is vertically centred within the native TRACK box, not within the
   input -- and with appearance:none and no styled track, browsers give that
   box an arbitrary height near the top of the input, so the 22px thumb rode
   above (single slider) or below (range pair) the 4px rail drawn at the
   track's 50% line. Styling the track to fill the input's 44px height is what
   actually centres the thumb; the drawn rail already sits at that centre. */
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-webkit-slider-runnable-track {
  height: 44px; background: transparent; border: 0;
}
/* WebKit pins the thumb's TOP to the track's top rather than centring it, so
   the 22px thumb needs the (44 − 22) / 2 offset itself — and border-box, or
   its 2.5px border grows the box and re-skews the centring it just fixed.
   Firefox centres the thumb on the track natively; no offset there. */
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-webkit-slider-thumb {
  box-sizing: border-box; margin-top: 11px;
}
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-moz-range-track {
  height: 44px; background: transparent; border: 0;
}
:is(.cff-budgetrange, .cff-slider) input[type="range"]::-moz-range-thumb {
  box-sizing: border-box;
}

/* ---- Tag suggestions ----
   The library's one-tap starters under the tag box, fed by the vendor's own
   options for the question (optional -- a tags question with none renders the
   box alone). Dashed like every other "this could exist" affordance in the
   module; a tap commits the word as a chip and the JS removes the used
   suggestion. */
.cff-tagsugg { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.cff-tagsugg button {
  min-height: 32px; padding: 0 11px;
  border: 1px dashed var(--line); border-radius: min(var(--o-r), 6px);
  background: transparent; color: var(--muted); cursor: pointer;
  font: 400 var(--cff-fs-2xs)/1 var(--sans);
  transition: border-color .12s ease, color .12s ease, background .12s ease;
}
.cff-tagsugg button:hover {
  border-style: solid; border-color: var(--brand);
  color: var(--brand-deep); background: var(--tint);
}
