/* Toast layer. Owned by fxf-ui.js; nothing else in the repo uses the fxt- prefix.
   Colours come from the variables styles.css already defines on :root
   (--wine, --ink, --panel, --line, --muted) — no new hex for anything that has
   one already. */

/* 🔴 z-index 1300 is deliberate, and these are the numbers it had to beat:
     .admin-header  1100   (styles.css)
     .nav-scrim     1150   (nav.css)
     .nav-drawer    1200   (nav.css)
     #fxfToasts     1300   <- here
   An error toast has to be readable even with the nav drawer open, so it sits
   above all three. Do not re-measure this; update the list if the stack moves.

   TOP, not bottom: the whole point of this layer is that messages at the bottom
   of a long page go unnoticed. The offset clears the sticky .admin-header, whose
   height is --app-header-h (styles.css).

   pointer-events: the container is transparent to clicks so it never blocks the
   page behind it; each toast re-enables them so it stays click-to-dismiss. */
#fxfToasts{
  position:fixed;
  /* Derived, never a magic number: the app header is --app-header-h tall at
     EVERY width (styles.css declares it beside the palette; nothing overrides
     it in an @media), so one expression is correct on desktop and on a phone.
     The literal is the fallback for a missing variable. */
  top:72px;
  top:calc(var(--app-header-h,68px) + 4px);
  left:50%;
  transform:translateX(-50%);
  z-index:1300;
  width:max-content;
  max-width:min(420px, calc(100vw - 32px));
  display:flex;
  flex-direction:column;
  align-items:stretch;
  gap:8px;
  pointer-events:none;
}

.fxt-toast{
  pointer-events:auto;
  cursor:pointer;
  box-sizing:border-box;
  max-width:100%;
  display:flex;
  align-items:flex-start;
  gap:10px;
  padding:11px 14px;
  border:1px solid var(--line);
  border-radius:9px;
  background:var(--panel);
  color:var(--ink);
  font-size:14px;
  line-height:1.4;
  text-align:left;
  /* A Supabase error can be one very long unbroken string; it wraps rather than
     spanning the viewport, and breaks rather than overflowing the box. */
  white-space:normal;
  overflow-wrap:anywhere;
  word-break:break-word;
  /* The toast is white on a cream page, so the lift has to come from the
     shadow rather than from a background contrast. */
  box-shadow:0 10px 28px rgba(30,20,10,.24);
  animation:fxt-in .18s ease-out;
}

/* The three kinds differ by a filled circular chip, not by a border tint. The
   old scheme gave success and failure the SAME wine left-border, so the only
   difference between "saved" and "that failed" was 2px of border width.

   🔴 THE CHIP IS A ::before, AND IT HAS TO STAY ONE. Toast text is raw
   Supabase error output, set with textContent in fxf-ui.js. Injecting an icon
   element would mean innerHTML, which would make every database error message a
   markup injection site. CSS draws the icon; JS never touches HTML.

   Shape, colour AND glyph all differ, so the kinds are still distinguishable in
   greyscale and to a colour-blind reader. */
.fxt-toast::before{
  flex:0 0 auto;
  box-sizing:border-box;
  width:22px;
  height:22px;
  border-radius:50%;
  display:flex;
  align-items:center;
  justify-content:center;
  color:#fff;
  font-size:13px;
  font-weight:700;
  line-height:1;
}

.fxt-ok::before{
  content:"\2713";
  background:var(--ok);
}

.fxt-error::before{
  content:"!";
  background:var(--wine);
}
.fxt-error{font-weight:600}

/* A spinning ring rather than a glyph: progress is sticky, and motion is what
   says "still working" as opposed to "finished with a grey result". The text
   itself stays full-strength var(--ink) — the chip carries the state, and a
   greyed-out message just reads as harder to read. */
.fxt-progress::before{
  content:"";
  background:none;
  border:2px solid var(--line);
  border-top-color:var(--muted);
  animation:fxt-spin .7s linear infinite;
}

@keyframes fxt-spin{
  to{transform:rotate(360deg)}
}

@keyframes fxt-in{
  from{opacity:0;transform:translateY(-6px)}
  to{opacity:1;transform:none}
}

/* ==========================================================================
   Overlay layer. Owned by fxf-ui.js; the fxo- prefix is used nowhere else in
   the repo. Colours are the :root variables styles.css already defines
   (--panel, --line, --ink, --wine, --muted, --cream) — no new hex.

   🔴 z-index, and these are the numbers the two below had to fit between:
     .admin-header  1100   (styles.css)
     .nav-scrim     1150   (nav.css)
     .nav-drawer    1200   (nav.css)
     .fxo-scrim     1210   <- here
     .fxo-panel     1220   <- here
     #fxfToasts     1300   (above)
   ABOVE the nav drawer, because an overlay opened over the page must not be
   sliced by a drawer sitting on top of it. BELOW the toasts, and that half is
   load-bearing: the controls inside the overlay report through setStatus(),
   and those status messages have to stay readable while the overlay is open.
   Do not re-measure this; update the list if the stack moves.
   ========================================================================== */
