/* ============================================================================
   form-controls.css — the site-wide control layer.

   Every non-text form control the app draws — checkbox, radio, switch, range,
   number, search, colour, file, date/time — is DRAWN BY US here, once, rather
   than left to the browser's native widget. Before this file owned them, a
   checkbox on the dark dashboard was a stark white OS square, a money field
   grew native spinner arrows on hover, and the same `.switch-input` markup
   rendered as a pill on two settings pages and as a bare checkbox on the
   others, because the only copy of its CSS lived in one page's <style> block.

   WHY EVERYTHING IS INSIDE `@layer zulli-controls`
   ------------------------------------------------
   These are DEFAULTS, not overrides. An unlayered rule always beats a layered
   one no matter how weak its selector, so every existing component style —
   `.cff-*` in contact_form_layouts.css (the public lead form) and the per-page
   <style> blocks in templates/ui_concept/pages/*.html — keeps winning without a
   single !important and without this file having to enumerate what to avoid. Adding a control style
   here therefore cannot break a component that already styles it; a component
   opts out just by styling the same property, as it always did.
   The one deliberate exception is the 16px zoom floor at the bottom, which is
   `!important` precisely so it CAN outrank those page-local blocks — and note
   that inside a layer `!important` is stronger still, since the cascade
   reverses layer order for important declarations.

   PALETTE BRIDGE
   --------------
   The file is loaded on surfaces that name their colours differently — the
   dashboard's `--accent`/`--hairline`/`--well`, the public bio's `--brass`/
   `--line`, the client portal's `--doc-*`, the document pages' `--brand`. The
   `--fc-*` bridge below resolves whichever of those is in scope, so one
   checkbox rule renders in the right colour on all of them. Custom-property
   fallbacks resolve at USE time against whatever is inherited, so the chain
   costs nothing where the first name already exists.
   ========================================================================== */

