/* ============================================================================
   Portmasters Form Field — form-field.css   (Visual System v2, forms lane)
   ----------------------------------------------------------------------------
   Consumes ONLY var() tokens from ../../tokens/tokens.css, tokens-semantic.css
   and tokens-type.css. No colour literals, no radius literals.

   WHAT THIS IS. The product has no FormField component — and it has the field
   wrapper anyway, hand-written the same way in every form. This sheet names it
   once. Nothing here was designed from scratch; every part below is a shape
   already shipping, quoted file:line against /Portmasters/portmasters-frontend.

   The canonical hand-written wrapper (views/support/Support.tsx:333-341):

     <div className="space-y-1.5">
       <Typography variant="label" size={3} element="label"
                   htmlFor="new-ticket-message" className="block">
         {t("message")}
       </Typography>
       <textarea id="new-ticket-message" … />
     </div>

   …the same three lines again at Support.tsx:276-280 (Booking → Select),
   :299-303 (Category → Select), :314-318 (Priority → Select),
   views/profile/Profile.tsx:620-626, and shared/active-booking/components/
   BookingSupportTab.tsx:283-290.

   WHY IT HAS TO EXIST HERE. TextField carries its own label and helper
   (TextField.tsx:84-100, 161-173). Select, textarea, DatePicker and the upload
   tiles do not — so half the fields in the product get their label from a
   loose <Typography> and their error from a second loose <Typography>, and the
   two halves of the form drift apart. This is the wrapper both halves share.

   Anatomy
     .pm-formfield                       wrapper (stacked by default)
       .pm-formfield__labelrow             label + required marker + aside slot
         .pm-formfield__label
           .pm-formfield__required         the asterisk
         .pm-formfield__labelaside         trailing slot (TextField `topElement`)
       .pm-formfield__control              the control slot — ANY control
       .pm-formfield__message              helper / error / success line

   Variants
     .pm-formfield--inline    label start, control end   (BookingContact rows)
     .pm-formfield__section   titled block + field grid  (ShippingOrder)

   State is one attribute on the wrapper: data-status="invalid" | "valid",
   plus .is-disabled. The same attribute drives text-field.css and select.css,
   so the wrapper never needs to know which control it is holding.
   ============================================================================ */

@import url("../../tokens/tokens.css");
@import url("../../tokens/tokens-semantic.css");
@import url("../../tokens/tokens-type.css");

/* ----------------------------------------------------------------------------
   WRAPPER
   PRODUCT: space-y-1.5 / mb-1.5 = 6px between label and control
            (Support.tsx:277,300,315,333).
   V2: 8px = --space-2. 6px is 1.5x the base step and has no token behind it,
   and this is the SAME decision text-field.css:55 already took for the same
   stack ("the label-above-control stack is the FAMILY anchor"). Two sheets
   describing one gap must not disagree — so this is 8px, matching it exactly.
   ---------------------------------------------------------------------------- */
.pm-formfield {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);               /* 8px — locked to text-field.css:55 */
  min-width: 0;                      /* lets a field shrink inside a grid column instead of forcing an overflow */
  font-family: inherit;
}

/* ----------------------------------------------------------------------------
   LABEL ROW
   PRODUCT: a bare block label. The trailing slot mirrors TextField's own
   `topElement`, which sits on the label row with justify-between
   (TextField.tsx:29, 85-100) — a declared slot in the shipped API with, as of
   this pass, NO live caller (grep across apps/ + packages/ returns only
   `BottomElement` uses). It is carried so a Select or a textarea can have the
   same slot a TextField already has; it is deliberately NOT demonstrated with
   an invented "Optional" tag, because the product marks optionality inside the
   placeholder instead ("Enter document description (optional)",
   en.json forms.placeholders.enterDocumentDescription).
   ---------------------------------------------------------------------------- */
.pm-formfield__labelrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);               /* 12px — parity with text-field.css:185 */
}

/* → body/strong (13.5 / 500). PRODUCT: Typography variant="label" size={3}.
   Identical rung to the TextField label (text-field.css:189-192), which is the
   point — a Select's label and a TextField's label are the same object. */
.pm-formfield__label {
  font-size: var(--type-body-strong-size);
  font-weight: var(--type-body-strong-weight);
  line-height: var(--type-body-strong-leading);
  letter-spacing: var(--type-body-strong-tracking);
  color: var(--text-primary);
  display: inline-flex;              /* keeps the asterisk on the label's baseline */
  align-items: baseline;
}