.fxo-scrim{
  position:fixed;
  inset:0;
  z-index:1210;
  background:rgba(30,20,10,.42);
  animation:fxo-fade .16s ease-out;
}

/* 🔴 THE PANEL SCROLLS ITS BODY, AND THAT IS NOW SAFE.
   It used to be forbidden: the style icon picker lived inside this panel as
   `position:absolute; bottom:100%`, opening UPWARD out of the top of its
   control, so an overflow on ANY ancestor clipped the icon grid with no error
   anywhere. As of this phase .fe-icon-popover is `position:fixed`, appended to
   <body> and placed by FXFUI.positionTo() — it is no longer a descendant of
   this panel and nothing here can clip it. So .fxo-body gets overflow:auto,
   and the panel becomes a flex column so a tall panel scrolls its body inside
   max-height instead of letting its centred top escape above the viewport. */
.fxo-panel{
  position:fixed;
  top:50%;
  left:50%;
  transform:translate(-50%,-50%);
  z-index:1220;
  box-sizing:border-box;
  display:flex;
  flex-direction:column;
  width:min(520px, calc(100vw - 24px));
  max-width:520px;
  max-height:calc(100vh - 48px);
  border:1px solid var(--line);
  border-radius:12px;
  background:var(--panel);
  color:var(--ink);
  box-shadow:0 18px 48px rgba(30,20,10,.32);
  animation:fxo-in .16s ease-out;
}

.fxo-head{
  flex:0 0 auto;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:12px 14px;
  border-bottom:1px solid var(--line);
  background:var(--cream);
  border-radius:12px 12px 0 0;
}

.fxo-title{
  margin:0;
  font-size:16px;
  line-height:1.3;
  color:var(--wine);
}

.fxo-close{
  flex:0 0 auto;
  cursor:pointer;
  width:30px;
  height:30px;
  padding:0;
  border:1px solid var(--line);
  border-radius:8px;
  background:var(--panel);
  color:var(--muted);
  font-size:18px;
  line-height:1;
}
.fxo-close:hover{color:var(--wine)}

.fxo-body{
  /* min-height:0 is what actually lets a flex child shrink and scroll. */
  flex:1 1 auto;
  min-height:0;
  overflow:auto;
  /* 3K-8e-2f: a scroll that reaches the end of the body stops here. The page lock below does the same job; this one still
     holds where a browser ignores the lock. */
  overscroll-behavior:contain;
  padding:14px;
}

/* 🔴 3K-8e-2f: THE PAGE BEHIND AN OPEN LAYER HOLDS STILL. fxf-ui.js puts .fxo-locked on <html> for exactly as long as a layer
   (an overlay or a menu) is open, so a wheel or a swipe that runs off the end of a dialog, or lands on its header, a menu or
   the scrim, has no page to scroll. With no layer open nothing here matches. .fxo-locked-gutter pads the page by the width
   of a scrollbar that took room (fxf-ui.js measures it), so the page does not shift sideways. */
html.fxo-locked{overflow:hidden}
html.fxo-locked-gutter{padding-right:var(--fxo-gutter)}

/* The menu: presentation 2 of the same layer, so it keeps the fxo- prefix and
   the panel's z-index. Its scrim is transparent — a menu must not black out the
   page — but it is a real full-viewport element, which is what makes an
   outside click dismiss the menu.

   🔴 THE MENU IS A BODY-LEVEL LAYER ON PURPOSE. Its trigger lives inside a card
   that carries overflow:hidden (.pv-card, products.css); a menu rendered as a
   child of that card would be clipped at the card's edge. Do not "simplify" it
   back into the card. top/left are written by positionTo() in fxf-ui.js. */
.fxo-menu{
  position:fixed;
  z-index:1220;
  box-sizing:border-box;
  min-width:180px;
  max-width:min(280px, calc(100vw - 16px));
  padding:5px;
  border:1px solid var(--line);
  border-radius:10px;
  background:var(--panel);
  box-shadow:0 12px 32px rgba(30,20,10,.26);
  animation:fxo-fade .12s ease-out;
}

.fxo-scrim--clear{background:none}