@layer zulli-controls {

  /* Layout safety net for text typed by visitors and clients (a 300-character
     "word", a pasted URL): wrap it instead of pushing the page sideways. Every
     surface loads this file, and break-word only kicks in when a line would
     overflow, so nothing that already fits reflows. Layered, so any surface
     that sets overflow-wrap itself still wins. */
  body{ overflow-wrap:break-word; }

  /* The colour tokens are declared at :root AND at every scope that re-pins
     the source tokens they read. var() inside a custom property substitutes
     at computed-value time ON THE DECLARING ELEMENT — declared only on :root,
     --fc-accent would freeze the dashboard theme's --accent and inherit that
     literal down into #pbld/#ifv2, which pin a fixed-light palette (and into
     the preview .sheet, which pins the vendor's), drawing their checkboxes
     and switches in the shell theme's colours. Re-declaring at those scopes
     re-runs the chains against the local values. :where() keeps specificity
     at zero so this stays a defaults layer. */
  :where(:root, #pbld, #ifv2, #pbld .sheet, #ifv2 .sheet){
    /* Accent: dashboard --accent, docs --brand, portal --doc-brass, bio --brass. */
    --fc-accent: var(--accent, var(--brand, var(--doc-brass, var(--brass, #2D64BC))));
    --fc-on-accent: var(--on-brand, var(--on-brass, #fff));
    --fc-line: var(--hairline, var(--line, var(--doc-line, #cfd4dc)));
    /* A tick box needs a HARDER edge than a panel divider. The dashboard's
       --hairline is #1a1a1a and its --well is #1A1E28: fine as a rule between
       two large surfaces, invisible as the outline of a 16px square sitting
       on that same well, which is exactly what an unchecked checkbox is.
       Deriving the edge from the ink instead measures it against whatever
       surface the control is composited on, in either theme — the same rule
       the palette roles follow (see tests_palette_contrast.py). */
    --fc-edge: color-mix(in srgb, var(--fc-ink-soft) 52%, transparent);
    --fc-bg: var(--well, var(--surface, var(--doc-well, var(--paper, #fff))));
    --fc-ink: var(--ink, #1b1f27);
    --fc-ink-soft: var(--ink-soft, var(--muted, #6b7280));
    /* 3px is the brand's field corner on every surface (base.css --radius and
       ui_concept.css --radius are both 3px); the tick box follows it. */
    --fc-radius: var(--radius, 3px);
  }
  /* Sizes stay on :root ONLY: they read no scoped tokens, and repeating them
     at the scopes above would let a scope's own 16px beat the touch-size
     override below (which raises them on :root alone). */
  :root{
    --fc-check: 16px;   /* checkbox / radio box */
    --fc-track: 4px;    /* range track */
    --fc-thumb: 16px;   /* range thumb */
  }

  /* ------------------------------------------------------------------------
     0. Containment. A control must never be the reason a page scrolls
     sideways: `width:100%` on a border-box input inside a padded flex/grid
     cell is safe, an intrinsic min-content width is not. These two lines are
     what let the narrow-screen layouts hold at 320px.
     ---------------------------------------------------------------------- */
  input, select, textarea{
    box-sizing:border-box;
    max-width:100%;
    font-family:inherit;
    /* Flex/grid children default to min-width:auto, which for a text input is
       its size attribute — the classic "this row will not shrink" overflow. */
    min-width:0;
  }
  /* `button` is deliberately NOT in that rule. Including it regressed real UI
     twice: `font-family:inherit` re-faced buttons whose label is a glyph sized
     against the UA font, and `max-width:100%` clamped the 28px sidebar toggle
     to the 24px box it deliberately overhangs, squashing its icon. Buttons
     already carry their own type and box on every surface, and they are not
     what pushes a narrow layout sideways — the value-bearing controls are. */

  /* ------------------------------------------------------------------------
     1. Checkbox + radio — one drawn box, both themes, every surface.
     ---------------------------------------------------------------------- */
  input[type="checkbox"],
  input[type="radio"]{
    -webkit-appearance:none; appearance:none;
    flex:none;
    width:var(--fc-check); height:var(--fc-check);
    margin:0;
    padding:0;
    display:inline-grid; place-content:center;
    vertical-align:-3px;
    border:1.5px solid var(--fc-edge);
    background:var(--fc-bg);
    color:var(--fc-on-accent);
    cursor:pointer;
    transition:background-color .13s ease, border-color .13s ease;
  }
  input[type="checkbox"]{ border-radius:min(var(--fc-radius), 6px); }
  input[type="radio"]{ border-radius:50%; }

  input[type="checkbox"]:hover:not(:disabled),
  input[type="radio"]:hover:not(:disabled){ border-color:var(--fc-accent); }

  input[type="checkbox"]:checked,
  input[type="checkbox"]:indeterminate,
  input[type="radio"]:checked{
    background:var(--fc-accent);
    border-color:var(--fc-accent);
  }

  /* The tick is a ::before so it inherits `color` from the control and needs
     no second colour token per surface. */
  input[type="checkbox"]::before{
    content:"";
    width:calc(var(--fc-check) * .56);
    height:calc(var(--fc-check) * .56);
    transform:scale(0);
    transition:transform .13s cubic-bezier(.2,.9,.3,1.3);
    background:currentColor;
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.6l4 4 8-9' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
            mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.6l4 4 8-9' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  }
  input[type="checkbox"]:checked::before{ transform:scale(1); }
  /* Indeterminate ("some of these rows") gets a bar, not a tick — the
     select-all box in the inquiries and bookings tables. */
  input[type="checkbox"]:indeterminate::before{
    transform:scale(1);
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8h10' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
            mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8h10' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
  }

  input[type="radio"]::before{
    content:"";
    width:calc(var(--fc-check) * .4); height:calc(var(--fc-check) * .4);
    border-radius:50%;
    background:currentColor;
    transform:scale(0);
    transition:transform .13s cubic-bezier(.2,.9,.3,1.3);
  }
  input[type="radio"]:checked::before{ transform:scale(1); }

  input[type="checkbox"]:disabled,
  input[type="radio"]:disabled{ opacity:.5; cursor:not-allowed; }

  /* --------------------------------------------------------------------
     2. Toggle switch. Same <input type=checkbox>, opted in with .switch-input.
     This used to live only in ui_concept/partials/_settings_styles.html, so
     the identical markup in core/_inquiry_form_field_rows.html and
     core/email_preferences.html fell back to a bare OS checkbox. One copy,
     here, and all three render the pill.
     ------------------------------------------------------------------ */
  input[type="checkbox"].switch-input{
    display:inline-block;              /* not the grid the tick box uses */
    position:relative; flex-shrink:0;
    width:38px; height:22px;
    border:none;
    border-radius:22px;
    background:color-mix(in srgb, var(--fc-ink-soft) 42%, transparent);
    cursor:pointer;
    transition:background .15s ease;
    margin:0;
    vertical-align:-6px;
  }
  /* The tick mask belongs to the box form; the pill has a thumb instead. */
  input[type="checkbox"].switch-input::before{ content:none; }
  input[type="checkbox"].switch-input::after{
    content:"";
    position:absolute; top:2px; left:2px;
    width:18px; height:18px;
    border-radius:50%;
    background:#fff;
    box-shadow:0 1px 3px rgba(0,0,0,.25);
    transition:transform .15s ease;
  }
  input[type="checkbox"].switch-input:checked{
    background:var(--fc-accent);
  }
  input[type="checkbox"].switch-input:checked::after{ transform:translateX(16px); }
  input[type="checkbox"].switch-input:hover:not(:disabled){ border-color:transparent; }

  /* ------------------------------------------------------------------------
     3. Range. A hairline track in the ink colour with an accent thumb, drawn
     identically by both engines.

     There is deliberately NO filled-to-the-left track. The two ways to get
     one without JS are a `::-moz-range-progress` (Firefox only, so the two
     engines would disagree) and the WebKit trick of a very wide box-shadow on
     the thumb clipped by `overflow:hidden` on the input. The second works
     right up until a component gives the input its own height — which several
     here do, e.g. `.bg-blur-field input[type=range]` at 28px for a coarse
     pointer — and then the clip is a 28px pill and the "fill" is a fat bar
     instead of a 4px track. Every slider in the app already prints its value
     beside it (`.lf-range output`, `.logo-size-val`, `.dp-slider[data-unit]`),
     so the fill was carrying no information worth that fragility.

     The thumb sizes off --fc-thumb, which the touch block at the bottom
     raises, so `margin-top` stays correct at either size.
     ---------------------------------------------------------------------- */
  input[type="range"]{
    -webkit-appearance:none; appearance:none;
    width:100%;
    height:var(--fc-thumb);
    margin:0; padding:0;
    background:none;
    border:none;
    cursor:pointer;
    -webkit-tap-highlight-color:transparent;
  }
  input[type="range"]::-webkit-slider-runnable-track{
    height:var(--fc-track);
    border-radius:999px;
    background:color-mix(in srgb, var(--fc-ink-soft) 32%, transparent);
  }
  input[type="range"]::-webkit-slider-thumb{
    -webkit-appearance:none; appearance:none;
    width:var(--fc-thumb); height:var(--fc-thumb);
    margin-top:calc((var(--fc-track) - var(--fc-thumb)) / 2);
    border-radius:50%;
    background:var(--fc-accent);
    border:2px solid var(--fc-bg);
    box-shadow:0 1px 3px rgba(0,0,0,.25);
    transition:transform .12s ease;
  }
  input[type="range"]:hover::-webkit-slider-thumb{ transform:scale(1.1); }
  input[type="range"]::-moz-range-track{
    height:var(--fc-track);
    border-radius:999px;
    background:color-mix(in srgb, var(--fc-ink-soft) 32%, transparent);
  }
  input[type="range"]::-moz-range-thumb{
    width:calc(var(--fc-thumb) - 4px); height:calc(var(--fc-thumb) - 4px);
    border-radius:50%;
    background:var(--fc-accent);
    border:2px solid var(--fc-bg);
    box-shadow:0 1px 3px rgba(0,0,0,.25);
  }
  input[type="range"]:disabled{ opacity:.5; cursor:not-allowed; }

  /* ------------------------------------------------------------------------
     4. Number. Every number field in the app is money, a count or a day
     offset sitting in a fixed-width cell — often behind a `$` prefix
     (.bk-field-amount-wrap) that the native spinner overlapped. The arrows go;
     the field keeps type=number for the mobile keypad and step validation.
     ---------------------------------------------------------------------- */
  input[type="number"]{ -moz-appearance:textfield; appearance:textfield; }
  input[type="number"]::-webkit-outer-spin-button,
  input[type="number"]::-webkit-inner-spin-button{
    -webkit-appearance:none; appearance:none; margin:0;
  }

  /* ------------------------------------------------------------------------
     5. Search. The OS clear "✕" is drawn in system chrome that ignores the
     page palette — it read as a bright dot on the dark dashboard. Every
     search field in the app already has its own clear affordance or clears
     on Escape. sidebar.css used to do this for the topbar field alone.
     ---------------------------------------------------------------------- */
  input[type="search"]{ -webkit-appearance:none; appearance:none; }
  input[type="search"]::-webkit-search-decoration,
  input[type="search"]::-webkit-search-cancel-button,
  input[type="search"]::-webkit-search-results-button,
  input[type="search"]::-webkit-search-results-decoration{
    -webkit-appearance:none; appearance:none;
  }

  /* ------------------------------------------------------------------------
     6. Colour. Chrome insets the swatch inside its own padding and border, so
     a sized, rounded input showed a small square floating in a grey frame.
     Stripped to nothing, the swatch becomes the control.
     ---------------------------------------------------------------------- */
  input[type="color"]{
    -webkit-appearance:none; appearance:none;
    padding:0;
    border:1px solid var(--fc-line);
    border-radius:var(--fc-radius);
    background:none;
    cursor:pointer;
  }
  input[type="color"]::-webkit-color-swatch-wrapper{ padding:0; }
  input[type="color"]::-webkit-color-swatch{ border:none; border-radius:inherit; }
  input[type="color"]::-moz-color-swatch{ border:none; border-radius:inherit; }

  /* ------------------------------------------------------------------------
     7. File. Nearly every file input in the app is hidden behind a dropzone or
     a button; these rules are for the few that aren't (and for the moment
     before JS wires one up), so a stray one still reads as ours.
     ---------------------------------------------------------------------- */
  input[type="file"]{ color:var(--fc-ink-soft); font-size:inherit; }
  input[type="file"]::file-selector-button{
    margin-right:10px;
    padding:6px 12px;
    border:1px solid var(--fc-line);
    border-radius:var(--radius-ctl, 8px);
    background:var(--fc-bg);
    color:var(--fc-ink);
    font:inherit; font-weight:600;
    cursor:pointer;
  }
  input[type="file"]::file-selector-button:hover{ border-color:var(--fc-accent); color:var(--fc-accent); }

  /* ------------------------------------------------------------------------
     8. Native <select> fallback. custom-select.js swaps every select for a
     styled listbox, but the native one is what shows before that script runs,
     if it fails, or for a select injected by a formset row. Give it the field
     chrome and our chevron so the swap is invisible instead of a flash of OS
     dropdown. `.cs-native` is the class the widget adds once it has taken
     over — it hides the element, so skipping it here just saves work.
     ---------------------------------------------------------------------- */
  select:not(.cs-native){
    -webkit-appearance:none; appearance:none;
    padding-right:30px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m19.5 8.25-7.5 7.5-7.5-7.5'/%3E%3C/svg%3E");
    background-repeat:no-repeat;
    background-position:right 10px center;
    background-size:14px;
    cursor:pointer;
  }

  /* ------------------------------------------------------------------------
     9. Focus. One visible ring for every control this file draws, keyed off
     :focus-visible so a mouse click doesn't light it up. Text fields keep
     their component's own border-colour focus treatment.
     ---------------------------------------------------------------------- */
  input[type="checkbox"]:focus-visible,
  input[type="radio"]:focus-visible,
  input[type="range"]:focus-visible,
  input[type="color"]:focus-visible,
  select:not(.cs-native):focus-visible{
    outline:2px solid var(--fc-accent);
    outline-offset:2px;
  }

  /* ------------------------------------------------------------------------
     10. Date / time. The native pickers stay (accessible, and on mobile they
     are the OS wheel, which is the right control) — this only restyles the
     text and swaps the browser's calendar/clock glyph for a currentColor
     mask, so the icon follows whatever palette the page uses. Firefox doesn't
     expose the indicator pseudo-element and keeps its native icon, which
     color-scheme already themes.
     ---------------------------------------------------------------------- */
  input[type=date], input[type=time], input[type=datetime-local]{
    font-variant-numeric:tabular-nums;
  }
  input[type=date]::-webkit-calendar-picker-indicator,
  input[type=time]::-webkit-calendar-picker-indicator,
  input[type=datetime-local]::-webkit-calendar-picker-indicator{
    width:15px; height:15px;
    margin-left:2px;
    padding:0;
    cursor:pointer;
    opacity:0.55;
    background:currentColor;
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4.5' width='18' height='17' rx='2.5'/%3E%3Cpath d='M8 2.5v4M16 2.5v4M3 9.5h18'/%3E%3C/svg%3E") center / contain no-repeat;
            mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4.5' width='18' height='17' rx='2.5'/%3E%3Cpath d='M8 2.5v4M16 2.5v4M3 9.5h18'/%3E%3C/svg%3E") center / contain no-repeat;
    transition:opacity 0.15s ease;
  }
  input[type=time]::-webkit-calendar-picker-indicator{
    -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7.5V12l3 2'/%3E%3C/svg%3E") center / contain no-repeat;
            mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7.5V12l3 2'/%3E%3C/svg%3E") center / contain no-repeat;
  }
  input[type=date]::-webkit-calendar-picker-indicator:hover,
  input[type=time]::-webkit-calendar-picker-indicator:hover,
  input[type=datetime-local]::-webkit-calendar-picker-indicator:hover{
    opacity:1;
  }

  /* ------------------------------------------------------------------------
     11. Touch sizing. Controls stay at their designed desktop size and only
     grow where the finger needs them to. `pointer: coarse` is the real
     signal — an iPad is 768-1024px wide, so a width query alone misses it —
     and the width query is kept alongside so the effect can be checked in a
     desktop browser.
     ---------------------------------------------------------------------- */
  @media (max-width: 900px), (pointer: coarse){
    :root{ --fc-check:18px; --fc-thumb:20px; --fc-track:5px; }
  }

  /* ------------------------------------------------------------------------
     12. The iOS zoom floor. Safari zooms the page when a focused field's text
     is under 16px and will not zoom back out, which is what made the booking
     and inquiry modals unusable one-handed. Rather than inflate the whole
     dashboard, the designed sizes stay and the floor applies only where the
     bug exists: touch devices, plus narrow windows so it can be checked on a
     desktop.

     `!important` is load-bearing: several controls are inline WYSIWYG editors
     with far more specific selectors than anything writable here
     (`.pkg-form .offering-badge-chip input[name$="-badge"]`), and the page
     templates carry their own <style> blocks that would otherwise win on
     source order. This is a constraint, not a preference. It lives in this
     file — not in ui_concept.css and dashboard.css, where two drifting copies
     used to sit — so the client portal, the public document pages and the
     auth screens get the same floor.

     Non-text controls are excluded: they never trigger the zoom, and forcing
     16px on a checkbox or a range would only break its sizing.
     ---------------------------------------------------------------------- */
  @media (max-width: 900px), (pointer: coarse){
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="image"]):not([type="reset"]),
    textarea,
    select{
      font-size:16px !important;
    }
  }

}

/* ============================================================================
   .fset / .dp-field — the field-editor label+input pattern shared by every
   panel that edits a vendor's own content: the inquiry form builder (v2,
   `.fset` markup) and the page builder (`.dp-field`/`.dp-field-label`
   markup). Both builders used to carry their own near-identical copy of this
   frame (plus a third, differently-tokened one as design_concept.css's old
   `.dp-field` rules) — a radius or focus-ring tweak had to be made two or
   three times and drifted. This is now the one definition; each builder's
   markup keeps its own class names (page builder's JS does `.closest('.dp-field')`
   for validation-note placement, and a test asserts on `.dp-field-label`), so
   nothing downstream had to change, but the visual design itself now has a
   single source. Left unlayered so it keeps beating any page-local rule that
   still targets these selectors, same as the rest of this file.
   ============================================================================ */
.fset > label,
.dp-field-label{
  display:block; font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:var(--muted, var(--ink-soft)); margin-bottom:6px;
}
.fset input:not([type="checkbox"]):not([type="radio"]),
.fset select,
.fset textarea,
.dp-field input:not([type="checkbox"]):not([type="radio"]),
.dp-field select,
.dp-field textarea{
  width:100%; font:400 13.5px/1.45 var(--font-body, "DM Sans", sans-serif); color:var(--ink);
  background:var(--paper, #fff); border:1px solid var(--hairline); border-radius:var(--radius-input, 9px);
  padding:10px 11px; outline:none;
}
.fset textarea,
.dp-field textarea{ resize:vertical; min-height:64px; }
/* input repeats the base rule's :not() exclusions so this matches (rather than
   loses to) the base rule's specificity — :not()'s argument counts toward
   specificity, so a bare `input:focus` here is LESS specific than
   `input:not([type=checkbox]):not([type=radio])` above and silently loses
   the border-color to it. */
.fset input:not([type="checkbox"]):not([type="radio"]):focus,
.fset select:focus, .fset textarea:focus,
.dp-field input:not([type="checkbox"]):not([type="radio"]):focus,
.dp-field select:focus, .dp-field textarea:focus{
  /* --brand, not --accent: both builders pin their own light-mode --brand
     locally (#ifv2/#pbld), but only the page builder also shadows --accent —
     in the inquiry builder --accent falls through to the app's global
     (possibly dark-theme) value, giving a mismatched focus color. */
  border-color:var(--brand, var(--accent));
}
@media (max-width:900px), (pointer:coarse){
  .fset input[type=text],
  .dp-field input[type=text]{ padding:12px 13px; }
}