/* REQUIRED MARKER
   PRODUCT: <span className="text-error-500 ms-1">*</span>
     views/booking-contact/BookingContact.tsx:543, 557, 571
     shared/cancel-order-modal/CancelOrderModal.tsx:61
     shared/reject-order-modal/RejectOrderModal.tsx:55 (line-forwarder app)
   …and the same glyph one ramp over, `text-red-500 ms-1` (Tailwind's own red,
   not the product's error ramp — a second small defect this consolidates):
     packages/shared/…/rich-text-editor/RichTextEditor.tsx:149, 164
     apps/admin/src/shared/ckeditor/CkEditor.tsx:188
   Geometry verbatim (ms-1 = 4px). INK ONLY changes: error-500 #F2407B is
   3.62:1 on white and fails AA as a text glyph; --text-error (#E22056, 4.59:1)
   is the first passing stop on the same ramp and is the role every error
   string in the family already uses. Same call as text-field.css.

   WIRING RULE (a11y): the glyph is decoration — aria-hidden="true" on the span,
   and required + aria-required="true" on the control. A red asterisk on its own
   is colour-alone signalling and is announced to nobody. */
.pm-formfield__required {
  margin-inline-start: var(--space-1);   /* ms-1 = 4px, verbatim */
  color: var(--text-error);
  font-weight: inherit;
}

/* the trailing slot — quieter than the label by one rung, so it reads as
   secondary information and never competes with the field's name */
.pm-formfield__labelaside {
  font-size: var(--type-meta-default-size);
  font-weight: var(--type-meta-default-weight);
  line-height: var(--type-meta-default-leading);
  letter-spacing: var(--type-meta-default-tracking);
  color: var(--text-muted);
  white-space: nowrap;
}

/* ----------------------------------------------------------------------------
   CONTROL SLOT — holds a TextField, a Select trigger, a textarea, a date
   picker, an upload tile. Owns no skin of its own; the control keeps its own.
   ---------------------------------------------------------------------------- */
.pm-formfield__control {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.pm-formfield__control > * { width: 100%; }   /* Support.tsx:288,305,323 all pass w-full to the trigger */

/* ----------------------------------------------------------------------------
   MESSAGE — helper / error / success
   PRODUCT: a loose line under the control, 4px clear —
     <Typography size={3} color="error" className="mt-1">{error}</Typography>
     shared/cancel-order-modal/CancelOrderModal.tsx:76
     views/support/Support.tsx:144

   COMPOSITION RULE, and it matters: this message is IN FLOW. TextField's own
   helper is absolutely positioned 24px below the box (text-field.css:227-229)
   so a validating form never reflows. Both behaviours are correct, for
   different jobs — but a field must use exactly ONE of them. When a control
   sits inside a .pm-formfield, the message belongs to the WRAPPER and the
   control's own __helper is left unrendered. Use both and you get the string
   twice, 24px apart.
   ---------------------------------------------------------------------------- */
.pm-formfield__message {
  /* → body/default (13.5 / 400) — the same rung the TextField helper uses. */
  font-size: var(--type-body-default-size);
  font-weight: var(--type-body-default-weight);
  line-height: var(--type-body-default-leading);
  letter-spacing: var(--type-body-default-tracking);
  color: var(--text-secondary);
  margin-top: calc(var(--space-1) - var(--space-2));  /* mt-1 net: 8px wrapper gap − 4px = the product's 4px clear */
}

/* ----------------------------------------------------------------------------
   STATUS — one attribute, both halves recolour. Ink roles are the ones
   text-field.css already settled (its :195-210 and :237-240 comments carry the
   AA measurements; --text-positive is the PAPER-tuned positive ink, 5.05:1 on
   tray, because --text-success only clears on pure white).
   NEVER colour-alone: the label AND the message move together, and the control
   inside gets the same data-status so its edge moves too.
   ---------------------------------------------------------------------------- */
.pm-formfield[data-status="invalid"] .pm-formfield__label    { color: var(--text-error-strong); }
.pm-formfield[data-status="invalid"] .pm-formfield__message  { color: var(--text-error); }
.pm-formfield[data-status="valid"]   .pm-formfield__label    { color: var(--text-positive); }
.pm-formfield[data-status="valid"]   .pm-formfield__message  { color: var(--text-positive); }

/* ----------------------------------------------------------------------------
   DISABLED — the wrapper greys its own text; the control greys itself.
   Values are the frozen product primitives text-field.css:265-268 uses, so a
   disabled Select label and a disabled TextField label are the same grey.
   ---------------------------------------------------------------------------- */
.pm-formfield.is-disabled .pm-formfield__label,
.pm-formfield.is-disabled .pm-formfield__message,
.pm-formfield.is-disabled .pm-formfield__labelaside,
.pm-formfield.is-disabled .pm-formfield__required { color: var(--neutral-300); }

/* ----------------------------------------------------------------------------
   INLINE VARIANT  —  .pm-formfield--inline
   PRODUCT: the profile rows on the booking-contact page put the label at the
   start of the row and a fixed-width control at the end
     views/booking-contact/BookingContact.tsx:540-548
       <div className="flex items-center justify-between">
         <Typography size={2} color="description-neutral">
           {t("firstName")}<span className="text-error-500 ms-1">*</span>
         </Typography>
         <TextField … className="w-[300px]" />
       </div>
     repeated at :554-562 (last name) and :568-… (phone), and the read-only
     twin of the same row is at :528-536.
   This is a genuinely different field layout, not a styling whim: it exists so
   an edit form can sit on the exact grid its read-only view used.
   ---------------------------------------------------------------------------- */
.pm-formfield--inline {
  --formfield-inline-control: 300px;   /* w-[300px], verbatim (BookingContact.tsx:549) */
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);                 /* 16px — a row needs more clear than a stack */
  flex-wrap: wrap;                     /* narrow viewports drop the control under its label */
}
.pm-formfield--inline .pm-formfield__labelrow {
  flex: 0 1 auto;
  justify-content: flex-start;
}
/* PRODUCT: the inline label is the quieter Typography size={2}
   color="description-neutral", not the bold stacked one — because here the
   VALUE is the thing you read and the label is the row's name. Kept. */