.fxo-item{
  display:block;
  width:100%;
  box-sizing:border-box;
  padding:9px 11px;
  border:0;
  border-radius:7px;
  background:none;
  color:var(--ink);
  font:inherit;
  font-size:13px;
  line-height:1.35;
  text-align:left;
  cursor:pointer;
  /* A DB-supplied variant name reaches these labels and can be long. */
  white-space:normal;
  overflow-wrap:anywhere;
}
.fxo-item:hover{background:var(--cream)}
.fxo-item:focus-visible{outline:2px solid var(--wine);outline-offset:-2px}
.fxo-item--danger{color:var(--wine)}

@keyframes fxo-fade{
  from{opacity:0}
  to{opacity:1}
}

@keyframes fxo-in{
  from{opacity:0;transform:translate(-50%,-50%) scale(.98)}
  to{opacity:1;transform:translate(-50%,-50%)}
}

/* First prefers-reduced-motion rule in this repo. */
@media (prefers-reduced-motion: reduce){
  .fxt-toast{animation:none}
  /* The overlay appears instantly rather than fading/scaling in. */
  .fxo-scrim{animation:none}
  .fxo-panel{animation:none}
  .fxo-menu{animation:none}
  /* Every chip, not just today's spinner: the progress ring stays visible and
     simply stops turning — a static ring still reads as the progress kind, which
     is the information the chip is there to carry — and any chip animated later
     is covered without anyone having to remember this block. */
  .fxt-toast::before{animation:none}
}

/* 🔴 DECOUPLED FROM products.css ON PURPOSE — DO NOT "RE-SYNC" THEM. This used
   to match a 400px query in products.css and said so. That query is gone: the
   card tiles now answer to their GRID's width via @container (max-width:475px),
   because how many columns fit is a question about the grid, not the device.
   A toast is not in a grid. It is fixed to the viewport, so its width IS a
   genuine viewport question and 400px stays a viewport number here. The two
   files now ask different questions and their numbers are unrelated; making
   them equal again would only make one of them wrong. */
@media(max-width:400px){
  /* No top override: --app-header-h is the header's height at every width, so
     the base rule is already right here. The 64px that used to live on this
     line was 4px short and overlapped the header. */
  #fxfToasts{
    left:12px;
    right:12px;
    width:auto;
    max-width:none;
    transform:none;
  }
}

/* ==========================================================================
   3K-8e-4 (part two) — THE SHARED "ADD A MATERIAL" DIALOG (add-material.js).
   The fxam- prefix is used nowhere else in the repo, exactly as fxo- is not.

   🔴 IT LIVES HERE AND NOT IN A FILE OF ITS OWN. fxf-ui.css already loads on BOTH
      designer.html and products.html, so the shared component's look already had a
      shared home; a second stylesheet would be a second entry in FILES, a second
      <link> on two pages and a second thing to forget.

   🔴 z-index, continuing the list at the top of this file:
        .fxo-scrim     1210
        .fxo-panel     1220
        .fxam-scrim    1230   <- here
        .fxam-panel    1240   <- here
        #fxfToasts     1300
      ABOVE the overlay, because this dialog opens ON TOP of one — the job materials
      editor is a .fxo-panel and this is the + inside it. BELOW the toasts, for the
      reason the overlay is below them: a status message must stay readable.

   🔴 NO PAGE LOCK OF ITS OWN. fxf-ui.js already put .fxo-locked on <html> for the
      layer underneath, and it is still open. Locking again would mean unlocking
      twice, and the second unlock would free the page while the editor is still up.

   The FIELD inside it reuses products.css's class names (.field, .recipe-search-*,
   .recipe-form-hint), because products.js's applyPriceOnlyForm() finds
   '.recipe-search-wrap' by name. Those rules are scoped under .fxam-panel here, so
   the products page — which has products.css and no .fxam-panel — is untouched, and
   the designer — which has no products.css — gets a styled field.
   ========================================================================== */
.fxam-scrim{
  position:fixed;
  inset:0;
  z-index:1230;
  background:rgba(30,20,10,.42);
  animation:fxo-fade .16s ease-out;
}

.fxam-panel{
  position:fixed;
  top:50%;
  left:50%;
  transform:translate(-50%,-50%);
  z-index:1240;
  box-sizing:border-box;
  display:flex;
  flex-direction:column;
  width:min(420px, calc(100vw - 24px));
  max-height:calc(100vh - 48px);
  border:1px solid var(--line);
  border-radius:12px;
  background:var(--panel);
  color:var(--ink);
  box-shadow:0 18px 48px rgba(30,20,10,.32);
  animation:fxo-in .16s ease-out;
}

.fxam-head{
  flex:0 0 auto;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:12px 14px;
  border-bottom:1px solid var(--line);
  background:var(--cream);
  border-radius:12px 12px 0 0;
}
.fxam-title{margin:0;font-size:15px;line-height:1.3;color:var(--wine)}
.fxam-close{
  flex:0 0 auto;
  width:32px;
  height:32px;
  padding:0;
  border:0;
  border-radius:8px;
  background:none;
  color:var(--muted);
  font-size:22px;
  line-height:1;
  cursor:pointer;
}
.fxam-close:hover{background:rgba(0,0,0,.06);color:var(--ink)}

/* 🔴 overflow:visible, NOT auto. The catalog results list is position:absolute inside
   this body; an overflow:auto ancestor clips it, which is the exact bug
   .recipe-search-results--float exists to work around in products.js's overlay. This
   form is five short fields, so it never needs to scroll — the list caps its own
   height below instead. */
.fxam-body{
  flex:0 1 auto;
  overflow:visible;
  padding:12px 14px 4px;
}
.fxam-intro{margin:0 0 10px;font-size:12.5px;line-height:1.35;color:var(--muted)}

.fxam-panel .field{margin:0 0 10px;min-width:0}
/* 3K-8e-4c: the Qty / Amount / Every N ft boxes are hidden by the counting rule. styles.css's .field{display:grid}
   outranks the [hidden] attribute's own display:none, so the attribute is honoured here. */
.fxam-panel .field[hidden]{display:none}
.fxam-panel .field label,
.fxam-panel .field .fxam-flabel{
  display:block;
  margin:0 0 3px;
  font-size:10.5px;
  font-weight:700;
  color:var(--muted);
  text-transform:uppercase;
  letter-spacing:.2px;
}
/* 🔴 16px, AND NOT NEGOTIABLE: iOS Safari zooms the whole page into any box whose text
   is under 16px, and this dialog opens over another one. */
.fxam-panel input,
.fxam-select{
  box-sizing:border-box;
  width:100%;
  padding:7px 8px;
  border:1.5px solid var(--line);
  border-radius:8px;
  background:#fff;
  color:var(--ink);
  font:inherit;
  font-size:16px;
  line-height:1.25;
}
.fxam-panel input:focus,
.fxam-select:focus{outline:none;border-color:var(--wine)}
.fxam-name{font-weight:700}
.fxam-num{text-align:right;font-variant-numeric:tabular-nums}
/* One fence on the job: the picker states it and asks nothing. */
.fxam-one-fence{margin:0;padding:7px 0;font-size:14px;font-weight:700;line-height:1.25;color:var(--ink)}

.fxam-row{display:flex;gap:10px}
.fxam-row .field{flex:1 1 0;min-width:0}

/* The one red line of this dialog, said under the fields and not beside them. */
.fxam-warn{margin:0 0 6px;font-size:12px;font-weight:600;line-height:1.35;color:#b3261e}

.fxam-foot{
  flex:0 0 auto;
  display:flex;
  justify-content:flex-end;
  gap:8px;
  padding:10px 14px 12px;
}
.fxam-cancel,
.fxam-add{
  box-sizing:border-box;
  height:40px;
  padding:0 14px;
  border-radius:8px;
  border:1.5px solid var(--line);
  background:#fff;
  color:var(--ink);
  font:inherit;
  font-weight:700;
  cursor:pointer;
}
.fxam-add{border-color:var(--ok);background:var(--ok);color:#fff}
.fxam-add:hover{background:#245f3d;border-color:#245f3d}
.fxam-cancel:hover{background:var(--cream)}

/* The catalog field, on the designer only (see the header note). */
.fxam-panel .recipe-search-wrap{position:relative;display:block}
.fxam-panel .recipe-form-hint{margin:4px 0 0;font-size:11.5px;line-height:1.35;color:var(--muted)}
.fxam-panel .recipe-search-results{
  position:absolute;
  top:calc(100% + 4px);
  left:0;
  right:0;
  z-index:2;
  max-height:min(216px, 34vh);
  overflow-y:auto;
  padding:4px;
  border:1px solid var(--line);
  border-radius:8px;
  background:var(--panel);
  box-shadow:0 12px 32px rgba(30,20,10,.26);
}
.fxam-panel .recipe-search-result{
  padding:8px 9px;
  border-radius:6px;
  font-size:13px;
  line-height:1.3;
  cursor:pointer;
  overflow-wrap:anywhere;
}
.fxam-panel .recipe-search-result:hover{background:var(--cream)}
.fxam-panel .recipe-search-empty{padding:8px 9px;font-size:13px;color:var(--muted)}

@media (prefers-reduced-motion: reduce){
  .fxam-scrim{animation:none}
  .fxam-panel{animation:none}
}