.pm-formfield--inline .pm-formfield__label {
  font-size: var(--type-body-default-size);
  font-weight: var(--type-body-default-weight);
  line-height: var(--type-body-default-leading);
  letter-spacing: var(--type-body-default-tracking);
  color: var(--text-secondary);
}
.pm-formfield--inline .pm-formfield__control {
  flex: 0 0 var(--formfield-inline-control);
  max-width: 100%;
}
.pm-formfield--inline .pm-formfield__message {
  flex: 1 1 100%;                      /* the message takes its own line under the row */
  margin-top: 0;
  text-align: end;                     /* sits under the control it belongs to; logical, flips in RTL */
}
/* the read-only twin of the same row (BookingContact.tsx:528-536): no control,
   just the value at the end. Same grid, so the two views do not jump. */
.pm-formfield--inline .pm-formfield__value {
  flex: 0 0 var(--formfield-inline-control);
  max-width: 100%;
  text-align: end;
  font-size: var(--type-body-strong-size);
  font-weight: var(--type-body-strong-weight);
  line-height: var(--type-body-strong-leading);
  letter-spacing: var(--type-body-strong-tracking);
  color: var(--text-primary);
}

/* ----------------------------------------------------------------------------
   SECTION + GRID  —  the block a set of fields lives in
   PRODUCT: shared/active-booking/components/ShippingOrder.tsx
     :666  <div className="bg-white rounded-xl space-y-0">        ← the card
     :668  <div className="px-4 py-6 space-y-4">                  ← first section
     :669  <Typography variant="label" size={2}>{t("shipperInformation")}</Typography>
     :672  <div className="grid md:grid-cols-3 gap-6">            ← the field grid
     :758, :848, :938 …  same block, plus `border-t border-neutral-200`
   Four identical sections (Shipper / Consignee / Notify party / Cargo), so the
   block is a shape, not a one-off.
   ---------------------------------------------------------------------------- */
.pm-formfield-section {
  padding: var(--space-5) var(--space-4);   /* py-6 px-4 = 24px / 16px, verbatim */
  display: flex;
  flex-direction: column;
  gap: var(--space-4);                      /* space-y-4 = 16px, verbatim */
}
.pm-formfield-section + .pm-formfield-section {
  border-top: 1px solid var(--border);      /* border-t border-neutral-200 → border role */
}
/* → title/container (16px). PRODUCT: Typography variant="label" size={2}. */
.pm-formfield-section__title {
  font-size: var(--type-title-container-size);
  font-weight: var(--type-title-container-weight);
  line-height: var(--type-title-container-leading);
  letter-spacing: var(--type-title-container-tracking);
  color: var(--text-primary);
  margin: 0;
}
.pm-formfield-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);                      /* gap-6 = 24px, verbatim — and it has to be this
                                               big: TextField's helper hangs 24px below its box
                                               (text-field.css:229) with no height of its own, so
                                               a tighter row gap lets an error message land on
                                               the field beneath it */
}
@media (min-width: 768px) {                 /* md: — the product's own breakpoint */
  .pm-formfield-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.pm-formfield-grid--2 { grid-template-columns: 1fr; }
@media (min-width: 768px) {
  .pm-formfield-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.pm-formfield-grid__full { grid-column: 1 / -1; }   /* md:col-span-3 (ShippingOrder.tsx:733) */

/* ----------------------------------------------------------------------------
   RTL — every inset/margin above is logical (margin-inline, text-align: end),
   so the asterisk, the trailing slot, the inline control column and the
   message alignment all flip with dir="rtl" without a second rule.
   ---------------------------------------------------------------------------- */
